PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/applications/dashboard/modules/class.headmodule.php

https://github.com/SteveBomber/Garden
PHP | 391 lines | 216 code | 58 blank | 117 comment | 52 complexity | 7cc9d22daea566f4cf64be1ce2293367 MD5 | raw file
  1. <?php if (!defined('APPLICATION')) exit();
  2. /*
  3. Copyright 2008, 2009 Vanilla Forums Inc.
  4. This file is part of Garden.
  5. Garden is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
  6. Garden is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  7. You should have received a copy of the GNU General Public License along with Garden. If not, see <http://www.gnu.org/licenses/>.
  8. Contact Vanilla Forums Inc. at support [at] vanillaforums [dot] com
  9. */
  10. if (!class_exists('HeadModule', FALSE)) {
  11. /**
  12. * Manages collections of items to be placed between the <HEAD> tags of the
  13. * page.
  14. */
  15. class HeadModule extends Gdn_Module {
  16. /**
  17. * The name of the key in a tag that refers to the tag's name.
  18. */
  19. const TAG_KEY = '_tag';
  20. const CONTENT_KEY = '_content';
  21. const SORT_KEY = '_sort';
  22. /**
  23. * A collection of tags to be placed in the head.
  24. */
  25. private $_Tags;
  26. /**
  27. * A collection of strings to be placed in the head.
  28. */
  29. private $_Strings;
  30. /**
  31. * The main text for the "title" tag in the head.
  32. */
  33. protected $_Title;
  34. /**
  35. * A string to be concatenated with $this->_Title.
  36. */
  37. protected $_SubTitle;
  38. /**
  39. * A string to be concatenated with $this->_Title if there is also a
  40. * $this->_SubTitle string being concatenated.
  41. */
  42. protected $_TitleDivider;
  43. public function __construct($Sender = '') {
  44. $this->_Tags = array();
  45. $this->_Strings = array();
  46. $this->_Title = '';
  47. $this->_SubTitle = '';
  48. $this->_TitleDivider = '';
  49. parent::__construct($Sender);
  50. }
  51. /**
  52. * Adds a "link" tag to the head containing a reference to a stylesheet.
  53. *
  54. * @param string $HRef Location of the stylesheet relative to the web root (if an absolute path with http:// is provided, it will use the HRef as provided). ie. /themes/default/css/layout.css or http://url.com/layout.css
  55. * @param string $Media Type media for the stylesheet. ie. "screen", "print", etc.
  56. * @param bool $AddVersion Whether to append version number as query string.
  57. * @param array $Options Additional properties to pass to AddTag, e.g. 'ie' => 'lt IE 7';
  58. */
  59. public function AddCss($HRef, $Media = '', $AddVersion = TRUE, $Options = NULL) {
  60. $Properties = array(
  61. 'rel' => 'stylesheet',
  62. 'type' => 'text/css',
  63. 'href' => Asset($HRef, FALSE, $AddVersion),
  64. 'media' => $Media);
  65. // Use same underscore convention as AddScript
  66. if (is_array($Options)) {
  67. foreach ($Options as $Key => $Value) {
  68. $Properties['_'.strtolower($Key)] = $Value;
  69. }
  70. }
  71. $this->AddTag('link', $Properties);
  72. }
  73. public function AddRss($HRef, $Title) {
  74. $this->AddTag('link', array(
  75. 'rel' => 'alternate',
  76. 'type' => 'application/rss+xml',
  77. 'title' => Gdn_Format::Text($Title),
  78. 'href' => Asset($HRef)
  79. ));
  80. }
  81. /**
  82. * Adds a new tag to the head.
  83. *
  84. * @param string The type of tag to add to the head. ie. "link", "script", "base", "meta".
  85. * @param array An associative array of property => value pairs to be placed in the tag.
  86. * @param string an index to give the tag for later manipulation.
  87. */
  88. public function AddTag($Tag, $Properties, $Content = NULL, $Index = NULL) {
  89. $Tag = array_merge(array(self::TAG_KEY => strtolower($Tag)), array_change_key_case($Properties));
  90. if ($Content)
  91. $Tag[self::CONTENT_KEY] = $Content;
  92. if (!array_key_exists(self::SORT_KEY, $Tag))
  93. $Tag[self::SORT_KEY] = count($this->_Tags);
  94. if ($Index !== NULL)
  95. $this->_Tags[$Index] = $Tag;
  96. // Make sure this item has not already been added.
  97. if (!in_array($Tag, $this->_Tags))
  98. $this->_Tags[] = $Tag;
  99. }
  100. /**
  101. * Adds a "script" tag to the head.
  102. *
  103. * @param string The location of the script relative to the web root. ie. "/js/jquery.js"
  104. * @param string The type of script being added. ie. "text/javascript"
  105. * @param mixed Additional options to add to the tag. The following values are accepted:
  106. * - numeric: This will be the script's sort.
  107. * - string: This will hint the script (inline will inline the file in the page.
  108. * - array: An array of options (ex. sort, hint, version).
  109. *
  110. */
  111. public function AddScript($Src, $Type = 'text/javascript', $Options = array()) {
  112. if (is_numeric($Options)) {
  113. $Options = array('sort' => $Options);
  114. } elseif (is_string($Options)) {
  115. $Options = array('hint' => $Options);
  116. } elseif (!is_array($Options)) {
  117. $Options = array();
  118. }
  119. $Attributes = array('src' => Asset($Src, FALSE, GetValue('version', $Options)), 'type' => $Type);
  120. foreach ($Options as $Key => $Value) {
  121. $Attributes['_'.strtolower($Key)] = $Value;
  122. }
  123. $this->AddTag('script', $Attributes);
  124. }
  125. /**
  126. * Adds a string to the collection of strings to be inserted into the head.
  127. *
  128. * @param string The string to be inserted.
  129. */
  130. public function AddString($String) {
  131. $this->_Strings[] = $String;
  132. }
  133. public function AssetTarget() {
  134. return 'Head';
  135. }
  136. /**
  137. * Removes any added stylesheets from the head.
  138. */
  139. public function ClearCSS() {
  140. $this->ClearTag('link', array('rel' => 'stylesheet'));
  141. }
  142. /**
  143. * Removes any script include tags from the head.
  144. */
  145. public function ClearScripts() {
  146. $this->ClearTag('script');
  147. }
  148. /**
  149. * Removes any tags with the specified $Tag, $Property, and $Value.
  150. *
  151. * Only $Tag is required.
  152. *
  153. * @param string The name of the tag to remove from the head. ie. "link"
  154. * @param string Any property to search for in the tag.
  155. * - If this is an array then it will be treated as a query of attribute/value pairs to match against.
  156. * @param string Any value to search for in the specified property.
  157. */
  158. public function ClearTag($Tag, $Property = '', $Value = '') {
  159. $Tag = strtolower($Tag);
  160. if (is_array($Property))
  161. $Query = array_change_key_case($Property);
  162. elseif ($Property)
  163. $Query = array(strtolower($Property), $Value);
  164. else
  165. $Query = FALSE;
  166. foreach($this->_Tags as $Index => $Collection) {
  167. $TagName = $Collection[self::TAG_KEY];
  168. if ($TagName == $Tag) {
  169. if ($Query && count(array_intersect_assoc($Query, $Collection)) == count($Query)) {
  170. unset($this->_Tags[$Index]);
  171. }
  172. }
  173. }
  174. }
  175. /**
  176. * Return all strings.
  177. */
  178. public function GetStrings() {
  179. return $this->_Strings;
  180. }
  181. /**
  182. * Return all Tags of the specified type (or all tags).
  183. */
  184. public function GetTags($RequestedType = '') {
  185. // Make sure that css loads before js (for jquery)
  186. usort($this->_Tags, array('HeadModule', 'TagCmp')); // "link" comes before "script"
  187. if ($RequestedType == '')
  188. return $this->_Tags;
  189. // Loop through each tag.
  190. $Tags = array();
  191. foreach ($this->_Tags as $Index => $Attributes) {
  192. $Tag = $Attributes[self::TAG_KEY];
  193. if ($TagType == $RequestedType)
  194. $Tags[] = $Attributes;
  195. }
  196. return $Tags;
  197. }
  198. /**
  199. * Sets the favicon location.
  200. *
  201. * @param string The location of the fav icon relative to the web root. ie. /themes/default/images/layout.css
  202. */
  203. public function SetFavIcon($HRef) {
  204. if (!$this->_FavIconSet) {
  205. $this->_FavIconSet = TRUE;
  206. $this->AddTag('link',
  207. array('rel' => 'shortcut icon', 'href' => $HRef, 'type' => 'image/x-icon'),
  208. NULL,
  209. 'favicon');
  210. }
  211. }
  212. private $_FavIconSet = FALSE;
  213. /**
  214. * Gets or sets the tags collection.
  215. *
  216. * @param array $Value.
  217. */
  218. public function Tags($Value = NULL) {
  219. if ($Value != NULL)
  220. $this->_Tags = $Value;
  221. return $this->_Tags;
  222. }
  223. public function Title($Title = '') {
  224. if ($Title != '') {
  225. // Apply $Title to $this->_Title and return it;
  226. $this->_Title = $Title;
  227. $this->_Sender->Title($Title);
  228. return $Title;
  229. } else if ($this->_Title != '') {
  230. // Return $this->_Title if set;
  231. return $this->_Title;
  232. } else {
  233. // Default Return title from controller's Data.Title + banner title;
  234. return ConcatSep(' - ', GetValueR('Data.Title', $this->_Sender, ''), C('Garden.Title'));
  235. }
  236. }
  237. public static function TagCmp($A, $B) {
  238. if ($A[self::TAG_KEY] == 'title')
  239. return -1;
  240. if ($B[self::TAG_KEY] == 'title')
  241. return 1;
  242. $Cmp = strcasecmp($A[self::TAG_KEY], $B[self::TAG_KEY]);
  243. if ($Cmp == 0) {
  244. $SortA = GetValue(self::SORT_KEY, $A, 0);
  245. $SortB = GetValue(self::SORT_KEY, $B, 0);
  246. if ($SortA < $SortB)
  247. $Cmp = -1;
  248. elseif ($SortA > $SortB)
  249. $Cmp = 1;
  250. }
  251. return $Cmp;
  252. }
  253. /**
  254. * Render the entire head module.
  255. */
  256. public function ToString() {
  257. // Add the canonical Url if necessary.
  258. if (method_exists($this->_Sender, 'CanonicalUrl')) {
  259. $CanonicalUrl = $this->_Sender->CanonicalUrl();
  260. $CanonicalUrl = Gdn::Router()->ReverseRoute($CanonicalUrl);
  261. $this->_Sender->CanonicalUrl($CanonicalUrl);
  262. $CurrentUrl = Url('', TRUE);
  263. if ($CurrentUrl != $CanonicalUrl) {
  264. $this->AddTag('link', array('rel' => 'canonical', 'href' => $CanonicalUrl));
  265. }
  266. }
  267. $this->FireEvent('BeforeToString');
  268. $Tags = $this->_Tags;
  269. // Make sure that css loads before js (for jquery)
  270. usort($this->_Tags, array('HeadModule', 'TagCmp')); // "link" comes before "script"
  271. $Tags2 = $this->_Tags;
  272. // Start with the title.
  273. $Head = '<title>'.Gdn_Format::Text($this->Title())."</title>\n";
  274. $TagStrings = array();
  275. // Loop through each tag.
  276. foreach ($this->_Tags as $Index => $Attributes) {
  277. $Tag = $Attributes[self::TAG_KEY];
  278. // Inline the content of the tag, if necessary.
  279. if (GetValue('_hint', $Attributes) == 'inline') {
  280. $Path = GetValue('_path', $Attributes);
  281. if (!StringBeginsWith($Path, 'http')) {
  282. $Attributes[self::CONTENT_KEY] = file_get_contents($Path);
  283. if (isset($Attributes['src'])) {
  284. $Attributes['_src'] = $Attributes['src'];
  285. unset($Attributes['src']);
  286. }
  287. if (isset($Attributes['href'])) {
  288. $Attributes['_href'] = $Attributes['href'];
  289. unset($Attributes['href']);
  290. }
  291. }
  292. }
  293. // If we set an IE conditional AND a "Not IE" condition, we will need to make a second pass.
  294. do {
  295. // Reset tag string
  296. $TagString = '';
  297. // IE conditional? Validates condition.
  298. $IESpecific = (isset($Attributes['_ie']) && preg_match('/((l|g)t(e)? )?IE [0-9\.]/', $Attributes['_ie']));
  299. // Only allow $NotIE if we're not doing a conditional this loop.
  300. $NotIE = (!$IESpecific && isset($Attributes['_notie']));
  301. // Open IE conditional tag
  302. if ($IESpecific)
  303. $TagString .= '<!--[if '.$Attributes['_ie'].']>';
  304. if ($NotIE)
  305. $TagString .= '<!--[if !IE]> -->';
  306. // Build tag
  307. $TagString .= '<'.$Tag.Attribute($Attributes, '_');
  308. if (array_key_exists(self::CONTENT_KEY, $Attributes))
  309. $TagString .= '>'.$Attributes[self::CONTENT_KEY].'</'.$Tag.'>';
  310. elseif ($Tag == 'script') {
  311. $TagString .= '></script>';
  312. } else
  313. $TagString .= ' />';
  314. // Close IE conditional tag
  315. if ($IESpecific)
  316. $TagString .= '<![endif]-->';
  317. if ($NotIE)
  318. $TagString .= '<!-- <![endif]-->';
  319. // Cleanup (prevent infinite loop)
  320. if ($IESpecific)
  321. unset($Attributes['_ie']);
  322. $TagStrings[] = $TagString;
  323. } while($IESpecific && isset($Attributes['_notie'])); // We need a second pass
  324. } //endforeach
  325. $Head .= implode("\n", array_unique($TagStrings));
  326. foreach ($this->_Strings as $String) {
  327. $Head .= $String;
  328. $Head .= "\n";
  329. }
  330. return $Head;
  331. }
  332. }
  333. }