Overview

Namespaces

  • RM
    • AssetsCollector
      • Compilers

Classes

  • AssetsCollector
  • Header
  • Overview
  • Namespace
  • Class
  • Tree
  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:  * Header
 12:  * This renderable component is ultimate solution for valid and complete HTML headers.
 13:  *
 14:  * @author Ondřej Mirtes
 15:  * @copyright (c) Ondřej Mirtes 2009, 2010
 16:  * @copyright (c) Roman Mátyus 2012
 17:  * @license MIT
 18:  * @package Header
 19:  */
 20: class Header extends Control
 21: {
 22: 
 23:     /**
 24:      * doctypes
 25:      */
 26:     const HTML_4 = self::HTML_4_STRICT; //backwards compatibility
 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; //backwards compatibility
 34:     const XHTML_1_STRICT = 'xhtml1_strict';
 35:     const XHTML_1_TRANSITIONAL = 'xhtml1_transitional';
 36:     const XHTML_1_FRAMESET = 'xhtml1_frameset';
 37: 
 38:     /**
 39:      * languages
 40:      */
 41:     const CZECH = 'cs';
 42:     const SLOVAK = 'sk';
 43:     const ENGLISH = 'en';
 44:     const GERMAN = 'de';
 45: 
 46:     /**
 47:      * content types
 48:      */
 49:     const TEXT_HTML = 'text/html';
 50:     const APPLICATION_XHTML = 'application/xhtml+xml';
 51: 
 52:     /** @var string doctype */
 53:     private $docType;
 54: 
 55:     /** @var bool whether doctype is XML compatible or not */
 56:     private $xml;
 57: 
 58:     /** @var string document language */
 59:     private $language;
 60: 
 61:     /** @var string document title */
 62:     private $title;
 63: 
 64:     /** @var string title separator */
 65:     private $titleSeparator;
 66: 
 67:     /** @var bool whether title should be rendered in reverse order or not */
 68:     private $titlesReverseOrder = TRUE;
 69: 
 70:     /** @var array document hierarchical titles */
 71:     private $titles = array();
 72: 
 73:     /** @var array site rss channels */
 74:     private $rssChannels = array();
 75: 
 76:     /** @var array header meta tags */
 77:     private $metaTags = array();
 78: 
 79:     /** @var Html &lt;html&gt; tag */
 80:     private $htmlTag;
 81: 
 82:     /** @var string document content type */
 83:     private $contentType;
 84: 
 85:     /** @var bool whether XML content type should be forced or not */
 86:     private $forceContentType;
 87: 
 88:     /** @var string path to favicon (without $basePath) */
 89:     private $favicon;
 90: 
 91:     /** @var \RM\AssetsCollector */
 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; //fluent interface
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; //fluent interface
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; //fluent interface
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; //fluent interface
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; //fluent interface
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 addRssChannel($title, $link)
227:     {
228:         $this->rssChannels[] = array(
229:                 'title' => $title,
230:                 'link' => $link,
231:         );
232: 
233:         return $this; //fluent interface
234:     }
235: 
236:     public function getRssChannels()
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; //fluent interface
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; //fluent interface
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; //fluent interface
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; //fluent interface
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; //fluent interface
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; //fluent interface
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; //fluent interface
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; // fluent interface
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 renderRss($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:      * Add CSS files to header.
539:      * Using \RM\AssetsCollector
540:      * @param file string|array
541:      * @return this
542:      */
543:     public function addCss($file)
544:     {
545:         $this->assetsCollector->addCss($file);
546:         return $this;
547:     }
548: 
549:     /**
550:      * Add JS files to header.
551:      * Using \RM\AssetsCollector
552:      * @param file string|array
553:      * @return this
554:      */
555:     public function addJs($file)
556:     {
557:         $this->assetsCollector->addJs($file);
558:         return $this;
559:     }
560: 
561:     /**
562:      * Add CSS files to header from plain entry.
563:      * Using \RM\AssetsCollector
564:      * @param content string
565:      * @return this
566:      */
567:     public function addCssContent($content)
568:     {
569:         $this->assetsCollector->addCssContent($content);
570:         return $this;
571:     }
572: 
573:     /**
574:      * Add JS files to header from plain entry.
575:      * Using \RM\AssetsCollector
576:      * @param content string
577:      * @return this
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: 
API documentation generated by ApiGen 2.8.0