PageRenderTime 51ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/protected/extensions/minify/EClientScript.php

https://bitbucket.org/RSol/fotobank
PHP | 1110 lines | 600 code | 71 blank | 439 comment | 103 complexity | 8477440b99e7c93fbae31d841c3acff3 MD5 | raw file
Possible License(s): MIT, LGPL-2.1, BSD-3-Clause, Apache-2.0
  1. <?php
  2. /**
  3. * CClientScript class file.
  4. *
  5. * @author Qiang Xue <qiang.xue@gmail.com>
  6. * @link http://www.yiiframework.com/
  7. * @copyright Copyright &copy; 2008-2011 Yii Software LLC
  8. * @license http://www.yiiframework.com/license/
  9. */
  10. /**
  11. * CClientScript manages JavaScript and CSS stylesheets for views.
  12. *
  13. * @author Qiang Xue <qiang.xue@gmail.com>
  14. * @version $Id: CClientScript.php 2799 2011-01-01 19:31:13Z qiang.xue $
  15. * @package system.web
  16. * @since 1.0
  17. */
  18. ////////////////////////////////////////////////////////////////////////////////
  19. /**
  20. * Extended Client Script Manager Class File
  21. *
  22. * @author Hightman <hightman2[at]yahoo[dot]com[dot]cn>
  23. * @link http://www.czxiu.com/
  24. * @copyright hightman
  25. * @license http://www.yiiframework.com/license/
  26. * @version 1.3
  27. *
  28. */
  29. /**
  30. Requirements
  31. --------------
  32. Yii 1.1.x or above
  33. Description:
  34. --------------
  35. This extension just extend from {link: CClientScript} using few codes, it will allow you
  36. to automatically combine all script files and css files into a single (or several) script or css files.
  37. Basically this will reduce the HTTP calls for resources files by merging several resources files into
  38. a single (or more) files.
  39. It can automatically detect the required list of files, and generate a unique filename hash,
  40. so boldly ease of use.
  41. ####Css Files:
  42. CSS files are merged based on there media attribute, background images with a relative path
  43. in file can also be displayed correctly.
  44. ####Script files:
  45. Script files are merged based on their position, If you use the 'CClientScript::POS_HEAD'
  46. you will end up with a single file for all the script files you've used on that page.
  47. If you use 'CClientScript::POS_HEAD' and 'CClientScript::POS_END' for example then you'll
  48. end up with two files for each page on that request, Since those resources are located in different positions.
  49. ####File optmization or compress (EXPERIMENTAL, @since: 1.1)
  50. [CssMin](http://code.google.com/p/cssmin/) used to optmize merged css file. You can set property 'optmizeCssFiles' of the component to enable this feature.
  51. [JSMinPlus](http://crisp.tweakblogs.net/blog/1856/jsmin+-version-13.html) used to optimize merged script file. You can set property 'optmizeScriptFiles' of the component to enable this feature.
  52. Usage:
  53. ---------------
  54. Using this extension is as simple as adding the following code to the application configuration under the components array:
  55. ~~~
  56. [php]
  57. 'clientScript' => array(
  58. 'class' => 'ext.minify.EClientScript',
  59. 'combineScriptFiles' => true, // By default this is set to false, set this to true if you'd like to combine the script files
  60. 'combineCssFiles' => true, // By default this is set to false, set this to true if you'd like to combine the css files
  61. 'optimizeScriptFiles' => false, // @since: 1.1
  62. 'optimizeCssFiles' => false, // @since: 1.1
  63. ),
  64. ~~~
  65. Then you'd use the regular 'registerScriptFile' & 'registerCssFile' methods as normal and the files will be combined automatically.
  66. NOTE:
  67. ---------------
  68. If you registered some external resource files that not in the web application root directory, they will be kept and not combined.
  69. Compression or optmization is a EXPERIMENTAL feature, please use it carefully(@since: 1.1)
  70. ChangesLog:
  71. ---------------
  72. Nov 23, 2010
  73. * Skip the minimization of files whose names include `.pack.`
  74. * Add the last modification time as the QUERY_STRING to merged file, to avoid not properly flush the browser cache when the file updated.
  75. Nov 6, 2010
  76. * New version number 1.3
  77. * Not repeat the minimization of files those who have been minimized, whose names include `.min.`
  78. * Fixed `getRelativeUrl()` platform compatibility issue. (thanks to Troto)
  79. Known Issues:
  80. ----------------
  81. When some resource files can not be merged and strictly dependent on loading order, then may have some problem.
  82. Reporting Issue:
  83. -----------------
  84. Reporting Issues and comments are welcome, plz report them to offical forum of Yii.
  85. [Report issue](http://www.yiiframework.com/forum/index.php?/topic/12476-extension-eclientscript/)
  86. */
  87. /**
  88. * Extended clientscript to automatically merge script and css files
  89. *
  90. * @author hightman <hightman2@yahoo.com.cn>
  91. * @version $Id $
  92. * @package extensions.minify
  93. * @since 1.0
  94. */
  95. //class EClientScript extends CClientScript
  96. //class CClientScript extends CApplicationComponent
  97. class EClientScript extends NLSClientScript
  98. {
  99. /**
  100. * The script is rendered in the head section right before the title element.
  101. */
  102. const POS_HEAD=0;
  103. /**
  104. * The script is rendered at the beginning of the body section.
  105. */
  106. const POS_BEGIN=1;
  107. /**
  108. * The script is rendered at the end of the body section.
  109. */
  110. const POS_END=2;
  111. /**
  112. * The script is rendered inside window onload function.
  113. */
  114. const POS_LOAD=3;
  115. /**
  116. * The body script is rendered inside a jQuery ready function.
  117. */
  118. const POS_READY=4;
  119. /**
  120. * @var boolean whether JavaScript should be enabled. Defaults to true.
  121. */
  122. public $enableJavaScript=true;
  123. /**
  124. * @var array the mapping between script file names and the corresponding script URLs.
  125. * The array keys are script file names (without directory part) and the array values are the corresponding URLs.
  126. * If an array value is false, the corresponding script file will not be rendered.
  127. * If an array key is '*.js' or '*.css', the corresponding URL will replace all
  128. * all JavaScript files or CSS files, respectively.
  129. *
  130. * This property is mainly used to optimize the generated HTML pages
  131. * by merging different scripts files into fewer and optimized script files.
  132. * @since 1.0.3
  133. */
  134. public $scriptMap=array();
  135. /**
  136. * @var array the registered CSS files (CSS URL=>media type).
  137. * @since 1.0.4
  138. */
  139. protected $cssFiles=array();
  140. protected $cssFilesOrder=array();
  141. /**
  142. * @var array the registered JavaScript files (position, key => URL)
  143. * @since 1.0.4
  144. */
  145. protected $scriptFiles=array();
  146. protected $scriptFilesOrder=array();
  147. /**
  148. * @var array the registered JavaScript code blocks (position, key => code)
  149. * @since 1.0.5
  150. */
  151. protected $scripts=array();
  152. protected $scriptsOrder=array();
  153. /**
  154. * @var array the register head meta tags. Each array element represents an option array
  155. * that will be passed as the last parameter of {@link CHtml::metaTag}.
  156. * @since 1.1.3
  157. */
  158. protected $metaTags=array();
  159. /**
  160. * @var array the register head link tags. Each array element represents an option array
  161. * that will be passed as the last parameter of {@link CHtml::linkTag}.
  162. * @since 1.1.3
  163. */
  164. protected $linkTags=array();
  165. /**
  166. * @var array the register css code blocks (key => array(CSS code, media type)).
  167. * @since 1.1.3
  168. */
  169. protected $css=array();
  170. protected $cssOrder=array();
  171. /**
  172. * @var integer Where the core scripts will be inserted in the page.
  173. * This can be one of the CClientScript::POS_* constants.
  174. * Defaults to CClientScript::POS_HEAD.
  175. * @since 1.1.3
  176. */
  177. public $coreScriptPosition=self::POS_HEAD;
  178. private $_hasScripts=false;
  179. private $_packages;
  180. private $_dependencies;
  181. private $_baseUrl;
  182. private $_coreScripts=array();
  183. private $_corePackages=array();
  184. /**
  185. * @var combined script file name
  186. */
  187. public $scriptFileName = 'script.js';
  188. /**
  189. * @var combined css stylesheet file name
  190. */
  191. public $cssFileName = 'style.css';
  192. /**
  193. * @var boolean if to combine the script files or not
  194. */
  195. public $combineScriptFiles = false;
  196. /**
  197. * @var boolean if to combine the css files or not
  198. */
  199. public $combineCssFiles = false;
  200. /**
  201. * @var boolean if to optimize the css files
  202. */
  203. public $optimizeCssFiles = false;
  204. /**
  205. * @var boolean if to optimize the script files via googleCompiler(this may cause to much slower)
  206. */
  207. public $optimizeScriptFiles = false;
  208. /**
  209. * Cleans all registered scripts.
  210. */
  211. public function reset()
  212. {
  213. $this->_hasScripts=false;
  214. $this->_coreScripts=array();
  215. $this->cssFiles=array();
  216. $this->css=array();
  217. $this->scriptFiles=array();
  218. $this->scripts=array();
  219. $this->metaTags=array();
  220. $this->linkTags=array();
  221. $this->recordCachingAction('clientScript','reset',array());
  222. }
  223. /**
  224. * Renders the registered scripts.
  225. * This method is called in {@link CController::render} when it finishes
  226. * rendering content. CClientScript thus gets a chance to insert script tags
  227. * at <code>head</code> and <code>body</code> sections in the HTML output.
  228. * @param string $output the existing output that needs to be inserted with script tags
  229. */
  230. public function render(&$output)
  231. {
  232. if(!$this->_hasScripts)
  233. return;
  234. $this->renderCoreScripts();
  235. if(!empty($this->scriptMap))
  236. $this->remapScripts();
  237. $this->unifyScripts();
  238. ////////////////////////////////////////////////////////////////////////////////
  239. // reorder the cssFiles
  240. if(!empty($this->cssFilesOrder))
  241. {
  242. ksort($this->cssFilesOrder);
  243. foreach($this->cssFilesOrder as $url)
  244. {
  245. $newCssFiles[$url]=$this->cssFiles[$url];
  246. }
  247. $this->cssFiles = $newCssFiles;
  248. }
  249. if(!empty($this->cssOrder))
  250. {
  251. ksort($this->cssOrder);
  252. foreach($this->cssOrder as $id)
  253. {
  254. $newCss[$id]=$this->css[$id];
  255. }
  256. $this->css = $newCss;
  257. }
  258. // reorder the scriptFiles
  259. if(!empty($this->scriptFilesOrder[self::POS_END]))
  260. {
  261. ksort($this->scriptFilesOrder[self::POS_END]);
  262. foreach($this->scriptFilesOrder[self::POS_END] as $url)
  263. {
  264. $newScriptFiles[$url]=$url;
  265. }
  266. $this->scriptFiles[self::POS_END] = $newScriptFiles;
  267. }
  268. if(!empty($this->scriptsOrder[self::POS_READY]))
  269. {
  270. ksort($this->scriptsOrder[self::POS_READY]);
  271. // reorder the css
  272. foreach($this->scriptsOrder[self::POS_READY] as $id)
  273. {
  274. $newScript[$id]=$this->scripts[self::POS_READY][$id];
  275. }
  276. $this->scripts[self::POS_READY] = $newScript;
  277. }
  278. ////////////////////////////////////////////////////////////////////////////////
  279. $this->renderHead($output);
  280. if($this->enableJavaScript)
  281. {
  282. $this->renderBodyBegin($output);
  283. $this->renderBodyEnd($output);
  284. }
  285. }
  286. /**
  287. * Removes duplicated scripts from {@link scriptFiles}.
  288. * @since 1.1.5
  289. */
  290. protected function unifyScripts()
  291. {
  292. if(!$this->enableJavaScript)
  293. return;
  294. $map=array();
  295. if(isset($this->scriptFiles[self::POS_HEAD]))
  296. $map=$this->scriptFiles[self::POS_HEAD];
  297. if(isset($this->scriptFiles[self::POS_BEGIN]))
  298. {
  299. foreach($this->scriptFiles[self::POS_BEGIN] as $key=>$scriptFile)
  300. {
  301. if(isset($map[$scriptFile]))
  302. unset($this->scriptFiles[self::POS_BEGIN][$key]);
  303. else
  304. $map[$scriptFile]=true;
  305. }
  306. }
  307. if(isset($this->scriptFiles[self::POS_END]))
  308. {
  309. foreach($this->scriptFiles[self::POS_END] as $key=>$scriptFile)
  310. {
  311. if(isset($map[$scriptFile]))
  312. unset($this->scriptFiles[self::POS_END][$key]);
  313. }
  314. }
  315. }
  316. /**
  317. * Uses {@link scriptMap} to re-map the registered scripts.
  318. * @since 1.0.3
  319. */
  320. protected function remapScripts()
  321. {
  322. $cssFiles=array();
  323. foreach($this->cssFiles as $url=>$media)
  324. {
  325. $name=basename($url);
  326. if(isset($this->scriptMap[$name]))
  327. {
  328. if($this->scriptMap[$name]!==false)
  329. $cssFiles[$this->scriptMap[$name]]=$media;
  330. }
  331. else if(isset($this->scriptMap['*.css']))
  332. {
  333. if($this->scriptMap['*.css']!==false)
  334. $cssFiles[$this->scriptMap['*.css']]=$media;
  335. }
  336. else
  337. $cssFiles[$url]=$media;
  338. }
  339. $this->cssFiles=$cssFiles;
  340. $jsFiles=array();
  341. foreach($this->scriptFiles as $position=>$scripts)
  342. {
  343. $jsFiles[$position]=array();
  344. foreach($scripts as $key=>$script)
  345. {
  346. $name=basename($script);
  347. if(isset($this->scriptMap[$name]))
  348. {
  349. if($this->scriptMap[$name]!==false)
  350. $jsFiles[$position][$this->scriptMap[$name]]=$this->scriptMap[$name];
  351. }
  352. else if(isset($this->scriptMap['*.js']))
  353. {
  354. if($this->scriptMap['*.js']!==false)
  355. $jsFiles[$position][$this->scriptMap['*.js']]=$this->scriptMap['*.js'];
  356. }
  357. else
  358. $jsFiles[$position][$key]=$script;
  359. }
  360. }
  361. $this->scriptFiles=$jsFiles;
  362. }
  363. /**
  364. * Renders the specified core javascript library.
  365. * @since 1.0.3
  366. */
  367. public function renderCoreScripts()
  368. {
  369. if($this->_coreScripts===null)
  370. return;
  371. $cssFiles=array();
  372. $jsFiles=array();
  373. foreach($this->_coreScripts as $name=>$package)
  374. {
  375. $baseUrl=$this->getCoreScriptUrl();
  376. if(!empty($package['js']))
  377. {
  378. foreach($package['js'] as $js)
  379. $jsFiles[$baseUrl.'/'.$js]=$baseUrl.'/'.$js;
  380. }
  381. if(!empty($package['css']))
  382. {
  383. foreach($package['css'] as $css)
  384. $cssFiles[$baseUrl.'/'.$css]='';
  385. }
  386. }
  387. // merge in place
  388. if($cssFiles!==array())
  389. {
  390. foreach($this->cssFiles as $cssFile=>$media)
  391. $cssFiles[$cssFile]=$media;
  392. $this->cssFiles=$cssFiles;
  393. }
  394. if($jsFiles!==array())
  395. {
  396. if(isset($this->scriptFiles[$this->coreScriptPosition]))
  397. {
  398. foreach($this->scriptFiles[$this->coreScriptPosition] as $url)
  399. $jsFiles[$url]=$url;
  400. }
  401. $this->scriptFiles[$this->coreScriptPosition]=$jsFiles;
  402. }
  403. }
  404. /**
  405. * Inserts the scripts in the head section.
  406. * @param string $output the output to be inserted with scripts.
  407. */
  408. public function renderHead(&$output)
  409. {
  410. if ($this->combineCssFiles)
  411. $this->combineCssFiles();
  412. if ($this->combineScriptFiles && $this->enableJavaScript)
  413. $this->combineScriptFiles(self::POS_HEAD);
  414. ////////////////////////////////////////////////////////////////////////////////
  415. $html='';
  416. foreach($this->metaTags as $meta)
  417. $html.=CHtml::metaTag($meta['content'],null,null,$meta)."\n";
  418. foreach($this->linkTags as $link)
  419. $html.=CHtml::linkTag(null,null,null,null,$link)."\n";
  420. foreach($this->cssFiles as $url=>$media)
  421. $html.=CHtml::cssFile($url,$media)."\n";
  422. foreach($this->css as $css)
  423. $html.=CHtml::css($css[0],$css[1])."\n";
  424. if($this->enableJavaScript)
  425. {
  426. if(isset($this->scriptFiles[self::POS_HEAD]))
  427. {
  428. foreach($this->scriptFiles[self::POS_HEAD] as $scriptFile)
  429. $html.=CHtml::scriptFile($scriptFile)."\n";
  430. }
  431. if(isset($this->scripts[self::POS_HEAD]))
  432. $html.=CHtml::script(implode("\n",$this->scripts[self::POS_HEAD]))."\n";
  433. }
  434. if($html!=='')
  435. {
  436. $count=0;
  437. $output=preg_replace('/(<title\b[^>]*>|<\\/head\s*>)/is','<###head###>$1',$output,1,$count);
  438. if($count)
  439. $output=str_replace('<###head###>',$html,$output);
  440. else
  441. $output=$html.$output;
  442. }
  443. }
  444. /**
  445. * Inserts the scripts at the beginning of the body section.
  446. * @param string $output the output to be inserted with scripts.
  447. */
  448. public function renderBodyBegin(&$output)
  449. {
  450. // $this->enableJavascript has been checked in parent::render()
  451. if ($this->combineScriptFiles)
  452. $this->combineScriptFiles(self::POS_BEGIN);
  453. $html='';
  454. if(isset($this->scriptFiles[self::POS_BEGIN]))
  455. {
  456. foreach($this->scriptFiles[self::POS_BEGIN] as $scriptFile)
  457. $html.=CHtml::scriptFile($scriptFile)."\n";
  458. }
  459. if(isset($this->scripts[self::POS_BEGIN]))
  460. $html.=CHtml::script(implode("\n",$this->scripts[self::POS_BEGIN]))."\n";
  461. if($html!=='')
  462. {
  463. $count=0;
  464. $output=preg_replace('/(<body\b[^>]*>)/is','$1<###begin###>',$output,1,$count);
  465. if($count)
  466. $output=str_replace('<###begin###>',$html,$output);
  467. else
  468. $output=$html.$output;
  469. }
  470. }
  471. /**
  472. * Inserts the scripts at the end of the body section.
  473. * @param string $output the output to be inserted with scripts.
  474. */
  475. public function renderBodyEnd(&$output)
  476. {
  477. // $this->enableJavascript has been checked in parent::render()
  478. if ($this->combineScriptFiles)
  479. $this->combineScriptFiles(self::POS_END);
  480. if(!isset($this->scriptFiles[self::POS_END]) && !isset($this->scripts[self::POS_END])
  481. && !isset($this->scripts[self::POS_READY]) && !isset($this->scripts[self::POS_LOAD]))
  482. return;
  483. $fullPage=0;
  484. $output=preg_replace('/(<\\/body\s*>)/is','<###end###>$1',$output,1,$fullPage);
  485. $html='';
  486. if(isset($this->scriptFiles[self::POS_END]))
  487. {
  488. foreach($this->scriptFiles[self::POS_END] as $scriptFile)
  489. $html.=CHtml::scriptFile($scriptFile)."\n";
  490. }
  491. $scripts=isset($this->scripts[self::POS_END]) ? $this->scripts[self::POS_END] : array();
  492. if(isset($this->scripts[self::POS_READY]))
  493. {
  494. if($fullPage)
  495. $scripts[]="jQuery(function($) {\n".implode("\n",$this->scripts[self::POS_READY])."\n});";
  496. else
  497. $scripts[]=implode("\n",$this->scripts[self::POS_READY]);
  498. }
  499. if(isset($this->scripts[self::POS_LOAD]))
  500. {
  501. if($fullPage)
  502. $scripts[]="jQuery(window).load(function() {\n".implode("\n",$this->scripts[self::POS_LOAD])."\n});";
  503. else
  504. $scripts[]=implode("\n",$this->scripts[self::POS_LOAD]);
  505. }
  506. if(!empty($scripts))
  507. $html.=CHtml::script(implode("\n",$scripts))."\n";
  508. if($fullPage)
  509. $output=str_replace('<###end###>',$html,$output);
  510. else
  511. $output=$output.$html;
  512. }
  513. /**
  514. * Returns the base URL of all core javascript files.
  515. * If the base URL is not explicitly set, this method will publish the whole directory
  516. * 'framework/web/js/source' and return the corresponding URL.
  517. * @return string the base URL of all core javascript files
  518. */
  519. public function getCoreScriptUrl()
  520. {
  521. if($this->_baseUrl!==null)
  522. return $this->_baseUrl;
  523. else
  524. return $this->_baseUrl=Yii::app()->getAssetManager()->publish(YII_PATH.'/web/js/source');
  525. }
  526. /**
  527. * Sets the base URL of all core javascript files.
  528. * This setter is provided in case when core javascript files are manually published
  529. * to a pre-specified location. This may save asset publishing time for large-scale applications.
  530. * @param string $value the base URL of all core javascript files.
  531. */
  532. public function setCoreScriptUrl($value)
  533. {
  534. $this->_baseUrl=$value;
  535. }
  536. /**
  537. * Registers a core javascript library.
  538. * @param string $name the core javascript library name
  539. * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5).
  540. * @see renderCoreScript
  541. */
  542. public function registerCoreScript($name)
  543. {
  544. if(isset($this->_coreScripts[$name]))
  545. return $this;
  546. if(isset($this->_packages[$name]))
  547. $package=$this->_packages[$name];
  548. else
  549. {
  550. if(!$this->_corePackages)
  551. $this->_corePackages=require(YII_PATH.'/web/js/packages.php');
  552. if(isset($this->_corePackages[$name]))
  553. $package=$this->_corePackages[$name];
  554. }
  555. /*if($this->_packages===null)
  556. {
  557. $config=require(YII_PATH.'/web/js/packages.php');
  558. $this->_packages=$config[0];
  559. $this->_dependencies=$config[1];
  560. */
  561. if(isset($package))
  562. {
  563. if(!empty($package['depends']))
  564. {
  565. foreach($package['depends'] as $p)
  566. $this->registerCoreScript($p);
  567. }
  568. $this->_coreScripts[$name]=$package;
  569. $this->_hasScripts=true;
  570. $params=func_get_args();
  571. $this->recordCachingAction('clientScript','registerCoreScript',$params);
  572. }
  573. /*
  574. if(!isset($this->_packages[$name]))
  575. return $this;
  576. if(isset($this->_dependencies[$name]))
  577. {
  578. foreach($this->_dependencies[$name] as $depName)
  579. $this->registerCoreScript($depName);
  580. }
  581. $this->_hasScripts=true;
  582. $this->_coreScripts[$name]=$name;
  583. $params=func_get_args();
  584. $this->recordCachingAction('clientScript','registerCoreScript',$params);
  585. */
  586. return $this;
  587. }
  588. /**
  589. * Registers a CSS file
  590. * @param string $url URL of the CSS file
  591. * @param string $media media that the CSS file should be applied to. If empty, it means all media types.
  592. * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5).
  593. */
  594. public function registerCssFile($url,$media='',$order=0)
  595. {
  596. $this->_hasScripts=true;
  597. if($order==0) $order=100+count($this->cssFiles);
  598. $this->cssFilesOrder[$order]=$url;
  599. $this->cssFiles[$url]=$media;
  600. $params=func_get_args();
  601. $this->recordCachingAction('clientScript','registerCssFile',$params);
  602. return $this;
  603. }
  604. /**
  605. * Registers a piece of CSS code.
  606. * @param string $id ID that uniquely identifies this piece of CSS code
  607. * @param string $css the CSS code
  608. * @param string $media media that the CSS code should be applied to. If empty, it means all media types.
  609. * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5).
  610. */
  611. public function registerCss($id,$css,$media='',$order=0)
  612. {
  613. $this->_hasScripts=true;
  614. if($order==0) $order=100+count($this->css);
  615. $this->cssOrder[$order]=$id;
  616. $this->css[$id]=array($css,$media);
  617. $params=func_get_args();
  618. $this->recordCachingAction('clientScript','registerCss',$params);
  619. return $this;
  620. }
  621. /**
  622. * Registers a javascript file.
  623. * @param string $url URL of the javascript file
  624. * @param integer $position the position of the JavaScript code. Valid values include the following:
  625. * <ul>
  626. * <li>CClientScript::POS_HEAD : the script is inserted in the head section right before the title element.</li>
  627. * <li>CClientScript::POS_BEGIN : the script is inserted at the beginning of the body section.</li>
  628. * <li>CClientScript::POS_END : the script is inserted at the end of the body section.</li>
  629. * </ul>
  630. * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5).
  631. */
  632. //public function registerScriptFile($url,$position=self::POS_HEAD)
  633. public function registerScriptFile($url,$position=self::POS_END,$order=0)
  634. {
  635. //print_r($url);
  636. $this->_hasScripts=true;
  637. if($order==0) $order=100+(isset($this->scriptFiles[$position]) ? count($this->scriptFiles[$position]) : 0);
  638. $this->scriptFilesOrder[$position][$order]=$url;
  639. $this->scriptFiles[$position][$url]=$url;
  640. $params=func_get_args();
  641. $this->recordCachingAction('clientScript','registerScriptFile',$params);
  642. return $this;
  643. }
  644. /**
  645. * Registers a piece of javascript code.
  646. * @param string $id ID that uniquely identifies this piece of JavaScript code
  647. * @param string $script the javascript code
  648. * @param integer $position the position of the JavaScript code. Valid values include the following:
  649. * <ul>
  650. * <li>CClientScript::POS_HEAD : the script is inserted in the head section right before the title element.</li>
  651. * <li>CClientScript::POS_BEGIN : the script is inserted at the beginning of the body section.</li>
  652. * <li>CClientScript::POS_END : the script is inserted at the end of the body section.</li>
  653. * <li>CClientScript::POS_LOAD : the script is inserted in the window.onload() function.</li>
  654. * <li>CClientScript::POS_READY : the script is inserted in the jQuery's ready function.</li>
  655. * </ul>
  656. * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5).
  657. */
  658. public function registerScript($id,$script,$position=self::POS_READY,$order=0)
  659. {
  660. $this->_hasScripts=true;
  661. if($order==0) $order=100+(isset($this->scripts[$position]) ? count($this->scripts[$position]) : 0);
  662. $this->scriptsOrder[$position][$order]=$id;
  663. $this->scripts[$position][$id]=$script;
  664. if($position===self::POS_READY || $position===self::POS_LOAD)
  665. $this->registerCoreScript('jquery');
  666. $params=func_get_args();
  667. $this->recordCachingAction('clientScript','registerScript',$params);
  668. return $this;
  669. }
  670. /**
  671. * Registers a meta tag that will be inserted in the head section (right before the title element) of the resulting page.
  672. * @param string $content content attribute of the meta tag
  673. * @param string $name name attribute of the meta tag. If null, the attribute will not be generated
  674. * @param string $httpEquiv http-equiv attribute of the meta tag. If null, the attribute will not be generated
  675. * @param array $options other options in name-value pairs (e.g. 'scheme', 'lang')
  676. * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5).
  677. * @since 1.0.1
  678. */
  679. public function registerMetaTag($content,$name=null,$httpEquiv=null,$options=array())
  680. {
  681. $this->_hasScripts=true;
  682. if($name!==null)
  683. $options['name']=$name;
  684. if($httpEquiv!==null)
  685. $options['http-equiv']=$httpEquiv;
  686. $options['content']=$content;
  687. $this->metaTags[serialize($options)]=$options;
  688. $params=func_get_args();
  689. $this->recordCachingAction('clientScript','registerMetaTag',$params);
  690. return $this;
  691. }
  692. /**
  693. * Registers a link tag that will be inserted in the head section (right before the title element) of the resulting page.
  694. * @param string $relation rel attribute of the link tag. If null, the attribute will not be generated.
  695. * @param string $type type attribute of the link tag. If null, the attribute will not be generated.
  696. * @param string $href href attribute of the link tag. If null, the attribute will not be generated.
  697. * @param string $media media attribute of the link tag. If null, the attribute will not be generated.
  698. * @param array $options other options in name-value pairs
  699. * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5).
  700. * @since 1.0.1
  701. */
  702. public function registerLinkTag($relation=null,$type=null,$href=null,$media=null,$options=array())
  703. {
  704. $this->_hasScripts=true;
  705. if($relation!==null)
  706. $options['rel']=$relation;
  707. if($type!==null)
  708. $options['type']=$type;
  709. if($href!==null)
  710. $options['href']=$href;
  711. if($media!==null)
  712. $options['media']=$media;
  713. $this->linkTags[serialize($options)]=$options;
  714. $params=func_get_args();
  715. $this->recordCachingAction('clientScript','registerLinkTag',$params);
  716. return $this;
  717. }
  718. /**
  719. * Checks whether the CSS file has been registered.
  720. * @param string $url URL of the CSS file
  721. * @return boolean whether the CSS file is already registered
  722. */
  723. public function isCssFileRegistered($url)
  724. {
  725. return isset($this->cssFiles[$url]);
  726. }
  727. /**
  728. * Checks whether the CSS code has been registered.
  729. * @param string $id ID that uniquely identifies the CSS code
  730. * @return boolean whether the CSS code is already registered
  731. */
  732. public function isCssRegistered($id)
  733. {
  734. return isset($this->css[$id]);
  735. }
  736. /**
  737. * Checks whether the JavaScript file has been registered.
  738. * @param string $url URL of the javascript file
  739. * @param integer $position the position of the JavaScript code. Valid values include the following:
  740. * <ul>
  741. * <li>CClientScript::POS_HEAD : the script is inserted in the head section right before the title element.</li>
  742. * <li>CClientScript::POS_BEGIN : the script is inserted at the beginning of the body section.</li>
  743. * <li>CClientScript::POS_END : the script is inserted at the end of the body section.</li>
  744. * </ul>
  745. * @return boolean whether the javascript file is already registered
  746. */
  747. public function isScriptFileRegistered($url,$position=self::POS_HEAD)
  748. {
  749. return isset($this->scriptFiles[$position][$url]);
  750. }
  751. /**
  752. * Checks whether the JavaScript code has been registered.
  753. * @param string $id ID that uniquely identifies the JavaScript code
  754. * @param integer $position the position of the JavaScript code. Valid values include the following:
  755. * <ul>
  756. * <li>CClientScript::POS_HEAD : the script is inserted in the head section right before the title element.</li>
  757. * <li>CClientScript::POS_BEGIN : the script is inserted at the beginning of the body section.</li>
  758. * <li>CClientScript::POS_END : the script is inserted at the end of the body section.</li>
  759. * <li>CClientScript::POS_LOAD : the script is inserted in the window.onload() function.</li>
  760. * <li>CClientScript::POS_READY : the script is inserted in the jQuery's ready function.</li>
  761. * </ul>
  762. * @return boolean whether the javascript code is already registered
  763. */
  764. public function isScriptRegistered($id,$position=self::POS_READY)
  765. {
  766. return isset($this->scripts[$position][$id]);
  767. }
  768. /**
  769. * Records a method call when an output cache is in effect.
  770. * This is a shortcut to Yii::app()->controller->recordCachingAction.
  771. * In case when controller is absent, nothing is recorded.
  772. * @param string $context a property name of the controller. It refers to an object
  773. * whose method is being called. If empty it means the controller itself.
  774. * @param string $method the method name
  775. * @param array $params parameters passed to the method
  776. * @see COutputCache
  777. * @since 1.0.5
  778. */
  779. protected function recordCachingAction($context,$method,$params)
  780. {
  781. if(($controller=Yii::app()->getController())!==null)
  782. $controller->recordCachingAction($context,$method,$params);
  783. }
  784. /**/
  785. /**
  786. * Combine the CSS files, if cached enabled then cache the result so we won't have to do that
  787. * Every time
  788. */
  789. protected function combineCssFiles()
  790. {
  791. // Check the need for combination
  792. if (count($this->cssFiles) < 2)
  793. return;
  794. $cssFiles = array();
  795. $toBeCombined = array();
  796. foreach ($this->cssFiles as $url => $media)
  797. {
  798. $file = $this->getLocalPath($url);
  799. if ($file === false)
  800. $cssFiles[$url] = $media;
  801. else
  802. {
  803. $media = strtolower($media);
  804. if ($media === '')
  805. $media = 'all';
  806. if (!isset($toBeCombined[$media]))
  807. $toBeCombined[$media] = array();
  808. $toBeCombined[$media][$url] = $file;
  809. }
  810. }
  811. foreach ($toBeCombined as $media => $files)
  812. {
  813. if ($media === 'all')
  814. $media = '';
  815. if (count($files) === 1)
  816. $url = key($files);
  817. else
  818. {
  819. // get unique combined filename
  820. $fname = $this->getCombinedFileName($this->cssFileName, $files, $media);
  821. $fpath = Yii::app()->assetManager->basePath . DIRECTORY_SEPARATOR . $fname;
  822. // check exists file
  823. if ($valid = file_exists($fpath))
  824. {
  825. $mtime = filemtime($fpath);
  826. foreach ($files as $file)
  827. {
  828. if ($mtime < filemtime($file))
  829. {
  830. $valid = false;
  831. break;
  832. }
  833. }
  834. }
  835. // re-generate the file
  836. if (!$valid)
  837. {
  838. $urlRegex = '#url\s*\(\s*([\'"])?(?!/|http://)([^\'"\s])#i';
  839. $fileBuffer = '';
  840. foreach ($files as $url => $file)
  841. {
  842. $contents = file_get_contents($file);
  843. if ($contents)
  844. {
  845. // Reset relative url() in css file
  846. if (preg_match($urlRegex, $contents))
  847. {
  848. $reurl = $this->getRelativeUrl(Yii::app()->assetManager->baseUrl, dirname($url));
  849. $contents = preg_replace($urlRegex, 'url(${1}' . $reurl . '/${2}', $contents);
  850. }
  851. // Append the contents to the fileBuffer
  852. $fileBuffer .= "/*** CSS File: {$url}";
  853. if ($this->optimizeCssFiles
  854. && strpos($file, '.min.') === false && strpos($file, '.pack.') === false)
  855. {
  856. $fileBuffer .= ", Original size: " . number_format(strlen($contents)) . ", Compressed size: ";
  857. $contents = $this->optimizeCssCode($contents);
  858. $fileBuffer .= number_format(strlen($contents));
  859. }
  860. $fileBuffer .= " ***/\n";
  861. $fileBuffer .= $contents . "\n\n";
  862. }
  863. }
  864. file_put_contents($fpath, $fileBuffer);
  865. }
  866. // real url of combined file
  867. $url = Yii::app()->assetManager->baseUrl . '/' . $fname . '?' . filemtime($fpath);
  868. }
  869. $cssFiles[$url] = $media;
  870. }
  871. // use new cssFiles list replace old ones
  872. $this->cssFiles = $cssFiles;
  873. }
  874. /**
  875. * Combine script files, we combine them based on their position, each is combined in a separate file
  876. * to load the required data in the required location.
  877. * @param $type CClientScript the type of script files currently combined
  878. */
  879. protected function combineScriptFiles($type = self::POS_HEAD)
  880. {
  881. // Check the need for combination
  882. if (!isset($this->scriptFiles[$type]) || count($this->scriptFiles[$type]) < 2)
  883. return;
  884. $scriptFiles = array();
  885. $toBeCombined = array();
  886. foreach ($this->scriptFiles[$type] as $url)
  887. {
  888. $file = $this->getLocalPath($url);
  889. if ($file === false)
  890. $scriptFiles[$url] = $url;
  891. else
  892. $toBeCombined[$url] = $file;
  893. }
  894. if (count($toBeCombined) === 1)
  895. {
  896. $url = key($toBeCombined);
  897. $scriptFiles[$url] = $url;
  898. }
  899. else if (count($toBeCombined) > 1)
  900. {
  901. // get unique combined filename
  902. $fname = $this->getCombinedFileName($this->scriptFileName, array_values($toBeCombined), $type);
  903. $fpath = Yii::app()->assetManager->basePath . DIRECTORY_SEPARATOR . $fname;
  904. // check exists file
  905. if ($valid = file_exists($fpath))
  906. {
  907. $mtime = filemtime($fpath);
  908. foreach ($toBeCombined as $file)
  909. {
  910. if ($mtime < filemtime($file))
  911. {
  912. $valid = false;
  913. break;
  914. }
  915. }
  916. }
  917. // re-generate the file
  918. if (!$valid)
  919. {
  920. $fileBuffer = '';
  921. foreach ($toBeCombined as $url => $file)
  922. {
  923. $contents = file_get_contents($file);
  924. if ($contents)
  925. {
  926. // Append the contents to the fileBuffer
  927. $fileBuffer .= "/*** Script File: {$url}";
  928. if ($this->optimizeScriptFiles
  929. && strpos($file, '.min.') === false && strpos($file, '.pack.') === false)
  930. {
  931. $fileBuffer .= ", Original size: " . number_format(strlen($contents)) . ", Compressed size: ";
  932. $contents = $this->optimizeScriptCode($contents);
  933. $fileBuffer .= number_format(strlen($contents));
  934. }
  935. if (!preg_match('~;$~', trim($contents))) $contents = $contents . ";\n";
  936. $fileBuffer .= " ***/\n";
  937. $fileBuffer .= $contents . "\n\n";
  938. }
  939. }
  940. file_put_contents($fpath, $fileBuffer);
  941. }
  942. // add the combined file into scriptFiles
  943. $url = Yii::app()->assetManager->baseUrl . '/' . $fname . '?' . filemtime($fpath);;
  944. $scriptFiles[$url] = $url;
  945. }
  946. // use new scriptFiles list replace old ones
  947. $this->scriptFiles[$type] = $scriptFiles;
  948. }
  949. /**
  950. * Get realpath of published file via its url, refer to {link: CAssetManager}
  951. * @return string local file path for this script or css url
  952. */
  953. private function getLocalPath($url)
  954. {
  955. $basePath = dirname(Yii::app()->request->scriptFile) . DIRECTORY_SEPARATOR;
  956. $baseUrl = Yii::app()->request->baseUrl . '/';
  957. if (!strncmp($url, $baseUrl, strlen($baseUrl)))
  958. {
  959. $url = $basePath . substr($url, strlen($baseUrl));
  960. return $url;
  961. }
  962. return false;
  963. }
  964. /**
  965. * Calculate the relative url
  966. * @param string $from source url, begin with slash and not end width slash.
  967. * @param string $to dest url
  968. * @return string result relative url
  969. */
  970. private function getRelativeUrl($from, $to)
  971. {
  972. $relative = '';
  973. while (true)
  974. {
  975. if ($from === $to)
  976. return $relative;
  977. else if ($from === dirname($from))
  978. return $relative . substr($to, 1);
  979. if (!strncmp($from . '/', $to, strlen($from) + 1))
  980. return $relative . substr($to, strlen($from) + 1);
  981. $from = dirname($from);
  982. $relative .= '../';
  983. }
  984. }
  985. /**
  986. * Get unique filename for combined files
  987. * @param string $name default filename
  988. * @param array $files files to be combined
  989. * @param string $type css media or script position
  990. * @return string unique filename
  991. */
  992. private function getCombinedFileName($name, $files, $type = '')
  993. {
  994. $pos = strrpos($name, '.');
  995. if (!$pos)
  996. $pos = strlen($pos);
  997. $hash = sprintf('%x', crc32(implode('+', $files)));
  998. $ret = substr($name, 0, $pos);
  999. if ($type !== '')
  1000. $ret .= '-' . $type;
  1001. $ret .= '-' . $hash . substr($name, $pos);
  1002. return $ret;
  1003. }
  1004. /**
  1005. * Optmize css, strip any spaces and newline
  1006. * @param string $data input css data
  1007. * @return string optmized css data
  1008. */
  1009. private function optimizeCssCode($code)
  1010. {
  1011. require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'CssMin.php';
  1012. return CssMin::minify($code, array('compress-unit-values' => true));
  1013. }
  1014. /**
  1015. * Optimize script via google compiler
  1016. * @param string $data script code
  1017. * @return string optimized script code
  1018. */
  1019. private function optimizeScriptCode($code)
  1020. {
  1021. require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'JSMinPlus.php';
  1022. $minified = JSMinPlus::minify($code);
  1023. return ($minified === false ? $code : $minified);
  1024. }
  1025. }