| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 | <?php namespace System;use System\File;use System\HTML;class Asset {	/**	 * All of the instantiated asset containers.	 *	 * Asset containers are created through the container method, and are singletons.	 *	 * @var array	 */	public static $containers = array();	/**	 * Get an asset container instance.	 *	 * If no container name is specified, the default container will be returned.	 * Containers provide a convenient method of grouping assets while maintaining	 * expressive code and a clean API.	 *	 * <code>	 *		// Get the default asset container	 *		$container = Asset::container();	 *	 *		// Get the "footer" asset contanier	 *		$container = Asset::container('footer');	 *	 *		// Add an asset to the "footer" container	 *		Asset::container('footer')->add('jquery', 'js/jquery.js');	 * </code>	 *	 * @param  string            $container	 * @return Asset_Container	 */	public static function container($container = 'default')	{		if ( ! isset(static::$containers[$container]))		{			static::$containers[$container] = new Asset_Container($container);		}		return static::$containers[$container];	}	/**	 * Magic Method for calling methods on the default Asset container.	 *	 * <code>	 *		// Add jQuery to the default container	 *		Asset::script('jquery', 'js/jquery.js');	 *	 *		// Equivalent call using the container method	 *		Asset::container()->script('jquery', 'js/jquery.js');	 * </code>	 */	public static function __callStatic($method, $parameters)	{		return call_user_func_array(array(static::container(), $method), $parameters);	}}class Asset_Container {	/**	 * The asset container name.	 *	 * @var string	 */	public $name;	/**	 * All of the registered assets.	 *	 * @var array	 */	public $assets = array();	/**	 * Create a new asset container instance.	 *	 * @param  string  $name	 * @return void	 */	public function __construct($name)	{		$this->name = $name;	}	/**	 * Add an asset to the container.	 *	 * The extension of the asset source will be used to determine the type of	 * asset being registered (CSS or JavaScript). If you are using a non-standard	 * extension, you may use the style or script methods to register assets.	 *	 * <code>	 *		// Register a jQuery asset	 *		Asset::add('jquery', 'js/jquery.js');	 * </code>	 *	 * You may also specify asset dependencies. This will instruct the class to	 * only link to the registered asset after its dependencies have been linked.	 * For example, you may wish to make jQuery UI dependent on jQuery.	 *	 * <code>	 *		// Register jQuery UI as dependent on jQuery	 *		Asset::add('jquery-ui', 'js/jquery-ui.js', 'jquery');	 *	 *		// Register jQuery UI with multiple dependencies	 *		Asset::add('jquery-ui', 'js/jquery-ui.js', array('jquery', 'fader'));	 * </code>	 *	 * @param  string  $name	 * @param  string  $source	 * @param  array   $dependencies	 * @param  array   $attributes	 * @return void	 */	public function add($name, $source, $dependencies = array(), $attributes = array())	{		// Since assets may contain timestamps to force a refresh, we will strip them		// off to get the "real" filename of the asset.		$segments = explode('?', $source);		$type = (File::extension($segments[0]) == 'css') ? 'style' : 'script';		return call_user_func(array($this, $type), $name, $source, $dependencies, $attributes);	}	/**	 * Add CSS to the registered assets.	 *	 * @param  string  $name	 * @param  string  $source	 * @param  array   $dependencies	 * @param  array   $attributes	 * @return void	 * @see    add	 */	public function style($name, $source, $dependencies = array(), $attributes = array())	{		if ( ! array_key_exists('media', $attributes))		{			$attributes['media'] = 'all';		}		$this->register('style', $name, $source, $dependencies, $attributes);	}	/**	 * Add JavaScript to the registered assets.	 *	 * @param  string  $name	 * @param  string  $source	 * @param  array   $dependencies	 * @param  array   $attributes	 * @return void	 * @see    add	 */	public function script($name, $source, $dependencies = array(), $attributes = array())	{		$this->register('script', $name, $source, $dependencies, $attributes);	}	/**	 * Add an asset to the registered assets.	 *	 * @param  string  $type	 * @param  string  $name	 * @param  string  $source	 * @param  array   $dependencies	 * @param  array   $attributes	 * @return void	 */	private function register($type, $name, $source, $dependencies, $attributes)	{		$dependencies = (array) $dependencies;		$this->assets[$type][$name] = compact('source', 'dependencies', 'attributes');	}	/**	 * Get the links to all of the registered CSS assets.	 *	 * @return  string	 */	public function styles()	{		return $this->get_group('style');	}	/**	 * Get the links to all of the registered JavaScript assets.	 *	 * @return  string	 */	public function scripts()	{		return $this->get_group('script');	}	/**	 * Get all of the registered assets for a given group.	 *	 * @param  string  $group	 * @return string	 */	private function get_group($group)	{		if ( ! isset($this->assets[$group]) or count($this->assets[$group]) == 0) return '';		$assets = '';		foreach ($this->arrange($this->assets[$group]) as $name => $data)		{			$assets .= $this->get_asset($group, $name);		}				return $assets;	}	/**	 * Get the link to a single registered CSS asset.	 *	 * <code>	 *		echo $container->get_style('common');	 * </code>	 *	 * @param  string  $name	 * @return string	 */	public function get_style($name)	{		return $this->get_asset('style', $name);	}	/**	 * Get the link to a single registered JavaScript asset.	 *	 * <code>	 *		echo $container->get_script('jquery');	 * </code>	 *	 * @param  string  $name	 * @return string	 */	public function get_script($name)	{		return $this->get_asset('script', $name);	}	/**	 * Get a registered asset.	 *	 * @param  string  $group	 * @param  string  $name	 * @return string	 */	private function get_asset($group, $name)	{		if ( ! isset($this->assets[$group][$name])) return '';		$asset = $this->assets[$group][$name];		return HTML::$group($asset['source'], $asset['attributes']);	}	/**	 * Sort and retrieve assets based on their dependencies	 *	 * @param   array  $assets	 * @return  array	 */	private function arrange($assets)	{		list($original, $sorted) = array($assets, array());		while (count($assets) > 0)		{			foreach ($assets as $asset => $value)			{				$this->evaluate_asset($asset, $value, $original, $sorted, $assets);			}		}				return $sorted;	}	/**	 * Evaluate an asset and its dependencies.	 *	 * @param  string  $asset	 * @param  string  $value	 * @param  array   $original	 * @param  array   $sorted	 * @param  array   $assets	 * @return void	 */	private function evaluate_asset($asset, $value, $original, &$sorted, &$assets)	{		// If the asset has no more dependencies, we can add it to the sorted list		// and remove it from the array of assets. Otherwise, we will not verify		// the asset's dependencies and determine if they have already been sorted.		if (count($assets[$asset]['dependencies']) == 0)		{			$sorted[$asset] = $value;			unset($assets[$asset]);		}		else		{			foreach ($assets[$asset]['dependencies'] as $key => $dependency)			{				if ( ! $this->dependency_is_valid($asset, $dependency, $original, $assets))				{					unset($assets[$asset]['dependencies'][$key]);					continue;				}				// If the dependency has not yet been added to the sorted list, we can not				// remove it from this asset's array of dependencies. We'll try again on				// the next trip through the loop.				if ( ! isset($sorted[$dependency])) continue;				unset($assets[$asset]['dependencies'][$key]);			}		}			}	/**	 * Check that a dependency is valid.	 *	 * @param  string  $asset	 * @param  string  $dependency	 * @param  array   $original	 * @param  array   $assets	 * @return bool	 */	private function dependency_is_valid($asset, $dependency, $original, $assets)	{		if ( ! isset($original[$dependency]))		{			return false;		}		elseif ($dependency === $asset)		{			throw new \Exception("Asset [$asset] is dependent on itself.");		}		elseif (isset($assets[$dependency]) and in_array($asset, $assets[$dependency]['dependencies']))		{			throw new \Exception("Assets [$asset] and [$dependency] have a circular dependency.");		}	}}
 |