1<?php
2/**
3 * Joomla! Content Management System
4 *
5 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
6 * @license    GNU General Public License version 2 or later; see LICENSE.txt
7 */
8
9namespace Joomla\CMS\Document;
10
11defined('JPATH_PLATFORM') or die;
12
13use Joomla\CMS\Date\Date;
14
15/**
16 * Document class, provides an easy interface to parse and display a document
17 *
18 * @since  1.7.0
19 */
20class Document
21{
22	/**
23	 * Document title
24	 *
25	 * @var    string
26	 * @since  1.7.0
27	 */
28	public $title = '';
29
30	/**
31	 * Document description
32	 *
33	 * @var    string
34	 * @since  1.7.0
35	 */
36	public $description = '';
37
38	/**
39	 * Document full URL
40	 *
41	 * @var    string
42	 * @since  1.7.0
43	 */
44	public $link = '';
45
46	/**
47	 * Document base URL
48	 *
49	 * @var    string
50	 * @since  1.7.0
51	 */
52	public $base = '';
53
54	/**
55	 * Contains the document language setting
56	 *
57	 * @var    string
58	 * @since  1.7.0
59	 */
60	public $language = 'en-gb';
61
62	/**
63	 * Contains the document direction setting
64	 *
65	 * @var    string
66	 * @since  1.7.0
67	 */
68	public $direction = 'ltr';
69
70	/**
71	 * Document generator
72	 *
73	 * @var    string
74	 * @since  1.7.0
75	 */
76	public $_generator = 'Joomla! - Open Source Content Management';
77
78	/**
79	 * Document modified date
80	 *
81	 * @var    string|Date
82	 * @since  1.7.0
83	 */
84	public $_mdate = '';
85
86	/**
87	 * Tab string
88	 *
89	 * @var    string
90	 * @since  1.7.0
91	 */
92	public $_tab = "\11";
93
94	/**
95	 * Contains the line end string
96	 *
97	 * @var    string
98	 * @since  1.7.0
99	 */
100	public $_lineEnd = "\12";
101
102	/**
103	 * Contains the character encoding string
104	 *
105	 * @var    string
106	 * @since  1.7.0
107	 */
108	public $_charset = 'utf-8';
109
110	/**
111	 * Document mime type
112	 *
113	 * @var    string
114	 * @since  1.7.0
115	 */
116	public $_mime = '';
117
118	/**
119	 * Document namespace
120	 *
121	 * @var    string
122	 * @since  1.7.0
123	 */
124	public $_namespace = '';
125
126	/**
127	 * Document profile
128	 *
129	 * @var    string
130	 * @since  1.7.0
131	 */
132	public $_profile = '';
133
134	/**
135	 * Array of linked scripts
136	 *
137	 * @var    array
138	 * @since  1.7.0
139	 */
140	public $_scripts = array();
141
142	/**
143	 * Array of scripts placed in the header
144	 *
145	 * @var    array
146	 * @since  1.7.0
147	 */
148	public $_script = array();
149
150	/**
151	 * Array of scripts options
152	 *
153	 * @var    array
154	 */
155	protected $scriptOptions = array();
156
157	/**
158	 * Array of linked style sheets
159	 *
160	 * @var    array
161	 * @since  1.7.0
162	 */
163	public $_styleSheets = array();
164
165	/**
166	 * Array of included style declarations
167	 *
168	 * @var    array
169	 * @since  1.7.0
170	 */
171	public $_style = array();
172
173	/**
174	 * Array of meta tags
175	 *
176	 * @var    array
177	 * @since  1.7.0
178	 */
179	public $_metaTags = array();
180
181	/**
182	 * The rendering engine
183	 *
184	 * @var    object
185	 * @since  1.7.0
186	 */
187	public $_engine = null;
188
189	/**
190	 * The document type
191	 *
192	 * @var    string
193	 * @since  1.7.0
194	 */
195	public $_type = null;
196
197	/**
198	 * Array of buffered output
199	 *
200	 * @var    mixed (depends on the renderer)
201	 * @since  1.7.0
202	 */
203	public static $_buffer = null;
204
205	/**
206	 * Document instances container.
207	 *
208	 * @var    array
209	 * @since  1.7.3
210	 */
211	protected static $instances = array();
212
213	/**
214	 * Media version added to assets
215	 *
216	 * @var    string
217	 * @since  3.2
218	 */
219	protected $mediaVersion = null;
220
221	/**
222	 * Class constructor.
223	 *
224	 * @param   array  $options  Associative array of options
225	 *
226	 * @since   1.7.0
227	 */
228	public function __construct($options = array())
229	{
230		if (array_key_exists('lineend', $options))
231		{
232			$this->setLineEnd($options['lineend']);
233		}
234
235		if (array_key_exists('charset', $options))
236		{
237			$this->setCharset($options['charset']);
238		}
239
240		if (array_key_exists('language', $options))
241		{
242			$this->setLanguage($options['language']);
243		}
244
245		if (array_key_exists('direction', $options))
246		{
247			$this->setDirection($options['direction']);
248		}
249
250		if (array_key_exists('tab', $options))
251		{
252			$this->setTab($options['tab']);
253		}
254
255		if (array_key_exists('link', $options))
256		{
257			$this->setLink($options['link']);
258		}
259
260		if (array_key_exists('base', $options))
261		{
262			$this->setBase($options['base']);
263		}
264
265		if (array_key_exists('mediaversion', $options))
266		{
267			$this->setMediaVersion($options['mediaversion']);
268		}
269	}
270
271	/**
272	 * Returns the global Document object, only creating it
273	 * if it doesn't already exist.
274	 *
275	 * @param   string  $type        The document type to instantiate
276	 * @param   array   $attributes  Array of attributes
277	 *
278	 * @return  object  The document object.
279	 *
280	 * @since   1.7.0
281	 */
282	public static function getInstance($type = 'html', $attributes = array())
283	{
284		$signature = serialize(array($type, $attributes));
285
286		if (empty(self::$instances[$signature]))
287		{
288			$type  = preg_replace('/[^A-Z0-9_\.-]/i', '', $type);
289			$ntype = null;
290
291			// Determine the path and class
292			$class = __NAMESPACE__ . '\\' . ucfirst($type) . 'Document';
293
294			if (!class_exists($class))
295			{
296				$class = 'JDocument' . ucfirst($type);
297			}
298
299			if (!class_exists($class))
300			{
301				// @deprecated 4.0 - Document objects should be autoloaded instead
302				$path = __DIR__ . '/' . $type . '/' . $type . '.php';
303
304				\JLoader::register($class, $path);
305
306				if (class_exists($class))
307				{
308					\JLog::add('Non-autoloadable Document subclasses are deprecated, support will be removed in 4.0.', \JLog::WARNING, 'deprecated');
309				}
310				// Default to the raw format
311				else
312				{
313					$ntype = $type;
314					$class = 'JDocumentRaw';
315				}
316			}
317
318			$instance = new $class($attributes);
319			self::$instances[$signature] = $instance;
320
321			if (!is_null($ntype))
322			{
323				// Set the type to the Document type originally requested
324				$instance->setType($ntype);
325			}
326		}
327
328		return self::$instances[$signature];
329	}
330
331	/**
332	 * Set the document type
333	 *
334	 * @param   string  $type  Type document is to set to
335	 *
336	 * @return  Document instance of $this to allow chaining
337	 *
338	 * @since   1.7.0
339	 */
340	public function setType($type)
341	{
342		$this->_type = $type;
343
344		return $this;
345	}
346
347	/**
348	 * Returns the document type
349	 *
350	 * @return  string
351	 *
352	 * @since   1.7.0
353	 */
354	public function getType()
355	{
356		return $this->_type;
357	}
358
359	/**
360	 * Get the contents of the document buffer
361	 *
362	 * @return  mixed
363	 *
364	 * @since   1.7.0
365	 */
366	public function getBuffer()
367	{
368		return self::$_buffer;
369	}
370
371	/**
372	 * Set the contents of the document buffer
373	 *
374	 * @param   string  $content  The content to be set in the buffer.
375	 * @param   array   $options  Array of optional elements.
376	 *
377	 * @return  Document instance of $this to allow chaining
378	 *
379	 * @since   1.7.0
380	 */
381	public function setBuffer($content, $options = array())
382	{
383		self::$_buffer = $content;
384
385		return $this;
386	}
387
388	/**
389	 * Gets a meta tag.
390	 *
391	 * @param   string  $name       Name of the meta HTML tag
392	 * @param   string  $attribute  Attribute to use in the meta HTML tag
393	 *
394	 * @return  string
395	 *
396	 * @since   1.7.0
397	 */
398	public function getMetaData($name, $attribute = 'name')
399	{
400		// B/C old http_equiv parameter.
401		if (!is_string($attribute))
402		{
403			$attribute = $attribute == true ? 'http-equiv' : 'name';
404		}
405
406		if ($name == 'generator')
407		{
408			$result = $this->getGenerator();
409		}
410		elseif ($name == 'description')
411		{
412			$result = $this->getDescription();
413		}
414		else
415		{
416			$result = isset($this->_metaTags[$attribute]) && isset($this->_metaTags[$attribute][$name]) ? $this->_metaTags[$attribute][$name] : '';
417		}
418
419		return $result;
420	}
421
422	/**
423	 * Sets or alters a meta tag.
424	 *
425	 * @param   string  $name       Name of the meta HTML tag
426	 * @param   mixed   $content    Value of the meta HTML tag as array or string
427	 * @param   string  $attribute  Attribute to use in the meta HTML tag
428	 *
429	 * @return  Document instance of $this to allow chaining
430	 *
431	 * @since   1.7.0
432	 */
433	public function setMetaData($name, $content, $attribute = 'name')
434	{
435		// Pop the element off the end of array if target function expects a string or this http_equiv parameter.
436		if (is_array($content) && (in_array($name, array('generator', 'description')) || !is_string($attribute)))
437		{
438			$content = array_pop($content);
439		}
440
441		// B/C old http_equiv parameter.
442		if (!is_string($attribute))
443		{
444			$attribute = $attribute == true ? 'http-equiv' : 'name';
445		}
446
447		if ($name == 'generator')
448		{
449			$this->setGenerator($content);
450		}
451		elseif ($name == 'description')
452		{
453			$this->setDescription($content);
454		}
455		else
456		{
457			$this->_metaTags[$attribute][$name] = $content;
458		}
459
460		return $this;
461	}
462
463	/**
464	 * Adds a linked script to the page
465	 *
466	 * @param   string  $url      URL to the linked script.
467	 * @param   array   $options  Array of options. Example: array('version' => 'auto', 'conditional' => 'lt IE 9')
468	 * @param   array   $attribs  Array of attributes. Example: array('id' => 'scriptid', 'async' => 'async', 'data-test' => 1)
469	 *
470	 * @return  Document instance of $this to allow chaining
471	 *
472	 * @since   1.7.0
473	 * @deprecated 4.0  The (url, mime, defer, async) method signature is deprecated, use (url, options, attributes) instead.
474	 */
475	public function addScript($url, $options = array(), $attribs = array())
476	{
477		// B/C before 3.7.0
478		if (!is_array($options) && (!is_array($attribs) || $attribs === array()))
479		{
480			\JLog::add('The addScript method signature used has changed, use (url, options, attributes) instead.', \JLog::WARNING, 'deprecated');
481
482			$argList = func_get_args();
483			$options = array();
484			$attribs = array();
485
486			// Old mime type parameter.
487			if (!empty($argList[1]))
488			{
489				$attribs['mime'] = $argList[1];
490			}
491
492			// Old defer parameter.
493			if (isset($argList[2]) && $argList[2])
494			{
495				$attribs['defer'] = true;
496			}
497
498			// Old async parameter.
499			if (isset($argList[3]) && $argList[3])
500			{
501				$attribs['async'] = true;
502			}
503		}
504
505		// Default value for type.
506		if (!isset($attribs['type']) && !isset($attribs['mime']))
507		{
508			$attribs['type'] = 'text/javascript';
509		}
510
511		$this->_scripts[$url]            = isset($this->_scripts[$url]) ? array_replace($this->_scripts[$url], $attribs) : $attribs;
512		$this->_scripts[$url]['options'] = isset($this->_scripts[$url]['options']) ? array_replace($this->_scripts[$url]['options'], $options) : $options;
513
514		return $this;
515	}
516
517	/**
518	 * Adds a linked script to the page with a version to allow to flush it. Ex: myscript.js?54771616b5bceae9df03c6173babf11d
519	 * If not specified Joomla! automatically handles versioning
520	 *
521	 * @param   string  $url      URL to the linked script.
522	 * @param   array   $options  Array of options. Example: array('version' => 'auto', 'conditional' => 'lt IE 9')
523	 * @param   array   $attribs  Array of attributes. Example: array('id' => 'scriptid', 'async' => 'async', 'data-test' => 1)
524	 *
525	 * @return  Document instance of $this to allow chaining
526	 *
527	 * @since   3.2
528	 * @deprecated 4.0  This method is deprecated, use addScript(url, options, attributes) instead.
529	 */
530	public function addScriptVersion($url, $options = array(), $attribs = array())
531	{
532		\JLog::add('The method is deprecated, use addScript(url, attributes, options) instead.', \JLog::WARNING, 'deprecated');
533
534		// B/C before 3.7.0
535		if (!is_array($options) && (!is_array($attribs) || $attribs === array()))
536		{
537			$argList = func_get_args();
538			$options = array();
539			$attribs = array();
540
541			// Old version parameter.
542			$options['version'] = isset($argList[1]) && !is_null($argList[1]) ? $argList[1] : 'auto';
543
544			// Old mime type parameter.
545			if (!empty($argList[2]))
546			{
547				$attribs['mime'] = $argList[2];
548			}
549
550			// Old defer parameter.
551			if (isset($argList[3]) && $argList[3])
552			{
553				$attribs['defer'] = true;
554			}
555
556			// Old async parameter.
557			if (isset($argList[4]) && $argList[4])
558			{
559				$attribs['async'] = true;
560			}
561		}
562		// Default value for version.
563		else
564		{
565			$options['version'] = 'auto';
566		}
567
568		return $this->addScript($url, $options, $attribs);
569	}
570
571	/**
572	 * Adds a script to the page
573	 *
574	 * @param   string  $content  Script
575	 * @param   string  $type     Scripting mime (defaults to 'text/javascript')
576	 *
577	 * @return  Document instance of $this to allow chaining
578	 *
579	 * @since   1.7.0
580	 */
581	public function addScriptDeclaration($content, $type = 'text/javascript')
582	{
583		if (!isset($this->_script[strtolower($type)]))
584		{
585			$this->_script[strtolower($type)] = $content;
586		}
587		else
588		{
589			$this->_script[strtolower($type)] .= chr(13) . $content;
590		}
591
592		return $this;
593	}
594
595	/**
596	 * Add option for script
597	 *
598	 * @param   string  $key      Name in Storage
599	 * @param   mixed   $options  Scrip options as array or string
600	 * @param   bool    $merge    Whether merge with existing (true) or replace (false)
601	 *
602	 * @return  Document instance of $this to allow chaining
603	 *
604	 * @since   3.5
605	 */
606	public function addScriptOptions($key, $options, $merge = true)
607	{
608		if (empty($this->scriptOptions[$key]))
609		{
610			$this->scriptOptions[$key] = array();
611		}
612
613		if ($merge && is_array($options))
614		{
615			$this->scriptOptions[$key] = array_replace_recursive($this->scriptOptions[$key], $options);
616		}
617		else
618		{
619			$this->scriptOptions[$key] = $options;
620		}
621
622		return $this;
623	}
624
625	/**
626	 * Get script(s) options
627	 *
628	 * @param   string  $key  Name in Storage
629	 *
630	 * @return  array  Options for given $key, or all script options
631	 *
632	 * @since   3.5
633	 */
634	public function getScriptOptions($key = null)
635	{
636		if ($key)
637		{
638			return (empty($this->scriptOptions[$key])) ? array() : $this->scriptOptions[$key];
639		}
640		else
641		{
642			return $this->scriptOptions;
643		}
644	}
645
646	/**
647	 * Adds a linked stylesheet to the page
648	 *
649	 * @param   string  $url      URL to the linked style sheet
650	 * @param   array   $options  Array of options. Example: array('version' => 'auto', 'conditional' => 'lt IE 9')
651	 * @param   array   $attribs  Array of attributes. Example: array('id' => 'stylesheet', 'data-test' => 1)
652	 *
653	 * @return  Document instance of $this to allow chaining
654	 *
655	 * @since   1.7.0
656	 * @deprecated 4.0  The (url, mime, media, attribs) method signature is deprecated, use (url, options, attributes) instead.
657	 */
658	public function addStyleSheet($url, $options = array(), $attribs = array())
659	{
660		// B/C before 3.7.0
661		if (is_string($options))
662		{
663			\JLog::add('The addStyleSheet method signature used has changed, use (url, options, attributes) instead.', \JLog::WARNING, 'deprecated');
664
665			$argList = func_get_args();
666			$options = array();
667			$attribs = array();
668
669			// Old mime type parameter.
670			if (!empty($argList[1]))
671			{
672				$attribs['type'] = $argList[1];
673			}
674
675			// Old media parameter.
676			if (isset($argList[2]) && $argList[2])
677			{
678				$attribs['media'] = $argList[2];
679			}
680
681			// Old attribs parameter.
682			if (isset($argList[3]) && $argList[3])
683			{
684				$attribs = array_replace($attribs, $argList[3]);
685			}
686		}
687
688		// Default value for type.
689		if (!isset($attribs['type']) && !isset($attribs['mime']))
690		{
691			$attribs['type'] = 'text/css';
692		}
693
694		$this->_styleSheets[$url] = isset($this->_styleSheets[$url]) ? array_replace($this->_styleSheets[$url], $attribs) : $attribs;
695
696		if (isset($this->_styleSheets[$url]['options']))
697		{
698			$this->_styleSheets[$url]['options'] = array_replace($this->_styleSheets[$url]['options'], $options);
699		}
700		else
701		{
702			$this->_styleSheets[$url]['options'] = $options;
703		}
704
705		return $this;
706	}
707
708	/**
709	 * Adds a linked stylesheet version to the page. Ex: template.css?54771616b5bceae9df03c6173babf11d
710	 * If not specified Joomla! automatically handles versioning
711	 *
712	 * @param   string  $url      URL to the linked style sheet
713	 * @param   array   $options  Array of options. Example: array('version' => 'auto', 'conditional' => 'lt IE 9')
714	 * @param   array   $attribs  Array of attributes. Example: array('id' => 'stylesheet', 'data-test' => 1)
715	 *
716	 * @return  Document instance of $this to allow chaining
717	 *
718	 * @since   3.2
719	 * @deprecated 4.0  This method is deprecated, use addStyleSheet(url, options, attributes) instead.
720	 */
721	public function addStyleSheetVersion($url, $options = array(), $attribs = array())
722	{
723		\JLog::add('The method is deprecated, use addStyleSheet(url, attributes, options) instead.', \JLog::WARNING, 'deprecated');
724
725		// B/C before 3.7.0
726		if (!is_array($options) && (!is_array($attribs) || $attribs === array()))
727		{
728			$argList = func_get_args();
729			$options = array();
730			$attribs = array();
731
732			// Old version parameter.
733			$options['version'] = isset($argList[1]) && !is_null($argList[1]) ? $argList[1] : 'auto';
734
735			// Old mime type parameter.
736			if (!empty($argList[2]))
737			{
738				$attribs['mime'] = $argList[2];
739			}
740
741			// Old media parameter.
742			if (isset($argList[3]) && $argList[3])
743			{
744				$attribs['media'] = $argList[3];
745			}
746
747			// Old attribs parameter.
748			if (isset($argList[4]) && $argList[4])
749			{
750				$attribs = array_replace($attribs, $argList[4]);
751			}
752		}
753		// Default value for version.
754		else
755		{
756			$options['version'] = 'auto';
757		}
758
759		return $this->addStyleSheet($url, $options, $attribs);
760	}
761
762	/**
763	 * Adds a stylesheet declaration to the page
764	 *
765	 * @param   string  $content  Style declarations
766	 * @param   string  $type     Type of stylesheet (defaults to 'text/css')
767	 *
768	 * @return  Document instance of $this to allow chaining
769	 *
770	 * @since   1.7.0
771	 */
772	public function addStyleDeclaration($content, $type = 'text/css')
773	{
774		if (!isset($this->_style[strtolower($type)]))
775		{
776			$this->_style[strtolower($type)] = $content;
777		}
778		else
779		{
780			$this->_style[strtolower($type)] .= chr(13) . $content;
781		}
782
783		return $this;
784	}
785
786	/**
787	 * Sets the document charset
788	 *
789	 * @param   string  $type  Charset encoding string
790	 *
791	 * @return  Document instance of $this to allow chaining
792	 *
793	 * @since   1.7.0
794	 */
795	public function setCharset($type = 'utf-8')
796	{
797		$this->_charset = $type;
798
799		return $this;
800	}
801
802	/**
803	 * Returns the document charset encoding.
804	 *
805	 * @return  string
806	 *
807	 * @since   1.7.0
808	 */
809	public function getCharset()
810	{
811		return $this->_charset;
812	}
813
814	/**
815	 * Sets the global document language declaration. Default is English (en-gb).
816	 *
817	 * @param   string  $lang  The language to be set
818	 *
819	 * @return  Document instance of $this to allow chaining
820	 *
821	 * @since   1.7.0
822	 */
823	public function setLanguage($lang = 'en-gb')
824	{
825		$this->language = strtolower($lang);
826
827		return $this;
828	}
829
830	/**
831	 * Returns the document language.
832	 *
833	 * @return  string
834	 *
835	 * @since   1.7.0
836	 */
837	public function getLanguage()
838	{
839		return $this->language;
840	}
841
842	/**
843	 * Sets the global document direction declaration. Default is left-to-right (ltr).
844	 *
845	 * @param   string  $dir  The language direction to be set
846	 *
847	 * @return  Document instance of $this to allow chaining
848	 *
849	 * @since   1.7.0
850	 */
851	public function setDirection($dir = 'ltr')
852	{
853		$this->direction = strtolower($dir);
854
855		return $this;
856	}
857
858	/**
859	 * Returns the document direction declaration.
860	 *
861	 * @return  string
862	 *
863	 * @since   1.7.0
864	 */
865	public function getDirection()
866	{
867		return $this->direction;
868	}
869
870	/**
871	 * Sets the title of the document
872	 *
873	 * @param   string  $title  The title to be set
874	 *
875	 * @return  Document instance of $this to allow chaining
876	 *
877	 * @since   1.7.0
878	 */
879	public function setTitle($title)
880	{
881		$this->title = $title;
882
883		return $this;
884	}
885
886	/**
887	 * Return the title of the document.
888	 *
889	 * @return  string
890	 *
891	 * @since   1.7.0
892	 */
893	public function getTitle()
894	{
895		return $this->title;
896	}
897
898	/**
899	 * Set the assets version
900	 *
901	 * @param   string  $mediaVersion  Media version to use
902	 *
903	 * @return  Document instance of $this to allow chaining
904	 *
905	 * @since   3.2
906	 */
907	public function setMediaVersion($mediaVersion)
908	{
909		$this->mediaVersion = strtolower($mediaVersion);
910
911		return $this;
912	}
913
914	/**
915	 * Return the media version
916	 *
917	 * @return  string
918	 *
919	 * @since   3.2
920	 */
921	public function getMediaVersion()
922	{
923		return $this->mediaVersion;
924	}
925
926	/**
927	 * Sets the base URI of the document
928	 *
929	 * @param   string  $base  The base URI to be set
930	 *
931	 * @return  Document instance of $this to allow chaining
932	 *
933	 * @since   1.7.0
934	 */
935	public function setBase($base)
936	{
937		$this->base = $base;
938
939		return $this;
940	}
941
942	/**
943	 * Return the base URI of the document.
944	 *
945	 * @return  string
946	 *
947	 * @since   1.7.0
948	 */
949	public function getBase()
950	{
951		return $this->base;
952	}
953
954	/**
955	 * Sets the description of the document
956	 *
957	 * @param   string  $description  The description to set
958	 *
959	 * @return  Document instance of $this to allow chaining
960	 *
961	 * @since   1.7.0
962	 */
963	public function setDescription($description)
964	{
965		$this->description = $description;
966
967		return $this;
968	}
969
970	/**
971	 * Return the description of the document.
972	 *
973	 * @return  string
974	 *
975	 * @since    1.7.0
976	 */
977	public function getDescription()
978	{
979		return $this->description;
980	}
981
982	/**
983	 * Sets the document link
984	 *
985	 * @param   string  $url  A url
986	 *
987	 * @return  Document instance of $this to allow chaining
988	 *
989	 * @since   1.7.0
990	 */
991	public function setLink($url)
992	{
993		$this->link = $url;
994
995		return $this;
996	}
997
998	/**
999	 * Returns the document base url
1000	 *
1001	 * @return string
1002	 *
1003	 * @since   1.7.0
1004	 */
1005	public function getLink()
1006	{
1007		return $this->link;
1008	}
1009
1010	/**
1011	 * Sets the document generator
1012	 *
1013	 * @param   string  $generator  The generator to be set
1014	 *
1015	 * @return  Document instance of $this to allow chaining
1016	 *
1017	 * @since   1.7.0
1018	 */
1019	public function setGenerator($generator)
1020	{
1021		$this->_generator = $generator;
1022
1023		return $this;
1024	}
1025
1026	/**
1027	 * Returns the document generator
1028	 *
1029	 * @return  string
1030	 *
1031	 * @since   1.7.0
1032	 */
1033	public function getGenerator()
1034	{
1035		return $this->_generator;
1036	}
1037
1038	/**
1039	 * Sets the document modified date
1040	 *
1041	 * @param   string|Date  $date  The date to be set
1042	 *
1043	 * @return  Document instance of $this to allow chaining
1044	 *
1045	 * @since   1.7.0
1046	 * @throws  \InvalidArgumentException
1047	 */
1048	public function setModifiedDate($date)
1049	{
1050		if (!is_string($date) && !($date instanceof Date))
1051		{
1052			throw new \InvalidArgumentException(
1053				sprintf(
1054					'The $date parameter of %1$s must be a string or a %2$s instance, a %3$s was given.',
1055					__METHOD__ . '()',
1056					'Joomla\\CMS\\Date\\Date',
1057					gettype($date) === 'object' ? (get_class($date) . ' instance') : gettype($date)
1058				)
1059			);
1060		}
1061
1062		$this->_mdate = $date;
1063
1064		return $this;
1065	}
1066
1067	/**
1068	 * Returns the document modified date
1069	 *
1070	 * @return  string|Date
1071	 *
1072	 * @since   1.7.0
1073	 */
1074	public function getModifiedDate()
1075	{
1076		return $this->_mdate;
1077	}
1078
1079	/**
1080	 * Sets the document MIME encoding that is sent to the browser.
1081	 *
1082	 * This usually will be text/html because most browsers cannot yet
1083	 * accept the proper mime settings for XHTML: application/xhtml+xml
1084	 * and to a lesser extent application/xml and text/xml. See the W3C note
1085	 * ({@link http://www.w3.org/TR/xhtml-media-types/
1086	 * http://www.w3.org/TR/xhtml-media-types/}) for more details.
1087	 *
1088	 * @param   string   $type  The document type to be sent
1089	 * @param   boolean  $sync  Should the type be synced with HTML?
1090	 *
1091	 * @return  Document instance of $this to allow chaining
1092	 *
1093	 * @since   1.7.0
1094	 *
1095	 * @link    http://www.w3.org/TR/xhtml-media-types
1096	 */
1097	public function setMimeEncoding($type = 'text/html', $sync = true)
1098	{
1099		$this->_mime = strtolower($type);
1100
1101		// Syncing with metadata
1102		if ($sync)
1103		{
1104			$this->setMetaData('content-type', $type . '; charset=' . $this->_charset, true);
1105		}
1106
1107		return $this;
1108	}
1109
1110	/**
1111	 * Return the document MIME encoding that is sent to the browser.
1112	 *
1113	 * @return  string
1114	 *
1115	 * @since   1.7.0
1116	 */
1117	public function getMimeEncoding()
1118	{
1119		return $this->_mime;
1120	}
1121
1122	/**
1123	 * Sets the line end style to Windows, Mac, Unix or a custom string.
1124	 *
1125	 * @param   string  $style  "win", "mac", "unix" or custom string.
1126	 *
1127	 * @return  Document instance of $this to allow chaining
1128	 *
1129	 * @since   1.7.0
1130	 */
1131	public function setLineEnd($style)
1132	{
1133		switch ($style)
1134		{
1135			case 'win':
1136				$this->_lineEnd = "\15\12";
1137				break;
1138			case 'unix':
1139				$this->_lineEnd = "\12";
1140				break;
1141			case 'mac':
1142				$this->_lineEnd = "\15";
1143				break;
1144			default:
1145				$this->_lineEnd = $style;
1146		}
1147
1148		return $this;
1149	}
1150
1151	/**
1152	 * Returns the lineEnd
1153	 *
1154	 * @return  string
1155	 *
1156	 * @since   1.7.0
1157	 */
1158	public function _getLineEnd()
1159	{
1160		return $this->_lineEnd;
1161	}
1162
1163	/**
1164	 * Sets the string used to indent HTML
1165	 *
1166	 * @param   string  $string  String used to indent ("\11", "\t", '  ', etc.).
1167	 *
1168	 * @return  Document instance of $this to allow chaining
1169	 *
1170	 * @since   1.7.0
1171	 */
1172	public function setTab($string)
1173	{
1174		$this->_tab = $string;
1175
1176		return $this;
1177	}
1178
1179	/**
1180	 * Returns a string containing the unit for indenting HTML
1181	 *
1182	 * @return  string
1183	 *
1184	 * @since   1.7.0
1185	 */
1186	public function _getTab()
1187	{
1188		return $this->_tab;
1189	}
1190
1191	/**
1192	 * Load a renderer
1193	 *
1194	 * @param   string  $type  The renderer type
1195	 *
1196	 * @return  DocumentRenderer
1197	 *
1198	 * @since   1.7.0
1199	 * @throws  \RuntimeException
1200	 */
1201	public function loadRenderer($type)
1202	{
1203		// Determine the path and class
1204		$class = __NAMESPACE__ . '\\Renderer\\' . ucfirst($this->getType()) . '\\' . ucfirst($type) . 'Renderer';
1205
1206		if (!class_exists($class))
1207		{
1208			$class = 'JDocumentRenderer' . ucfirst($this->getType()) . ucfirst($type);
1209		}
1210
1211		if (!class_exists($class))
1212		{
1213			// "Legacy" class name structure
1214			$class = 'JDocumentRenderer' . $type;
1215
1216			if (!class_exists($class))
1217			{
1218				// @deprecated 4.0 - Non-autoloadable class support is deprecated, only log a message though if a file is found
1219				$path = __DIR__ . '/' . $this->getType() . '/renderer/' . $type . '.php';
1220
1221				if (!file_exists($path))
1222				{
1223					throw new \RuntimeException('Unable to load renderer class', 500);
1224				}
1225
1226				\JLoader::register($class, $path);
1227
1228				\JLog::add('Non-autoloadable JDocumentRenderer subclasses are deprecated, support will be removed in 4.0.', \JLog::WARNING, 'deprecated');
1229
1230				// If the class still doesn't exist after including the path, we've got issues
1231				if (!class_exists($class))
1232				{
1233					throw new \RuntimeException('Unable to load renderer class', 500);
1234				}
1235			}
1236		}
1237
1238		return new $class($this);
1239	}
1240
1241	/**
1242	 * Parses the document and prepares the buffers
1243	 *
1244	 * @param   array  $params  The array of parameters
1245	 *
1246	 * @return  Document instance of $this to allow chaining
1247	 *
1248	 * @since   1.7.0
1249	 */
1250	public function parse($params = array())
1251	{
1252		return $this;
1253	}
1254
1255	/**
1256	 * Outputs the document
1257	 *
1258	 * @param   boolean  $cache   If true, cache the output
1259	 * @param   array    $params  Associative array of attributes
1260	 *
1261	 * @return  void  The rendered data
1262	 *
1263	 * @since   1.7.0
1264	 */
1265	public function render($cache = false, $params = array())
1266	{
1267		$app = \JFactory::getApplication();
1268
1269		if ($mdate = $this->getModifiedDate())
1270		{
1271			if (!($mdate instanceof Date))
1272			{
1273				$mdate = new Date($mdate);
1274			}
1275
1276			$app->modifiedDate = $mdate;
1277		}
1278
1279		$app->mimeType = $this->_mime;
1280		$app->charSet  = $this->_charset;
1281	}
1282}
1283