1: <?php
2: namespace RM;
3:
4: use Nette\FileNotFoundException,
5: Nette\InvalidStateException,
6: Nette\Utils\Html,
7: Nette\Environment,
8: Nette\Application\UI\Control;
9:
10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
20: class Header extends Control
21: {
22:
23: 24: 25:
26: const HTML_4 = self::HTML_4_STRICT;
27: const HTML_4_STRICT = 'html4_strict';
28: const HTML_4_TRANSITIONAL = 'html4_transitional';
29: const HTML_4_FRAMESET = 'html4_frameset';
30:
31: const HTML_5 = 'html5';
32:
33: const XHTML_1 = self::XHTML_1_STRICT;
34: const XHTML_1_STRICT = 'xhtml1_strict';
35: const XHTML_1_TRANSITIONAL = 'xhtml1_transitional';
36: const XHTML_1_FRAMESET = 'xhtml1_frameset';
37:
38: 39: 40:
41: const CZECH = 'cs';
42: const SLOVAK = 'sk';
43: const ENGLISH = 'en';
44: const GERMAN = 'de';
45:
46: 47: 48:
49: const TEXT_HTML = 'text/html';
50: const APPLICATION_XHTML = 'application/xhtml+xml';
51:
52:
53: private $docType;
54:
55:
56: private $xml;
57:
58:
59: private $language;
60:
61:
62: private $title;
63:
64:
65: private $titleSeparator;
66:
67:
68: private $titlesReverseOrder = TRUE;
69:
70:
71: private $titles = array();
72:
73:
74: private = array();
75:
76:
77: private $metaTags = array();
78:
79:
80: private $htmlTag;
81:
82:
83: private $contentType;
84:
85:
86: private $forceContentType;
87:
88:
89: private $favicon;
90:
91:
92: private $assetsCollector;
93:
94: public function __construct(IComponentContainer $parent = NULL, $name = NULL)
95: {
96: parent::__construct($parent, $name);
97: $this->setDocType(self::HTML_5);
98: $this->setContentType(self::TEXT_HTML);
99:
100: try {
101: $this->setFavicon('/favicon.ico');
102: } catch (FileNotFoundException $e) {
103:
104: }
105: }
106:
107: public function setDocType($docType)
108: {
109: if ($docType == self::HTML_4_STRICT || $docType == self::HTML_4_TRANSITIONAL ||
110: $docType == self::HTML_4_FRAMESET || $docType == self::HTML_5 ||
111: $docType == self::XHTML_1_STRICT || $docType == self::XHTML_1_TRANSITIONAL ||
112: $docType == self::XHTML_1_FRAMESET) {
113: $this->docType = $docType;
114: $this->xml = Html::$xhtml = ($docType == self::XHTML_1_STRICT ||
115: $docType == self::XHTML_1_TRANSITIONAL ||
116: $docType == self::XHTML_1_FRAMESET);
117: } else {
118: throw new InvalidArgumentException("Doctype $docType is not supported.");
119: }
120:
121: return $this;
122: }
123:
124: public function getDocType()
125: {
126: return $this->docType;
127: }
128:
129: public function isXml()
130: {
131: return $this->xml;
132: }
133:
134: public function setLanguage($language)
135: {
136: $this->language = $language;
137:
138: return $this;
139: }
140:
141: public function getLanguage()
142: {
143: return $this->language;
144: }
145:
146: public function setTitle($title)
147: {
148: if ($title != NULL && $title != '') {
149: $this->title = $title;
150: } else {
151: throw new InvalidArgumentException("Title must be non-empty string.");
152: }
153:
154: return $this;
155: }
156:
157: public function getTitle($index = 0)
158: {
159: if (count($this->titles) == 0) {
160: return $this->title;
161: } else if (count($this->titles)-1-$index < 0) {
162: return $this->getTitle();
163: } else {
164: return $this->titles[count($this->titles)-1-$index];
165: }
166: }
167:
168: public function addTitle($title)
169: {
170: if ($this->titleSeparator) {
171: $this->titles[] = $title;
172: } else {
173: throw new InvalidStateException('Title separator is not set.');
174: }
175:
176: return $this;
177: }
178:
179: public function getTitles()
180: {
181: return $this->titles;
182: }
183:
184: public function setTitleSeparator($separator)
185: {
186: $this->titleSeparator = $separator;
187:
188: return $this;
189: }
190:
191: public function getTitleSeparator()
192: {
193: return $this->titleSeparator;
194: }
195:
196: public function setTitlesReverseOrder($reverseOrder)
197: {
198: $this->titlesReverseOrder = (bool) $reverseOrder;
199:
200: return $this;
201: }
202:
203: public function isTitlesOrderReversed()
204: {
205: return $this->titlesReverseOrder;
206: }
207:
208: public function getTitleString()
209: {
210: if ($this->titles) {
211: if (!$this->titlesReverseOrder) {
212: array_unshift($this->titles, $this->title);
213: } else {
214: $this->titles = array_reverse($this->titles);
215: ksort($this->titles);
216: array_push($this->titles, $this->title);
217: }
218:
219: return implode($this->titleSeparator, $this->titles);
220:
221: } else {
222: return $this->title;
223: }
224: }
225:
226: public function ($title, $link)
227: {
228: $this->rssChannels[] = array(
229: 'title' => $title,
230: 'link' => $link,
231: );
232:
233: return $this;
234: }
235:
236: public function ()
237: {
238: return $this->rssChannels;
239: }
240:
241: public function setContentType($contentType, $force = FALSE)
242: {
243: if ($contentType == self::APPLICATION_XHTML &&
244: $this->docType != self::XHTML_1_STRICT && $this->docType != self::XHTML_1_TRANSITIONAL &&
245: $this->docType != self::XHTML_1_FRAMESET) {
246: throw new InvalidArgumentException("Cannot send $contentType type with non-XML doctype.");
247: }
248:
249: if ($contentType == self::TEXT_HTML || $contentType == self::APPLICATION_XHTML) {
250: $this->contentType = $contentType;
251: } else {
252: throw new InvalidArgumentException("Content type $contentType is not supported.");
253: }
254:
255: $this->forceContentType = (bool) $force;
256:
257: return $this;
258: }
259:
260: public function getContentType()
261: {
262: return $this->contentType;
263: }
264:
265: public function isContentTypeForced()
266: {
267: return $this->forceContentType;
268: }
269:
270: public function setFavicon($filename)
271: {
272: if (file_exists(WWW_DIR . '/' . $filename)) {
273: $this->favicon = $filename;
274: } else {
275: throw new FileNotFoundException('Favicon ' . WWW_DIR . $filename . ' not found.');
276: }
277:
278: return $this;
279: }
280:
281: public function getFavicon()
282: {
283: return $this->favicon;
284: }
285:
286: public function setMetaTag($name, $value)
287: {
288: $this->metaTags[$name] = $value;
289:
290: return $this;
291: }
292:
293: public function getMetaTag($name)
294: {
295: return isset($this->metaTags[$name]) ? $this->metaTags[$name] : NULL;
296: }
297:
298: public function getMetaTags()
299: {
300: return $this->metaTags;
301: }
302:
303: public function setAuthor($author)
304: {
305: $this->setMetaTag('author', $author);
306:
307: return $this;
308: }
309:
310: public function getAuthor()
311: {
312: return $this->getMetaTag('author');
313: }
314:
315: public function setDescription($description)
316: {
317: $this->setMetaTag('description', $description);
318:
319: return $this;
320: }
321:
322: public function getDescription()
323: {
324: return $this->getMetaTag('description');
325: }
326:
327: public function addKeywords($keywords)
328: {
329: if (is_array($keywords)) {
330: if ($this->keywords) {
331: $this->setMetaTag('keywords', $this->getKeywords() . ', ' . implode(', ', $keywords));
332: } else {
333: $this->setMetaTag('keywords', implode(', ', $keywords));
334: }
335: } else if (is_string($keywords)) {
336: if ($this->keywords) {
337: $this->setMetaTag('keywords', $this->getKeywords() . ', ' . $keywords);
338: } else {
339: $this->setMetaTag('keywords', $keywords);
340: }
341: } else {
342: throw new InvalidArgumentException('Type of keywords argument is not supported.');
343: }
344:
345: return $this;
346: }
347:
348: public function getKeywords()
349: {
350: return $this->getMetaTag('keywords');
351: }
352:
353: public function setRobots($robots)
354: {
355: $this->setMetaTag('robots', $robots);
356:
357: return $this;
358: }
359:
360: public function getRobots()
361: {
362: return $this->getMetaTag('robots');
363: }
364:
365: public function setHtmlTag(Html $htmlTag)
366: {
367: $this->htmlTag = $html;
368:
369: return $this;
370: }
371:
372: public function getHtmlTag()
373: {
374: if ($this->htmlTag == NULL) {
375: $html = Html::el('html');
376:
377: if ($this->xml) {
378: $html->attrs['xmlns'] = 'http://www.w3.org/1999/xhtml';
379: $html->attrs['xml:lang'] = $this->language;
380: $html->attrs['lang'] = $this->language;
381: }
382:
383: if ($this->docType == self::HTML_5) {
384: $html->attrs['lang'] = $this->language;
385: }
386:
387: $this->htmlTag = $html;
388: }
389:
390: return $this->htmlTag;
391: }
392:
393: public function render()
394: {
395: $this->renderBegin();
396: $this->renderRss();
397: $this->renderCss();
398: echo "\n";
399: $this->renderJs();
400: echo "\n";
401: $this->renderEnd();
402: }
403:
404: public function renderCss()
405: {
406: foreach ($this->assetsCollector->getCss() as $item) {
407: $link = Html::el('link');
408: $link->attrs['rel'] = 'stylesheet';
409: $link->attrs['href'] = $item;
410: echo $link . "\n";
411: }
412: }
413:
414: public function renderJs()
415: {
416: foreach ($this->assetsCollector->getJs() as $item) {
417: $link = Html::el('script');
418: $link->attrs['src'] = $item;
419: echo $link . "\n";
420: }
421: }
422:
423: public function renderBegin()
424: {
425: $response = Environment::getHttpResponse();
426: if ($this->docType == self::XHTML_1_STRICT &&
427: $this->contentType == self::APPLICATION_XHTML &&
428: ($this->forceContentType || $this->isClientXhtmlCompatible())) {
429: $contentType = self::APPLICATION_XHTML;
430: if (!headers_sent()) {
431: $response->setHeader('Vary', 'Accept');
432: }
433: } else {
434: $contentType = self::TEXT_HTML;
435: }
436:
437: if (!headers_sent()) {
438: $response->setContentType($contentType, 'utf-8');
439: }
440:
441: if ($contentType == self::APPLICATION_XHTML) {
442: echo "<?xml version='1.0' encoding='utf-8'?>\n";
443: }
444:
445: echo $this->getDocTypeString() . "\n";
446:
447: echo $this->getHtmlTag()->startTag() . "\n";
448:
449: echo Html::el('head')->startTag() . "\n";
450:
451: if ($this->docType != self::HTML_5) {
452: $metaLanguage = Html::el('meta');
453: $metaLanguage->attrs['http-equiv'] = 'Content-Language';
454: $metaLanguage->content($this->language);
455: echo $metaLanguage . "\n";
456: }
457:
458: $metaContentType = Html::el('meta');
459: $metaContentType->attrs['http-equiv'] = 'Content-Type';
460: $metaContentType->content($contentType . '; charset=utf-8');
461: echo $metaContentType . "\n";
462:
463: echo Html::el('title', $this->getTitleString()) . "\n";
464:
465: if ($this->favicon != '') {
466: echo Html::el('link')->rel('shortcut icon')
467: ->href($this->favicon) . "\n";
468: }
469:
470: foreach ($this->metaTags as $name=>$content) {
471: echo Html::el('meta')->name($name)->content($content) . "\n";
472: }
473: }
474:
475: public function renderEnd()
476: {
477: echo Html::el('head')->endTag();
478: }
479:
480: public function ($channels = NULL)
481: {
482: if ($channels !== NULL) {
483: $this->rssChannels = array();
484:
485: foreach ($channels as $title => $link) {
486: $this->addRssChannel($title, $link);
487: }
488: }
489:
490: foreach ($this->rssChannels as $channel) {
491: echo Html::el('link')->rel('alternate')->type('application/rss+xml')
492: ->title($channel['title'])
493: ->href(Environment::getApplication()->getPresenter()->link($channel['link'])) . "\n";
494: }
495: }
496:
497: private function getDocTypeString($docType = NULL)
498: {
499: if ($docType == NULL) {
500: $docType = $this->docType;
501: }
502:
503: switch ($docType) {
504: case self::HTML_4_STRICT:
505: return '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">';
506: break;
507: case self::HTML_4_TRANSITIONAL:
508: return '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">';
509: break;
510: case self::HTML_4_FRAMESET:
511: return '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">';
512: break;
513: case self::HTML_5:
514: return '<!DOCTYPE html>';
515: break;
516: case self::XHTML_1_STRICT:
517: return '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
518: break;
519: case self::XHTML_1_TRANSITIONAL:
520: return '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
521: break;
522: case self::XHTML_1_FRAMESET:
523: return '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">';
524: break;
525: default:
526: throw new InvalidStateException("Doctype $docType is not supported.");
527: }
528: }
529:
530: private function isClientXhtmlCompatible()
531: {
532: $req = Environment::getHttpRequest();
533: return stristr($req->getHeader('Accept'), 'application/xhtml+xml') ||
534: $req->getHeader('Accept') == '*/*';
535: }
536:
537: 538: 539: 540: 541: 542:
543: public function addCss($file)
544: {
545: $this->assetsCollector->addCss($file);
546: return $this;
547: }
548:
549: 550: 551: 552: 553: 554:
555: public function addJs($file)
556: {
557: $this->assetsCollector->addJs($file);
558: return $this;
559: }
560:
561: 562: 563: 564: 565: 566:
567: public function addCssContent($content)
568: {
569: $this->assetsCollector->addCssContent($content);
570: return $this;
571: }
572:
573: 574: 575: 576: 577: 578:
579: public function addJsContent($content)
580: {
581: $this->assetsCollector->addJsContent($content);
582: return $this;
583: }
584:
585: public function setAssetsCollector(AssetsCollector $assetsCollector)
586: {
587: if ($this->assetsCollector) {
588: throw new \Nette\InvalidStateException('AssetsCollector has already been set');
589: }
590: $this->assetsCollector = $assetsCollector;
591: }
592:
593: public function injectAssetsCollector(AssetsCollector $assetsCollector)
594: {
595: if ($this->assetsCollector) {
596: throw new \Nette\InvalidStateException('AssetsCollector has already been set');
597: }
598: $this->assetsCollector = $assetsCollector;
599: }
600: }
601: