/classes/casset.php

https://github.com/studiofrenetic/fuel-casset · PHP · 1334 lines · 776 code · 120 blank · 438 comment · 100 complexity · 8581b6cc81b99415668e4402a52c2651 MD5 · raw file

  1. <?php
  2. /**
  3. * Casset: Convenient asset library for FuelPHP.
  4. *
  5. * @package Casset
  6. * @version v1.19
  7. * @author Antony Male
  8. * @license MIT License
  9. * @copyright 2012 Antony Male
  10. * @link http://github.com/canton7/fuelphp-casset
  11. */
  12. namespace Casset;
  13. class Casset {
  14. /**
  15. * @var array Array of paths in which the css, js, img directory structure
  16. * can be found, relative to $asset_url
  17. */
  18. protected static $asset_paths = array(
  19. 'core' => 'assets/',
  20. );
  21. /*
  22. * @var string The key in $asset_paths to use if no key is given
  23. */
  24. protected static $default_path_key = 'core';
  25. /**
  26. * @var string The URL to be prepanded to all assets.
  27. */
  28. protected static $asset_url = null;
  29. /**
  30. * @var array The folders in which css, js, and images can be found.
  31. */
  32. protected static $default_folders = array(
  33. 'css' => 'css/',
  34. 'js' => 'js/',
  35. 'img' => 'img/',
  36. );
  37. /**
  38. * @var string The directory, relative to public/, where cached minified failes
  39. * are stored.
  40. */
  41. protected static $cache_path = 'assets/cache/';
  42. /**
  43. * @var array Holds groups of assets. Is documenented fully in the config file.
  44. */
  45. protected static $groups = array(
  46. 'css' => array(),
  47. 'js' => array(),
  48. );
  49. /**
  50. * @var array Holds inline js and css.
  51. */
  52. protected static $inline_assets = array(
  53. 'css' => array(),
  54. 'js' => array(),
  55. );
  56. /**
  57. *
  58. * @var array Defaults for a group
  59. */
  60. protected static $default_options = array(
  61. 'enabled' => true,
  62. 'combine' => true,
  63. 'min' => true,
  64. 'inline' => false,
  65. 'attr' => array(),
  66. 'deps' => array(),
  67. );
  68. /**
  69. * @var int How deep to go when resolving deps
  70. */
  71. protected static $deps_max_depth = 5;
  72. /**
  73. * @var bool Whether to show comments above the <script>/<link> tags showing
  74. * which files have been minified into that file.
  75. */
  76. protected static $show_files = false;
  77. /**
  78. * @var bool Whether to show comments inside minified files showing which
  79. * original file is where.
  80. */
  81. protected static $show_files_inline = false;
  82. /**
  83. * @var function If given, the function to call when we've read a file, before
  84. * minifying.
  85. * Note that it's only called if $combine for the file is true
  86. * Prototype: callback(content, filename, type, group_name);
  87. */
  88. protected static $post_load_callback = null;
  89. /*
  90. * @var function If given, the function to call when we've decided on the name
  91. * for a file, but want to allow the user to tweak it before we write it to the
  92. * page.
  93. * Prototype: callback($filepath, $type, $remote);
  94. */
  95. protected static $filepath_callback = null;
  96. /**
  97. * @var array Keeps a record of which groups have been rendered.
  98. * We then check this when deciding whether to render a dep.
  99. */
  100. protected static $rendered_groups = array('js' => array(), 'css' => array());
  101. /**
  102. * @var string which css uri rewriter we want to use. Options are 'absolute', 'relative', 'none'
  103. */
  104. protected static $css_uri_rewriter = 'absolute';
  105. /**
  106. * @var bool Wether we've been initialized.
  107. */
  108. public static $initialized = false;
  109. /**
  110. * Loads in the config and sets the variables
  111. */
  112. public static function _init()
  113. {
  114. // Prevent multiple initializations
  115. if (static::$initialized)
  116. {
  117. return;
  118. }
  119. \Config::load('casset', true);
  120. static::$asset_url = \Config::get('casset.url');
  121. if (!static::$asset_url)
  122. static::$asset_url = preg_replace('#^https?://#','//', \Uri::base(false));
  123. static::$asset_url = rtrim(static::$asset_url, '/') . '/';
  124. static::$default_folders = array(
  125. 'css' => \Config::get('casset.css_dir', static::$default_folders['css']),
  126. 'js' => \Config::get('casset.js_dir', static::$default_folders['js']),
  127. 'img' => \Config::get('casset.img_dir', static::$default_folders['img']),
  128. );
  129. $paths = \Config::get('casset.paths', static::$asset_paths);
  130. foreach($paths as $key => $path)
  131. {
  132. static::add_path($key, $path);
  133. }
  134. static::$cache_path = \Config::get('casset.cache_path', static::$cache_path);
  135. static::$default_options['min'] = \Config::get('casset.min', static::$default_options['min']);
  136. static::$default_options['combine'] = \Config::get('casset.combine', static::$default_options['combine']);
  137. static::$deps_max_depth = \Config::get('casset.deps_max_depth', static::$deps_max_depth);
  138. $group_sets = \Config::get('casset.groups', array());
  139. foreach ($group_sets as $group_type => $groups)
  140. {
  141. foreach ($groups as $group_name => $group)
  142. {
  143. $options = static::prep_new_group_options($group);
  144. static::add_group($group_type, $group_name, $group['files'], $options);
  145. }
  146. }
  147. // Add the global group if it doesn't already exist.
  148. // This is so that set_group_option() can be used on it. This function will
  149. // throw an exception if the named group doesn't exist.
  150. if (!static::group_exists('js', 'global'))
  151. static::add_group_base('js', 'global');
  152. if (!static::group_exists('css', 'global'))
  153. static::add_group_base('css', 'global');
  154. static::$show_files = \Config::get('casset.show_files', static::$show_files);
  155. static::$show_files_inline = \Config::get('casset.show_files_inline', static::$show_files_inline);
  156. static::$post_load_callback = \Config::get('casset.post_load_callback', static::$post_load_callback);
  157. static::$filepath_callback = \Config::get('casset.filepath_callback', static::$filepath_callback);
  158. static::$css_uri_rewriter = \Config::get('casset.css_uri_rewriter', static::$css_uri_rewriter);
  159. static::$initialized = true;
  160. }
  161. /**
  162. * Getter for cache_path
  163. *
  164. * @return string
  165. */
  166. public static function get_cache_path() {
  167. return static::$cache_path;
  168. }
  169. /**
  170. * Sets up options for new groups setup via casset/config.php.
  171. * Abstracts away from _init method. Also easier if options are
  172. * added in future as iterates through defaults to do checking.
  173. *
  174. * @param array $options Options as defined in group in config.php
  175. * @return void
  176. */
  177. protected static function prep_new_group_options($group_options)
  178. {
  179. $options = array();
  180. foreach (static::$default_options as $key => $option_val) {
  181. if (array_key_exists($key, $group_options)) {
  182. $options[$key] = $group_options[$key];
  183. }
  184. }
  185. return $options;
  186. }
  187. /**
  188. * Parses oen of the 'paths' config keys into the format used internally.
  189. * Config file format:
  190. * 'paths' => array(
  191. * 'assets/',
  192. * array(
  193. * 'path' => 'assets_2/',
  194. * 'js_dir' => 'js/',
  195. * 'css_dir' => 'css/',
  196. * ),
  197. * ),
  198. * In the event that the value isn't an array, it is turned into one.
  199. * If js_dir, css_dir or img_dir are not given, they are populated with
  200. * the defaults, giving in the 'js_dir', 'css_dir' and 'img_dir' config keys.
  201. * @param string $path_key the key of the path
  202. * @param mixed $path_attr the path attributes, as described above
  203. */
  204. public static function add_path($path_key, $path_attr)
  205. {
  206. $path_val = array();
  207. if (!is_array($path_attr))
  208. $path_attr = array('path' => $path_attr, 'dirs' => array());
  209. elseif (!array_key_exists('dirs', $path_attr))
  210. $path_attr['dirs'] = array();
  211. $path_val['path'] = $path_attr['path'];
  212. $path_val['dirs'] = array(
  213. 'js' => array_key_exists('js_dir', $path_attr) ? $path_attr['js_dir'] : static::$default_folders['js'],
  214. 'css' => array_key_exists('css_dir', $path_attr) ? $path_attr['css_dir'] : static::$default_folders['css'],
  215. 'img' => array_key_exists('img_dir', $path_attr) ? $path_attr['img_dir'] : static::$default_folders['img'],
  216. );
  217. static::$asset_paths[$path_key] = $path_val;
  218. }
  219. /**
  220. * Set the current default path
  221. *
  222. * @param $path_key the path key to set the default to.
  223. */
  224. public static function set_path($path_key = 'core')
  225. {
  226. if (!array_key_exists($path_key, static::$asset_paths))
  227. throw new Casset_Exception("Asset path key $path_key doesn't exist");
  228. static::$default_path_key = $path_key;
  229. }
  230. /**
  231. * Adds a group of assets. If a group of this name exists, the function returns.
  232. *
  233. * @param string $group_type 'js' or 'css'
  234. * @param string $group_name The name of the group
  235. * @param array $options. An array of options. array(
  236. * 'enabled' => true/false,
  237. * 'combine' => true/false,
  238. * 'min' => true/false,
  239. * 'inline' => true/false,
  240. * 'deps' => array(),
  241. * );
  242. */
  243. protected static function add_group_base($group_type, $group_name, $options = array())
  244. {
  245. // Insert defaults
  246. $options += static::$default_options;
  247. if (!is_array($options['deps']))
  248. $options['deps'] = array($options['deps']);
  249. $options['files'] = array();
  250. // If it already exists, don't overwrite it
  251. if (array_key_exists($group_name, static::$groups[$group_type]))
  252. throw new Casset_Exception("Group $group_name already exists: can't create it.");
  253. static::$groups[$group_type][$group_name] = $options;
  254. }
  255. /**
  256. * Adds a group for assets, and adds assets to that group.
  257. *
  258. * @param string $group_type 'js' or 'css'
  259. * @param string $group_name The name of the group
  260. * @param array $options. An array of options. array(
  261. * 'enabled' => true/false,
  262. * 'combine' => true/false,
  263. * 'min' => true/false,
  264. * 'inline' => true/false,
  265. * 'attr' => array(),
  266. * 'deps' => array(),
  267. * );
  268. * To maintain backwards compatibility, you can also pass $enabled here.
  269. * @param bool $combine_dep DEPRECATED. Whether to combine files in this group. Default (null) means use config setting
  270. * @param boo $min_dep DEPRECATED/ Whether to minify files in this group. Default (null) means use config setting
  271. */
  272. public static function add_group($group_type, $group_name, $files, $options = array(), $combine_dep = null, $min_dep = null)
  273. {
  274. // Bit of backwards compatibity.
  275. // Order used to be add_group(group_type, group_name, files, enabled, combine, min)
  276. if (!is_array($options))
  277. {
  278. $options = array(
  279. 'enabled' => $options,
  280. 'combine' => $combine_dep,
  281. 'min' => $min_dep,
  282. );
  283. }
  284. // We're basically faking the old add_group. However, the approach has changed since those days
  285. // Therefore we create the group it it doesn't already exist, then add the files to it
  286. static::add_group_base($group_type, $group_name, $options);
  287. foreach ($files as $file) {
  288. if (!is_array($file))
  289. $file = array($file, false);
  290. static::add_asset($group_type, $file[0], $file[1], $group_name);
  291. }
  292. }
  293. /**
  294. * Returns true if the given group exists
  295. *
  296. * @param string $group_type 'js' or 'css
  297. * @param type $group_name the nam eof the group
  298. */
  299. public static function group_exists($group_type, $group_name)
  300. {
  301. return array_key_exists($group_name, static::$groups[$group_type]);
  302. }
  303. /**
  304. * Enables both js and css groups of the given name.
  305. *
  306. * @param mixed $group The group to enable, or array of groups
  307. */
  308. public static function enable($groups)
  309. {
  310. static::asset_enabled('js', $groups, true);
  311. static::asset_enabled('css', $groups, true);
  312. }
  313. /**
  314. * Disables both js and css groups of the given name.
  315. *
  316. * @param string $group The group to disable, or array of groups
  317. */
  318. public static function disable($groups)
  319. {
  320. static::asset_enabled('js', $groups, false);
  321. static::asset_enabled('css', $groups, false);
  322. }
  323. /**
  324. * Enable a group of javascript assets.
  325. *
  326. * @param string $group The group to enable, or array of groups
  327. */
  328. public static function enable_js($groups)
  329. {
  330. static::asset_enabled('js', $groups, true);
  331. }
  332. /**
  333. * Disable a group of javascript assets.
  334. *
  335. * @param string $group The group to disable, or array of groups
  336. */
  337. public static function disable_js($groups)
  338. {
  339. static::asset_enabled('js', $groups, false);
  340. }
  341. /**
  342. * Enable a group of css assets.
  343. *
  344. * @param string $group The group to enable, or array of groups
  345. */
  346. public static function enable_css($groups)
  347. {
  348. static::asset_enabled('css', $groups, true);
  349. }
  350. /**
  351. * Disable a group of css assets.
  352. *
  353. * @param string $group The group to disable, or array of groups
  354. */
  355. public static function disable_css($groups)
  356. {
  357. static::asset_enabled('css', $groups, false);
  358. }
  359. /**
  360. * Enables / disables an asset.
  361. *
  362. * @param string $type 'css' / 'js'
  363. * @param string $group The group to enable/disable, or array of groups
  364. * @param bool $enabled True to enabel to group, false odisable
  365. */
  366. protected static function asset_enabled($type, $groups, $enabled)
  367. {
  368. if (!is_array($groups))
  369. $groups = array($groups);
  370. foreach ($groups as $group)
  371. {
  372. // If the group doesn't exist it's of no consequence
  373. if (!array_key_exists($group, static::$groups[$type]))
  374. continue;
  375. static::$groups[$type][$group]['enabled'] = $enabled;
  376. }
  377. }
  378. /**
  379. * Set group options on-the-fly.
  380. *
  381. * @param string $type 'js' / 'css
  382. * @param mixed $group_names Group name to change, or array of groups to change,
  383. * or '' for global group, or '*' for all groups.
  384. * @param string $option_key The name of the option to change
  385. * @param mixed $option_value What to set the option to
  386. */
  387. public static function set_group_option($type, $group_names, $option_key, $option_value)
  388. {
  389. if ($group_names == '')
  390. $group_names = array('global');
  391. else if ($group_names == '*')
  392. {
  393. // Change the default
  394. static::$default_options[$option_key] = $option_value;
  395. $group_names = array_keys(static::$groups[$type]);
  396. }
  397. else if (!is_array($group_names))
  398. $group_names = array($group_names);
  399. // Allow them to specify a single string dep
  400. if ($option_key == 'deps' && !is_array($option_value))
  401. $option_value = array($option_value);
  402. foreach ($group_names as $group_name)
  403. {
  404. // If the group doesn't exist, throw a fuss
  405. if (!static::group_exists($type, $group_name))
  406. throw new Casset_Exception("Can't set option for group '$group_name' ($type), as it doesn't exist.");
  407. static::$groups[$type][$group_name][$option_key] = $option_value;
  408. }
  409. }
  410. /**
  411. * Set group options on-the-fly, js version
  412. *
  413. * @param mixed $group_names Group name to change, or array of groups to change,
  414. * or '' for global group, or '*' for all groups.
  415. * @param string $option_key The name of the option to change
  416. * @param mixed $option_value What to set the option to
  417. */
  418. public static function set_js_option($group_names, $option_key, $option_value)
  419. {
  420. static::set_group_option('js', $group_names, $option_key, $option_value);
  421. }
  422. /**
  423. * Set group options on-the-fly, css version
  424. *
  425. * @param mixed $group_names Group name to change, or array of groups to change,
  426. * or '' for global group, or '*' for all groups.
  427. * @param string $option_key The name of the option to change
  428. * @param mixed $option_value What to set the option to
  429. */
  430. public static function set_css_option($group_names, $option_key, $option_value)
  431. {
  432. static::set_group_option('css', $group_names, $option_key, $option_value);
  433. }
  434. /**
  435. * Add a javascript asset.
  436. *
  437. * @param string $script The script to add.
  438. * @param string $script_min If given, will be used when $min = true
  439. * If omitted, $script will be minified internally
  440. * @param string $group The group to add this asset to. Defaults to 'global'
  441. */
  442. public static function js($script, $script_min = false, $group = 'global')
  443. {
  444. static::add_asset('js', $script, $script_min, $group);
  445. }
  446. /**
  447. * Add a css asset.
  448. *
  449. * @param string $sheet The script to add
  450. * @param string $sheet_min If given, will be used when $min = true
  451. * If omitted, $script will be minified internally
  452. * @param string $group The group to add this asset to. Defaults to 'global'
  453. */
  454. public static function css($sheet, $sheet_min = false, $group = 'global')
  455. {
  456. static::add_asset('css', $sheet, $sheet_min, $group);
  457. }
  458. /**
  459. * Abstraction of js() and css().
  460. *
  461. * @param string $type 'css' / 'js'
  462. * @param string $script The script to add.
  463. * @param string $script_min If given, will be used when $min = true
  464. * If omitted, $script will be minified internally
  465. * @param string $group The group to add this asset to
  466. */
  467. protected static function add_asset($type, $script, $script_min, $group)
  468. {
  469. // Allow the user to specify any non-string value for an asset, and it
  470. // will be ignore. This can be handy when using ternary operators
  471. // in the groups config.
  472. if (!is_string($script))
  473. return;
  474. // Don't force the user to remember that 'false' is used when not supplying
  475. // a pre-minified file.
  476. if (!is_string($script_min))
  477. $script_min = false;
  478. $files = array($script, $script_min);
  479. // If the user hasn't specified a path key, add $default_path_key
  480. foreach ($files as &$file)
  481. {
  482. if ($file != false && strpos($file, '::') === false)
  483. $file = static::$default_path_key.'::'.$file;
  484. }
  485. if (!array_key_exists($group, static::$groups[$type]))
  486. {
  487. // Assume they want the group enabled
  488. static::add_group_base($type, $group);
  489. }
  490. array_push(static::$groups[$type][$group]['files'], $files);
  491. }
  492. /**
  493. * Add a string containing javascript, which can be printed inline with
  494. * js_render_inline().
  495. *
  496. * @param string $content The javascript to add
  497. */
  498. public static function js_inline($content)
  499. {
  500. static::add_asset_inline('js', $content);
  501. }
  502. /**
  503. * Add a string containing css, which can be printed inline with
  504. * css_render_inline().
  505. *
  506. * @param string $content The css to add
  507. */
  508. public static function css_inline($content)
  509. {
  510. static::add_asset_inline('css', $content);
  511. }
  512. /**
  513. * Abstraction of js_inline() and css_inline().
  514. *
  515. * @param string $type 'css' / 'js'
  516. * @param string $content The css / js to add
  517. */
  518. protected static function add_asset_inline($type, $content)
  519. {
  520. array_push(static::$inline_assets[$type], $content);
  521. }
  522. /**
  523. * Return the path for the given JS asset. Ties into find_files, so supports
  524. * everything that, say, Casset::js() does.
  525. * Throws an exception if the file isn't found.
  526. * @param string $script the name of the asset to find
  527. * @param bool $add_url whether to add the 'url' config key to the filename
  528. * @param bool $force_array by default, when one file is found a string is
  529. * returned. Setting this to true causes a single-element array to be returned.
  530. */
  531. public static function get_filepath_js($filename, $add_url = false, $force_array = false)
  532. {
  533. return static::get_filepath($filename, 'js', $add_url, $force_array);
  534. }
  535. /**
  536. * Return the path for the given CSS asset. Ties into find_files, so supports
  537. * everything that, say, Casset::js() does.
  538. * Throws an exception if the file isn't found.
  539. * @param string $script the name of the asset to find
  540. * @param bool $add_url whether to add the 'url' config key to the filename
  541. * @param bool $force_array by default, when one file is found a string is
  542. * returned. Setting this to true causes a single-element array to be returned.
  543. */
  544. public static function get_filepath_css($filename, $add_url = false, $force_array = false)
  545. {
  546. return static::get_filepath($filename, 'css', $add_url, $force_array);
  547. }
  548. /**
  549. * Return the path for the given img asset. Ties into find_files, so supports
  550. * everything that, say, Casset::js() does.
  551. * Throws an exception if the file isn't found.
  552. * @param string $script the name of the asset to find
  553. * @param bool $add_url whether to add the 'url' config key to the filename
  554. * @param bool $force_array by default, when one file is found a string is
  555. * returned. Setting this to true causes a single-element array to be returned.
  556. */
  557. public static function get_filepath_img($filename, $add_url = false, $force_array = false)
  558. {
  559. return static::get_filepath($filename, 'img', $add_url, $force_array);
  560. }
  561. /**
  562. * Return the path for the given asset. Ties into find_files, so supports
  563. * everything that, say, Casset::js() does.
  564. * Throws an exception if the file isn't found.
  565. * @param string $script the name of the asset to find
  566. * @param string $type js, css or img
  567. * @param bool $add_url whether to add the 'url' config key to the filename
  568. * @param bool $force_array by default, when one file is found a string is
  569. * returned. Setting this to true causes a single-element array to be returned.
  570. */
  571. public static function get_filepath($filename, $type, $add_url = false, $force_array = false)
  572. {
  573. if (strpos($filename, '::') === false)
  574. $filename = static::$default_path_key.'::'.$filename;
  575. $files = static::find_files($filename, $type);
  576. foreach ($files as &$file)
  577. {
  578. $remote = (strpos($file, '//') !== false);
  579. $file = static::process_filepath($file, $type, $remote);
  580. if ($remote)
  581. continue;
  582. if ($add_url)
  583. $file = static::$asset_url.$file;
  584. }
  585. if (count($files) == 1 && !$force_array)
  586. return $files[0];
  587. return $files;
  588. }
  589. /**
  590. * Can be used to add deps to a group.
  591. *
  592. * @param string $type 'js' / 'css
  593. * @param string $group The group name to add deps to
  594. * @param array $deps An array of group names to add as deps.
  595. */
  596. public static function add_deps($type, $group, $new_deps)
  597. {
  598. if (!is_array($new_deps))
  599. $new_deps = array($new_deps);
  600. if (!array_key_exists($group, static::$groups[$type]))
  601. throw new Casset_Exception("Group $group ($type) doesn't exist, so can't add deps to it.");
  602. // Avoid duplicates in deps array
  603. $deps = &static::$groups[$type][$group]['deps'];
  604. $deps = array_unique(array_merge($deps, $new_deps));
  605. }
  606. /**
  607. * Sugar for add_deps(), for js groups
  608. * @param string $group The group name to add deps to
  609. * @param array $deps An array of group names to add as deps.
  610. */
  611. public static function add_js_deps($group, $deps)
  612. {
  613. static::add_deps('js', $group, $deps);
  614. }
  615. /**
  616. * Sugar for add_deps(), for css groups
  617. * @param string $group The group name to add deps to
  618. * @param array $deps An array of group names to add as deps.
  619. */
  620. public static function add_css_deps($group, $deps)
  621. {
  622. static::add_deps('css', $group, $deps);
  623. }
  624. /**
  625. * Sticks the given filename through the filepath callback, if given.
  626. *
  627. * @param string $filepath The filepath to process
  628. * @param string $type The type of asset, passed to the callback
  629. * @param bool $remote Whether the asset is on another machine, passed to the callback
  630. */
  631. protected static function process_filepath($filepath, $type, $remote = null)
  632. {
  633. if (static::$filepath_callback)
  634. {
  635. if ($remote === null)
  636. $remote = (strpos($filepath, '//') !== false);
  637. $func = static::$filepath_callback;
  638. $filepath = $func($filepath, $type, $remote);
  639. }
  640. return $filepath;
  641. }
  642. /**
  643. * Shortcut to render_js() and render_css().
  644. *
  645. * @param string $group Which group to render. If omitted renders all groups
  646. * @param bool $inline_dep DEPRECATED. If true, the result is printed inline. If false, is
  647. * written to a file and linked to. In fact, $inline = true also causes
  648. * a cache file to be written for speed purposes
  649. * @return string The javascript tags to be written to the page
  650. */
  651. public static function render($group = false, $inline_dep = null, $attr = array())
  652. {
  653. $r = static::render_css($group, $inline_dep, $attr);
  654. $r.= static::render_js($group, $inline_dep, $attr);
  655. return $r;
  656. }
  657. /**
  658. * Renders the specific javascript group, or all groups if no group specified.
  659. *
  660. * @param string $group Which group to render. If omitted renders all groups
  661. * @param array $options An array of options to use. Only supported option is 'gen_tags'
  662. * @param bool $inline_dep DEPRECATED. If true, the result is printed inline. If false, is
  663. * written to a file and linked to. In fact, $inline = true also causes
  664. * a cache file to be written for speed purposes
  665. * @return string The javascript tags to be written to the page
  666. */
  667. public static function render_js($group = false, $options = array(), $attr_dep = array())
  668. {
  669. // Don't force the user to remember that false is used for ommitted non-bool arguments
  670. if (!is_string($group))
  671. $group = false;
  672. if (!is_array($attr_dep))
  673. $attr_dep = array();
  674. // Backwards compat: We used to have $inline where $options currently sits
  675. // So, if $options isn't an array, then assume they're still passing $inline
  676. $inline_dep = null;
  677. if (!is_array($options))
  678. {
  679. $inline_dep = $options;
  680. $options = array();
  681. }
  682. $options += array(
  683. 'gen_tags' => true,
  684. );
  685. $file_groups = static::files_to_render('js', $group);
  686. // If not generating tags, return an array of content
  687. $ret = ($options['gen_tags']) ? '' : array();
  688. foreach ($file_groups as $group_name => $file_group)
  689. {
  690. // We used to take $inline as 2nd argument. However, we now use a group option.
  691. // It's easiest if we let $inline override this group option, though.
  692. $inline = ($inline_dep === null) ? static::$groups['js'][$group_name]['inline'] : $inline_dep;
  693. // $attr is also deprecated. If specified, entirely overrides the group option.
  694. $attr = (!count($attr_dep)) ? static::$groups['js'][$group_name]['attr'] : $attr_dep;
  695. // the type attribute is not required for script elements under html5
  696. // @link http://www.w3.org/TR/html5/scripting-1.html#attr-script-type
  697. if (!\Html::$html5)
  698. $attr = array( 'type' => 'text/javascript' ) + $attr;
  699. if (static::$groups['js'][$group_name]['combine'])
  700. {
  701. $filename = static::combine('js', $file_group, static::$groups['js'][$group_name]['min'], $inline);
  702. if (!$inline && static::$show_files && $options['gen_tags'])
  703. {
  704. $ret .= '<!--'.PHP_EOL.'Group: '.$group_name.PHP_EOL.implode('', array_map(function($a){
  705. return "\t".$a['file'].PHP_EOL;
  706. }, $file_group)).'-->'.PHP_EOL;
  707. }
  708. if ($inline)
  709. {
  710. $content = file_get_contents(DOCROOT.static::$cache_path.$filename);
  711. if ($options['gen_tags'])
  712. $ret .= html_tag('script', $attr, PHP_EOL.$content.PHP_EOL).PHP_EOL;
  713. else
  714. $ret[] = $content;
  715. }
  716. else
  717. {
  718. $filepath = static::$asset_url.static::process_filepath(static::$cache_path.$filename, 'js');
  719. if ($options['gen_tags'])
  720. $ret .= html_tag('script', array('src' => $filepath,)+$attr, '').PHP_EOL;
  721. else
  722. $ret[] = $filepath;
  723. }
  724. }
  725. else
  726. {
  727. foreach ($file_group as $file)
  728. {
  729. if ($inline)
  730. {
  731. $content = static::load_file($file['file'], 'js');
  732. if ($options['gen_tags'])
  733. $ret .= html_tag('script', $attr, PHP_EOL.$content.PHP_EOL).PHP_EOL;
  734. else
  735. $ret[] = $content;
  736. }
  737. else
  738. {
  739. $remote = (strpos($file['file'], '//') !== false);
  740. $base = ($remote) ? '' : static::$asset_url;
  741. $filepath = $base.static::process_filepath($file['file'], 'js', $remote);
  742. if ($options['gen_tags'])
  743. $ret .= html_tag('script', array('src' => $filepath,)+$attr, '').PHP_EOL;
  744. else
  745. $ret[] = $filepath;
  746. }
  747. }
  748. }
  749. }
  750. return $ret;
  751. }
  752. /**
  753. * Renders the specific css group, or all groups if no group specified.
  754. *
  755. * @param string $group Which group to render. If omitted renders all groups
  756. * @param array $options ANnarray of options to use. Only supported option is 'gen_tags'
  757. * @return string The css tags to be written to the page
  758. */
  759. public static function render_css($group = false, $options = array(), $attr_dep = array())
  760. {
  761. // Don't force the user to remember that false is used for ommitted non-bool arguments
  762. if (!is_string($group))
  763. $group = false;
  764. if (!is_array($attr_dep))
  765. $attr_dep = array();
  766. // Backwards compat: We used to have $inline where $options currently sits
  767. // So, if $options isn't an array, then assume they're still passing $inline
  768. $inline_dep = null;
  769. if (!is_array($options))
  770. {
  771. $inline_dep = $options;
  772. $options = array();
  773. }
  774. $options += array(
  775. 'gen_tags' => true,
  776. );
  777. $file_groups = static::files_to_render('css', $group);
  778. // If not generating tags, return an array of content
  779. $ret = ($options['gen_tags']) ? '' : array();
  780. foreach ($file_groups as $group_name => $file_group)
  781. {
  782. // We used to take $inline as 2nd argument. However, we now use a group option.
  783. // It's easiest if we let $inline override this group option, though.
  784. $inline = ($inline_dep === null) ? static::$groups['css'][$group_name]['inline'] : $inline_dep;
  785. // $attr is also deprecated. If specified, entirely overrides the group option.
  786. $attr = (!count($attr_dep)) ? static::$groups['css'][$group_name]['attr'] : $attr_dep;
  787. // the type attribute is not required for style or link[rel="stylesheet"] elements under html5
  788. // @link http://www.w3.org/TR/html5/links.html#link-type-stylesheet
  789. // @link http://www.w3.org/TR/html5/semantics.html#attr-style-type
  790. if (!\Html::$html5)
  791. $attr = array( 'type' => 'text/css' ) + $attr;
  792. if (static::$groups['css'][$group_name]['combine'])
  793. {
  794. $filename = static::combine('css', $file_group, static::$groups['css'][$group_name]['min'], $inline);
  795. if (!$inline && static::$show_files && $options['gen_tags'])
  796. {
  797. $ret .= '<!--'.PHP_EOL.'Group: '.$group_name.PHP_EOL.implode('', array_map(function($a){
  798. return "\t".$a['file'].PHP_EOL;
  799. }, $file_group)).'-->'.PHP_EOL;
  800. }
  801. if ($inline)
  802. {
  803. $content = file_get_contents(DOCROOT.static::$cache_path.$filename);
  804. // We'll need to fix the uris, unless they were rewritten absolutely to start with
  805. $content = static::css_rewrite_uris($content, static::$cache_path.$filename, \Uri::string());
  806. if ($options['gen_tags'])
  807. $ret .= html_tag('style', $attr, PHP_EOL.$content.PHP_EOL).PHP_EOL;
  808. else
  809. $ret[] = $content;
  810. }
  811. else
  812. {
  813. $filepath = static::$asset_url.static::process_filepath(static::$cache_path.$filename, 'css');
  814. if ($options['gen_tags'])
  815. $ret .= html_tag('link', array('rel' => 'stylesheet', 'href' => $filepath)+$attr).PHP_EOL;
  816. else
  817. $ret[] = $filepath;
  818. }
  819. }
  820. else
  821. {
  822. foreach ($file_group as $file)
  823. {
  824. if ($inline)
  825. {
  826. $content = static::load_file($file['file'], 'css', false, \Uri::string());
  827. if ($options['gen_tags'])
  828. $ret .= html_tag('style', $attr, PHP_EOL.$content.PHP_EOL).PHP_EOL;
  829. else
  830. $ret[] = $content;
  831. }
  832. else
  833. {
  834. $remote = (strpos($file['file'], '//') !== false);
  835. $base = ($remote) ? '' : static::$asset_url;
  836. $filepath = $base.static::process_filepath($file['file'], 'css', $remote);
  837. if ($options['gen_tags'])
  838. $ret .= html_tag('link', array('rel' => 'stylesheet', 'href' => $filepath)+$attr).PHP_EOL;
  839. else
  840. $ret[] = $filepath;
  841. }
  842. }
  843. }
  844. }
  845. return $ret;
  846. }
  847. /**
  848. * Figures out where a file should be, based on its namespace and type.
  849. *
  850. * @param string $file The name of the asset to search for
  851. * @param string $asset_type 'css', 'js' or 'img'
  852. * @return string The path to the asset, relative to $asset_url
  853. */
  854. protected static function find_files($file, $asset_type)
  855. {
  856. $parts = explode('::', $file, 2);
  857. if (!array_key_exists($parts[0], static::$asset_paths))
  858. throw new Casset_Exception("Could not find namespace {$parts[0]}");
  859. $path = static::$asset_paths[$parts[0]]['path'];
  860. $file = $parts[1];
  861. $folder = $file[0] == '/' ? '' : static::$asset_paths[$parts[0]]['dirs'][$asset_type];
  862. $file = ltrim($file, '/');
  863. $remote = (strpos($path, '//') !== false);
  864. if ($remote)
  865. {
  866. // Glob doesn't work on remote locations, so just assume they
  867. // specified a file, not a glob pattern.
  868. // Don't look for the file now either. That'll be done by
  869. // file_get_contents later on, if need be.
  870. return array($path.$folder.$file);
  871. }
  872. else
  873. {
  874. $glob_files = array_filter(glob($path.$folder.$file), 'is_file');
  875. if (!$glob_files || !count($glob_files))
  876. throw new Casset_Exception("Found no files matching $path$folder$file");
  877. return $glob_files;
  878. }
  879. }
  880. /**
  881. * Given a list of group names, adds to that list, in the appropriate places,
  882. * and groups which are listed as dependencies of those group.
  883. * Duplicate group names are not a problem, as a group is disabled when it's
  884. * rendered.
  885. *
  886. * @param string $type 'js' /or/ 'css'
  887. * @param array $group_names Array of group names to check
  888. * @param int $depth Used by this function to check for potentially infinite recursion
  889. * @return array List of group names with deps resolved
  890. */
  891. protected static function resolve_deps($type, $group_names, $depth=0)
  892. {
  893. if ($depth > static::$deps_max_depth)
  894. {
  895. throw new Casset_Exception("Reached depth $depth trying to resolve dependencies. ".
  896. "You've probably got some circular ones involving ".implode(',', $group_names).". ".
  897. "If not, adjust the config key deps_max_depth.");
  898. }
  899. // Insert the dep just before what it's a dep for
  900. foreach ($group_names as $i => $group_name)
  901. {
  902. // If the group's already been rendered, bottle
  903. if (in_array($group_name, static::$rendered_groups[$type]))
  904. continue;
  905. // Don't pay attention to bottom-level groups which are disabled
  906. if (!static::$groups[$type][$group_name]['enabled'] && $depth == 0)
  907. continue;
  908. // Otherwise, enable the group. Fairly obvious, as the whole point of
  909. // deps is to render disabled groups
  910. static::asset_enabled($type, $group_name, true);
  911. if (count(static::$groups[$type][$group_name]['deps']))
  912. {
  913. array_splice($group_names, $i, 0, static::resolve_deps($type, static::$groups[$type][$group_name]['deps'], $depth+1));
  914. }
  915. }
  916. return $group_names;
  917. }
  918. /**
  919. * Determines the list of files to be rendered, along with whether they
  920. * have been minified already.
  921. *
  922. * @param string $type 'css' / 'js'
  923. * @param array $group The groups to render. If false, takes all groups
  924. * @return array An array of array('file' => file_name, 'minified' => whether_minified)
  925. */
  926. protected static function files_to_render($type, $group)
  927. {
  928. // If no group specified, print all groups.
  929. if ($group == false)
  930. $group_names = array_keys(static::$groups[$type]);
  931. // If a group was specified, but it doesn't exist
  932. else if (!array_key_exists($group, static::$groups[$type]))
  933. return array();
  934. else
  935. $group_names = array($group);
  936. $files = array();
  937. $minified = false;
  938. $group_names = static::resolve_deps($type, $group_names);
  939. foreach ($group_names as $group_name)
  940. {
  941. if (static::$groups[$type][$group_name]['enabled'] == false)
  942. continue;
  943. // If there are no files in the group, there's no point in printing it.
  944. if (count(static::$groups[$type][$group_name]['files']) == 0)
  945. continue;
  946. $files[$group_name] = array();
  947. // Mark the group as disabled to avoid the same group being printed twice
  948. static::asset_enabled($type, $group_name, false);
  949. // Add it to the list of rendered groups
  950. array_push(static::$rendered_groups[$type], $group_name);
  951. foreach (static::$groups[$type][$group_name]['files'] as $file_set)
  952. {
  953. if (static::$groups[$type][$group_name]['min'])
  954. {
  955. $assets = static::find_files(($file_set[1]) ? $file_set[1] : $file_set[0], $type);
  956. $minified = ($file_set[1] != false);
  957. }
  958. else
  959. {
  960. $assets = static::find_files($file_set[0], $type);
  961. }
  962. foreach ($assets as $file) {
  963. array_push($files[$group_name], array(
  964. 'file' => $file,
  965. 'minified' => $minified,
  966. ));
  967. }
  968. }
  969. }
  970. return $files;
  971. }
  972. /**
  973. * Used to load a file from disk.
  974. * Also calls the post_load callback.
  975. *
  976. * @param type $filename
  977. * @return type
  978. */
  979. protected static function load_file($filename, $type, $file_group = false, $destination_filename = null)
  980. {
  981. $content = file_get_contents($filename);
  982. if (static::$post_load_callback != null)
  983. {
  984. // For some reason, PHP doesn't like you calling member closure directly
  985. $func = static::$post_load_callback;
  986. $content = $func($content, $filename, $type, $file_group);
  987. }
  988. if ($type == 'css')
  989. $content = static::css_rewrite_uris($content, $filename, $destination_filename);
  990. return $content;
  991. }
  992. /**
  993. * Selects the correct css uri rewriter, and applies it
  994. *
  995. * @param string $content the contents of the file to rewrite
  996. * @param string $filename the original location of the file
  997. * @param string $destination_filename the name of the file where the css will be written to
  998. * @return string The re-written content
  999. */
  1000. protected static function css_rewrite_uris($content, $filename, $destination_filename) {
  1001. switch (static::$css_uri_rewriter) {
  1002. case 'absolute':
  1003. $rewritten = Casset_Cssurirewriter::rewrite($content, dirname($filename));
  1004. break;
  1005. case 'relative':
  1006. $rewritten = Casset_Cssurirewriterrelative::rewrite_css($content, dirname($filename), dirname($destination_filename));
  1007. break;
  1008. case 'none':
  1009. $rewritten = $content;
  1010. break;
  1011. default:
  1012. throw new Casset_Exception('Unknown CSS URI rewriter: '.static::$css_uri_rewriter);
  1013. break;
  1014. }
  1015. return $rewritten;
  1016. }
  1017. /**
  1018. * Takes a list of files, and combines them into a single minified file.
  1019. * Doesn't bother if none of the files have been modified since the cache
  1020. * file was written.
  1021. *
  1022. * @param string $type 'css' / 'js'
  1023. * @param array $file_group Array of ('file' => filename, 'minified' => is_minified)
  1024. * to combine and minify.
  1025. * @param bool $minify whether to minify the files, as well as combining them
  1026. * @return string The path to the cache file which was written.
  1027. */
  1028. protected static function combine($type, $file_group, $minify, $inline)
  1029. {
  1030. // Get the last modified time of all of the component files
  1031. $last_mod = 0;
  1032. foreach ($file_group as $file)
  1033. {
  1034. // If it's a remote file just assume it isn't modified, otherwise
  1035. // we're stuck making a ton of HTTP requests
  1036. if (strpos($file['file'], '//') !== false)
  1037. continue;
  1038. $mod = filemtime(DOCROOT.$file['file']);
  1039. if ($mod > $last_mod)
  1040. $last_mod = $mod;
  1041. }
  1042. $filename = md5(implode('', array_map(function($a) {
  1043. return $a['file'];
  1044. }, $file_group)).($minify ? 'min' : '').$last_mod).'.'.$type;
  1045. $rel_filepath = static::$cache_path.'/'.$filename;
  1046. $abs_filepath = DOCROOT.$rel_filepath;
  1047. $needs_update = (!file_exists($abs_filepath));
  1048. if ($needs_update)
  1049. {
  1050. $content = '';
  1051. foreach ($file_group as $file)
  1052. {
  1053. if (static::$show_files_inline)
  1054. $content .= PHP_EOL.'/* '.$file['file'].' */'.PHP_EOL.PHP_EOL;
  1055. if ($file['minified'] || !$minify)
  1056. $content .= static::load_file($file['file'], $type, $file_group, $rel_filepath).PHP_EOL;
  1057. else
  1058. {
  1059. $file_content = static::load_file($file['file'], $type, $file_group, $rel_filepath);
  1060. if ($file_content === false)
  1061. throw new Casset_Exception("Couldn't not open file {$file['file']}");
  1062. if ($type == 'js')
  1063. {
  1064. $content .= Casset_JSMin::minify($file_content).PHP_EOL;
  1065. }
  1066. elseif ($type == 'css')
  1067. {
  1068. $content .= Casset_Csscompressor::process($file_content).PHP_EOL;
  1069. }
  1070. }
  1071. }
  1072. file_put_contents($abs_filepath, $content, LOCK_EX);
  1073. $mtime = time();
  1074. }
  1075. return $filename;
  1076. }
  1077. /**
  1078. * Renders the javascript added through js_inline().
  1079. *
  1080. * @return string <script> tags containing the inline javascript
  1081. */
  1082. public static function render_js_inline()
  1083. {
  1084. // the type attribute is not required for script elements under html5
  1085. // @link http://www.w3.org/TR/html5/scripting-1.html#attr-script-type
  1086. if (!\Html::$html5)
  1087. $attr = array( 'type' => 'text/javascript' );
  1088. else
  1089. $attr = array();
  1090. return html_tag('script', $attr, PHP_EOL.implode(PHP_EOL, static::$inline_assets['js']).PHP_EOL).PHP_EOL;
  1091. }
  1092. /**
  1093. * Renders the css added through css_inline().
  1094. *
  1095. * @return string <style> tags containing the inline css
  1096. */
  1097. public static function render_css_inline()
  1098. {
  1099. // the type attribute is not required for style elements under html5
  1100. // @link http://www.w3.org/TR/html5/semantics.html#attr-style-type
  1101. if (!\Html::$html5)
  1102. $attr = array( 'type' => 'text/css' );
  1103. else
  1104. $attr = array();
  1105. return html_tag('style', $attr, PHP_EOL.implode(PHP_EOL, static::$inline_assets['css']).PHP_EOL).PHP_EOL;
  1106. }
  1107. /**
  1108. * Sets the post_load file callback. It's pretty basic, and you're expected
  1109. * to handle e.g. filtering for the right file yourself.
  1110. * @param function the function to set
  1111. */
  1112. public static function set_post_load_callback($callback) {
  1113. static::$post_load_callback = $callback;
  1114. }
  1115. /**
  1116. * Sets the filepath callback
  1117. * @param function The function to set
  1118. */
  1119. public static function set_filepath_callback($callback) {
  1120. static::$filepath_callback = $callback;
  1121. }
  1122. /**
  1123. * Locates the given image(s), and returns the resulting <img> tag.
  1124. *
  1125. * @param mixed $images Image(s) to print. Can be string or array of strings
  1126. * @param string $alt The alternate text
  1127. * @param array $attr Attributes to apply to each image (eg width)
  1128. * @return string The resulting <img> tag(s)
  1129. */
  1130. public static function img($images, $alt = '', $attr = array())
  1131. {
  1132. if (!is_array($images))
  1133. $images = array($images);
  1134. $attr['alt'] = $alt;
  1135. $ret = '';
  1136. foreach ($images as $image)
  1137. {
  1138. if (strpos($image, '::') === false)
  1139. $image = static::$default_path_key.'::'.$image;
  1140. $image_paths = static::find_files($image, 'img');
  1141. foreach ($image_paths as $image_path)
  1142. {
  1143. $remote = (strpos($image_path, '//') !== false);
  1144. $image_path = static::process_filepath($image_path, 'img', $remote);
  1145. $base = ($remote) ? '' : static::$asset_url;
  1146. $attr['src'] = $base.$image_path;
  1147. $ret .= html_tag('img', $attr);
  1148. }
  1149. }
  1150. return $ret;
  1151. }
  1152. /**
  1153. * Cleares all cache files last modified before $before.
  1154. *
  1155. * @param type $before Time before which to delete files. Defaults to 'now'.
  1156. * Uses strtotime.
  1157. */
  1158. public static function clear_cache($before = 'now')
  1159. {
  1160. static::clear_cache_base('*', $before);
  1161. }
  1162. /**
  1163. * Cleares all JS cache files last modified before $before.
  1164. *
  1165. * @param type $before Time before which to delete files. Defaults to 'now'.
  1166. * Uses strtotime.
  1167. */
  1168. public static function clear_js_cache($before = 'now')
  1169. {
  1170. static::clear_cache_base('*.js', $before);
  1171. }
  1172. /**
  1173. * Cleares CSS all cache files last modified before $before.
  1174. *
  1175. * @param type $before Time before which to delete files. Defaults to 'now'.
  1176. * Uses strtotime.
  1177. */
  1178. public static function clear_css_cache($before = 'now')
  1179. {
  1180. static::clear_cache_base('*.css', $before);
  1181. }
  1182. /**
  1183. * Base cache clear function.
  1184. *
  1185. * @param type $filter Glob filter to use when selecting files to delete.
  1186. * @param type $before Time before which to delete files. Defaults to 'now'.
  1187. * Uses strtotime.
  1188. */
  1189. protected static function clear_cache_base($filter = '*', $before = 'now')
  1190. {
  1191. $before = strtotime($before);
  1192. $files = glob(DOCROOT.static::$cache_path.$filter);
  1193. foreach ($files as $file)
  1194. {
  1195. if (filemtime($file) < $before)
  1196. unlink($file);
  1197. }
  1198. }
  1199. }
  1200. class Casset_Exception extends \FuelException {
  1201. }
  1202. /* End of file casset.php */