asset.php 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. <?php namespace Laravel;
  2. class Asset {
  3. /**
  4. * All of the instantiated asset containers.
  5. *
  6. * @var array
  7. */
  8. public static $containers = array();
  9. /**
  10. * Get an asset container instance.
  11. *
  12. * <code>
  13. * // Get the default asset container
  14. * $container = Asset::container();
  15. *
  16. * // Get a named asset container
  17. * $container = Asset::container('footer');
  18. * </code>
  19. *
  20. * @param string $container
  21. * @return Asset_Container
  22. */
  23. public static function container($container = 'default')
  24. {
  25. if ( ! isset(static::$containers[$container]))
  26. {
  27. static::$containers[$container] = new Asset_Container($container);
  28. }
  29. return static::$containers[$container];
  30. }
  31. /**
  32. * Magic Method for calling methods on the default container.
  33. *
  34. * <code>
  35. * // Call the "styles" method on the default container
  36. * echo Asset::styles();
  37. *
  38. * // Call the "add" method on the default container
  39. * Asset::add('jquery', 'js/jquery.js');
  40. * </code>
  41. */
  42. public static function __callStatic($method, $parameters)
  43. {
  44. return call_user_func_array(array(static::container(), $method), $parameters);
  45. }
  46. }
  47. class Asset_Container {
  48. /**
  49. * The asset container name.
  50. *
  51. * @var string
  52. */
  53. public $name;
  54. /**
  55. * The bundle that the assets belong to.
  56. *
  57. * @var string
  58. */
  59. public $bundle = DEFAULT_BUNDLE;
  60. /**
  61. * All of the registered assets.
  62. *
  63. * @var array
  64. */
  65. public $assets = array();
  66. /**
  67. * Create a new asset container instance.
  68. *
  69. * @param string $name
  70. * @return void
  71. */
  72. public function __construct($name)
  73. {
  74. $this->name = $name;
  75. }
  76. /**
  77. * Add an asset to the container.
  78. *
  79. * The extension of the asset source will be used to determine the type of
  80. * asset being registered (CSS or JavaScript). When using a non-standard
  81. * extension, the style/script methods may be used to register assets.
  82. *
  83. * <code>
  84. * // Add an asset to the container
  85. * Asset::container()->add('jquery', 'js/jquery.js');
  86. *
  87. * // Add an asset that has dependencies on other assets
  88. * Asset::add('jquery', 'js/jquery.js', 'jquery-ui');
  89. *
  90. * // Add an asset that should have attributes applied to its tags
  91. * Asset::add('jquery', 'js/jquery.js', null, array('defer'));
  92. * </code>
  93. *
  94. * @param string $name
  95. * @param string $source
  96. * @param array $dependencies
  97. * @param array $attributes
  98. * @return void
  99. */
  100. public function add($name, $source, $dependencies = array(), $attributes = array())
  101. {
  102. $type = (pathinfo($source, PATHINFO_EXTENSION) == 'css') ? 'style' : 'script';
  103. return $this->$type($name, $source, $dependencies, $attributes);
  104. }
  105. /**
  106. * Add a CSS file to the registered assets.
  107. *
  108. * @param string $name
  109. * @param string $source
  110. * @param array $dependencies
  111. * @param array $attributes
  112. * @return Asset_Container
  113. */
  114. public function style($name, $source, $dependencies = array(), $attributes = array())
  115. {
  116. if ( ! array_key_exists('media', $attributes))
  117. {
  118. $attributes['media'] = 'all';
  119. }
  120. $this->register('style', $name, $source, $dependencies, $attributes);
  121. return $this;
  122. }
  123. /**
  124. * Add a JavaScript file to the registered assets.
  125. *
  126. * @param string $name
  127. * @param string $source
  128. * @param array $dependencies
  129. * @param array $attributes
  130. * @return Asset_Container
  131. */
  132. public function script($name, $source, $dependencies = array(), $attributes = array())
  133. {
  134. $this->register('script', $name, $source, $dependencies, $attributes);
  135. return $this;
  136. }
  137. /**
  138. * Returns the full-path for an asset.
  139. *
  140. * @param string $source
  141. * @return string
  142. */
  143. public function path($source)
  144. {
  145. return Bundle::assets($this->bundle).$source;
  146. }
  147. /**
  148. * Set the bundle that the container's assets belong to.
  149. *
  150. * @param string $bundle
  151. * @return Asset_Container
  152. */
  153. public function bundle($bundle)
  154. {
  155. $this->bundle = $bundle;
  156. return $this;
  157. }
  158. /**
  159. * Add an asset to the array of registered assets.
  160. *
  161. * @param string $type
  162. * @param string $name
  163. * @param string $source
  164. * @param array $dependencies
  165. * @param array $attributes
  166. * @return void
  167. */
  168. protected function register($type, $name, $source, $dependencies, $attributes)
  169. {
  170. $dependencies = (array) $dependencies;
  171. $attributes = (array) $attributes;
  172. $this->assets[$type][$name] = compact('source', 'dependencies', 'attributes');
  173. }
  174. /**
  175. * Get the links to all of the registered CSS assets.
  176. *
  177. * @return string
  178. */
  179. public function styles()
  180. {
  181. return $this->group('style');
  182. }
  183. /**
  184. * Get the links to all of the registered JavaScript assets.
  185. *
  186. * @return string
  187. */
  188. public function scripts()
  189. {
  190. return $this->group('script');
  191. }
  192. /**
  193. * Get all of the registered assets for a given type / group.
  194. *
  195. * @param string $group
  196. * @return string
  197. */
  198. protected function group($group)
  199. {
  200. if ( ! isset($this->assets[$group]) or count($this->assets[$group]) == 0) return '';
  201. $assets = '';
  202. foreach ($this->arrange($this->assets[$group]) as $name => $data)
  203. {
  204. $assets .= $this->asset($group, $name);
  205. }
  206. return $assets;
  207. }
  208. /**
  209. * Get the HTML link to a registered asset.
  210. *
  211. * @param string $group
  212. * @param string $name
  213. * @return string
  214. */
  215. protected function asset($group, $name)
  216. {
  217. if ( ! isset($this->assets[$group][$name])) return '';
  218. $asset = $this->assets[$group][$name];
  219. // If the bundle source is not a complete URL, we will go ahead and prepend
  220. // the bundle's asset path to the source provided with the asset. This will
  221. // ensure that we attach the correct path to the asset.
  222. if (filter_var($asset['source'], FILTER_VALIDATE_URL) === false)
  223. {
  224. $asset['source'] = $this->path($asset['source']);
  225. }
  226. return HTML::$group($asset['source'], $asset['attributes']);
  227. }
  228. /**
  229. * Sort and retrieve assets based on their dependencies
  230. *
  231. * @param array $assets
  232. * @return array
  233. */
  234. protected function arrange($assets)
  235. {
  236. list($original, $sorted) = array($assets, array());
  237. while (count($assets) > 0)
  238. {
  239. foreach ($assets as $asset => $value)
  240. {
  241. $this->evaluate_asset($asset, $value, $original, $sorted, $assets);
  242. }
  243. }
  244. return $sorted;
  245. }
  246. /**
  247. * Evaluate an asset and its dependencies.
  248. *
  249. * @param string $asset
  250. * @param string $value
  251. * @param array $original
  252. * @param array $sorted
  253. * @param array $assets
  254. * @return void
  255. */
  256. protected function evaluate_asset($asset, $value, $original, &$sorted, &$assets)
  257. {
  258. // If the asset has no more dependencies, we can add it to the sorted list
  259. // and remove it from the array of assets. Otherwise, we will not verify
  260. // the asset's dependencies and determine if they've been sorted.
  261. if (count($assets[$asset]['dependencies']) == 0)
  262. {
  263. $sorted[$asset] = $value;
  264. unset($assets[$asset]);
  265. }
  266. else
  267. {
  268. foreach ($assets[$asset]['dependencies'] as $key => $dependency)
  269. {
  270. if ( ! $this->dependency_is_valid($asset, $dependency, $original, $assets))
  271. {
  272. unset($assets[$asset]['dependencies'][$key]);
  273. continue;
  274. }
  275. // If the dependency has not yet been added to the sorted list, we can not
  276. // remove it from this asset's array of dependencies. We'll try again on
  277. // the next trip through the loop.
  278. if ( ! isset($sorted[$dependency])) continue;
  279. unset($assets[$asset]['dependencies'][$key]);
  280. }
  281. }
  282. }
  283. /**
  284. * Verify that an asset's dependency is valid.
  285. *
  286. * A dependency is considered valid if it exists, is not a circular reference, and is
  287. * not a reference to the owning asset itself. If the dependency doesn't exist, no
  288. * error or warning will be given. For the other cases, an exception is thrown.
  289. *
  290. * @param string $asset
  291. * @param string $dependency
  292. * @param array $original
  293. * @param array $assets
  294. * @return bool
  295. */
  296. protected function dependency_is_valid($asset, $dependency, $original, $assets)
  297. {
  298. if ( ! isset($original[$dependency]))
  299. {
  300. return false;
  301. }
  302. elseif ($dependency === $asset)
  303. {
  304. throw new \Exception("Asset [$asset] is dependent on itself.");
  305. }
  306. elseif (isset($assets[$dependency]) and in_array($asset, $assets[$dependency]['dependencies']))
  307. {
  308. throw new \Exception("Assets [$asset] and [$dependency] have a circular dependency.");
  309. }
  310. return true;
  311. }
  312. }