yf_assets class| File | plugins/assets/classes/yf_assets.class.php |
|---|---|
| Use | _class('assets') |
A site sets them in its config with $PROJECT_CONF['assets']['NAME'] = ...;
| Property | Default | Description |
|---|---|---|
$ADD_IS_DIRECT_OUT |
false |
|
$ALLOW_GET_FROM_BOWER |
false |
|
$ALLOW_GET_FROM_CDN |
false |
|
$ALLOW_GET_FROM_GITHUB |
false |
|
$ALLOW_URL_CONTROL |
true |
|
$ASSETS_GET_PRIORITY |
['github', 'cdn', 'versions'] |
|
$BOWER_COMPONENTS_DIR_TPL |
'{app_path}/bower_components/{name}/' |
|
$BOWER_TARGET_DIR_TPL |
'{project_path}/uploads/bower_components/{name}/{version}/' |
|
$CACHE_CHECK_SOURCE_MTIME |
false |
|
$CACHE_CSS_KEEP_DIRS |
false |
|
$CACHE_DIR_TPL |
'{project_path}/templates/{main_type}/cache/{lang}/{asset_'... |
|
$CACHE_IMAGES_DATA_URI_MAX_SIZE |
5000 |
|
$CACHE_IMAGES_USE_DATA_URI |
false |
|
$CACHE_INLINE_ALLOW |
true |
|
$CACHE_INLINE_MIN_SIZE |
1000 |
|
$CACHE_OUT_ADD_MTIME |
true |
|
$CACHE_TTL |
86400 |
|
$CDN_TARGET_DIR_TPL |
'{project_path}/uploads/cdn_cache/{name}/{version}/' |
|
$COMBINE |
false |
|
$COMBINED_CHECK_SOURCES |
false |
|
$COMBINED_CONFIG |
null |
|
$COMBINED_LOCK |
false |
|
$COMBINED_PREVIOUS_GRACE |
3600 |
|
$COMBINED_VERSION_TPL |
'{year}{month}' |
|
$FILE_ASSETS_DATA_URI_MAX_SIZE |
5000 |
|
$FILE_ASSETS_USE_DATA_URI |
false |
|
$FILTERS_AUTO_APPLY |
false |
|
$FORCE_LOCAL_STORAGE |
false |
|
$FORCE_LOCAL_TTL |
86400000 |
|
$GITHUB_RAW_URL_TPL |
'https://raw.githubusercontent.com/{repo}/{branch}/' |
|
$GITHUB_TARGET_DIR_TPL |
'{project_path}/uploads/github_cache/{repo}/{version}/' |
|
$INLINE_ASSETS_USE_DATA_URI |
false |
|
$MAIN_TPL_CSS |
'style_css' |
|
$MAIN_TPL_JS |
'script_js' |
|
$OUT_ADD_ASSET_NAME |
true |
|
$SHORTEN_LOCAL_URL |
true |
|
$SRI |
false |
|
$THEMES_TARGET_DIR_TPL |
'{project_path}/uploads/themes_cache/' |
|
$URL_CONTROL_REQUIRE_DEBUG |
false |
|
$URL_FILE_CACHE_DIR |
'' |
|
$URL_FILE_CACHE_TTL |
3600 |
|
$URL_REWRITES |
[] |
|
$URL_TIMEOUT |
30 |
|
$USE_CACHE |
false |
|
$USE_REQUIRE_JS |
false |
|
$VENDOR_DIR |
'' |
|
$VENDOR_LOCAL |
false |
|
$VENDOR_TARGET_DIR_TPL |
'{project_path}/uploads/vendor_cache/' |
|
$_abandoned_noticed |
[] |
|
$_assets_added |
[] |
|
$_autoload_registered |
null |
|
$_avail_filters |
null |
|
$_bundles_added |
[] |
|
$_cache_cdn |
[] |
|
$_cache_date |
null |
|
$_cache_github |
[] |
|
$_cache_html5fw |
null |
|
$_cache_language |
null |
|
$_cache_themes |
[] |
|
$_cache_vendor |
[] |
|
$_cache_vendor_manifest |
null |
|
$_ext_mismatch_noticed |
[] |
|
$_init_css_done |
null |
|
$_init_js_done |
null |
|
$_override |
[] |
|
$_theme_copy_warned |
false |
|
$_time |
null |
|
$_unknown_filters_noticed |
[] |
|
$_url_rewrites_badarray |
[] |
URL_REWRITES keys that are not valid regexes, reported once |
$_url_rewrites_ok |
[] |
|
$already_required |
[] |
|
$inherit_asset_types_map |
['js' => ['jquery']] |
|
$supported_asset_types |
|
|
$supported_content_types |
['asset', 'url', 'file', 'inline'] |
|
$supported_out_types |
['js', 'css'] |
public function add($content, $asset_type = 'bundle', $content_type_hint = 'auto', $params = [])
Add asset item into current workflow.
$content: string/array
$asset_type: = bundle|asset|js|jquery|css (img and font throw)
$content_type_hint: = auto|asset|url|file|inline
public function add($content, $asset_type = 'bundle', $content_type_hint = 'auto', $params = [])
{
$trace = DEBUG_MODE ? main()->trace_string() : '';
if ( ! is_string($content) && is_callable($content)) {
$content = $content($params, $this);
}
if ($asset_type === 'js' && ! $this->_init_js_done) {
$this->init_js();
} elseif ($asset_type === 'css' && ! $this->_init_css_done) {
$this->init_css();
}
if (is_array($content_type_hint)) {
$params = (array) $params + $content_type_hint;
$content_type_hint = $params['type'] ?? null;
}
if ( ! $asset_type || $asset_type === 'asset') {
$asset_type = 'bundle';
}
if ( ! in_array($asset_type, $this->supported_asset_types)) {
throw new Exception('Assets add(): unsupported asset type: ' . $asset_type);
}
if (is_array($content) && isset($content['content'])) {
if (is_array($content['params'] ?? null)) {
$params += $content['params'];
}
$content = $content['content'];
}
if ($asset_type === 'jquery') {
return $this->jquery($content, $params);
}
$DIRECT_OUT = isset($params['direct_out']) ? $params['direct_out'] : $this->ADD_IS_DIRECT_OUT;
if (empty($content)) {
return $DIRECT_OUT ? $this->show($asset_type) : $this;
}
if ( ! is_array($content)) {
$content = [$content];
}
if ( ! $content_type_hint) {
$content_type_hint = 'auto';
}
if (is_array($content_type_hint)) {
$params = (array) $params + $content_type_hint;
$content_type_hint = $params['type'] ?? null;
}
foreach ((array) $content as $_content) {
$_params = $params;
if ( ! is_string($_content) && is_callable($_content)) {
$_content = $_content($_params, $this);
}
if (is_array($_content) && isset($_content['content'])) {
if (is_array($_content['params'] ?? null)) {
$_params += $_content['params'];
}
$_content = $_content['content'];
}
if (is_array($_content)) {
$this->add($_content, $asset_type, $content_type_hint, $_params);
continue;
}
$_content = trim($_content);
if ( ! $_content) {
continue;
}
$md5 = md5($_content);
if ($this->_is_content_added($asset_type, $md5)) {
continue;
}
if ($asset_type === 'bundle') {
$this->_add_bundle($_content, $_params);
continue;
}
$content_type = '';
if (in_array($content_type_hint, $this->supported_content_types)) {
$content_type = $content_type_hint;
} else {
$content_type = $this->detect_content_type($asset_type, $_content);
}
if ($content_type === 'asset') {
$this->_add_asset($_content, $asset_type, $_params);
} elseif ($content_type === 'url') {
$this->set_content($asset_type, $md5, 'url', $_content, $_params);
} elseif ($content_type === 'file') {
if (file_exists($_content)) {
$str = file_get_contents($_content);
if (strlen($str)) {
$this->set_content($asset_type, $md5, 'file', $_content, $_params);
}
}
} elseif ($content_type === 'inline') {
$this->set_content($asset_type, $md5, 'inline', $_content, $_params);
}
if (DEBUG_MODE) {
debug('assets_add[]', [
'asset_type' => $asset_type,
'content_type' => $content_type,
'md5' => $md5,
'content' => $_content,
'is_added' => $this->get_content($asset_type, $md5) !== null,
'preview' => '',
'params' => $_params,
'trace' => $trace,
]);
}
}
return $DIRECT_OUT ? $this->show_css() . $this->show_js() : $this;
}
public function add_asset($content, $asset_type, $params = [])
Shortcut.
public function add_asset($content, $asset_type, $params = [])
{
return $this->add($content, $asset_type, 'asset', $params);
}
public function add_coffee($content, $content_type_hint = 'auto', $params = [])
Removed in AS-4: the SCSS / LESS / CoffeeScript compilers it needed are abandoned. Kept so that a site call does not fatal.
public function add_coffee($content, $content_type_hint = 'auto', $params = [])
{
return $this->_removed_content_compiler('add_coffee');
}
public function add_css($content, $content_type_hint = 'auto', $params = [])
Shortcut.
public function add_css($content, $content_type_hint = 'auto', $params = [])
{
return $this->add($content, 'css', $content_type_hint, $params);
}
public function add_file($content, $asset_type, $params = [])
Shortcut.
public function add_file($content, $asset_type, $params = [])
{
return $this->add($content, $asset_type, 'file', $params);
}
public function add_inline($content, $asset_type, $params = [])
Shortcut.
public function add_inline($content, $asset_type, $params = [])
{
return $this->add($content, $asset_type, 'inline', $params);
}
public function add_js($content, $content_type_hint = 'auto', $params = [])
Shortcut.
public function add_js($content, $content_type_hint = 'auto', $params = [])
{
return $this->add($content, 'js', $content_type_hint, $params);
}
public function add_less($content, $content_type_hint = 'auto', $params = [])
Removed in AS-4: the SCSS / LESS / CoffeeScript compilers it needed are abandoned. Kept so that a site call does not fatal.
public function add_less($content, $content_type_hint = 'auto', $params = [])
{
return $this->_removed_content_compiler('add_less');
}
public function add_sass($content, $content_type_hint = 'auto', $params = [])
Removed in AS-4: the SCSS / LESS / CoffeeScript compilers it needed are abandoned. Kept so that a site call does not fatal.
public function add_sass($content, $content_type_hint = 'auto', $params = [])
{
return $this->_removed_content_compiler('add_sass');
}
public function add_url($content, $asset_type, $params = [])
Shortcut.
public function add_url($content, $asset_type, $params = [])
{
return $this->add($content, $asset_type, 'url', $params);
}
public function bundle_register($name, $config)
Register new bundle or replace existing by name on-the-fly. $config is an array or a closure returning one (as descriptor files do).
public function bundle_register($name, $config)
{
if ( ! is_string($config) && is_callable($config)) {
$config = $config($this);
}
if ( ! is_array($config)) {
throw new InvalidArgumentException(__CLASS__ . ': bundle "' . $name . '" must be an array or a closure returning an array');
}
$this->assets[$name] = $config;
return $this;
}
public function clean_all()
public function clean_all()
{
$this->content = [];
$this->filters = [];
$this->_assets_added = [];
$this->_bundles_added = [];
}
public function clean_content($asset_type)
Clean content for given asset type.
public function clean_content($asset_type)
{
if ( ! $this->ADD_IS_DIRECT_OUT) {
$this->already_required[$asset_type] = [];
}
$this->content[$asset_type] = [];
$this->_assets_added[$asset_type] = [];
$this->_bundles_added = [];
return [];
}
public function detect_content_type($asset_type, $content = '')
Auto-detection on content type.
public function detect_content_type($asset_type, $content = '')
{
$content = trim($content);
$type = false;
if (isset($this->assets[$content])) {
$type = 'asset';
} elseif ($asset_type === 'js') {
if (preg_match('~^(http://|https://|//)[a-z0-9]+~ims', $content) && ! preg_match('~\s+~ims', $content)) {
$type = 'url';
} elseif (preg_match('~^/[a-z0-9\./_-]+\.js$~ims', $content) && file_exists($content)) {
$type = 'file';
} else {
$type = 'inline';
}
} elseif ($asset_type === 'css') {
if (preg_match('~^(http://|https://|//)[a-z0-9]+~ims', $content) && ! preg_match('~\s+~ims', $content)) {
$type = 'url';
} elseif (preg_match('~^/[a-z0-9\./_-]+\.css$~ims', $content) && file_exists($content)) {
$type = 'file';
} else {
$type = 'inline';
}
} elseif ( ! in_array($asset_type, ['js', 'css'])) {
$type = 'inline';
}
return $type;
}
public function filters_add($asset_type, $callback, $params = [])
Add filters to processing chain, both custom and built-in supported. They run on inline content only, when filters_process_js() / filters_process_css() is called, or in show() when FILTERS_AUTO_APPLY is on.
public function filters_add($asset_type, $callback, $params = [])
{
if ( ! $asset_type) {
throw new Exception('Assets: ' . __FUNCTION__ . ' missing asset_type');
return $this;
}
if (is_array($callback)) {
$func = __FUNCTION__;
foreach ($callback as $k => $v) {
$this->$func($asset_type, $v, $params);
}
return $this;
}
$this->filters[$asset_type][] = [
'callback' => $callback,
'params' => $params,
];
return $this;
}
public function filters_add_css($callback, $params = [])
Shortcut for filters_add with css asset.
public function filters_add_css($callback, $params = [])
{
return $this->filters_add('css', $callback, $params);
}
public function filters_add_js($callback, $params = [])
Shortcut for filters_add with js asset.
public function filters_add_js($callback, $params = [])
{
return $this->filters_add('js', $callback, $params);
}
public function filters_clean($asset_type = '')
Clean list of added filters (applied by filters_process_*(), and by show() when FILTERS_AUTO_APPLY is on).
public function filters_clean($asset_type = '')
{
if ($asset_type) {
$this->filters[$asset_type] = [];
} else {
$this->filters = [];
}
}
public function filters_get_added($asset_type)
Get list of filters, added to procesing chain, both custom and built-in.
public function filters_get_added($asset_type)
{
if ( ! $asset_type) {
return false;
}
return $this->filters[$asset_type] ?? [];
}
public function filters_get_avail()
Get list of built-in filters.
public function filters_get_avail()
{
if (isset($this->_avail_filters)) {
return $this->_avail_filters;
}
$suffix = '.class.php';
$prefix = 'assets_filter_';
$prefix2 = YF_PREFIX;
$slen = strlen($suffix);
$plen = strlen($prefix);
$plen2 = strlen($prefix2);
$names = [];
$patterns = [
YF_PATH . 'classes/assets/*' . $prefix . '*' . $suffix,
YF_PATH . 'plugins/*/classes/assets/*' . $prefix . '*' . $suffix,
YF_PATH . 'classes/assets_filters/*' . $prefix . '*' . $suffix,
YF_PATH . 'plugins/*/classes/assets_filters/*' . $prefix . '*' . $suffix,
PROJECT_PATH . 'classes/assets/*' . $prefix . '*' . $suffix,
PROJECT_PATH . 'plugins/*/classes/assets/*' . $prefix . '*' . $suffix,
PROJECT_PATH . 'classes/assets_filters/*' . $prefix . '*' . $suffix,
PROJECT_PATH . 'plugins/*/classes/assets_filters/*' . $prefix . '*' . $suffix,
APP_PATH . 'classes/assets/*' . $prefix . '*' . $suffix,
APP_PATH . 'plugins/*/classes/assets/*' . $prefix . '*' . $suffix,
APP_PATH . 'classes/assets_filters/*' . $prefix . '*' . $suffix,
APP_PATH . 'plugins/*/classes/assets_filters/*' . $prefix . '*' . $suffix,
];
if (is_site_path()) {
$patterns[] = SITE_PATH . 'classes/assets/*' . $prefix . '*' . $suffix;
$patterns[] = SITE_PATH . 'plugins/*/classes/assets/*' . $prefix . '*' . $suffix;
$patterns[] = SITE_PATH . 'classes/assets_filters/*' . $prefix . '*' . $suffix;
$patterns[] = SITE_PATH . 'plugins/*/classes/assets_filters/*' . $prefix . '*' . $suffix;
}
foreach ($patterns as $glob) {
foreach (glob($glob) as $path) {
$name = substr(basename($path), 0, -$slen);
if (substr($name, 0, $plen2) === $prefix2) {
$name = substr($name, $plen2);
}
if (substr($name, 0, $plen) === $prefix) {
$name = substr($name, $plen);
}
$names[$name] = $path;
}
}
$this->_avail_filters = $names;
return $names;
}
public function filters_process_added($asset_type, $params = [])
Apply added filters to gathered content of the given asset type.
public function filters_process_added($asset_type, $params = [])
{
if ( ! $asset_type) {
return false;
}
$filters = $this->filters_get_added($asset_type);
if ( ! $filters) {
return $this;
}
$content = $this->get_content($asset_type);
if ( ! $content) {
return $this;
}
foreach ($content as $md5 => $info) {
// TODO: support for other content types
if ($info['content_type'] !== 'inline') {
continue;
}
$_content = $info['content'];
$processed = $this->filters_process_input($_content, $filters, $params);
if ($_content !== $processed) {
$this->content[$asset_type][$md5]['content'] = $processed;
}
}
return $this;
}
public function filters_process_css($params = [])
Shortcut for filters_content_process with css asset.
public function filters_process_css($params = [])
{
return $this->filters_process_added('css', $params);
}
public function filters_process_input($in, $filters = [], $params = [])
Apply filters from names array to input string.
public function filters_process_input($in, $filters = [], $params = [])
{
if (is_array($in)) {
$out = [];
$func = __FUNCTION__;
foreach ($in as $k => $v) {
$out[$k] = $this->$func($v, $filters, $params);
}
return $out;
}
$this->_autoload_libs();
if ( ! is_array($filters)) {
$filters = [$filters];
}
$out = $in;
$avail_filters = $this->filters_get_avail();
foreach ($filters as $filter) {
$_params = [];
if (is_array($filter)) {
$_params = $filter['params'] ?? [];
$filter = $filter['callback'];
}
if ( ! $filter) {
continue;
}
$result = null;
if ( ! is_string($filter) && is_callable($filter)) {
$result = $filter($out, $params, $this);
} elseif (is_string($filter) && isset($avail_filters[$filter])) {
$result = _class('assets_filter_' . $filter, 'classes/assets/')->apply($out, $params);
} elseif (is_string($filter) && ! isset($this->_unknown_filters_noticed[$filter])) {
$this->_unknown_filters_noticed[$filter] = true;
trigger_error('Assets: filter "' . $filter . '" does not exist (the stub filters were removed in AS-4), content kept unchanged', E_USER_NOTICE);
}
// A filter returning null must not wipe the content
if (null !== $result) {
$out = $result;
}
}
return $out;
}
public function filters_process_js($params = [])
Shortcut for filters_content_process with js asset.
public function filters_process_js($params = [])
{
return $this->filters_process_added('js', $params);
}
public function find_asset_type_for_module($asset_type, $module = '')
Search for assets for current module in several places, where it can be stored.
public function find_asset_type_for_module($asset_type, $module = '')
{
if ( ! $module) {
$module = $_GET['object'] ?? '';
}
$ext = '.' . $asset_type;
$path = $module . '/' . $module . $ext;
$paths = [
'yf_admin' => MAIN_TYPE_ADMIN ? YF_PATH . 'templates/admin/' . $path : '',
'yf_user' => YF_PATH . 'templates/user/' . $path,
'yf_plugins_admin' => MAIN_TYPE_ADMIN ? YF_PATH . 'plugins/' . $module . '/templates/admin/' . $path : '',
'yf_plugins_user' => YF_PATH . 'plugins/' . $module . '/templates/user/' . $path,
'project_admin' => MAIN_TYPE_ADMIN ? PROJECT_PATH . 'templates/admin/' . $path : '',
'project_user' => PROJECT_PATH . 'templates/user/' . $path,
'site_user' => is_site_path() ? SITE_PATH . 'templates/user/' . $path : '',
];
$found = '';
foreach (array_reverse($paths, true) as $path) {
if ( ! strlen($path)) {
continue;
}
if (file_exists($path)) {
$found = $path;
break;
}
}
return $found;
}
public function find_version_best_match($version = '', $avail_versions = [])
Versions idea from https://getcomposer.org/doc/01-basic-usage.md#package-versions In the previous example we were requiring version 1.0.* of monolog. This means any version in the 1.0 development branch. It would match 1.0.0, 1.0.2 or 1.0.20. Version constraints can be specified in a few different ways. Exact version 1.0.2 You can specify the exact version of a package. Range >=1.0 >=1.0,<2.0 >=1.0,<1.1 | >=1.2 By using comparison operators you can specify ranges of valid versions. Valid operators are >, >=, <, <=, !=. You can define multiple ranges. Ranges separated by a comma (,) will be treated as a logical AND. A pipe (|) will be treated as a logical OR. AND has higher precedence than OR. Wildcard 1.0.* You can specify a pattern with a * wildcard. 1.0.* is the equivalent of >=1.0,<1.1. Tilde Operator ~1.2 Very useful for projects that follow semantic versioning. ~1.2 is equivalent to >=1.2,<2.0. For more details, read the next section below.
public function find_version_best_match($version = '', $avail_versions = [])
{
if (empty($avail_versions)) {
return null;
}
if ( ! $version) {
return current(array_slice($avail_versions, -1, 1, true));
}
$version = trim((string) $version);
foreach ($avail_versions as $v) {
if ((string) $v === $version) {
return $v;
}
}
if ($version === 'latest') {
$version = '*';
}
// yf_assets works without the lock class: then only exact versions resolve
$lock = _class('services_lock');
if ( ! method_exists($lock, 'satisfies')) {
return null;
}
$best = null;
$best_norm = '';
foreach ($avail_versions as $v) {
try {
$norm = $lock->normalize_version($v);
if ( ! $lock->satisfies($v, $version)) {
continue;
}
} catch (Throwable $e) {
// A key like "master" or a pattern that is not a constraint matches nothing
continue;
}
if ($best === null || version_compare($norm, $best_norm, '>')) {
$best = $v;
$best_norm = $norm;
}
}
return $best;
}
public function get_asset($name, $asset_type, $version = '')
Return named asset, also can return specific version.
public function get_asset($name, $asset_type, $version = '')
{
if ( ! $name) {
return null;
}
strpos($name, '#') !== false && list($name, $version) = explode('#', $name);
$asset_data = $this->get_asset_details($name);
if ( ! $asset_data) {
return null;
}
if ( ! is_string($asset_data) && is_callable($asset_data)) {
$asset_data = $asset_data($this);
}
foreach ((array) $this->ASSETS_GET_PRIORITY as $method) {
if ($method == 'github') {
$data = ($asset_data[$method] ?? null) ?: [];
$_files = $data[$asset_type] ?? null;
if ($this->ALLOW_GET_FROM_GITHUB && $_files) {
$_name = ($data['name'] ?? null) ?: $name;
strpos($_name, '#') !== false && list($_name, $_version) = explode('#', $_name);
$_version ??= $version;
$_version ??= $data['version'] ?? null;
$_version ??= 'master';
return $this->get_asset_from_github($_name, $_version, $data, $asset_type);
}
} elseif ($method == 'cdn' && $this->ALLOW_GET_FROM_CDN && isset($asset_data[$method]) && isset($asset_data[$method][$asset_type])) {
$data = $asset_data[$method];
$_version = $version ?: ($data['version'] ?? null) ?: 'master';
return $this->get_asset_from_cdn($name, $_version, $data, $asset_type);
} elseif ($method == 'versions') {
if ( ! is_array($asset_data[$method] ?? null)) {
return null;
}
$data = ($asset_data[$method] ?? null) ?: [];
$version = $this->find_version_best_match($version, array_keys($data));
if ( ! $version || ! isset($data[$version])) {
return null;
}
$version_info = $data[$version];
$content = $version_info[$asset_type] ?? null;
return $this->_process_versions_content($content, $asset_type);
}
}
}
public function get_asset_details($name)
public function get_asset_details($name)
{
// If overall asset is callable - call it and save result to prevent multiple calls
if ( ! is_string($this->assets[$name] ?? null) && is_callable($this->assets[$name] ?? null)) {
$func = $this->assets[$name];
$this->assets[$name] = $func($this);
}
return $this->assets[$name] ?? null;
}
public function get_asset_from_cdn($name, $version = 'master', $asset_data = [], $asset_type = '')
public function get_asset_from_cdn($name, $version = 'master', $asset_data = [], $asset_type = '')
{
if ( ! $name || ! $asset_data || ! isset($asset_data[$asset_type]) || empty($asset_data['url'])) {
return false;
}
$cdn_url_tpl = $asset_data['url'];
$files = $asset_data[$asset_type];
! $version && $version = 'master';
$cache = &$this->_cache_cdn[$asset_type];
if (isset($cache[$name])) {
return $cache[$name];
}
$replace = $this->_get_asset_tpl_replace($name, $version);
$target_dir = str_replace(['///', '//'], '/', str_replace(array_keys($replace), array_values($replace), $this->CDN_TARGET_DIR_TPL));
// Please do not replace 2 slashes inside url
$cdn_url = str_replace(array_keys($replace), array_values($replace), $cdn_url_tpl);
$web = [];
foreach ((array) $files as $from => $to) {
if (is_numeric($from)) {
$from = $to;
}
$to_path = rtrim($target_dir, '/') . '/' . ltrim($to, '/');
if ( ! file_exists($to_path)) {
$url = rtrim($cdn_url, '/') . '/' . ltrim($from, '/');
$data = $this->_url_get_contents($url);
if (strlen($data)) {
$dir_to = dirname($to_path);
! file_exists($dir_to) && mkdir($dir_to, 0755, true);
$this->_atomic_write($to_path, $data);
}
}
$web[] = MEDIA_PATH . substr($to_path, strlen(PROJECT_PATH));
}
$cache[$name] = $web;
return $cache[$name];
}
public function get_asset_from_github($name, $version = 'master', $asset_data = [], $asset_type = '')
public function get_asset_from_github($name, $version = 'master', $asset_data = [], $asset_type = '')
{
if ( ! $name || ! $asset_data || ! isset($asset_data[$asset_type])) {
return false;
}
$files = $asset_data[$asset_type];
! $version && $version = 'master';
$cache = &$this->_cache_github[$asset_type];
if (isset($cache[$name])) {
return $cache[$name];
}
$replace = $this->_get_asset_tpl_replace($name, $version);
$target_dir = str_replace(['///', '//'], '/', str_replace(array_keys($replace), array_values($replace), $this->GITHUB_TARGET_DIR_TPL));
// Please do not replace 2 slashes inside url
$github_url = str_replace(array_keys($replace), array_values($replace), $this->GITHUB_RAW_URL_TPL);
$web = [];
foreach ((array) $files as $from => $to) {
if (is_numeric($from)) {
$from = $to;
}
$to_path = rtrim($target_dir, '/') . '/' . ltrim($to, '/');
if ( ! file_exists($to_path)) {
$url = rtrim($github_url, '/') . '/' . ltrim($from, '/');
$data = $this->_url_get_contents($url);
if (strlen($data)) {
$dir_to = dirname($to_path);
! file_exists($dir_to) && mkdir($dir_to, 0755, true);
$this->_atomic_write($to_path, $data);
}
}
$web[] = MEDIA_PATH . substr($to_path, strlen(PROJECT_PATH));
}
$cache[$name] = $web;
return $cache[$name];
}
public function get_cache($out_type, $md5, $data = [])
public function get_cache($out_type, $md5, $data = [])
{
$cache_path = $this->_cache_path($out_type, $md5, $data);
if (file_exists($cache_path) && ! $this->_cache_expired($cache_path)) {
if ($this->CACHE_CHECK_SOURCE_MTIME && ! $this->_cache_source_is_current($cache_path, $data)) {
return false;
}
return $cache_path;
}
return false;
}
public function get_content($asset_type, $params = [])
Return content for given asset type, optionally only for md5 of it.
public function get_content($asset_type, $params = [])
{
$md5 = (is_string($params) && strlen($params) === 32) ? $params : (isset($params['md5']) ? $params['md5'] : '');
if ( ! isset($this->content[$asset_type])) {
$this->content[$asset_type] = [];
}
return $md5 ? ($this->content[$asset_type][$md5] ?? null) : $this->content[$asset_type];
}
public function helper_js_library($lib_name, $content, $params = [])
Helper for JS library code.
public function helper_js_library($lib_name, $content, $params = [])
{
$asset_type = 'js';
if ( ! isset($this->already_required[$asset_type][$lib_name])) {
$this->add_asset($lib_name, $asset_type, $this->ADD_IS_DIRECT_OUT ? ['direct_out' => false] : []);
$this->already_required[$asset_type][$lib_name] = true;
}
return $this->add($content, $asset_type, 'inline', $params);
}
public function html_out($out_type, $content_type, $str, $params = [])
Generate html output for desired asset out type and content type.
public function html_out($out_type, $content_type, $str, $params = [])
{
if ( ! $out_type || ! $content_type || ! strlen($str)) {
return false;
}
$func = __FUNCTION__;
$out = '';
$media_path = $this->_get_media_path();
// try to find web path for file and show it as url
if ($content_type === 'file') {
if (substr($str, 0, strlen(PROJECT_PATH)) === PROJECT_PATH) {
$url = $media_path . substr($str, strlen(PROJECT_PATH));
return $this->$func($out_type, 'url', $url, $params);
}
}
if ($content_type === 'url' && $this->SHORTEN_LOCAL_URL && MEDIA_PATH == WEB_PATH) {
$slen = strlen($media_path);
if (substr($str, 0, $slen) === $media_path) {
$str = '/' . substr($str, $slen);
}
}
if ($this->OUT_ADD_ASSET_NAME && ($params['asset_name'] ?? null) && ! isset($params['id']) && in_array($content_type, ['inline', 'file'])) {
$params['data-asset'] = 'asset_' . $out_type . '_' . $content_type . '_' . $params['asset_name'];
}
if ($out_type === 'js') {
$params['type'] = 'text/javascript';
if ($content_type === 'file') {
$str = file_get_contents($str);
}
if ($this->INLINE_ASSETS_USE_DATA_URI && $content_type === 'inline') {
$content_type = 'url';
$params['src'] = 'data:' . $params['type'] . ';base64,' . base64_encode($this->_strip_js_input($str));
} elseif ($this->FILE_ASSETS_USE_DATA_URI && strlen($str) <= $this->FILE_ASSETS_DATA_URI_MAX_SIZE) {
$content_type = 'url';
$params['src'] = 'data:' . $params['type'] . ';base64,' . base64_encode($str);
}
if ($content_type === 'url') {
if ( ! isset($params['src'])) {
$params['src'] = $str . $this->_cached_url_get_mtime($str);
}
$out = '<script' . _attrs($params, ['src', 'type', 'class', 'id', 'integrity', 'crossorigin']) . '></script>';
} elseif ($content_type === 'file') {
if ($params['raw'] ?? null) {
$out = $str;
} else {
$out = '<script' . _attrs($params, ['type', 'class', 'id']) . '>' . PHP_EOL . $str . PHP_EOL . '</script>';
}
} elseif ($content_type === 'inline') {
if ($params['raw'] ?? null) {
$out = $str;
} else {
$str = $this->_strip_js_input($str);
$out = '<script' . _attrs($params, ['type', 'class', 'id']) . '>' . PHP_EOL . $str . PHP_EOL . '</script>';
}
}
} elseif ($out_type === 'css') {
$params['type'] = 'text/css';
if ($content_type === 'file') {
$str = file_get_contents($str);
}
if ($this->INLINE_ASSETS_USE_DATA_URI && $content_type === 'inline') {
$content_type = 'url';
$params['href'] = 'data:' . $params['type'] . ';base64,' . base64_encode($this->_strip_css_input($str));
} elseif ($this->FILE_ASSETS_USE_DATA_URI && strlen($str) <= $this->FILE_ASSETS_DATA_URI_MAX_SIZE) {
$content_type = 'url';
$params['href'] = 'data:' . $params['type'] . ';base64,' . base64_encode($str);
}
if ($content_type === 'url') {
$params['rel'] = 'stylesheet';
if ( ! isset($params['href'])) {
$params['href'] = $str . $this->_cached_url_get_mtime($str);
}
$out = '<link' . _attrs($params, ['href', 'rel', 'class', 'id', 'integrity', 'crossorigin']) . ' />';
} elseif ($content_type === 'file') {
$out = '<style' . _attrs($params, ['type', 'class', 'id']) . '>' . PHP_EOL . $str . PHP_EOL . '</style>';
} elseif ($content_type === 'inline') {
$str = $this->_strip_css_input($str);
$out = '<style' . _attrs($params, ['type', 'class', 'id']) . '>' . PHP_EOL . $str . PHP_EOL . '</style>';
}
}
return $out;
}
public function http_get_contents($url)
public function http_get_contents($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_AUTOREFERER, true);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
curl_setopt($ch, CURLOPT_USERAGENT, '');
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_TIMEOUT, $this->URL_TIMEOUT);
$data = curl_exec($ch);
return $data;
}
public function init_css($force = false)
Main CSS from theme stpl.
public function init_css($force = false)
{
if ($this->_init_css_done && ! $force) {
return false;
}
$this->_init_css_done = true;
// Check if disabled
if ( ! $this->MAIN_TPL_CSS) {
return false;
}
$main_style_css = trim(tpl()->parse_if_exists($this->MAIN_TPL_CSS));
// single string = automatically generated by compass
if (strlen($main_style_css) && strpos($main_style_css, "\n") === false && preg_match('~^css/style.css\?[0-9]{10}$~ims', $main_style_css)) {
$media_path = $this->_get_media_path();
$this->add($media_path . tpl()->TPL_PATH . $main_style_css, 'css', 'url');
} else {
$this->add($main_style_css, 'css', 'inline');
}
}
public function init_js($force = false)
Main JS from theme stpl.
public function init_js($force = false)
{
if ($this->_init_js_done && ! $force) {
return false;
}
$this->_init_js_done = true;
// Check if disabled
if ( ! $this->MAIN_TPL_JS) {
return false;
}
$main_script_js = trim(tpl()->parse_if_exists($this->MAIN_TPL_JS));
// single string = automatically generated by compass
if (strlen($main_script_js) && strpos($main_script_js, "\n") === false && preg_match('~^js/script.js\?[0-9]{10}$~ims', $main_script_js)) {
$media_path = $this->_get_media_path();
$this->add($media_path . tpl()->TPL_PATH . $main_script_js, 'js', 'url');
} else {
$this->add($main_script_js, 'js', 'inline');
}
}
public function jquery($content, $params = [])
Helper for jquery on document ready.
public function jquery($content, $params = [])
{
return $this->helper_js_library(__FUNCTION__, $content, $params + ['wrap' => '$(function(){' . PHP_EOL . '%s' . PHP_EOL . '})']);
}
public function load_combined_config($force = false)
public function load_combined_config($force = false)
{
if ( ! $this->COMBINE && ! $force) {
return false;
}
if (isset($this->COMBINED_CONFIG) && ! $force) {
return $this->COMBINED_CONFIG;
}
$path = CONFIG_PATH . 'assets_combine.php';
if (file_exists($path)) {
$this->COMBINED_CONFIG = include $path;
}
return $this->COMBINED_CONFIG;
}
public function load_predefined_assets($force = false)
public function load_predefined_assets($force = false)
{
// Cleanup previously filled assets
if ($force) {
$this->assets = [];
}
$assets = [];
$suffix = '.php';
$patterns = [
'framework' => [
YF_PATH . 'assets/*' . $suffix,
YF_PATH . 'assets/*/*' . $suffix,
YF_PATH . 'share/assets/*' . $suffix,
YF_PATH . 'share/assets/*/*' . $suffix,
YF_PATH . 'plugins/*/assets/*' . $suffix,
YF_PATH . 'plugins/*/assets/*/*' . $suffix,
YF_PATH . 'plugins/*/share/assets/*' . $suffix,
YF_PATH . 'plugins/*/share/assets/*/*' . $suffix,
],
'project' => [
PROJECT_PATH . 'assets/*' . $suffix,
PROJECT_PATH . 'assets/*/*' . $suffix,
PROJECT_PATH . 'share/assets/*' . $suffix,
PROJECT_PATH . 'share/assets/*/*' . $suffix,
PROJECT_PATH . 'plugins/*/assets/*' . $suffix,
PROJECT_PATH . 'plugins/*/assets/*/*' . $suffix,
PROJECT_PATH . 'plugins/*/share/assets/*' . $suffix,
PROJECT_PATH . 'plugins/*/share/assets/*/*' . $suffix,
],
'app' => [
APP_PATH . 'assets/*' . $suffix,
APP_PATH . 'assets/*/*' . $suffix,
APP_PATH . 'share/assets/*' . $suffix,
APP_PATH . 'share/assets/*/*' . $suffix,
APP_PATH . 'plugins/*/assets/*' . $suffix,
APP_PATH . 'plugins/*/assets/*/*' . $suffix,
APP_PATH . 'plugins/*/share/assets/*' . $suffix,
APP_PATH . 'plugins/*/share/assets/*/*' . $suffix,
],
];
if (is_site_path()) {
$patterns['site'] = [
SITE_PATH . 'assets/*' . $suffix,
SITE_PATH . 'assets/*/*' . $suffix,
SITE_PATH . 'share/assets/*' . $suffix,
SITE_PATH . 'share/assets/*/*' . $suffix,
SITE_PATH . 'plugins/*/assets/*' . $suffix,
SITE_PATH . 'plugins/*/assets/*/*' . $suffix,
SITE_PATH . 'plugins/*/share/assets/*' . $suffix,
SITE_PATH . 'plugins/*/share/assets/*/*' . $suffix,
];
}
$slen = strlen($suffix);
$names = [];
foreach ($patterns as $glob_patterns) {
foreach ($glob_patterns as $glob) {
foreach (glob($glob) as $path) {
$name = substr(basename($path), 0, -$slen);
$names[$name] = $path;
}
}
}
// This double iterating code ensures we can inherit/replace assets with same name inside project
foreach ($names as $name => $path) {
$assets[$name] = include $path;
if (defined('DEBUG_INFO') && constant('DEBUG_INFO')) {
debug('assets_names[]', [
'name' => $name,
'path' => $path,
'content' => $assets[$name],
]);
}
}
$this->assets += $assets;
return $assets;
}
public function set_cache($out_type, $md5, $data = [])
public function set_cache($out_type, $md5, $data = [])
{
if ( ! $this->USE_CACHE) {
return false;
}
$cache_path = $this->_cache_path($out_type, $md5, $data);
$content = $data['content'];
$content_type = $data['content_type'];
$content_url = '';
if ($content_type === 'url') {
$content_url = $content;
$content = $this->_url_get_contents($content_url);
} elseif ($content_type === 'file') {
// mtime first: an edit between the two reads then only makes the next request refresh the copy
clearstatcache(true, $content);
$info_extra = is_file($content) ? ['source_mtime' => filemtime($content)] : [];
$content = file_get_contents($content);
}
if ( ! strlen($content)) {
return false;
}
$info_extra ??= [];
// Content is same, no need to overwrite it
$cache_existed = file_exists($cache_path);
if ($cache_existed && file_get_contents($cache_path) === $content) {
is_writable($cache_path) && touch($cache_path);
$this->CACHE_CHECK_SOURCE_MTIME && $info_extra && $this->_write_cache_info($cache_path, '', $content, $info_extra);
return $cache_path;
}
$this->_atomic_write($cache_path, $content);
// Decode gzip content to not confuse browser and web server, as gzdecode($content) is PHP 5.4+ only
// gzip file beginnning: \x1F\x8B
if (bin2hex(substr($content, 0, 2)) === '1f8b') {
ob_start();
readgzfile($cache_path);
$content = ob_get_clean();
$this->_atomic_write($cache_path, $content);
}
if ($out_type === 'css' && in_array($content_type, ['url', 'inline', 'file'])) {
$content_before = $content;
$content = $this->_css_urls_rewrite_and_save($content, $content_url, $cache_path, $content_type, $data['content']);
if ($content_before !== $content) {
$this->_atomic_write($cache_path, $content);
}
unset($content_before);
}
if ($out_type === 'js' && $content_type === 'url') {
$this->_js_map_save($content, $content_url, $cache_path);
}
// Content is same, no need to overwrite info about it
// This is not a mistake to do this check again, after url rewrite content can still be same
if ($cache_existed && file_get_contents($cache_path) === $content && ! ($this->CACHE_CHECK_SOURCE_MTIME && $info_extra)) {
return $cache_path;
}
$this->_write_cache_info($cache_path, $content_url, $content, $info_extra);
return $cache_path;
}
public function set_content($asset_type, $md5, $content_type, $content, $params = [])
Set unique content entry for given asset type.
public function set_content($asset_type, $md5, $content_type, $content, $params = [])
{
if (isset($this->content[$asset_type][$md5])) {
return $this->content[$asset_type][$md5];
}
if (isset($params['wrap']) && false !== strpos($params['wrap'], '%s')) {
$content = str_replace('%s', $content, $params['wrap']);
}
$name = '';
if (isset($params['name'])) {
$name = $params['name'];
unset($params['name']);
}
// Skip any related content, that was specially cached or listed, but not need to be included here
if (in_array($content_type, ['file']) && in_array($asset_type, ['js', 'css'])) {
// This line needed to strip query string from file name like this:
// /templates/user/css/style.css?1416914173 -> templates/user/css/style.css
if (pathinfo((string) parse_url($content, PHP_URL_PATH), PATHINFO_EXTENSION) != $asset_type) {
return $this->_skip_ext_mismatch($asset_type, $content);
}
} elseif (in_array($content_type, ['url']) && in_array($asset_type, ['js', 'css'])) {
// This line needed to strip query string from file name like this:
// /templates/user/css/style.css?1416914173 -> templates/user/css/style.css
$parsed_url = parse_url($content);
if ( ! strlen($parsed_url['host'] ?? '') && strlen($parsed_url['path'] ?? '') && pathinfo($parsed_url['path'] ?? '', PATHINFO_EXTENSION) != $asset_type) {
return $this->_skip_ext_mismatch($asset_type, $content);
}
}
return $this->content[$asset_type][$md5] = [
'content_type' => $content_type,
'content' => $content,
'name' => $name,
'version' => $name ?: 'master',
'params' => $params,
];
}
public function show($out_type, $params = [])
Main method to display overall content by out type (js, css, images, fonts). Can be called from main template like this: {exec_last(assets,show_js)} {exec_last(assets,show_css)}.
public function show($out_type, $params = [])
{
if ( ! $out_type || ! in_array($out_type, $this->supported_out_types)) {
throw new Exception('Assets: unsupported out content type: ' . $out_type);
return null;
}
if ($out_type === 'js') {
$this->init_js();
} elseif ($out_type === 'css') {
$this->init_css();
}
if ( ! is_array($params)) {
$params = ! empty($params) ? [$params] : [];
}
// Assets from current module
$module_assets_path = $this->find_asset_type_for_module($out_type, $_GET['object'] ?? '');
if ($module_assets_path) {
$this->add_file($module_assets_path, $out_type);
}
if ($out_type === 'js' && $this->USE_REQUIRE_JS) {
// the RequireJS output is a stub (''): fall through to the normal output instead of a page without JS
$require_js = $this->show_require_js($params);
if ($require_js !== '') {
return $require_js;
}
}
if ($this->FILTERS_AUTO_APPLY) {
$this->filters_process_added($out_type, $params);
}
if ($this->COMBINE) {
$combined_file = $this->_get_combined_path($out_type);
// with COMBINED_CHECK_SOURCES the file name depends on the elements, known only after the loop below
$md5_inside_combined = $this->COMBINED_CHECK_SOURCES ? [] : $this->_combined_elements($combined_file);
}
$bs_current_theme = common()->bs_current_theme();
$media_path = $this->_get_media_path();
$prepend = _class('core_events')->fire('assets.prepend');
// Process previously added content, depending on its type
$out = [];
$to_combine = [];
foreach ((array) $this->_get_all_content_for_out($out_type) as $md5 => $v) {
if ( ! is_array($v)) {
continue;
}
$_params = ((array) $v['params'] + (array) $params) ?? [];
$content_type = $v['content_type'];
$cached_path = '';
$use_cache = $this->USE_CACHE && ! ($_params['no_cache'] ?? false) && ! ($_params['config']['no_cache'] ?? false);
if ($use_cache && $content_type === 'inline') {
if ( ! $this->CACHE_INLINE_ALLOW || ! ($_params['config']['inline_cache'] ?? false) || strlen($v['content']) < $this->CACHE_INLINE_MIN_SIZE) {
$use_cache = false;
}
}
if ($use_cache) {
if ($v['name'] === 'bootstrap-theme') {
$v['name'] .= '-' . $bs_current_theme;
}
$cached_path = $this->get_cache($out_type, $md5, $v);
if ( ! $cached_path && ! $this->FORCE_LOCAL_STORAGE) {
$cached_path = $this->set_cache($out_type, $md5, $v);
}
if ($cached_path) {
$content_type = 'url';
$v['content'] = $media_path . substr($cached_path, strlen(PROJECT_PATH));
// the local copy is rewritten by set_cache(), so the CDN hash no longer applies
unset($_params['integrity'], $_params['crossorigin']);
}
}
$str = $v['content'];
$before = $_params['config']['before'] ?? '';
$after = $_params['config']['after'] ?? '';
if ($_params['config']['class'] ?? '') {
$_params['class'] = $_params['config']['class'];
}
$use_combine = $this->COMBINE && $use_cache && in_array($content_type, ['url', 'file']) && empty($before) && empty($after) && empty($_params['class']) && empty($_params['id']);
if ($use_combine) {
$to_combine[$md5] = [
'content' => $str,
'content_type' => $content_type,
'name' => $v['name'],
];
}
if (DEBUG_MODE) {
$debug = [];
foreach ((array) debug('assets_add') as $d) {
if ($d['md5'] === $md5) {
$debug = $d;
break;
}
}
$dname = $out_type . '_' . $md5;
$trace_short = str_replace(['<', '>'], ['<', '>'], implode('; ', array_slice(explode(PHP_EOL, $debug['trace']), 2, 2, true)));
$ctype = $debug['content_type'];
if ($ctype === 'asset') {
$ctype .= ':' . $debug['content'];
}
$before = PHP_EOL . '<!-- asset start: ' . $dname . ' | ' . $ctype . ' | ' . $trace_short . ' -->' . PHP_EOL . $before;
$after = $after . PHP_EOL . '<!-- asset end: ' . $dname . ' -->' . PHP_EOL;
debug('assets_out[]', [
'out_type' => $out_type,
'name' => $v['name'],
'md5' => $md5,
'content_type' => $content_type,
'content' => $str,
'preview' => '',
'params' => $_params,
'cached' => $cached_path ? 1 : 0,
'combined' => (int) isset($to_combine[$md5]),
'trace' => $debug['trace'],
]);
}
$out[$md5] = $before . $this->html_out($out_type, $content_type, $str, $_params + ['asset_name' => $v['name']]) . $after;
}
if ($this->COMBINE && $to_combine) {
if ($this->COMBINED_CHECK_SOURCES) {
$combined_file = $this->_get_combined_path($out_type, $this->_combined_content_hash($to_combine));
$md5_inside_combined = $this->_combined_elements($combined_file);
}
$out = $this->_combine_content($out, $out_type, $to_combine, $combined_file, $md5_inside_combined);
}
$append = _class('core_events')->fire('assets.append', ['out' => &$out]);
$this->clean_content($out_type);
return implode(PHP_EOL, $prepend) . implode(PHP_EOL, $out) . implode(PHP_EOL, $append);
}
public function show_css($params = [])
Shortcut.
public function show_css($params = [])
{
return $this->show('css', $params);
}
public function show_js($params = [])
Shortcut.
public function show_js($params = [])
{
return $this->show('js', $params);
}
public function show_require_js($params = [])
Stub: the RequireJS integration (USE_REQUIRE_JS, ?assets_requirejs=1) was never finished, so it outputs nothing.
public function show_require_js($params = [])
{
return '';
}
_ hooks and helpers; not routable as page actionspublic function __call($name, $args)
Catch missing method call.
public function __call($name, $args)
{
return main()->extend_call($this, $name, $args);
}
public function __clone()
public function __clone()
{
return $this->clean_all();
}
public function __toString(): string
Direct call to object as to string is not allowed, return empty string instead.
public function __toString()
{
return '';
}
public function _add_asset($_content, $asset_type, $_params = [])
public function _add_asset($_content, $asset_type, $_params = [])
{
if ( ! $_content) {
return false;
}
// Prevent recursion
if (is_string($_content)) {
if (isset($this->_assets_added[$asset_type][$_content])) {
return false;
}
$this->_assets_added[$asset_type][$_content] = true;
}
$asset_data = $this->get_asset_details($_content);
if ( ! $asset_data) {
return false;
}
if ( ! is_string($asset_data) && is_callable($asset_data)) {
$asset_data = $asset_data($_params, $this);
}
if ( ! $asset_data) {
return false;
}
if (is_string($_content) && is_array($asset_data) && ! empty($asset_data['abandoned']) && is_string($asset_data['abandoned']) && ! isset($this->_abandoned_noticed[$_content])) {
$this->_abandoned_noticed[$_content] = true;
trigger_error('Assets: "' . $_content . '" is abandoned: ' . $asset_data['abandoned'], E_USER_NOTICE);
}
if (isset($asset_data['config']) && is_array($asset_data['config'])) {
$_params['config'] = (array) ($_params['config'] ?? null) + (array) $asset_data['config'];
}
$DIRECT_OUT = isset($_params['direct_out']) ? $_params['direct_out'] : $this->ADD_IS_DIRECT_OUT;
$_params += ($DIRECT_OUT ? ['direct_out' => false] : []);
if (is_string($_content)) {
$_params['name'] = $_content;
}
$__params = $_params;
// Prevent inherit no_cache and other package-related settings into other required packages
if (isset($__params['config'])) {
unset($__params['config']);
}
$inherit_types_map = $this->inherit_asset_types_map;
$types = [];
$types[$asset_type] = $asset_type;
$inherit_types = (array) ($inherit_types_map[$asset_type] ?? null) ?: [];
foreach ((array) $inherit_types as $inherit_type) {
$types[$inherit_type] = $inherit_type;
}
foreach ((array) $types as $atype) {
$data = $asset_data['require'][$atype] ?? null;
if ($data) {
$this->_sub_add($data, $atype, $__params);
}
}
foreach ((array) $types as $atype) {
$data = $this->get_asset($_content, $atype);
if ($data) {
$this->_sub_add($data, $atype, $_params);
}
}
foreach ((array) $types as $atype) {
$data = $asset_data['add'][$atype] ?? null;
if ($data) {
$this->_sub_add($data, $atype, $__params);
}
}
}
public function _add_bundle($_content, $_params = [])
public function _add_bundle($_content, $_params = [])
{
if ( ! $_content) {
return false;
}
// Prevent recursion
if (is_string($_content)) {
if (isset($this->_bundles_added[$_content])) {
return false;
}
$this->_bundles_added[$_content] = true;
}
$bundle_details = $this->get_asset_details($_content);
if ( ! $bundle_details) {
return false;
}
if ( ! is_string($bundle_details) && is_callable($bundle_details)) {
$bundle_details = $bundle_details($_params, $this);
}
if ( ! $bundle_details) {
return false;
}
if (isset($bundle_details['config']) && is_array($bundle_details['config'])) {
$_params['config'] = (array) ($_params['config'] ?? null) + (array) $bundle_details['config'];
}
$DIRECT_OUT = isset($_params['direct_out']) ? $_params['direct_out'] : $this->ADD_IS_DIRECT_OUT;
$_params += ($DIRECT_OUT ? ['direct_out' => false] : []);
if (is_string($_content)) {
$_params['name'] = $_content;
}
$__params = $_params;
// Prevent inherit no_cache and other package-related settings into other required packages
if (isset($__params['config'])) {
unset($__params['config']);
}
$inherit_types_map = $this->inherit_asset_types_map;
$types = [];
foreach ((array) $this->supported_asset_types as $k => $atype) {
if ($atype === 'jquery') {
continue;
}
$types[$atype] = $atype;
$inherit_types = (array) ($inherit_types_map[$atype] ?? null) ?: [];
foreach ((array) $inherit_types as $inherit_type) {
$types[$inherit_type] = $inherit_type;
}
}
foreach ((array) $types as $atype) {
$data = $bundle_details['require'][$atype] ?? null;
if ($data) {
$this->_sub_add($data, $atype, $__params);
}
}
foreach ((array) $types as $atype) {
$data = $this->get_asset($_content, $atype);
if ($data) {
$this->_sub_add($data, $atype, $_params);
}
}
foreach ((array) $types as $atype) {
$data = $bundle_details['add'][$atype] ?? null;
if ($data) {
$this->_sub_add($data, $atype, $__params);
}
}
}
public function _atomic_write($path, $content)
Write through a temp file in the same dir and rename(), so a concurrent request never reads a half written file.
public function _atomic_write($path, $content)
{
$tmp = $path . '.' . bin2hex(random_bytes(6)) . '.tmp';
$written = file_put_contents($tmp, $content, LOCK_EX);
if (false === $written) {
@unlink($tmp);
return false;
}
if ( ! rename($tmp, $path)) {
unlink($tmp);
return false;
}
return $written;
}
public function _autoload_libs()
public function _autoload_libs()
{
if (isset($this->_autoload_registered)) {
return (bool) $this->_autoload_registered;
}
$this->_autoload_registered = false;
$paths = [
APP_PATH . 'vendor/autoload.php',
YF_PATH . 'vendor/autoload.php',
'/usr/local/share/composer/vendor/autoload.php',
];
foreach ($paths as $path) {
if (file_exists($path)) {
$this->_autoload_registered = $path;
require_once $path;
break;
}
}
return (bool) $this->_autoload_registered;
}
public function _cache_dir($out_type, $asset_name = '', $version = '')
public function _cache_dir($out_type, $asset_name = '', $version = '')
{
if ( ! is_string($this->CACHE_DIR_TPL) && is_callable($this->CACHE_DIR_TPL)) {
$func = $this->CACHE_DIR_TPL;
$cache_dir = $func($out_type, $asset_name, $version, $this);
} else {
$main_type = ($this->_override['main_type'] ?? null) ?: MAIN_TYPE;
$replace = $this->_tpl_placeholders($main_type, $out_type) + [
'{asset_name}' => $asset_name,
'{version}' => $version,
];
$cache_dir = str_replace(['///', '//'], '/', str_replace(array_keys($replace), array_values($replace), $this->CACHE_DIR_TPL));
}
! is_dir($cache_dir) && mkdir($cache_dir, 0755, true);
return $cache_dir;
}
public function _cache_expired($cache_path)
public function _cache_expired($cache_path)
{
return file_exists($cache_path) && filemtime($cache_path) <= (time() - $this->CACHE_TTL);
}
public function _cache_name($out_type, $md5, $data = [])
public function _cache_name($out_type, $md5, $data = [])
{
$content = $data['content'] ?? '';
$content_type = $data['content_type'] ?? '';
$_name = '';
if ($content_type === 'url') {
$_name = pathinfo((string) parse_url($content, PHP_URL_PATH), PATHINFO_FILENAME);
while (pathinfo($_name, PATHINFO_EXTENSION) === $out_type && $out_type) {
$_name = pathinfo($_name, PATHINFO_FILENAME);
}
} elseif ($content_type === 'file') {
$_name = pathinfo($content, PATHINFO_FILENAME);
} elseif ($content_type === 'inline') {
$_name = md5($content);
}
return $_name;
}
public function _cache_path($out_type, $md5, $data = [])
public function _cache_path($out_type, $md5, $data = [])
{
$cache_dir = $this->_cache_dir($out_type, $data['name'], $data['version']);
$cache_name = $this->_cache_name($out_type, $md5, $data);
return $cache_dir . ($cache_name ?: ($data['name'] ? $md5 . '_' . $data['name'] : '') ?: $md5) . '.' . $out_type;
}
public function _cache_source_is_current($cache_path, $data = [])
Local file asset: is the source mtime kept in the .info the same as now. A cache without that (written by an older version or not a file) counts as current, the TTL rule decides.
public function _cache_source_is_current($cache_path, $data = [])
{
if (($data['content_type'] ?? '') !== 'file' || ! is_file($data['content'])) {
return true;
}
$info = is_file($cache_path . '.info') ? json_decode((string) file_get_contents($cache_path . '.info'), $as_array = true) : null;
if ( ! is_array($info) || ! isset($info['source_mtime'])) {
return true;
}
clearstatcache(true, $data['content']);
return (int) $info['source_mtime'] === (int) filemtime($data['content']);
}
public function _cached_url_get_mtime($str = '')
Add modification time to url, if this is local file, no matter cached or not, we just need to be able to get its mtime.
public function _cached_url_get_mtime($str = '')
{
$url_param = 'yfmt';
if ( ! $this->CACHE_OUT_ADD_MTIME || ! strlen($str) || false !== strpos($str, $url_param . '=')) {
return false;
}
$mtime = '';
$file = '';
$has_question_sign = (false !== strpos($str, '?'));
$media_path = $this->_get_media_path();
$media_path_len = strlen($media_path);
// short url paths like /templates/user/cache/..., but not //domain.com/
if (substr($str, 0, 1) === '/' && substr($str, 1, 1) !== '/') {
if (substr($str, 0, strlen(PROJECT_PATH)) === PROJECT_PATH) {
$file = $str;
} else {
$file = PROJECT_PATH . ltrim($str, '/');
}
} elseif (substr($str, 0, $media_path_len) === $media_path) {
$file = PROJECT_PATH . ltrim(substr($str, $media_path_len), '/');
}
// Avoid different url params when checking local file path
if ($file) {
$file = parse_url($file, PHP_URL_PATH);
}
if ($file && file_exists($file)) {
$mtime = filemtime($file);
}
return $mtime ? ($has_question_sign ? '&' : '?') . urlencode($url_param) . '=' . $mtime : '';
}
public function _combine_content(array $out, $out_type, array $to_combine, $combined_file, array $md5_inside_combined)
$out array$to_combine array$md5_inside_combined array public function _combine_content(array $out, $out_type, array $to_combine, $combined_file, array $md5_inside_combined)
{
$combined = '';
$lock = false;
if ($this->COMBINED_LOCK && ! file_exists($combined_file)) {
$lock = fopen($combined_file . '.lock', 'c');
if ( ! $lock || ! flock($lock, LOCK_EX | LOCK_NB)) {
$lock && fclose($lock);
// Another request is building it, do not wait: the elements go out one by one
return $out;
}
clearstatcache();
}
if ( ! file_exists($combined_file)) {
$divider = PHP_EOL;
if ($out_type === 'js') {
$divider = PHP_EOL . ';' . PHP_EOL;
}
$combined = [];
$combined_names = [];
foreach ($to_combine as $md5 => $info) {
$content_type = $info['content_type'];
$content = $info['content'];
if ($content_type === 'url') {
$combined[$md5] = $this->_url_get_contents($content);
} elseif ($content_type === 'file' && file_exists($content)) {
$combined[$md5] = file_get_contents($content);
}
if ($out_type === 'css' && in_array($content_type, ['url', 'inline'])) {
$combined[$md5] = $this->_css_urls_rewrite_and_save($combined[$md5], $content, $combined_file, $content_type);
}
if ($out_type === 'js' && $content_type === 'url') {
$this->_js_map_save($combined[$md5], $content, $combined_file);
}
$md5_inside_combined[$md5] = $md5;
$combined_names[$info['name']] = $info['name'];
}
if ($combined) {
$combined_md5 = array_keys($combined);
$combined = implode($divider, $combined);
// .info first: _combined_elements() deletes a combined file without one, a request between the two writes must not see that
$this->_write_cache_info($combined_file, '', $combined, ['elements' => implode(',', $combined_md5), 'names' => implode(',', $combined_names)]);
$this->_atomic_write($combined_file, $combined);
$this->COMBINED_CHECK_SOURCES && $this->_combined_remove_previous($combined_file, implode(',', $combined_md5));
}
}
if ($lock) {
flock($lock, LOCK_UN);
fclose($lock);
unlink($combined_file . '.lock');
}
foreach ($to_combine as $md5 => $info) {
if (isset($md5_inside_combined[$md5])) {
unset($out[$md5]);
}
}
$before = '';
$after = '';
if (DEBUG_MODE) {
$dname = 'combined';
$trace = main()->trace_string();
$trace_short = str_replace(['<', '>'], ['<', '>'], implode('; ', array_slice(explode(PHP_EOL, $trace), 2, 2, true)));
$before = PHP_EOL . '<!-- asset start: ' . $dname . ' | ' . $out_type . ' | ' . $trace_short . ' -->' . PHP_EOL . $before;
$after = $after . PHP_EOL . '<!-- asset end: ' . $dname . ' -->' . PHP_EOL;
debug('assets_out[]', [
'out_type' => $out_type,
'name' => $dname,
'md5' => '',
'content_type' => 'file',
'content' => $combined_file,
'preview' => '',
'params' => '',
'cached' => '1',
'combined' => '',
'trace' => $trace,
]);
}
return [
md5(is_string($combined) ? $combined : '') => $before . $this->html_out($out_type, 'file', $combined_file) . $after,
] + $out;
}
public function _combine_element_content($info)
public function _combine_element_content($info)
{
if ($info['content_type'] === 'url') {
return $this->_url_get_contents($info['content']);
}
return file_exists($info['content']) ? file_get_contents($info['content']) : null;
}
public function _combined_content_hash(array $to_combine)
$to_combine array public function _combined_content_hash(array $to_combine)
{
$hash = [];
$media_path = $this->_get_media_path();
foreach ($to_combine as $md5 => $info) {
if ($info['content_type'] === 'url' && substr($info['content'], 0, strlen($media_path)) !== $media_path) {
// Remote and not cached locally: never fetched at request time, so its content changes are not detected
$hash[] = md5('url:' . $info['content']);
continue;
}
$hash[] = md5((string) $this->_combine_element_content($info));
}
return md5(implode(',', $hash));
}
public function _combined_elements($combined_file)
Ids of the elements inside an existing combined file, an unusable file (no .info) is removed.
public function _combined_elements($combined_file)
{
$md5_inside_combined = [];
if (file_exists($combined_file)) {
$combined_info = is_file($combined_file . '.info') ? json_decode(file_get_contents($combined_file . '.info'), $as_array = true) : null;
if (is_array($combined_info) && isset($combined_info['elements'])) {
$md5_inside_combined = explode(',', $combined_info['elements']);
$md5_inside_combined = array_combine($md5_inside_combined, $md5_inside_combined);
} else {
// Combined file without a usable .info is unusable, rebuild it
unlink($combined_file);
}
}
return $md5_inside_combined;
}
public function _combined_remove_previous($combined_file, $elements)
Remove the previous combined file(s) of the same set of elements (same element ids in the same order, other content).
public function _combined_remove_previous($combined_file, $elements)
{
$ext = pathinfo($combined_file, PATHINFO_EXTENSION);
foreach ((array) glob(dirname($combined_file) . '/*combined.' . $ext . '.info') as $info_file) {
$file = substr($info_file, 0, -5);
if ($file === $combined_file) {
continue;
}
$info = json_decode((string) file_get_contents($info_file), $as_array = true);
// Kept for a while: pages already served or cached elsewhere still link it
if (is_array($info) && ($info['elements'] ?? null) === $elements && ! (is_file($file) && filemtime($file) > time() - $this->COMBINED_PREVIOUS_GRACE)) {
is_file($file) && unlink($file);
unlink($info_file);
}
}
}
public function _css_keep_dirs_rel($path, $host = '')
Path of a file referenced by CSS under the cache dir, with its dirs: no "..", so it cannot leave the cache dir.
$path mixed absolute local path or url path$host mixed set for a remote file, becomes the first dir public function _css_keep_dirs_rel($path, $host = '')
{
$path = $this->_get_absolute_path($path);
if (strlen($host)) {
$host = preg_replace('~[^a-z0-9._-]~i', '_', $host);
$path = (trim($host, '.') === '' ? '_' : $host) . '/' . $path;
} elseif (strpos('/' . $path, PROJECT_PATH) === 0) {
$path = substr('/' . $path, strlen(PROJECT_PATH));
}
return ltrim($path, '/');
}
public function _css_rel_url($from_dir, $to_path)
Relative url from a dir to a file under the same root.
public function _css_rel_url($from_dir, $to_path)
{
$from = array_values(array_filter(explode('/', $from_dir), 'strlen'));
$to = array_values(array_filter(explode('/', $to_path), 'strlen'));
while (count($to) > 1 && $from && $from[0] === $to[0]) {
array_shift($from);
array_shift($to);
}
return str_repeat('../', count($from)) . implode('/', $to);
}
public function _css_urls_rewrite_and_save($content, $content_url, $cache_path, $content_type = 'url', $orig_content = '', $base_dir = '')
process and save CSS url() and @import.
$base_dir mixed dir of the CSS file the urls are written into, empty means the dir of $cache_path (CACHE_CSS_KEEP_DIRS only) public function _css_urls_rewrite_and_save($content, $content_url, $cache_path, $content_type = 'url', $orig_content = '', $base_dir = '')
{
$_this = $this;
$self_func = __FUNCTION__;
return preg_replace_callback('~url\([\'"\s]*(?P<url>[^\'"\)]+?)[\'"\s]*\)~ims', function ($m) use ($_this, $content_url, $cache_path, $content_type, $orig_content, $self_func, $base_dir) {
$url = trim($m['url']);
if (strpos($url, 'data:') === 0) {
return $m[0];
}
// Inline CSS has no base url: a relative or root path cannot be fetched, the original text is the best we have
if ($_this->CACHE_CSS_KEEP_DIRS && $content_type === 'inline' && ! preg_match('~^(https?:)?//~i', $url)) {
return $m[0];
}
$str = '';
$save_path = '';
$is_local_file = false;
if ($content_type === 'file') {
// examples: ../ ./
if (substr($url, 0, 1) === '.') {
$is_local_file = true;
// full url like http://test.dev/image.png
} elseif (false === strpos($url, 'http://') && false === strpos($url, 'https://')) {
$is_local_file = true;
// should not match: //domain.com/some_path
} elseif (substr($url, 0, 1) === '/' && substr($url, 1, 1) !== '/') {
$is_local_file = true;
}
}
if ($is_local_file) {
$basename = basename($url);
// Fix for local files and url params inside path
if (false !== ($pos = strpos($basename, '?'))) {
$url = dirname($url) . '/' . substr($basename, 0, $pos);
}
// /templates/user/theme/default/image/smile_1.0_unactive.png
if (substr($url, 0, 1) === '/') {
$try_path = PROJECT_PATH . ltrim($url, '/');
} else {
$try_path = dirname($orig_content) . '/' . ltrim($url, '/');
}
$path = $_this->_get_absolute_path($try_path);
$path = '/' . ltrim($path, '/');
$save_path = $_this->_get_absolute_path(dirname($cache_path) . '/' . ($_this->CACHE_CSS_KEEP_DIRS ? $_this->_css_keep_dirs_rel($path) : basename($path)));
$save_path = '/' . ltrim($save_path, '/');
// path cleanup from *.*#someworlds
$path = preg_replace('/([\/\w\d]+?\.[a-z]{1,4})#[\w\d\-]+$/is', '${1}', $path);
// singleton for getting urls contents
if ( ! file_exists($save_path) && file_exists($path)) {
$str = file_get_contents($path);
}
} else {
$orig_url = $url;
if (substr($url, 0, 2) !== '//' && substr($url, 0, strlen('http')) !== 'http') {
$url = dirname($content_url) . '/' . $url;
}
$u = parse_url($url);
$host = $u['host'] ?? '';
$path = $u['path'] ?? '';
$query = $u['query'] ?? '';
// example: //fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700
if ($host === 'fonts.googleapis.com') {
$ext = '.' . ($path === '/css' ? 'css' : trim($path, '/')); // example: /css
$path = preg_replace('~[^a-z0-9_-]~ims', '_', strtolower($query)) . $ext;
}
$save_path = $_this->_get_absolute_path(dirname($cache_path) . '/' . ($_this->CACHE_CSS_KEEP_DIRS ? $_this->_css_keep_dirs_rel($path, $host) : basename($path)));
$save_path = '/' . ltrim($save_path, '/');
// singleton for getting urls contents
if ( ! file_exists($save_path)) {
$url = $_this->_get_absolute_url($url) . ($query ? '?' . $query : '');
$str = $_this->_url_get_contents($url);
}
}
if (strlen($str)) {
$str = $_this->$self_func($str, $content_url, $cache_path, $content_type, $orig_content, dirname($save_path));
! is_dir(dirname($save_path)) && mkdir(dirname($save_path), 0755, true);
$_this->_atomic_write($save_path, $str);
$_this->_write_cache_info($save_path, $url, $str);
}
if ($_this->CACHE_IMAGES_USE_DATA_URI) {
$ext = strtolower(pathinfo($save_path, PATHINFO_EXTENSION));
if (in_array($ext, ['png', 'gif', 'jpg', 'jpeg'])) {
$max_size = $_this->CACHE_IMAGES_DATA_URI_MAX_SIZE;
$size = filesize($save_path);
if ($size <= $max_size) {
$data_type = 'image/' . ($ext == 'jpg' ? 'jpeg' : $ext);
return 'url(\'data:' . $data_type . ';base64,' . base64_encode(file_get_contents($save_path)) . '\')';
}
}
}
if ($_this->CACHE_CSS_KEEP_DIRS) {
return 'url(\'' . $_this->_css_rel_url($base_dir ?: dirname($cache_path), $save_path) . '\')';
}
return 'url(\'' . basename($save_path) . '\')';
}, $content);
}
public function _do_cache()
public function _do_cache()
{
$this->_do_purge();
$bak['ADD_IS_DIRECT_OUT'] = $this->ADD_IS_DIRECT_OUT;
$this->ADD_IS_DIRECT_OUT = true;
$bak['USE_CACHE'] = $this->USE_CACHE;
$this->USE_CACHE = true;
$combined_names = $this->load_combined_config($force = true);
foreach ((array) $this->supported_out_types as $out_type) {
foreach ((array) ($combined_names[MAIN_TYPE] ?? []) as $name) {
$this->add_asset($name, $out_type);
}
}
header('X-YF-assets-do-cache: true');
$this->ADD_IS_DIRECT_OUT = $bak['ADD_IS_DIRECT_OUT'];
$this->USE_CACHE = $bak['USE_CACHE'];
}
public function _do_combine()
public function _do_combine()
{
$this->_do_cache();
$bak['ADD_IS_DIRECT_OUT'] = $this->ADD_IS_DIRECT_OUT;
$this->ADD_IS_DIRECT_OUT = true;
$bak['USE_CACHE'] = $this->USE_CACHE;
$this->USE_CACHE = true;
$bak['COMBINE'] = $this->COMBINE;
$this->COMBINE = true;
$combined_names = $this->load_combined_config($force = true);
foreach ((array) $this->supported_out_types as $out_type) {
$combined_path = $this->_get_combined_path($out_type);
if (file_exists($combined_path)) {
unlink($combined_path);
unlink($combined_path . '.info');
}
foreach ((array) ($combined_names[MAIN_TYPE] ?? []) as $name) {
$this->add_asset($name, $out_type);
}
$this->show($out_type);
}
header('X-YF-assets-do-combine: true');
$this->ADD_IS_DIRECT_OUT = $bak['ADD_IS_DIRECT_OUT'];
$this->USE_CACHE = $bak['USE_CACHE'];
$this->COMBINE = $bak['COMBINE'];
}
public function _do_purge()
public function _do_purge()
{
$cache_dir_tpl = preg_replace('~/+~', '/', str_replace('{project_path}', PROJECT_PATH, $this->CACHE_DIR_TPL));
$cache_dir = substr($cache_dir_tpl, 0, strpos($cache_dir_tpl, '{')) ?: $cache_dir_tpl;
if (substr($cache_dir, 0, strlen(PROJECT_PATH)) === PROJECT_PATH && strlen($cache_dir) > strlen(PROJECT_PATH)) {
_class('dir')->delete($cache_dir, $and_start_dir = true);
}
headers_sent() || header('X-YF-assets-do-purge: true');
}
public function _get_absolute_path($path)
public function _get_absolute_path($path)
{
$parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
$absolutes = [];
foreach ($parts as $part) {
if ('.' == $part) {
continue;
}
if ('..' == $part) {
array_pop($absolutes);
} else {
$absolutes[] = $part;
}
}
return implode(DIRECTORY_SEPARATOR, $absolutes);
}
public function _get_absolute_url($url)
public function _get_absolute_url($url)
{
$u = parse_url($url);
if (substr($url, 0, 2) === '//') {
$u['scheme'] = 'http';
}
$host = $u['host'] ?? '';
$path = $this->_get_absolute_path($u['path'] ?? '');
$port = isset($u['port']) ? ':' . $u['port'] : '';
return strlen($host) && strlen($path) ? ($u['scheme'] ?? 'http') . '://' . $host . $port . '/' . ltrim($path, '/') : null;
}
public function _get_all_content_for_out($out_type, $params = [])
public function _get_all_content_for_out($out_type, $params = [])
{
$is_ajax = main()->is_ajax();
// Move down inlined content
$all_content = $this->get_content($out_type);
$top = [];
$bottom = [];
$last = [];
$names_to_md5 = [];
$out_before = [];
$out_after = [];
foreach ((array) $all_content as $md5 => $v) {
if ($v['name']) {
$names_to_md5[$v['name']] = $md5;
}
}
foreach ((array) $all_content as $md5 => $v) {
if ($v['params']['out_before'] ?? null) {
$out_before[$md5] = $names_to_md5[$v['params']['out_before']] ?? null;
} elseif ($v['params']['out_after'] ?? null) {
$out_after[$md5] = $names_to_md5[$v['params']['out_after']] ?? null;
}
$content_type = $v['content_type'];
if ($is_ajax && $content_type !== 'inline') {
continue;
}
if ($v['params']['is_last'] ?? null) {
$last[$md5] = $v;
} elseif (in_array($content_type, ['inline'])) {
$bottom[$md5] = $v;
} else {
$top[$md5] = $v;
}
}
$data = $top + $bottom + $last;
if ($out_before) {
foreach ((array) $out_before as $self_md5 => $before_md5) {
$pos = 0;
$self_data = [$self_md5 => $data[$self_md5]];
unset($data[$self_md5]);
foreach ($data as $_md5 => $v) {
if ($_md5 === $before_md5) {
break;
}
$pos++;
}
if ($pos && $self_data) {
$data_before = array_slice($data, 0, $pos, $preserve_keys = true);
$data_after = array_slice($data, $pos, null, $preserve_keys = true);
$data = $data_before + $self_data + $data_after;
}
}
}
if ($out_after) {
foreach ((array) $out_after as $self_md5 => $after_md5) {
$pos = 0;
$self_data = [$self_md5 => $data[$self_md5]];
unset($data[$self_md5]);
foreach ($data as $_md5 => $v) {
if ($_md5 === $after_md5) {
break;
}
$pos++;
}
if ($pos && $self_data) {
$data_before = array_slice($data, 0, $pos + 1, $preserve_keys = true);
$data_after = array_slice($data, $pos + 1, null, $preserve_keys = true);
$data = $data_before + $self_data + $data_after;
}
}
}
return $data;
}
public function _get_asset_tpl_replace($name, $version = 'master')
public function _get_asset_tpl_replace($name, $version = 'master')
{
! isset($this->_cache_language) && $this->_cache_language = conf('language') ?: 'en';
return [
'{site_path}' => rtrim(SITE_PATH, '/'),
'{app_path}' => rtrim(APP_PATH, '/'),
'{project_path}' => rtrim(PROJECT_PATH, '/'),
'{repo}' => $name,
'{name}' => $name,
'{version}' => $version,
'{branch}' => $version,
'{main_type}' => ($this->_override['main_type'] ?? null) ?: MAIN_TYPE,
'{host}' => ($this->_override['host'] ?? null) ?: ($_SERVER['HTTP_HOST'] ?? ''),
'{lang}' => $this->_cache_language,
];
}
public function _get_combined_path($out_type, $content_hash = '')
$content_hash mixed md5 of the elements content, empty for the file of the month public function _get_combined_path($out_type, $content_hash = '')
{
return $this->_cache_path($out_type, $content_hash, [
'name' => 'combined',
'version' => $this->_get_combined_version($out_type),
]);
}
public function _get_combined_version($out_type = '')
public function _get_combined_version($out_type = '')
{
if ( ! is_string($this->COMBINED_VERSION_TPL) && is_callable($this->COMBINED_VERSION_TPL)) {
$func = $this->COMBINED_VERSION_TPL;
$version = $func($out_type, $this);
} else {
$replace = $this->_tpl_placeholders(MAIN_TYPE, $out_type);
$version = str_replace(['///', '//'], '/', str_replace(array_keys($replace), array_values($replace), $this->COMBINED_VERSION_TPL));
}
return $version;
}
public function _get_media_path()
public function _get_media_path()
{
$media_path = MEDIA_PATH;
if (substr($media_path, 0, 2) === '//') {
$media_path = 'http:' . $media_path;
}
return $media_path;
}
public function _init()
public function _init()
{
$this->_url_control();
if ($this->FORCE_LOCAL_STORAGE) {
$this->USE_CACHE = true;
$this->CACHE_TTL = $this->FORCE_LOCAL_TTL;
}
$this->load_predefined_assets();
$this->load_combined_config();
}
public function _is_content_added($asset_type, $md5)
public function _is_content_added($asset_type, $md5)
{
return isset($this->content[$asset_type][$md5]);
}
public function _js_map_save($content, $content_url, $cache_path)
Try to find and save JS map file. It is used for navigating minified files inside browser's developer tools.
public function _js_map_save($content, $content_url, $cache_path)
{
$map_ext = '.map';
$map_url = '';
// Parse inline map url suggest, example: //# sourceMappingURL=lightbox.min.map
if (preg_match('~#\s*sourceMappingURL=(?P<map_url>.+\.map)~ims', $content, $m)) {
$map_url = trim($m['map_url']);
if (strlen($map_url) && strpos($map_url, '/') === false) {
$map_url = dirname($content_url) . '/' . $map_url;
}
}
if ( ! $map_url) {
return false;
}
$map_path = dirname($cache_path) . '/' . basename($map_url);
if (file_exists($map_path)) {
return true;
}
$map_content = $this->_url_get_contents($map_url);
if ( ! strlen($map_content)) {
return false;
}
if (file_exists($map_path) && file_get_contents($map_path) === $map_content) {
is_writable($map_path) && touch($map_path);
return true;
}
$this->_write_cache_info($map_path, $map_url, $map_content);
return $this->_atomic_write($map_path, $map_content);
}
public function _process_versions_content($content, $asset_type = '')
public function _process_versions_content($content, $asset_type = '')
{
if ( ! $this->URL_REWRITES && ! $this->VENDOR_LOCAL && ! $this->SRI) {
return $content;
}
if (is_string($content)) {
$content = $this->_rewrite_url($content);
if ( ! preg_match('~^(?:https?:)?//[^\s]+$~', $content)) {
return $content;
}
$local = $this->_vendor_local_url($content);
if ($local !== null) {
return $local;
}
$params = $this->_sri_params($content);
return $params ? ['content' => $content, 'params' => $params] : $content;
}
if (is_array($content)) {
if (isset($content['content']) && is_string($content['content'])) {
$item = $this->_process_versions_content($content['content'], $asset_type);
if (is_array($item)) {
$content['params'] = (array) ($content['params'] ?? []) + $item['params'];
$item = $item['content'];
}
$content['content'] = $item;
return $content;
}
foreach ($content as $k => $v) {
$content[$k] = $this->_process_versions_content($v, $asset_type);
}
}
return $content;
}
public function _removed_content_compiler($method)
public function _removed_content_compiler($method)
{
trigger_error('Assets: ' . $method . '() was removed, compile the source ahead of time and add the result with add_css() / add_js()', E_USER_DEPRECATED);
return $this;
}
public function _rewrite_url($str)
public function _rewrite_url($str)
{
foreach ((array) $this->URL_REWRITES as $regex => $replace) {
if (isset($this->_url_rewrites_bad[$regex])) {
continue;
}
if ( ! isset($this->_url_rewrites_ok[$regex])) {
set_error_handler(static fn () => true);
$valid = false !== preg_match($regex, '');
restore_error_handler();
if ( ! $valid) {
$this->_url_rewrites_bad[$regex] = true;
trigger_error(__CLASS__ . ': URL_REWRITES key is not a valid regex, ignored: ' . $regex, E_USER_WARNING);
continue;
}
$this->_url_rewrites_ok[$regex] = true;
}
$str = preg_replace($regex, $replace, $str) ?? $str;
}
return $str;
}
public function _skip_ext_mismatch($asset_type, $content)
A file or local url whose extension does not match the asset type is not stored: it used to leave an empty entry that printed a blank line.
public function _skip_ext_mismatch($asset_type, $content)
{
if ( ! isset($this->_ext_mismatch_noticed[$asset_type . ':' . $content])) {
$this->_ext_mismatch_noticed[$asset_type . ':' . $content] = true;
trigger_error('Assets: "' . $content . '" skipped, its extension does not match the "' . $asset_type . '" asset type', E_USER_NOTICE);
}
return null;
}
public function _sri_params($url)
public function _sri_params($url)
{
if ( ! $this->SRI) {
return null;
}
$entry = $this->_vendor_manifest()[$url] ?? null;
if ( ! is_array($entry) || empty($entry['integrity'])) {
return null;
}
return ['integrity' => $entry['integrity'], 'crossorigin' => 'anonymous'];
}
public function _strip_css_input($str)
Cleanup for CSS strings.
public function _strip_css_input($str)
{
// Extracting url from <link rel="stylesheet" href="path.to/style.css">
$str = preg_replace_callback('~<link[\s]+[^>]*href=["\']([^"\']+?)["\'][^>]*>~ims', function ($m) {
return $m[1];
}, $str);
for ($i = 0; $i < 10; $i++) {
if (strpos($str, 'style') === false) {
break;
}
$str = preg_replace('~^<style[^>]*?>~ims', '', $str);
$str = preg_replace('~</style>$~ims', '', $str);
}
return $str;
}
public function _strip_js_input($str)
Cleanup for JS strings.
public function _strip_js_input($str)
{
// Extracting url from <script src="path.to/scripts.js"></script>
$str = preg_replace_callback('~<script[\s]+[^>]*src=["\']([^"\']+?)["\'][^>]*>~ims', function ($m) {
return $m[1];
}, $str);
for ($i = 0; $i < 10; $i++) {
if (strpos($str, 'script') === false) {
break;
}
$str = preg_replace('~^<script[^>]*?>~ims', '', $str);
$str = preg_replace('~</script>$~ims', '', $str);
}
return $str;
}
public function _sub_add($info, $asset_type, $_params = [])
public function _sub_add($info, $asset_type, $_params = [])
{
if ( ! $info) {
return false;
}
if ( ! is_string($info) && is_callable($info)) {
$info = $info($_params, $this);
}
if ( ! $info) {
return false;
}
if ( ! is_array($info)) {
$info = [$info];
}
if (is_array($info) && isset($info['content'])) {
if (is_array($info['params'] ?? null)) {
$_params += $info['params'];
}
$info = $info['content'];
if ( ! $info) {
return false;
}
if ( ! is_array($info)) {
$info = [$info];
}
}
if ( ! $info) {
return false;
}
foreach ((array) $info as $_info) {
if ($_info) {
$this->add($_info, $asset_type, 'auto', $_params);
}
}
}
public function _theme_copy_warning($reason)
One warning per request when an internal theme cannot be served (the page then uses stock Bootstrap).
$reason string public function _theme_copy_warning($reason)
{
if ( ! $this->_theme_copy_warned) {
$this->_theme_copy_warned = true;
trigger_error('assets: internal theme not copied (' . $reason . '), using stock Bootstrap; see yf_assets::$THEMES_TARGET_DIR_TPL', E_USER_WARNING);
}
}
public function _theme_file_url($rel)
Web url of a file of the internal Bootstrap themes (YF_PATH/assets/themes/). The file is copied once into THEMES_TARGET_DIR_TPL/<content hash>/ with the files its relative url()s point at (fonts), so the framework dir is only read and a framework upgrade gets a new url.
$rel string path below assets/themes/, e.g. "bs5/slate/bootstrap.min.css"Returns null|string null when the file is unknown or cannot be copied
public function _theme_file_url($rel)
{
$rel = (string) $rel;
$target_dir = $this->_theme_target_dir();
$memo = $rel . '|' . $target_dir;
if (array_key_exists($memo, $this->_cache_themes)) {
return $this->_cache_themes[$memo];
}
$this->_cache_themes[$memo] = null;
$base = realpath(YF_PATH . 'assets/themes');
$from = $base ? realpath($base . '/' . $rel) : false;
if ( ! $from || strpos($from, $base . DIRECTORY_SEPARATOR) !== 0 || ! is_file($from)) {
return null;
}
if ( ! $this->_theme_target_ok($target_dir, false)) {
return null;
}
// the lock pins the fonts, so a font update changes the dir even when the stylesheet stays the same
$hash = md5((string) md5_file($from) . (string) @md5_file($base . '/themes.lock'));
$target = $target_dir . substr($hash, 0, 12) . '/';
$to = $target . substr($from, strlen($base) + 1);
if ( ! file_exists($to)) {
if ( ! $this->_theme_target_ok($target_dir, true)) {
return null;
}
$data = (string) file_get_contents($from);
// the referenced files first: an existing stylesheet copy means its fonts are in place
preg_match_all('~url\(\s*([\'"]?)(?!data:|[a-z]+:|//|#)([^\'")?#]+)~i', $data, $m);
foreach (array_unique($m[2]) as $ref) {
$ref_from = realpath(dirname($from) . '/' . trim($ref));
if ( ! $ref_from || strpos($ref_from, $base . DIRECTORY_SEPARATOR) !== 0 || ! is_file($ref_from)) {
continue;
}
$ref_to = $target . substr($ref_from, strlen($base) + 1);
if ( ! file_exists($ref_to)) {
! is_dir(dirname($ref_to)) && @mkdir(dirname($ref_to), 0755, true);
if (false === $this->_atomic_write($ref_to, file_get_contents($ref_from))) {
$this->_theme_copy_warning('cannot write ' . $ref_to);
return null;
}
}
}
! is_dir(dirname($to)) && @mkdir(dirname($to), 0755, true);
if (false === $this->_atomic_write($to, $data)) {
$this->_theme_copy_warning('cannot write ' . $to);
return null;
}
}
$this->_cache_themes[$memo] = MEDIA_PATH . substr($to, strlen(PROJECT_PATH));
return $this->_cache_themes[$memo];
}
public function _theme_generated_url($css)
Web url of a stylesheet generated by yf_bs_theme (site themes and overrides), written once to THEMES_TARGET_DIR_TPL/site/<md5>.css.
$css stringReturns null|string null when it cannot be written (one warning per request)
public function _theme_generated_url($css)
{
$css = (string) $css;
$target_dir = $this->_theme_target_dir();
$to = $target_dir . 'site/' . md5($css) . '.css';
if (array_key_exists($to, $this->_cache_themes)) {
return $this->_cache_themes[$to];
}
$this->_cache_themes[$to] = null;
if ( ! $this->_theme_target_ok($target_dir, ! file_exists($to))) {
return null;
}
if ( ! file_exists($to)) {
! is_dir(dirname($to)) && @mkdir(dirname($to), 0755, true);
if (false === $this->_atomic_write($to, $css)) {
$this->_theme_copy_warning('cannot write ' . $to);
return null;
}
}
$this->_cache_themes[$to] = MEDIA_PATH . substr($to, strlen(PROJECT_PATH));
return $this->_cache_themes[$to];
}
public function _theme_target_dir()
THEMES_TARGET_DIR_TPL with its placeholders resolved, with a trailing slash.
public function _theme_target_dir()
{
$replace = $this->_get_asset_tpl_replace('themes', 'local');
return rtrim(str_replace(['///', '//'], '/', str_replace(array_keys($replace), array_values($replace), $this->THEMES_TARGET_DIR_TPL)), '/') . '/';
}
public function _theme_target_ok($target_dir, $write)
The target dir is below PROJECT_PATH (it is served by url) and, when something must be written, its nearest existing parent is writable.
$target_dir string$write bool public function _theme_target_ok($target_dir, $write)
{
if (strpos($target_dir, PROJECT_PATH) !== 0) {
$this->_theme_copy_warning('THEMES_TARGET_DIR_TPL "' . $target_dir . '" is not below PROJECT_PATH');
return false;
}
if ($write) {
$parent = $target_dir;
while ( ! is_dir($parent) && dirname($parent) !== $parent) {
$parent = dirname($parent);
}
if ( ! is_writable($parent)) {
$this->_theme_copy_warning($parent . ' is not writable');
return false;
}
}
return true;
}
public function _tpl_placeholders($main_type, $out_type)
Placeholders shared by CACHE_DIR_TPL and COMBINED_VERSION_TPL.
public function _tpl_placeholders($main_type, $out_type)
{
$host = ($this->_override['host'] ?? null) ?: ($_SERVER['HTTP_HOST'] ?? '');
! isset($this->_cache_language) && $this->_cache_language = conf('language') ?: 'en';
$lang = ($this->_override['language'] ?? null) ?: $this->_cache_language;
! isset($this->_cache_html5fw) && $this->_cache_html5fw = conf('css_framework') ?: 'bs3';
$html5fw = ($this->_override['html5fw'] ?? null) ?: $this->_cache_html5fw;
! isset($this->_cache_date) && $this->_cache_date = explode('-', date('Y-m-d-H-i-s'));
$date = ($this->_override['date'] ?? null) ?: $this->_cache_date;
return [
'{site_path}' => SITE_PATH,
'{app_path}' => APP_PATH,
'{project_path}' => PROJECT_PATH,
'{main_type}' => $main_type,
'{host}' => $host,
'{lang}' => $lang,
'{out_type}' => $out_type,
'{html5fw}' => $html5fw,
'{year}' => $date[0],
'{month}' => $date[1],
'{day}' => $date[2],
'{hour}' => $date[3],
'{minute}' => $date[4],
'{second}' => $date[5],
];
}
public function _url_cache_path($url)
Cache file of a fetched url. Site id and project path are in the name: two sites sharing the dir do not read each other's entries.
public function _url_cache_path($url)
{
$cache_dir = strlen($this->URL_FILE_CACHE_DIR) ? rtrim($this->URL_FILE_CACHE_DIR, '/') : '/tmp/yf_assets';
return $cache_dir . '/' . md5($url) . '_' . (int) conf('SITE_ID') . '_' . substr(md5(PROJECT_PATH), 0, 8) . '.cache';
}
public function _url_control()
public function _url_control()
{
if ( ! $this->ALLOW_URL_CONTROL) {
return false;
}
if ($this->URL_CONTROL_REQUIRE_DEBUG && ! DEBUG_MODE && ! main()->is_console() && ! main()->ADMIN_ID && empty($_SESSION['admin_id'])) {
return false;
}
$used = [];
if (isset($_GET['assets_cache'])) {
$this->USE_CACHE = (bool) ($_GET['assets_cache'] ?? null);
$used[] = 'assets_cache';
}
if (isset($_GET['assets_combine'])) {
$this->COMBINE = (bool) ($_GET['assets_combine'] ?? null);
if ($this->COMBINE) {
$this->USE_CACHE = true;
}
$used[] = 'assets_combine';
}
if (isset($_GET['assets_requirejs'])) {
$this->USE_REQUIRE_JS = (bool) $_GET['assets_requirejs'];
$used[] = 'assets_requirejs';
}
if (isset($_GET['assets_out_mtime'])) {
$this->CACHE_OUT_ADD_MTIME = (bool) ($_GET['assets_out_mtime'] ?? null);
$used[] = 'assets_out_mtime';
}
if (DEBUG_MODE) {
if ($_GET['assets_do_cache'] ?? false) {
$this->_do_cache();
$used[] = 'assets_do_cache';
}
if ($_GET['assets_do_combine'] ?? false) {
$this->_do_combine();
$used[] = 'assets_do_combine';
}
if ($_GET['assets_do_purge'] ?? false) {
$this->_do_purge();
$used[] = 'assets_do_purge';
}
}
if ($used) {
header('X-Robots-Tag: noindex,nofollow,noarchive,nosnippet');
}
}
public function _url_get_contents($url)
Smart wrapper with temp file cache.
public function _url_get_contents($url)
{
if ( ! strlen($url)) {
return false;
}
// Possibly inline code: a URL never contains a line break or a tab
if (preg_match('~[\r\n\t]~', $url)) {
return $url;
}
$media_path = $this->_get_media_path();
// Do not use web server for self-accessible paths
if (substr($url, 0, strlen($media_path)) === $media_path) {
$path = PROJECT_PATH . substr($url, strlen($media_path));
// This line needed to strip query string from file name like this:
// /templates/user/css/style.css?1416914173 -> templates/user/css/style.css
$path = parse_url($path, PHP_URL_PATH);
return file_get_contents($path);
}
$url = (substr($url, 0, 2) === '//' ? 'http:' : '') . $url;
// Save syscall
if ( ! isset($this->_time)) {
$this->_time = time();
}
$cache_path = $this->_url_cache_path($url);
$cache_dir = dirname($cache_path);
// 24 hours tmp file cache
if (file_exists($cache_path) && filemtime($cache_path) > ($this->_time - $this->URL_FILE_CACHE_TTL)) {
return file_get_contents($cache_path);
}
$data = $this->http_get_contents($url);
// Do not cache a failed fetch, it would be served as an empty file for the whole TTL
if (false !== $data) {
if ( ! file_exists($cache_dir)) {
mkdir($cache_dir, strlen($this->URL_FILE_CACHE_DIR) ? 0750 : 0755, $recurse = true);
}
$this->_atomic_write($cache_path, $data);
}
return $data;
}
public function _vendor_copy_css_ref($url, array $local)
Copy a vendored font / image of a stylesheet into the stylesheet's target dir, same layout as assets/vendor/.
$url string scheme-less CDN url of the file$local array see _vendor_css_rewrite_urls()Returns bool false: not vendored (or not copyable), the caller keeps the CDN url
public function _vendor_copy_css_ref($url, array $local)
{
$entry = $this->_vendor_manifest()[$url] ?? null;
if ( ! is_array($entry) || empty($entry['via'])) {
return false;
}
if (preg_match('~^//cdn\.jsdelivr\.net/gh/(?P<owner>[^/@]+)/(?P<repo>[^/@]+)@(?P<ref>[^/]+)/(?P<path>.+)$~', $url, $m)) {
$rel = $m['owner'] . '/' . $m['repo'] . '/' . $m['ref'] . '/' . $m['path'];
} elseif (preg_match('~^//(?P<host>[^/@?#\s]+)/(?P<path>[^?#\s]+)$~', $url, $m)) {
$rel = strtolower($m['host']) . '/' . $m['path'];
} else {
return false;
}
if (in_array('..', explode('/', $rel), true)) {
return false;
}
$from = realpath($local['vendor_dir'] . '/' . $rel);
if ( ! $from || strpos($from, $local['vendor_dir'] . DIRECTORY_SEPARATOR) !== 0 || ! is_file($from)) {
return false;
}
$to = $local['target_dir'] . '/' . $rel;
if (file_exists($to)) {
return true;
}
$dir = dirname($to);
! file_exists($dir) && mkdir($dir, 0755, true);
return false !== $this->_atomic_write($to, file_get_contents($from));
}
public function _vendor_css_rewrite_urls($css, $base, $local = null)
Relative url() references inside a copied stylesheet: a font / image vendored for it (a manifest entry with `via`) is copied next to the copied stylesheet and the reference stays relative; any other one points at the CDN directory of the original file.
$base mixed CDN directory of the stylesheet, scheme-less$local null|array ['vendor_dir' => realpath of assets/vendor, 'target_dir' => where the stylesheet copy lives] public function _vendor_css_rewrite_urls($css, $base, $local = null)
{
return preg_replace_callback('~url\(\s*([\'"]?)(?!data:|https?:|//|#)([^\'")]+)\1\s*\)~i', function ($m) use ($base, $local) {
$parts = [];
foreach (explode('/', rtrim($base, '/') . '/' . trim($m[2])) as $part) {
if ($part === '..') {
array_pop($parts);
} elseif ($part !== '.') {
$parts[] = $part;
}
}
$url = implode('/', $parts);
if (is_array($local) && $this->_vendor_copy_css_ref(preg_replace('~[?#].*$~', '', $url), $local)) {
return $m[0];
}
return 'url(' . $m[1] . $url . $m[1] . ')';
}, $css);
}
public function _vendor_local_url($url)
public function _vendor_local_url($url)
{
if ( ! $this->VENDOR_LOCAL) {
return null;
}
if (preg_match('~^(?:https?:)?//cdn\.jsdelivr\.net/gh/(?P<owner>[^/@]+)/(?P<repo>[^/@]+)@(?P<ref>[^/]+)/(?P<path>.+)$~', $url, $m)) {
$rel = $m['owner'] . '/' . $m['repo'] . '/' . $m['ref'] . '/' . $m['path'];
$name = $m['repo'];
$version = $m['ref'];
$css_base = '//cdn.jsdelivr.net/gh/' . $m['owner'] . '/' . $m['repo'] . '@' . $m['ref'] . '/' . dirname($m['path']) . '/';
} elseif (isset($this->_vendor_manifest()[$url]) && preg_match('~^(?:https?:)?//(?P<host>[^/@?#\s]+)/(?P<path>[^?#\s]+)$~', $url, $m)) {
// Other hosts (cdnjs, ...) are vendored under assets/vendor/<host>/<path>; only manifest keys are looked up
$rel = $m['host'] . '/' . $m['path'];
$name = $m['host'];
$version = 'master';
$css_base = '//' . substr($rel, 0, (int) strrpos($rel, '/') + 1);
} else {
return null;
}
if (array_key_exists($url, $this->_cache_vendor)) {
return $this->_cache_vendor[$url];
}
$this->_cache_vendor[$url] = null;
if (in_array('..', explode('/', $rel), true)) {
return null;
}
$vendor_dir = $this->VENDOR_DIR ?: YF_PATH . 'assets/vendor/';
$base = realpath($vendor_dir);
$from = realpath(rtrim($vendor_dir, '/') . '/' . $rel);
if ( ! $base || ! $from || strpos($from, $base . DIRECTORY_SEPARATOR) !== 0 || ! is_file($from)) {
return null;
}
$replace = $this->_get_asset_tpl_replace($name, $version);
$target_dir = str_replace(['///', '//'], '/', str_replace(array_keys($replace), array_values($replace), $this->VENDOR_TARGET_DIR_TPL));
$to_path = rtrim($target_dir, '/') . '/' . $rel;
if ( ! file_exists($to_path)) {
$dir_to = dirname($to_path);
! file_exists($dir_to) && mkdir($dir_to, 0755, true);
$data = file_get_contents($from);
if (substr($rel, -4) === '.css') {
$data = $this->_vendor_css_rewrite_urls($data, $css_base, ['vendor_dir' => $base, 'target_dir' => rtrim($target_dir, '/')]);
}
if (false === $this->_atomic_write($to_path, $data)) {
return null;
}
}
$this->_cache_vendor[$url] = MEDIA_PATH . substr($to_path, strlen(PROJECT_PATH));
return $this->_cache_vendor[$url];
}
public function _vendor_manifest()
public function _vendor_manifest()
{
if ($this->_cache_vendor_manifest === null) {
$file = rtrim($this->VENDOR_DIR ?: YF_PATH . 'assets/vendor/', '/') . '/manifest.json';
$data = is_file($file) ? json_decode((string) file_get_contents($file), true) : null;
$this->_cache_vendor_manifest = is_array($data) ? $data : [];
}
return $this->_cache_vendor_manifest;
}
public function _write_cache_info($cache_path, $url, $content, $extra = [])
public function _write_cache_info($cache_path, $url, $content, $extra = [])
{
$data = [
'url' => $url,
'date' => date('Y-m-d H:i:s'),
'md5' => md5($content),
];
if ($extra) {
foreach ($extra as $k => $v) {
$data[$k] = $v;
}
}
return $this->_atomic_write($cache_path . '.info', json_encode($data, JSON_PRETTY_PRINT));
}