PageRenderTime 48ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/www/app/FrontModule/components/HeaderControl/HeaderControl.php

https://github.com/bazo/Mokuji
PHP | 457 lines | 328 code | 99 blank | 30 comment | 66 complexity | 1da88b8ef87ffbaed2e07b84d71267db MD5 | raw file
Possible License(s): BSD-3-Clause, MIT
  1. <?php
  2. /**
  3. * HeaderControl<br />
  4. * This renderable component is ultimate solution for valid and complete HTML headers.
  5. *
  6. * @author Ondřej Mirtes
  7. * @copyright (c) Ondřej Mirtes 2009, 2010
  8. * @license MIT
  9. * @package HeaderControl
  10. */
  11. class HeaderControl extends Control {
  12. /**
  13. * doctypes
  14. */
  15. const HTML_4 = self::HTML_4_STRICT; //backwards compatibility
  16. const HTML_4_STRICT = 'html4_strict';
  17. const HTML_4_TRANSITIONAL = 'html4_transitional';
  18. const HTML_4_FRAMESET = 'html4_frameset';
  19. const HTML_5 = 'html5';
  20. const XHTML_1 = self::XHTML_1_STRICT; //backwards compatibility
  21. const XHTML_1_STRICT = 'xhtml1_strict';
  22. const XHTML_1_TRANSITIONAL = 'xhtml1_transitional';
  23. const XHTML_1_FRAMESET = 'xhtml1_frameset';
  24. /**
  25. * languages
  26. */
  27. const CZECH = 'cs';
  28. const SLOVAK = 'sk';
  29. const ENGLISH = 'en';
  30. const GERMAN = 'de';
  31. /**
  32. * content types
  33. */
  34. const TEXT_HTML = 'text/html';
  35. const APPLICATION_XHTML = 'application/xhtml+xml';
  36. /** @var string doctype */
  37. private $docType;
  38. /** @var bool whether doctype is XML compatible or not */
  39. private $xml;
  40. /** @var string document language */
  41. private $language;
  42. /** @var string document title */
  43. private $title;
  44. /** @var string title separator */
  45. private $titleSeparator;
  46. /** @var bool whether title should be rendered in reverse order or not */
  47. private $titlesReverseOrder = true;
  48. /** @var array document hierarchical titles */
  49. private $titles = array();
  50. /** @var array site rss channels */
  51. private $rssChannels = array();
  52. /** @var array header meta tags */
  53. private $metaTags = array();
  54. /** @var string document content type */
  55. private $contentType;
  56. /** @var bool whether XML content type should be forced or not */
  57. private $forceContentType;
  58. /** @var string path to favicon (without $basePath) */
  59. private $favicon;
  60. public function __construct($docType, $language, $title) {
  61. $this->setDocType($docType);
  62. $this->setLanguage($language);
  63. $this->setTitle($title);
  64. $this->setContentType(self::TEXT_HTML);
  65. try {
  66. $this->setFavicon('favicon.ico');
  67. } catch (FileNotFoundException $e) {
  68. }
  69. }
  70. protected function createComponentCss() {
  71. return new CssLoader;
  72. }
  73. protected function createComponentJs() {
  74. return new JavaScriptLoader;
  75. }
  76. public function setDocType($docType) {
  77. if ($docType == self::HTML_4_STRICT || $docType == self::HTML_4_TRANSITIONAL ||
  78. $docType == self::HTML_4_FRAMESET || $docType == self::HTML_5 ||
  79. $docType == self::XHTML_1_STRICT || $docType == self::XHTML_1_TRANSITIONAL ||
  80. $docType == self::XHTML_1_FRAMESET) {
  81. $this->docType = $docType;
  82. $this->xml = Html::$xhtml = ($docType == self::XHTML_1_STRICT ||
  83. $docType == self::XHTML_1_TRANSITIONAL ||
  84. $docType == self::XHTML_1_FRAMESET);
  85. } else {
  86. throw new InvalidArgumentException("Doctype $docType is not supported.");
  87. }
  88. return $this; //fluent interface
  89. }
  90. public function getDocType() {
  91. return $this->docType;
  92. }
  93. public function isXml() {
  94. return $this->xml;
  95. }
  96. public function setLanguage($language) {
  97. $this->language = $language;
  98. return $this; //fluent interface
  99. }
  100. public function getLanguage() {
  101. return $this->language;
  102. }
  103. public function setTitle($title) {
  104. if ($title != null && $title != '') {
  105. $this->title = $title;
  106. } else {
  107. throw new InvalidArgumentException("Title must be non-empty string.");
  108. }
  109. return $this; //fluent interface
  110. }
  111. public function getTitle($index = 0) {
  112. if (count($this->titles) == 0) {
  113. return $this->title;
  114. } else if (count($this->titles)-1-$index < 0) {
  115. return $this->getTitle();
  116. } else {
  117. return $this->titles[count($this->titles)-1-$index];
  118. }
  119. }
  120. public function addTitle($title) {
  121. if ($this->titleSeparator) {
  122. $this->titles[] = $title;
  123. } else {
  124. throw new InvalidStateException('Title separator is not set.');
  125. }
  126. return $this;
  127. }
  128. public function getTitles() {
  129. return $this->titles;
  130. }
  131. public function setTitleSeparator($separator) {
  132. $this->titleSeparator = $separator;
  133. return $this; //fluent interface
  134. }
  135. public function getTitleSeparator() {
  136. return $this->titleSeparator;
  137. }
  138. public function setTitlesReverseOrder($reverseOrder) {
  139. $this->titlesReverseOrder = (bool) $reverseOrder;
  140. return $this; //fluent interface
  141. }
  142. public function isTitlesOrderReversed() {
  143. return $this->titlesReverseOrder;
  144. }
  145. public function getTitleString() {
  146. if ($this->titles) {
  147. if (!$this->titlesReverseOrder) {
  148. array_unshift($this->titles, $this->title);
  149. } else {
  150. $this->titles = array_reverse($this->titles);
  151. ksort($this->titles);
  152. array_push($this->titles, $this->title);
  153. }
  154. return implode($this->titleSeparator, $this->titles);
  155. } else {
  156. return $this->title;
  157. }
  158. }
  159. public function addRssChannel($title, $link) {
  160. $this->rssChannels[] = array(
  161. 'title' => $title,
  162. 'link' => $link,
  163. );
  164. return $this; //fluent interface
  165. }
  166. public function getRssChannels() {
  167. return $this->rssChannels;
  168. }
  169. public function setContentType($contentType, $force=false) {
  170. if ($contentType == self::APPLICATION_XHTML &&
  171. $this->docType != self::XHTML_1_STRICT && $this->docType != self::XHTML_1_TRANSITIONAL &&
  172. $this->docType != self::XHTML_1_FRAMESET) {
  173. throw new InvalidArgumentException("Cannot send $contentType type with non-XML doctype.");
  174. }
  175. if ($contentType == self::TEXT_HTML || $contentType == self::APPLICATION_XHTML) {
  176. $this->contentType = $contentType;
  177. } else {
  178. throw new InvalidArgumentException("Content type $contentType is not supported.");
  179. }
  180. $this->forceContentType = (bool) $force;
  181. return $this; //fluent interface
  182. }
  183. public function getContentType() {
  184. return $this->contentType;
  185. }
  186. public function isContentTypeForced() {
  187. return $this->forceContentType;
  188. }
  189. public function setFavicon($filename) {
  190. if (file_exists(WWW_DIR . '/' . $filename)) {
  191. $this->favicon = $filename;
  192. } else {
  193. throw new FileNotFoundException('Favicon ' . WWW_DIR . Environment::getVariable('baseUri') . $filename . ' not found.');
  194. }
  195. return $this; //fluent interface
  196. }
  197. public function getFavicon() {
  198. return $this->favicon;
  199. }
  200. public function setMetaTag($name, $value) {
  201. $this->metaTags[$name] = $value;
  202. return $this; //fluent interface
  203. }
  204. public function getMetaTag($name) {
  205. return isset($this->metaTags[$name]) ? $this->metaTags[$name] : null;
  206. }
  207. public function getMetaTags() {
  208. return $this->metaTags;
  209. }
  210. public function setAuthor($author) {
  211. $this->setMetaTag('author', $author);
  212. return $this; //fluent interface
  213. }
  214. public function getAuthor() {
  215. return $this->getMetaTag('author');
  216. }
  217. public function setDescription($description) {
  218. $this->setMetaTag('description', $description);
  219. return $this; //fluent interface
  220. }
  221. public function getDescription() {
  222. return $this->getMetaTag('description');
  223. }
  224. public function addKeywords($keywords) {
  225. if (is_array($keywords)) {
  226. if ($this->keywords) {
  227. $this->setMetaTag('keywords', $this->getKeywords() . ', ' . implode(', ', $keywords));
  228. } else {
  229. $this->setMetaTag('keywords', implode(', ', $keywords));
  230. }
  231. } else if (is_string($keywords)) {
  232. if ($this->keywords) {
  233. $this->setMetaTag('keywords', $this->getKeywords() . ', ' . $keywords);
  234. } else {
  235. $this->setMetaTag('keywords', $keywords);
  236. }
  237. } else {
  238. throw new InvalidArgumentException('Type of keywords argument is not supported.');
  239. }
  240. return $this; //fluent interface
  241. }
  242. public function getKeywords() {
  243. return $this->getMetaTag('keywords');
  244. }
  245. public function setRobots($robots) {
  246. $this->setMetaTag('robots', $robots);
  247. return $this; //fluent interface
  248. }
  249. public function getRobots() {
  250. return $this->getMetaTag('robots');
  251. }
  252. public function render() {
  253. $this->renderBegin();
  254. $this->renderRss();
  255. $this->renderCss();
  256. $this->renderJs();
  257. $this->renderEnd();
  258. }
  259. public function renderBegin() {
  260. $response = Environment::getHttpResponse();
  261. if ($this->docType == self::XHTML_1_STRICT &&
  262. $this->contentType == self::APPLICATION_XHTML &&
  263. ($this->forceContentType || $this->isClientXhtmlCompatible())) {
  264. $contentType = self::APPLICATION_XHTML;
  265. $response->setHeader('Vary', 'Accept');
  266. echo "<?xml version='1.0' encoding='utf-8'?>\n";
  267. } else {
  268. $contentType = self::TEXT_HTML;
  269. Environment::getHttpResponse()->setContentType($contentType, 'utf-8');
  270. }
  271. $response->setContentType($contentType, 'utf-8');
  272. echo $this->getDocTypeString() . "\n";
  273. echo '<html' . ($this->xml ? ' xmlns="http://www.w3.org/1999/xhtml" xml:lang="'
  274. . $this->language . '" lang="' . $this->language . '"' : '') . ">\n";
  275. echo "<head>\n";
  276. $metaLanguage = Html::el('meta')->content($this->language);
  277. $metaLanguage->attrs['http-equiv'] = 'Content-Language';
  278. echo $metaLanguage . "\n";
  279. $metaContentType = Html::el('meta')->content($contentType);
  280. $metaContentType->attrs['http-equiv'] = 'Content-Type';
  281. echo $metaContentType . "\n";
  282. echo Html::el('title', $this->getTitleString()) . "\n";
  283. if ($this->favicon != '') {
  284. echo Html::el('link')->rel('shortcut icon')
  285. ->href(Environment::getVariable('baseUri') . $this->favicon) . "\n";
  286. }
  287. foreach ($this->metaTags as $name=>$content) {
  288. echo Html::el('meta')->name($name)->content($content) . "\n";
  289. }
  290. }
  291. public function renderEnd() {
  292. echo "</head>\n";
  293. }
  294. public function renderRss($channels=null) {
  295. if ($channels !== null) {
  296. $this->rssChannels = array();
  297. foreach ($channels as $title => $link) {
  298. $this->addRssChannel($title, $link);
  299. }
  300. }
  301. foreach ($this->rssChannels as $channel) {
  302. echo Html::el('link')->rel('alternate')->type('application/rss+xml')
  303. ->title($channel['title'])
  304. ->href(Environment::getApplication()->getPresenter()->link($channel['link'])) . "\n";
  305. }
  306. }
  307. public function renderCss() {
  308. $css = $this['css'];
  309. if (func_num_args() > 0) {
  310. $css->addFiles(func_get_args());
  311. }
  312. $css->render();
  313. echo "\n";
  314. }
  315. public function renderJs() {
  316. $js = $this['js'];
  317. if (func_num_args() > 0) {
  318. $js->addFiles(func_get_args());
  319. }
  320. $js->render();
  321. echo "\n";
  322. }
  323. private function getDocTypeString($docType=null) {
  324. if ($docType == null) {
  325. $docType = $this->docType;
  326. }
  327. switch ($docType) {
  328. case self::HTML_4_STRICT:
  329. return '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">';
  330. break;
  331. case self::HTML_4_TRANSITIONAL:
  332. return '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">';
  333. break;
  334. case self::HTML_4_FRAMESET:
  335. return '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">';
  336. break;
  337. case self::HTML_5:
  338. return '<!DOCTYPE html>';
  339. break;
  340. case self::XHTML_1_STRICT:
  341. return '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
  342. break;
  343. case self::XHTML_1_TRANSITIONAL:
  344. return '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
  345. break;
  346. case self::XHTML_1_FRAMESET:
  347. return '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">';
  348. break;
  349. default:
  350. throw new InvalidStateException("Doctype $docType is not supported.");
  351. }
  352. }
  353. private function isClientXhtmlCompatible() {
  354. $req = Environment::getHttpRequest();
  355. return stristr($req->getHeader('Accept'), 'application/xhtml+xml') ||
  356. $req->getHeader('Accept') == '*/*';
  357. }
  358. }