1<?php
2/**
3 * CClientScript class file.
4 *
5 * @author Qiang Xue <qiang.xue@gmail.com>
6 * @link http://www.yiiframework.com/
7 * @copyright 2008-2013 Yii Software LLC
8 * @license http://www.yiiframework.com/license/
9 */
10
11/**
12 * CClientScript manages JavaScript and CSS stylesheets for views.
13 *
14 * @property string $coreScriptUrl The base URL of all core javascript files.
15 *
16 * @author Qiang Xue <qiang.xue@gmail.com>
17 * @package system.web
18 * @since 1.0
19 */
20class CClientScript extends CApplicationComponent
21{
22	/**
23	 * The script is rendered in the head section right before the title element.
24	 */
25	const POS_HEAD=0;
26	/**
27	 * The script is rendered at the beginning of the body section.
28	 */
29	const POS_BEGIN=1;
30	/**
31	 * The script is rendered at the end of the body section.
32	 */
33	const POS_END=2;
34	/**
35	 * The script is rendered inside window onload function.
36	 */
37	const POS_LOAD=3;
38	/**
39	 * The body script is rendered inside a jQuery ready function.
40	 */
41	const POS_READY=4;
42
43	/**
44	 * @var boolean whether JavaScript should be enabled. Defaults to true.
45	 */
46	public $enableJavaScript=true;
47	/**
48	 * @var array the mapping between script file names and the corresponding script URLs.
49	 * The array keys are script file names (without directory part) and the array values are the corresponding URLs.
50	 * If an array value is false, the corresponding script file will not be rendered.
51	 * If an array key is '*.js' or '*.css', the corresponding URL will replace all
52	 * JavaScript files or CSS files, respectively.
53	 *
54	 * This property is mainly used to optimize the generated HTML pages
55	 * by merging different scripts files into fewer and optimized script files.
56	 */
57	public $scriptMap=array();
58	/**
59	 * @var array list of custom script packages (name=>package spec).
60	 * This property keeps a list of named script packages, each of which can contain
61	 * a set of CSS and/or JavaScript script files, and their dependent package names.
62	 * By calling {@link registerPackage}, one can register a whole package of client
63	 * scripts together with their dependent packages and render them in the HTML output.
64	 *
65	 * The array structure is as follows:
66	 * <pre>
67	 * array(
68	 *   'package-name'=>array(
69	 *     'basePath'=>'alias of the directory containing the script files',
70	 *     'baseUrl'=>'base URL for the script files',
71	 *     'js'=>array(list of js files relative to basePath/baseUrl),
72	 *     'css'=>array(list of css files relative to basePath/baseUrl),
73	 *     'depends'=>array(list of dependent packages),
74	 *   ),
75	 *   ......
76	 * )
77	 * </pre>
78	 *
79	 * The JS and CSS files listed are relative to 'basePath'.
80	 * For example, if 'basePath' is 'application.assets', a script named 'comments.js'
81	 * will refer to the file 'protected/assets/comments.js'.
82	 *
83	 * When a script is being rendered in HTML, it will be prefixed with 'baseUrl'.
84	 * For example, if 'baseUrl' is '/assets', the 'comments.js' script will be rendered
85	 * using URL '/assets/comments.js'.
86	 *
87	 * If 'baseUrl' does not start with '/', the relative URL of the application entry
88	 * script will be inserted at the beginning. For example, if 'baseUrl' is 'assets'
89	 * and the current application runs with the URL 'http://localhost/demo/index.php',
90	 * then the 'comments.js' script will be rendered using URL '/demo/assets/comments.js'.
91	 *
92	 * If 'baseUrl' is not set, the script will be published by {@link CAssetManager}
93	 * and the corresponding published URL will be used.
94	 *
95	 * When calling {@link registerPackage} to register a script package,
96	 * this property will be checked first followed by {@link corePackages}.
97	 * If a package is found, it will be registered for rendering later on.
98	 *
99	 * @since 1.1.7
100	 */
101	public $packages=array();
102	/**
103	 * @var array list of core script packages (name=>package spec).
104	 * Please refer to {@link packages} for details about package spec.
105	 *
106	 * By default, the core script packages are specified in 'framework/web/js/packages.php'.
107	 * You may configure this property to customize the core script packages.
108	 *
109	 * When calling {@link registerPackage} to register a script package,
110	 * {@link packages} will be checked first followed by this property.
111	 * If a package is found, it will be registered for rendering later on.
112	 *
113	 * @since 1.1.7
114	 */
115	public $corePackages;
116	/**
117	 * @var array the registered JavaScript code blocks (position, key => code)
118	 */
119	public $scripts=array();
120	/**
121	 * @var array the registered CSS files (CSS URL=>media type).
122	 */
123	protected $cssFiles=array();
124	/**
125	 * @var array the registered JavaScript files (position, key => URL)
126	 */
127	protected $scriptFiles=array();
128	/**
129	 * @var array the registered head meta tags. Each array element represents an option array
130	 * that will be passed as the last parameter of {@link CHtml::metaTag}.
131	 * @since 1.1.3
132	 */
133	protected $metaTags=array();
134	/**
135	 * @var array the registered head link tags. Each array element represents an option array
136	 * that will be passed as the last parameter of {@link CHtml::linkTag}.
137	 * @since 1.1.3
138	 */
139	protected $linkTags=array();
140	/**
141	 * @var array the registered css code blocks (key => array(CSS code, media type)).
142	 * @since 1.1.3
143	 */
144	protected $css=array();
145	/**
146	 * @var boolean whether there are any javascript or css to be rendered.
147	 * @since 1.1.7
148	 */
149	protected $hasScripts=false;
150	/**
151	 * @var array the registered script packages (name => package spec)
152	 * @since 1.1.7
153	 */
154	protected $coreScripts=array();
155	/**
156	 * @var integer Where the scripts registered using {@link registerCoreScript} or {@link registerPackage}
157	 * will be inserted in the page. This can be one of the CClientScript::POS_* constants.
158	 * Defaults to CClientScript::POS_HEAD.
159	 * @since 1.1.3
160	 */
161	public $coreScriptPosition=self::POS_HEAD;
162	/**
163	 * @var integer Where the scripts registered using {@link registerScriptFile} will be inserted in the page.
164	 * This can be one of the CClientScript::POS_* constants.
165	 * Defaults to CClientScript::POS_HEAD.
166	 * @since 1.1.11
167	 */
168	public $defaultScriptFilePosition=self::POS_HEAD;
169	/**
170	 * @var integer Where the scripts registered using {@link registerScript} will be inserted in the page.
171	 * This can be one of the CClientScript::POS_* constants.
172	 * Defaults to CClientScript::POS_READY.
173	 * @since 1.1.11
174	 */
175	public $defaultScriptPosition=self::POS_READY;
176
177	private $_baseUrl;
178
179	/**
180	 * Cleans all registered scripts.
181	 */
182	public function reset()
183	{
184		$this->hasScripts=false;
185		$this->coreScripts=array();
186		$this->cssFiles=array();
187		$this->css=array();
188		$this->scriptFiles=array();
189		$this->scripts=array();
190		$this->metaTags=array();
191		$this->linkTags=array();
192
193		$this->recordCachingAction('clientScript','reset',array());
194	}
195
196	/**
197	 * Renders the registered scripts.
198	 * This method is called in {@link CController::render} when it finishes
199	 * rendering content. CClientScript thus gets a chance to insert script tags
200	 * at <code>head</code> and <code>body</code> sections in the HTML output.
201	 * @param string $output the existing output that needs to be inserted with script tags
202	 */
203	public function render(&$output)
204	{
205		if(!$this->hasScripts)
206			return;
207
208		$this->renderCoreScripts();
209
210		if(!empty($this->scriptMap))
211			$this->remapScripts();
212
213		$this->unifyScripts();
214
215		$this->renderHead($output);
216		if($this->enableJavaScript)
217		{
218			$this->renderBodyBegin($output);
219			$this->renderBodyEnd($output);
220		}
221	}
222
223	/**
224	 * Removes duplicated scripts from {@link scriptFiles}.
225	 * @since 1.1.5
226	 */
227	protected function unifyScripts()
228	{
229		if(!$this->enableJavaScript)
230			return;
231		$map=array();
232		if(isset($this->scriptFiles[self::POS_HEAD]))
233			$map=$this->scriptFiles[self::POS_HEAD];
234
235		if(isset($this->scriptFiles[self::POS_BEGIN]))
236		{
237			foreach($this->scriptFiles[self::POS_BEGIN] as $scriptFile=>$scriptFileValue)
238			{
239				if(isset($map[$scriptFile]))
240					unset($this->scriptFiles[self::POS_BEGIN][$scriptFile]);
241				else
242					$map[$scriptFile]=true;
243			}
244		}
245
246		if(isset($this->scriptFiles[self::POS_END]))
247		{
248			foreach($this->scriptFiles[self::POS_END] as $key=>$scriptFile)
249			{
250				if(isset($map[$key]))
251					unset($this->scriptFiles[self::POS_END][$key]);
252			}
253		}
254	}
255
256	/**
257	 * Uses {@link scriptMap} to re-map the registered scripts.
258	 */
259	protected function remapScripts()
260	{
261		$cssFiles=array();
262		foreach($this->cssFiles as $url=>$media)
263		{
264			$name=basename($url);
265			if(isset($this->scriptMap[$name]))
266			{
267				if($this->scriptMap[$name]!==false)
268					$cssFiles[$this->scriptMap[$name]]=$media;
269			}
270			elseif(isset($this->scriptMap['*.css']))
271			{
272				if($this->scriptMap['*.css']!==false)
273					$cssFiles[$this->scriptMap['*.css']]=$media;
274			}
275			else
276				$cssFiles[$url]=$media;
277		}
278		$this->cssFiles=$cssFiles;
279
280		$jsFiles=array();
281		foreach($this->scriptFiles as $position=>$scriptFiles)
282		{
283			$jsFiles[$position]=array();
284			foreach($scriptFiles as $scriptFile=>$scriptFileValue)
285			{
286				$name=basename($scriptFile);
287				if(isset($this->scriptMap[$name]))
288				{
289					if($this->scriptMap[$name]!==false)
290						$jsFiles[$position][$this->scriptMap[$name]]=$this->scriptMap[$name];
291				}
292				elseif(isset($this->scriptMap['*.js']))
293				{
294					if($this->scriptMap['*.js']!==false)
295						$jsFiles[$position][$this->scriptMap['*.js']]=$this->scriptMap['*.js'];
296				}
297				else
298					$jsFiles[$position][$scriptFile]=$scriptFileValue;
299			}
300		}
301		$this->scriptFiles=$jsFiles;
302	}
303
304	/**
305	 * Composes script HTML block from the given script values,
306	 * attempting to group scripts at single 'script' tag if possible.
307	 * @param array $scripts script values to process.
308	 * @return string HTML output
309	 */
310	protected function renderScriptBatch(array $scripts)
311	{
312		$html = '';
313		$scriptBatches = array();
314		foreach($scripts as $scriptValue)
315		{
316			if(is_array($scriptValue))
317			{
318				$scriptContent = $scriptValue['content'];
319				unset($scriptValue['content']);
320				$scriptHtmlOptions = $scriptValue;
321				ksort($scriptHtmlOptions);
322			}
323			else
324			{
325				$scriptContent = $scriptValue;
326				$scriptHtmlOptions = array();
327			}
328			$key=serialize($scriptHtmlOptions);
329			$scriptBatches[$key]['htmlOptions']=$scriptHtmlOptions;
330			$scriptBatches[$key]['scripts'][]=$scriptContent;
331		}
332		foreach($scriptBatches as $scriptBatch)
333			if(!empty($scriptBatch['scripts']))
334				$html.=CHtml::script(implode("\n",$scriptBatch['scripts']),$scriptBatch['htmlOptions'])."\n";
335		return $html;
336	}
337
338	/**
339	 * Renders the specified core javascript library.
340	 */
341	public function renderCoreScripts()
342	{
343		if($this->coreScripts===null)
344			return;
345		$cssFiles=array();
346		$jsFiles=array();
347		foreach($this->coreScripts as $name=>$package)
348		{
349			$baseUrl=$this->getPackageBaseUrl($name);
350			if(!empty($package['js']))
351			{
352				foreach($package['js'] as $js)
353					$jsFiles[$baseUrl.'/'.$js]=$baseUrl.'/'.$js;
354			}
355			if(!empty($package['css']))
356			{
357				foreach($package['css'] as $css)
358					$cssFiles[$baseUrl.'/'.$css]='';
359			}
360		}
361		// merge in place
362		if($cssFiles!==array())
363		{
364			foreach($this->cssFiles as $cssFile=>$media)
365				$cssFiles[$cssFile]=$media;
366			$this->cssFiles=$cssFiles;
367		}
368		if($jsFiles!==array())
369		{
370			if(isset($this->scriptFiles[$this->coreScriptPosition]))
371			{
372				foreach($this->scriptFiles[$this->coreScriptPosition] as $url => $value)
373					$jsFiles[$url]=$value;
374			}
375			$this->scriptFiles[$this->coreScriptPosition]=$jsFiles;
376		}
377	}
378
379	/**
380	 * Inserts the scripts in the head section.
381	 * @param string $output the output to be inserted with scripts.
382	 */
383	public function renderHead(&$output)
384	{
385		$html='';
386		foreach($this->metaTags as $meta)
387			$html.=CHtml::metaTag($meta['content'],null,null,$meta)."\n";
388		foreach($this->linkTags as $link)
389			$html.=CHtml::linkTag(null,null,null,null,$link)."\n";
390		foreach($this->cssFiles as $url=>$media)
391			$html.=CHtml::cssFile($url,$media)."\n";
392		foreach($this->css as $css)
393			$html.=CHtml::css($css[0],$css[1])."\n";
394		if($this->enableJavaScript)
395		{
396			if(isset($this->scriptFiles[self::POS_HEAD]))
397			{
398				foreach($this->scriptFiles[self::POS_HEAD] as $scriptFileValueUrl=>$scriptFileValue)
399				{
400					if(is_array($scriptFileValue))
401						$html.=CHtml::scriptFile($scriptFileValueUrl,$scriptFileValue)."\n";
402					else
403						$html.=CHtml::scriptFile($scriptFileValueUrl)."\n";
404				}
405			}
406
407			if(isset($this->scripts[self::POS_HEAD]))
408				$html.=$this->renderScriptBatch($this->scripts[self::POS_HEAD]);
409		}
410
411		if($html!=='')
412		{
413			$count=0;
414			$output=preg_replace('/(<title\b[^>]*>|<\\/head\s*>)/is','<###head###>$1',$output,1,$count);
415			if($count)
416				$output=str_replace('<###head###>',$html,$output);
417			else
418				$output=$html.$output;
419		}
420	}
421
422	/**
423	 * Inserts the scripts at the beginning of the body section.
424	 * @param string $output the output to be inserted with scripts.
425	 */
426	public function renderBodyBegin(&$output)
427	{
428		$html='';
429		if(isset($this->scriptFiles[self::POS_BEGIN]))
430		{
431			foreach($this->scriptFiles[self::POS_BEGIN] as $scriptFileUrl=>$scriptFileValue)
432			{
433				if(is_array($scriptFileValue))
434					$html.=CHtml::scriptFile($scriptFileUrl,$scriptFileValue)."\n";
435				else
436					$html.=CHtml::scriptFile($scriptFileUrl)."\n";
437			}
438		}
439		if(isset($this->scripts[self::POS_BEGIN]))
440			$html.=$this->renderScriptBatch($this->scripts[self::POS_BEGIN]);
441
442		if($html!=='')
443		{
444			$count=0;
445			$output=preg_replace('/(<body\b[^>]*>)/is','$1<###begin###>',$output,1,$count);
446			if($count)
447				$output=str_replace('<###begin###>',$html,$output);
448			else
449				$output=$html.$output;
450		}
451	}
452
453	/**
454	 * Inserts the scripts at the end of the body section.
455	 * @param string $output the output to be inserted with scripts.
456	 */
457	public function renderBodyEnd(&$output)
458	{
459		if(!isset($this->scriptFiles[self::POS_END]) && !isset($this->scripts[self::POS_END])
460			&& !isset($this->scripts[self::POS_READY]) && !isset($this->scripts[self::POS_LOAD]))
461			return;
462
463		$fullPage=0;
464		$output=preg_replace('/(<\\/body\s*>)/is','<###end###>$1',$output,1,$fullPage);
465		$html='';
466		if(isset($this->scriptFiles[self::POS_END]))
467		{
468			foreach($this->scriptFiles[self::POS_END] as $scriptFileUrl=>$scriptFileValue)
469			{
470				if(is_array($scriptFileValue))
471					$html.=CHtml::scriptFile($scriptFileUrl,$scriptFileValue)."\n";
472				else
473					$html.=CHtml::scriptFile($scriptFileUrl)."\n";
474			}
475		}
476		$scripts=isset($this->scripts[self::POS_END]) ? $this->scripts[self::POS_END] : array();
477		if(isset($this->scripts[self::POS_READY]))
478		{
479			if($fullPage)
480				$scripts[]="jQuery(function($) {\n".implode("\n",$this->scripts[self::POS_READY])."\n});";
481			else
482				$scripts[]=implode("\n",$this->scripts[self::POS_READY]);
483		}
484		if(isset($this->scripts[self::POS_LOAD]))
485		{
486			if($fullPage)
487				$scripts[]="jQuery(window).on('load',function() {\n".implode("\n",$this->scripts[self::POS_LOAD])."\n});";
488			else
489				$scripts[]=implode("\n",$this->scripts[self::POS_LOAD]);
490		}
491		if(!empty($scripts))
492			$html.=$this->renderScriptBatch($scripts);
493
494		if($fullPage)
495			$output=str_replace('<###end###>',$html,$output);
496		else
497			$output=$output.$html;
498	}
499
500	/**
501	 * Returns the base URL of all core javascript files.
502	 * If the base URL is not explicitly set, this method will publish the whole directory
503	 * 'framework/web/js/source' and return the corresponding URL.
504	 * @return string the base URL of all core javascript files
505	 */
506	public function getCoreScriptUrl()
507	{
508		if($this->_baseUrl!==null)
509			return $this->_baseUrl;
510		else
511			return $this->_baseUrl=Yii::app()->getAssetManager()->publish(YII_PATH.'/web/js/source');
512	}
513
514	/**
515	 * Sets the base URL of all core javascript files.
516	 * This setter is provided in case when core javascript files are manually published
517	 * to a pre-specified location. This may save asset publishing time for large-scale applications.
518	 * @param string $value the base URL of all core javascript files.
519	 */
520	public function setCoreScriptUrl($value)
521	{
522		$this->_baseUrl=$value;
523	}
524
525	/**
526	 * Returns the base URL for a registered package with the specified name.
527	 * If needed, this method may publish the assets of the package and returns the published base URL.
528	 * @param string $name the package name
529	 * @return string the base URL for the named package. False is returned if the package is not registered yet.
530	 * @see registerPackage
531	 * @since 1.1.8
532	 */
533	public function getPackageBaseUrl($name)
534	{
535		if(!isset($this->coreScripts[$name]))
536			return false;
537		$package=$this->coreScripts[$name];
538		if(isset($package['baseUrl']))
539		{
540			$baseUrl=$package['baseUrl'];
541			if($baseUrl==='' || $baseUrl[0]!=='/' && strpos($baseUrl,'://')===false)
542				$baseUrl=Yii::app()->getRequest()->getBaseUrl().'/'.$baseUrl;
543			$baseUrl=rtrim($baseUrl,'/');
544		}
545		elseif(isset($package['basePath']))
546			$baseUrl=Yii::app()->getAssetManager()->publish(Yii::getPathOfAlias($package['basePath']));
547		else
548			$baseUrl=$this->getCoreScriptUrl();
549
550		return $this->coreScripts[$name]['baseUrl']=$baseUrl;
551	}
552
553    /**
554     * Checks if package is available.
555     * @param string $name the name of the script package.
556     * @return bool
557     * @since 1.1.18
558     * @see registerPackage
559     */
560	public function hasPackage($name)
561    {
562        if(isset($this->coreScripts[$name]))
563            return true;
564        if(isset($this->packages[$name]))
565            return true;
566        else
567        {
568            if($this->corePackages===null)
569                $this->corePackages=require(YII_PATH.'/web/js/packages.php');
570            if(isset($this->corePackages[$name]))
571                return true;
572        }
573        return false;
574    }
575
576	/**
577	 * Registers a script package that is listed in {@link packages}.
578	 * This method is the same as {@link registerCoreScript}.
579	 * @param string $name the name of the script package.
580	 * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5).
581	 * @since 1.1.7
582	 * @see renderCoreScript
583	 */
584	public function registerPackage($name)
585	{
586		return $this->registerCoreScript($name);
587	}
588
589	/**
590	 * Registers a script package that is listed in {@link packages}.
591	 * @param string $name the name of the script package.
592	 * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5).
593	 * @see renderCoreScript
594	 * @throws CException
595	 */
596	public function registerCoreScript($name)
597	{
598		if(isset($this->coreScripts[$name]))
599			return $this;
600		if(isset($this->packages[$name]))
601			$package=$this->packages[$name];
602		else
603		{
604			if($this->corePackages===null)
605				$this->corePackages=require(YII_PATH.'/web/js/packages.php');
606			if(isset($this->corePackages[$name]))
607				$package=$this->corePackages[$name];
608		}
609		if(isset($package))
610		{
611			if(!empty($package['depends']))
612			{
613				foreach($package['depends'] as $p)
614					$this->registerCoreScript($p);
615			}
616			$this->coreScripts[$name]=$package;
617			$this->hasScripts=true;
618			$params=func_get_args();
619			$this->recordCachingAction('clientScript','registerCoreScript',$params);
620		}
621		elseif(YII_DEBUG)
622			throw new CException('There is no CClientScript package: '.$name);
623		else
624			Yii::log('There is no CClientScript package: '.$name,CLogger::LEVEL_WARNING,'system.web.CClientScript');
625
626		return $this;
627	}
628
629	/**
630	 * Registers a CSS file
631	 * @param string $url URL of the CSS file
632	 * @param string $media media that the CSS file should be applied to. If empty, it means all media types.
633	 * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5).
634	 */
635	public function registerCssFile($url,$media='')
636	{
637		$this->hasScripts=true;
638		$this->cssFiles[$url]=$media;
639		$params=func_get_args();
640		$this->recordCachingAction('clientScript','registerCssFile',$params);
641		return $this;
642	}
643
644	/**
645	 * Registers a piece of CSS code.
646	 * @param string $id ID that uniquely identifies this piece of CSS code
647	 * @param string $css the CSS code
648	 * @param string $media media that the CSS code should be applied to. If empty, it means all media types.
649	 * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5).
650	 */
651	public function registerCss($id,$css,$media='')
652	{
653		$this->hasScripts=true;
654		$this->css[$id]=array($css,$media);
655		$params=func_get_args();
656		$this->recordCachingAction('clientScript','registerCss',$params);
657		return $this;
658	}
659
660	/**
661	 * Registers a javascript file.
662	 * @param string $url URL of the javascript file
663	 * @param integer $position the position of the JavaScript code. Valid values include the following:
664	 * <ul>
665	 * <li>CClientScript::POS_HEAD : the script is inserted in the head section right before the title element.</li>
666	 * <li>CClientScript::POS_BEGIN : the script is inserted at the beginning of the body section.</li>
667	 * <li>CClientScript::POS_END : the script is inserted at the end of the body section.</li>
668	 * </ul>
669	 * @param array $htmlOptions additional HTML attributes
670	 * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5).
671	 */
672	public function registerScriptFile($url,$position=null,array $htmlOptions=array())
673	{
674		$params=func_get_args();
675
676		if($position===null)
677			$position=$this->defaultScriptFilePosition;
678		$this->hasScripts=true;
679		if(empty($htmlOptions))
680			$value=$url;
681		else
682		{
683			$value=$htmlOptions;
684			$value['src']=$url;
685		}
686		$this->scriptFiles[$position][$url]=$value;
687		$this->recordCachingAction('clientScript','registerScriptFile',$params);
688		return $this;
689	}
690
691	/**
692	 * Registers a piece of javascript code.
693	 * @param string $id ID that uniquely identifies this piece of JavaScript code
694	 * @param string $script the javascript code
695	 * @param integer $position the position of the JavaScript code. Valid values include the following:
696	 * <ul>
697	 * <li>CClientScript::POS_HEAD : the script is inserted in the head section right before the title element.</li>
698	 * <li>CClientScript::POS_BEGIN : the script is inserted at the beginning of the body section.</li>
699	 * <li>CClientScript::POS_END : the script is inserted at the end of the body section.</li>
700	 * <li>CClientScript::POS_LOAD : the script is inserted in the window.onload() function.</li>
701	 * <li>CClientScript::POS_READY : the script is inserted in the jQuery's ready function.</li>
702	 * </ul>
703	 * @param array $htmlOptions additional HTML attributes
704	 * Note: HTML attributes are not allowed for script positions "CClientScript::POS_LOAD" and "CClientScript::POS_READY".
705	 * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5).
706	 * @throws CException
707	 */
708	public function registerScript($id,$script,$position=null,array $htmlOptions=array())
709	{
710		$params=func_get_args();
711
712		if($position===null)
713			$position=$this->defaultScriptPosition;
714		$this->hasScripts=true;
715		if(empty($htmlOptions))
716			$scriptValue=$script;
717		else
718		{
719			if($position==self::POS_LOAD || $position==self::POS_READY)
720				throw new CException(Yii::t('yii','Script HTML options are not allowed for "CClientScript::POS_LOAD" and "CClientScript::POS_READY".'));
721			$scriptValue=$htmlOptions;
722			$scriptValue['content']=$script;
723		}
724		$this->scripts[$position][$id]=$scriptValue;
725		if($position===self::POS_READY || $position===self::POS_LOAD)
726			$this->registerCoreScript('jquery');
727		$this->recordCachingAction('clientScript','registerScript',$params);
728		return $this;
729	}
730
731	/**
732	 * Registers a meta tag that will be inserted in the head section (right before the title element) of the resulting page.
733	 *
734	 * <b>Note:</b>
735	 * Each call of this method will cause a rendering of new meta tag, even if their attributes are equal.
736	 *
737	 * <b>Example:</b>
738	 * <pre>
739	 *    $cs->registerMetaTag('example', 'description', null, array('lang' => 'en'));
740	 *    $cs->registerMetaTag('beispiel', 'description', null, array('lang' => 'de'));
741	 * </pre>
742	 * @param string $content content attribute of the meta tag
743	 * @param string $name name attribute of the meta tag. If null, the attribute will not be generated
744	 * @param string $httpEquiv http-equiv attribute of the meta tag. If null, the attribute will not be generated
745	 * @param array $options other options in name-value pairs (e.g. 'scheme', 'lang')
746	 * @param string $id Optional id of the meta tag to avoid duplicates
747	 * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5).
748	 */
749	public function registerMetaTag($content,$name=null,$httpEquiv=null,$options=array(),$id=null)
750	{
751		$params=func_get_args();
752
753		$this->hasScripts=true;
754		if($name!==null)
755			$options['name']=$name;
756		if($httpEquiv!==null)
757			$options['http-equiv']=$httpEquiv;
758		$options['content']=$content;
759		$this->metaTags[null===$id?count($this->metaTags):$id]=$options;
760		$this->recordCachingAction('clientScript','registerMetaTag',$params);
761		return $this;
762	}
763
764	/**
765	 * Registers a link tag that will be inserted in the head section (right before the title element) of the resulting page.
766	 * @param string $relation rel attribute of the link tag. If null, the attribute will not be generated.
767	 * @param string $type type attribute of the link tag. If null, the attribute will not be generated.
768	 * @param string $href href attribute of the link tag. If null, the attribute will not be generated.
769	 * @param string $media media attribute of the link tag. If null, the attribute will not be generated.
770	 * @param array $options other options in name-value pairs
771	 * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5).
772	 */
773	public function registerLinkTag($relation=null,$type=null,$href=null,$media=null,$options=array())
774	{
775		$params=func_get_args();
776
777		$this->hasScripts=true;
778		if($relation!==null)
779			$options['rel']=$relation;
780		if($type!==null)
781			$options['type']=$type;
782		if($href!==null)
783			$options['href']=$href;
784		if($media!==null)
785			$options['media']=$media;
786		$this->linkTags[serialize($options)]=$options;
787		$this->recordCachingAction('clientScript','registerLinkTag',$params);
788		return $this;
789	}
790
791	/**
792	 * Checks whether the CSS file has been registered.
793	 * @param string $url URL of the CSS file
794	 * @return boolean whether the CSS file is already registered
795	 */
796	public function isCssFileRegistered($url)
797	{
798		return isset($this->cssFiles[$url]);
799	}
800
801	/**
802	 * Checks whether the CSS code has been registered.
803	 * @param string $id ID that uniquely identifies the CSS code
804	 * @return boolean whether the CSS code is already registered
805	 */
806	public function isCssRegistered($id)
807	{
808		return isset($this->css[$id]);
809	}
810
811	/**
812	 * Checks whether the JavaScript file has been registered.
813	 * @param string $url URL of the javascript file
814	 * @param integer $position the position of the JavaScript code. Valid values include the following:
815	 * <ul>
816	 * <li>CClientScript::POS_HEAD : the script is inserted in the head section right before the title element.</li>
817	 * <li>CClientScript::POS_BEGIN : the script is inserted at the beginning of the body section.</li>
818	 * <li>CClientScript::POS_END : the script is inserted at the end of the body section.</li>
819	 * </ul>
820	 * @return boolean whether the javascript file is already registered
821	 */
822	public function isScriptFileRegistered($url,$position=self::POS_HEAD)
823	{
824		return isset($this->scriptFiles[$position][$url]);
825	}
826
827	/**
828	 * Checks whether the JavaScript code has been registered.
829	 * @param string $id ID that uniquely identifies the JavaScript code
830	 * @param integer $position the position of the JavaScript code. Valid values include the following:
831	 * <ul>
832	 * <li>CClientScript::POS_HEAD : the script is inserted in the head section right before the title element.</li>
833	 * <li>CClientScript::POS_BEGIN : the script is inserted at the beginning of the body section.</li>
834	 * <li>CClientScript::POS_END : the script is inserted at the end of the body section.</li>
835	 * <li>CClientScript::POS_LOAD : the script is inserted in the window.onload() function.</li>
836	 * <li>CClientScript::POS_READY : the script is inserted in the jQuery's ready function.</li>
837	 * </ul>
838	 * @return boolean whether the javascript code is already registered
839	 */
840	public function isScriptRegistered($id,$position=self::POS_READY)
841	{
842		return isset($this->scripts[$position][$id]);
843	}
844
845	/**
846	 * Records a method call when an output cache is in effect.
847	 * This is a shortcut to Yii::app()->controller->recordCachingAction.
848	 * In case when controller is absent, nothing is recorded.
849	 * @param string $context a property name of the controller. It refers to an object
850	 * whose method is being called. If empty it means the controller itself.
851	 * @param string $method the method name
852	 * @param array $params parameters passed to the method
853	 * @see COutputCache
854	 */
855	protected function recordCachingAction($context,$method,$params)
856	{
857		if(($controller=Yii::app()->getController())!==null)
858			$controller->recordCachingAction($context,$method,$params);
859	}
860
861	/**
862	 * Adds a package to packages list.
863	 *
864	 * @param string $name the name of the script package.
865	 * @param array $definition the definition array of the script package,
866	 * @see CClientScript::packages.
867	 * @return static the CClientScript object itself (to support method chaining, available since version 1.1.10).
868	 *
869	 * @since 1.1.9
870	 */
871	public function addPackage($name,$definition)
872	{
873		$this->packages[$name]=$definition;
874		return $this;
875	}
876}
877