You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
633 lines
15 KiB
633 lines
15 KiB
<?php
|
|
/**
|
|
* Part of the Fuel framework.
|
|
*
|
|
* @package Fuel
|
|
* @version 1.8
|
|
* @author Fuel Development Team
|
|
* @license MIT License
|
|
* @copyright 2010 - 2016 Fuel Development Team
|
|
* @link http://fuelphp.com
|
|
*/
|
|
|
|
namespace Fuel\Core;
|
|
|
|
/**
|
|
* The Asset class allows you to easily work with your apps assets.
|
|
* It allows you to specify multiple paths to be searched for the
|
|
* assets.
|
|
*
|
|
* You can configure the paths by copying the core/config/asset.php
|
|
* config file into your app/config folder and changing the settings.
|
|
*
|
|
* @package Fuel
|
|
* @subpackage Core
|
|
*/
|
|
class Asset_Instance
|
|
{
|
|
/**
|
|
* @var array the asset paths to be searched
|
|
*/
|
|
protected $_asset_paths = array(
|
|
'css' => array(),
|
|
'js' => array(),
|
|
'img' => array(),
|
|
);
|
|
|
|
/**
|
|
* @var array the sub-folders to be searched
|
|
*/
|
|
protected $_path_folders = array(
|
|
'css' => 'css/',
|
|
'js' => 'js/',
|
|
'img' => 'img/',
|
|
);
|
|
|
|
/**
|
|
* @var array custom type renderers
|
|
*/
|
|
protected $_renderers = array(
|
|
);
|
|
|
|
/**
|
|
* @var string the URL to be prepended to all assets
|
|
*/
|
|
protected $_asset_url = '/';
|
|
|
|
/**
|
|
* @var bool whether to append the file mtime to the url
|
|
*/
|
|
protected $_add_mtime = true;
|
|
|
|
/**
|
|
* @var array holds the groups of assets
|
|
*/
|
|
protected $_groups = array();
|
|
|
|
/**
|
|
* @var string prefix for generated output to provide proper indentation
|
|
*/
|
|
protected $_indent = '';
|
|
|
|
/**
|
|
* @var bool if true, directly renders the output of no group name is given
|
|
*/
|
|
protected $_auto_render = true;
|
|
|
|
/**
|
|
* @var bool if true the 'not found' exception will not be thrown and the asset is ignored.
|
|
*/
|
|
protected $_fail_silently = false;
|
|
|
|
/**
|
|
* @var bool if true, will always true to resolve assets. if false, it will only try to resolve if the asset url is relative.
|
|
*/
|
|
protected $_always_resolve = false;
|
|
|
|
/**
|
|
* Parse the config and initialize the object instance
|
|
*
|
|
* @param array $config
|
|
*/
|
|
public function __construct(Array $config)
|
|
{
|
|
// look for global search path folders
|
|
foreach ($config as $key => $value)
|
|
{
|
|
if (\Str::ends_with($key, '_dir'))
|
|
{
|
|
$key = substr($key, 0, -4);
|
|
$this->_path_folders[$key] = $this->_unify_path($value);
|
|
}
|
|
}
|
|
|
|
// global search paths
|
|
foreach ($config['paths'] as $path)
|
|
{
|
|
$this->add_path($path);
|
|
}
|
|
|
|
// per-type search paths
|
|
foreach ($config['folders'] as $type => $folders)
|
|
{
|
|
is_array($folders) or $folders = array($folders);
|
|
|
|
foreach ($folders as $path)
|
|
{
|
|
$this->add_path($path, $type);
|
|
}
|
|
}
|
|
|
|
$this->_add_mtime = (bool) $config['add_mtime'];
|
|
$this->_asset_url = $config['url'];
|
|
$this->_indent = str_repeat($config['indent_with'], $config['indent_level']);
|
|
$this->_auto_render = (bool) $config['auto_render'];
|
|
$this->_fail_silently = (bool) $config['fail_silently'];
|
|
$this->_always_resolve = (bool) $config['always_resolve'];
|
|
}
|
|
|
|
/**
|
|
* Provide backward compatibility for old type methods
|
|
*
|
|
* @param $method
|
|
* @param $args
|
|
* @return mixed
|
|
* @throws \ErrorException
|
|
*/
|
|
public function __call($method, $args)
|
|
{
|
|
// check if we can render this type
|
|
if ( ! isset($this->_path_folders[$method]))
|
|
{
|
|
throw new \ErrorException('Call to undefined method Fuel\Core\Asset_Instance::'.$method.'()');
|
|
}
|
|
|
|
// add the type to the arguments
|
|
array_unshift($args, $method);
|
|
|
|
// call assettype to store the info
|
|
return call_user_func_array(array($this, 'assettype'), $args);
|
|
}
|
|
|
|
/**
|
|
* Adds a new asset type to the list so we can load files of this type
|
|
*
|
|
* @param string $type new path type
|
|
* @param string $path optional default path
|
|
* @param Closure $renderer function to custom render this type
|
|
*
|
|
* @return object current instance
|
|
*/
|
|
public function add_type($type, $path = null, $renderer = null)
|
|
{
|
|
isset($this->_asset_paths[$type]) or $this->_asset_paths[$type] = array();
|
|
isset($this->_path_folders[$type]) or $this->_path_folders[$type] = $type.'/';
|
|
|
|
if ( ! is_null($path))
|
|
{
|
|
$path = $this->_unify_path($path);
|
|
$this->_asset_paths[$type][] = $path;
|
|
}
|
|
|
|
if ( ! is_null($renderer))
|
|
{
|
|
if ( ! $renderer instanceOf \Closure)
|
|
{
|
|
throw new \OutOfBoundsException('Asset type renderer must be passed as a Closure!');
|
|
}
|
|
|
|
$this->_renderers[$type] = $renderer;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Adds the given path to the front of the asset paths array. It adds paths
|
|
* in a way so that asset paths are used First in Last Out.
|
|
*
|
|
* @param string $path the path to add
|
|
* @param string $type optional path type (js, css or img)
|
|
* @return object current instance
|
|
*/
|
|
public function add_path($path, $type = null)
|
|
{
|
|
is_null($type) and $type = $this->_path_folders;
|
|
empty($path) and $path = DOCROOT;
|
|
|
|
if( is_array($type))
|
|
{
|
|
foreach ($type as $key => $folder)
|
|
{
|
|
is_numeric($key) and $key = $folder;
|
|
$folder = $this->_unify_path($path).ltrim($this->_unify_path($folder), DS);
|
|
in_array($folder, $this->_asset_paths[$key]) or array_unshift($this->_asset_paths[$key], $folder);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// create the asset type if it doesn't exist
|
|
if ( ! isset($this->_asset_paths[$type]))
|
|
{
|
|
$this->_asset_paths[$type] = array();
|
|
$this->_path_folders[$type] = $type.'/';
|
|
}
|
|
|
|
$path = $this->_unify_path($path);
|
|
in_array($path, $this->_asset_paths[$type]) or array_unshift($this->_asset_paths[$type], $path);
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Removes the given path from the asset paths array
|
|
*
|
|
* @param string $path the path to remove
|
|
* @param string $type optional path type (js, css or img)
|
|
* @return object current instance
|
|
*/
|
|
public function remove_path($path, $type = null)
|
|
{
|
|
is_null($type) and $type = $this->_path_folders;
|
|
|
|
if( is_array($type))
|
|
{
|
|
foreach ($type as $key => $folder)
|
|
{
|
|
is_numeric($key) and $key = $folder;
|
|
$folder = $this->_unify_path($path).ltrim($this->_unify_path($folder), DS);
|
|
if (($found = array_search($folder, $this->_asset_paths[$key])) !== false)
|
|
{
|
|
unset($this->_asset_paths[$key][$found]);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$path = $this->_unify_path($path);
|
|
if (($key = array_search($path, $this->_asset_paths[$type])) !== false)
|
|
{
|
|
unset($this->_asset_paths[$type][$key]);
|
|
}
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
// --------------------------------------------------------------------
|
|
|
|
/**
|
|
* Asset type store.
|
|
*
|
|
* Either adds the asset to the group, or directly return the tag.
|
|
*
|
|
* @param string $type The asset type
|
|
* @param mixed $files The file name, or an array files.
|
|
* @param array $attr An array of extra attributes
|
|
* @param string $group The asset group name
|
|
* @param boolean $raw whether to return the raw file or not when group is not set (optional)
|
|
* @return string|object Rendered asset or current instance when adding to group
|
|
*/
|
|
public function assettype($type, $files = array(), $attr = array(), $group = null, $raw = false)
|
|
{
|
|
static $temp_group = 50000000;
|
|
|
|
if ($group === null)
|
|
{
|
|
$render = $this->_auto_render;
|
|
$group = $render ? (string) (++$temp_group) : '_default_';
|
|
}
|
|
else
|
|
{
|
|
$render = false;
|
|
}
|
|
|
|
$this->_parse_assets($type, $files, $attr, $group, $raw);
|
|
|
|
if ($render)
|
|
{
|
|
return $this->render($group, $raw);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
// --------------------------------------------------------------------
|
|
|
|
/**
|
|
* Find File
|
|
*
|
|
* Locates a file in all the asset paths.
|
|
*
|
|
* @param string $file The filename to locate
|
|
* @param string $type The type of asset file to search
|
|
* @param string $folder The sub-folder to look in (optional)
|
|
* @return mixed Either the path to the file or false if not found
|
|
*/
|
|
public function find_file($file, $type, $folder = '')
|
|
{
|
|
foreach ($this->_asset_paths[$type] as $path)
|
|
{
|
|
empty($folder) or $folder = $this->_unify_path($folder);
|
|
|
|
if (is_file($newfile = $path.$folder.$this->_unify_path($file, null, false)))
|
|
{
|
|
// return the file found, make sure it uses forward slashes on Windows
|
|
return str_replace(DS, '/', $newfile);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// --------------------------------------------------------------------
|
|
|
|
/**
|
|
* Get File
|
|
*
|
|
* Locates a file in all the asset paths, and return it relative to the docroot
|
|
*
|
|
* @param string $file The filename to locate
|
|
* @param string $type The type of asset file
|
|
* @param string $folder The sub-folder to look in (optional)
|
|
* @return mixed Either the path to the file or false if not found
|
|
*/
|
|
public function get_file($file, $type, $folder = '')
|
|
{
|
|
if ($file = $this->find_file($file, $type, $folder))
|
|
{
|
|
strpos($file, DOCROOT) === 0 and $file = substr($file, strlen(DOCROOT));
|
|
|
|
return $this->_asset_url.$file;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Renders the given group. Each tag will be separated by a line break.
|
|
* You can optionally tell it to render the files raw. This means that
|
|
* all CSS and JS files in the group will be read and the contents included
|
|
* in the returning value.
|
|
*
|
|
* @param mixed $group the group to render
|
|
* @param bool $raw whether to return the raw file or not
|
|
* @return string the group's output
|
|
* @throws \FuelException
|
|
*/
|
|
public function render($group = null, $raw = false)
|
|
{
|
|
// determine the group to render
|
|
is_null($group) and $group = '_default_';
|
|
|
|
if (is_string($group))
|
|
{
|
|
isset($this->_groups[$group]) and $group = $this->_groups[$group];
|
|
}
|
|
|
|
is_array($group) or $group = array();
|
|
|
|
// storage for the result
|
|
$result = array();
|
|
|
|
// pre-define known types so the order is correct
|
|
foreach($this->_path_folders as $type => $unused)
|
|
{
|
|
$result[$type] = '';
|
|
}
|
|
|
|
// loop over the group entries
|
|
foreach ($group as $key => $item)
|
|
{
|
|
// determine file name and inline status
|
|
$type = $item['type'];
|
|
$filename = $item['file'];
|
|
$attr = $item['attr'];
|
|
$inline = $item['raw'];
|
|
|
|
// make sure we have storage space for this result
|
|
if ( ! isset($result[$type]))
|
|
{
|
|
$result[$type] = '';
|
|
}
|
|
|
|
// only do a file search if the asset is not a URI
|
|
if ($this->_always_resolve or ! preg_match('|^(\w+:)?//|', $filename))
|
|
{
|
|
// and only if the asset is local to the applications base_url
|
|
if ($this->_always_resolve or ! preg_match('|^(\w+:)?//|', $this->_asset_url) or strpos($this->_asset_url, \Config::get('base_url')) === 0)
|
|
{
|
|
if ( ! ($file = $this->find_file($filename, $type)))
|
|
{
|
|
if ($raw or $inline)
|
|
{
|
|
$file = $filename;
|
|
}
|
|
else
|
|
{
|
|
if ($this->_fail_silently)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
throw new \FuelException('Could not find asset: '.$filename);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ($raw or $inline)
|
|
{
|
|
$file = file_get_contents($file);
|
|
$inline = true;
|
|
}
|
|
else
|
|
{
|
|
$file = $this->_asset_url.$file.($this->_add_mtime ? '?'.filemtime($file) : '');
|
|
$file = str_replace(str_replace(DS, '/', DOCROOT), '', $file);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// a remote file and multiple paths? use the first one!
|
|
$path = reset($this->_asset_paths[$type]);
|
|
$file = $this->_asset_url.$path.$filename;
|
|
if ($raw or $inline)
|
|
{
|
|
$file = file_get_contents($file);
|
|
$inline = true;
|
|
}
|
|
else
|
|
{
|
|
$file = str_replace(str_replace(DS, '/', DOCROOT), '', $file);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$file = $filename;
|
|
}
|
|
|
|
// deal with stray backslashes on Windows
|
|
$file = str_replace('\\', '/', $file);
|
|
|
|
// call the renderer for this type
|
|
if (isset($this->_renderers[$type]))
|
|
{
|
|
$method = $this->_renderers[$type];
|
|
$result[$type] .= $method($file, $attr, $inline);
|
|
}
|
|
else
|
|
{
|
|
$method = 'render_'.$type;
|
|
if (method_exists($this, $method))
|
|
{
|
|
$result[$type] .= $this->$method($file, $attr, $inline);
|
|
}
|
|
else
|
|
{
|
|
throw new \OutOfBoundsException('Asset does not know how to render files of type "'.$type.'"!');
|
|
}
|
|
}
|
|
}
|
|
|
|
// return them in the correct order, as a string
|
|
return implode("", $result);
|
|
}
|
|
|
|
// --------------------------------------------------------------------
|
|
|
|
/**
|
|
* CSS tag renderer
|
|
*
|
|
* @param $file
|
|
* @param $attr
|
|
* @param $inline
|
|
* @return string
|
|
*/
|
|
protected function render_css($file, $attr, $inline)
|
|
{
|
|
// storage for the result
|
|
$result = '';
|
|
|
|
// make sure we have a type
|
|
isset($attr['type']) or $attr['type'] = 'text/css';
|
|
|
|
// render inline. or not
|
|
if ($inline)
|
|
{
|
|
$result = html_tag('style', $attr, PHP_EOL.$file.PHP_EOL).PHP_EOL;
|
|
}
|
|
else
|
|
{
|
|
if ( ! isset($attr['rel']) or empty($attr['rel']))
|
|
{
|
|
$attr['rel'] = 'stylesheet';
|
|
}
|
|
$attr['href'] = $file;
|
|
|
|
$result = $this->_indent.html_tag('link', $attr).PHP_EOL;
|
|
}
|
|
|
|
// return the result
|
|
return $result;
|
|
}
|
|
|
|
// --------------------------------------------------------------------
|
|
|
|
/**
|
|
* JS tag renderer
|
|
*
|
|
* @param $file
|
|
* @param $attr
|
|
* @param $inline
|
|
* @return string
|
|
*/
|
|
protected function render_js($file, $attr, $inline)
|
|
{
|
|
// storage for the result
|
|
$result = '';
|
|
|
|
// render inline. or not
|
|
$attr['type'] = 'text/javascript';
|
|
if ($inline)
|
|
{
|
|
$result = html_tag('script', $attr, PHP_EOL.$file.PHP_EOL).PHP_EOL;
|
|
}
|
|
else
|
|
{
|
|
$attr['src'] = $file;
|
|
|
|
$result = $this->_indent.html_tag('script', $attr, '').PHP_EOL;
|
|
}
|
|
|
|
// return the result
|
|
return $result;
|
|
}
|
|
|
|
// --------------------------------------------------------------------
|
|
|
|
/**
|
|
* IMG tag renderer
|
|
*
|
|
* @param $file
|
|
* @param $attr
|
|
* @param $inline
|
|
* @return string
|
|
*/
|
|
protected function render_img($file, $attr, $inline)
|
|
{
|
|
// storage for the result
|
|
$result = '';
|
|
|
|
// render the image
|
|
$attr['src'] = $file;
|
|
$attr['alt'] = isset($attr['alt']) ? $attr['alt'] : '';
|
|
|
|
$result = html_tag('img', $attr );
|
|
|
|
// return the result
|
|
return $result;
|
|
}
|
|
|
|
// --------------------------------------------------------------------
|
|
|
|
/**
|
|
* Parse Assets
|
|
*
|
|
* Pareses the assets and adds them to the group
|
|
*
|
|
* @param string $type The asset type
|
|
* @param mixed $assets The file name, or an array files.
|
|
* @param array $attr An array of extra attributes
|
|
* @param string $group The asset group name
|
|
* @param bool $raw
|
|
* @return string
|
|
*/
|
|
protected function _parse_assets($type, $assets, $attr, $group, $raw = false)
|
|
{
|
|
if ( ! is_array($assets))
|
|
{
|
|
$assets = array($assets);
|
|
}
|
|
|
|
foreach ($assets as $key => $asset)
|
|
{
|
|
// Prevent duplicate files in a group.
|
|
if (\Arr::get($this->_groups, "$group.$key.file") == $asset)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$this->_groups[$group][] = array(
|
|
'type' => $type,
|
|
'file' => $asset,
|
|
'raw' => $raw,
|
|
'attr' => (array) $attr,
|
|
);
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------------------
|
|
|
|
/**
|
|
* Unify the path
|
|
*
|
|
* make sure the directory separator in the path is correct for the
|
|
* platform used, is terminated with a directory separator, and all
|
|
* relative path references are removed
|
|
*
|
|
* @param string $path The path
|
|
* @param mixed $ds Optional directory separator
|
|
* @param boolean $trailing Optional whether to add trailing directory separator
|
|
* @return string
|
|
*/
|
|
protected function _unify_path($path, $ds = null, $trailing = true)
|
|
{
|
|
$ds === null and $ds = DS;
|
|
|
|
return rtrim(str_replace(array('\\', '/'), $ds, $path), $ds).($trailing ? $ds : '');
|
|
}
|
|
|
|
}
|