plugins/assets/admin_modules/yf_manage_assets.class.php

Summary

Maintainability
F
3 days
Test Coverage
<?php

// TODO: requirejs integration, auto-generate its config with switcher on/off
// TODO: cache fill from console, with ability to put into cron task
// TODO: support for multiple media servers
// TODO: support for .min, using some of console minifier (yahoo, google, jsmin ...)
// TODO: move to web accessible folder only after completion to ensure atomicity
// TODO: upload to S3, FTP

class yf_manage_assets
{
    /**
     * Catch missing method call.
     * @param mixed $name
     * @param mixed $args
     */
    public function __call($name, $args)
    {
        return main()->extend_call($this, $name, $args);
    }

    /**
     * @param mixed $method
     */
    public function _module_action_handler($method)
    {
        $prepend = $this->_menu();
        if ( ! $method || substr($method, 0, 1) === '_' || ! method_exists($this, $method)) {
            $method = 'show';
        }
        return $prepend . $this->$method() . $append;
    }


    public function _init()
    {
        _class('assets')->USE_CACHE = false;
        _class('assets')->COMBINE = false;
    }


    public function _menu()
    {
        return html()->module_menu($this, [
            ['/@object/cache_info/', 'Cache info', 'fa fa-info'],
            ['/@object/cache_purge/', 'Cache purge', 'fa fa-recycle'],
            ['/@object/cache_fill/', 'Cache fill', 'fa fa-refresh'],
            ['/@object/combine/', 'Combine', 'fa fa-rocket'],
            ['/@object/settings/', 'Settings', 'fa fa-wrench'],
            ['/@object/search_used/', 'Search used', 'fa fa-search'],
            ['/@object/upload/', 'Upload', 'fa fa-upload'],
        ]) . '<br /><br />' . PHP_EOL;
    }


    public function show()
    {
        return redirect('/@object/cache_info');
    }


    public function search_used()
    {
        $exclude_paths = [
            '*/.git/*',
            '*/.dev/*',
            '*/test/*',
            '*/tests/*',
            '*/cache/*',
            '*/test*.class.php',
            '*/' . YF_PREFIX . 'test*.class.php',
        ];
        $regex_php = '~[\s](asset|js|css)\([\s"\']+(?P<name>[^\(\)\{\}\$]+)[\s"\']+\)~ims';
        $raw_in_php = _class('dir')->grep($regex_php, APP_PATH, '*.php', ['exclude_paths' => $exclude_paths]);
        $names = [];
        foreach ((array) $raw_in_php as $path => $matches) {
            $lines = file($path);
            foreach ((array) $matches as $raw) {
                preg_match($regex_php, $raw, $m);
                $name = trim(trim(trim($m['name']), '"\''));
                $names[$name] = $name;
                $raw = trim(trim(trim($raw), '"\''));
                foreach ((array) $lines as $n => $line) {
                    if (strpos($line, $raw) !== false) {
                        $by_line[$path][$raw][$n] = $n;
                        $by_path[$name][$path][$n] = $n;
                    }
                }
            }
        }
        $regex_tpl = '~\{(asset|js|css)\(\)\}\s+(?P<name>[^\{\}\(\)\$]+?)\s+\{\/\1\}~ims';
        $raw_in_tpl = _class('dir')->grep($regex_tpl, APP_PATH, '*.stpl', ['exclude_paths' => $exclude_paths]);
        foreach ((array) $raw_in_tpl as $path => $matches) {
            $lines = file($path);
            foreach ((array) $matches as $raw) {
                preg_match($regex_tpl, $raw, $m);
                $name = trim(trim(trim($m['name']), '"\''));
                $names[$name] = $name;
                $raw = trim(trim(trim($raw), '"\''));
                foreach ((array) $lines as $n => $line) {
                    if (strpos($line, $raw) !== false) {
                        $by_line[$path][$raw][$n] = $n;
                        $by_path[$name][$path][$n] = $n;
                    }
                }
            }
        }
        $assets = _class('assets');
        $names_by_type = [
            'user' => [],
            'admin' => [],
        ];
        foreach ((array) $names as $k => $v) {
            if (substr($k, 0, 2) === '//' || substr($k, 0, 7) === 'http://' || substr($k, 0, 8) === 'https://') {
                unset($names[$k]);
                continue;
            }
            $details = $assets->get_asset_details($k);
            if (empty($details) || (isset($details['config']) && $details['config']['no_cache'])) {
                unset($names[$k]);
                continue;
            }
            if (isset($details['config']['main_type'])) {
                $main_type = $details['config']['main_type'];
                $names_by_type[$main_type][$k] = $v;
            } else {
                $names_by_type['user'][$k] = $v;
                $names_by_type['admin'][$k] = $v;
            }
        }
        ksort($names);
        ksort($names_by_type['user']);
        ksort($names_by_type['admin']);
        $table = [];
        foreach ((array) $names as $name) {
            $table[$name] = '<small>' . implode('<br>', array_keys($by_path[$name])) . '</small>';
        }
        $export = '<' . '?php' . PHP_EOL . 'return array(' . PHP_EOL
            . '\'user\' => ' . preg_replace('~\s{2}[0-9]+\s+=>\s+~i', '  ', var_export(array_keys($names_by_type['user']), 1)) . ',' . PHP_EOL
            . '\'admin\' => ' . preg_replace('~\s{2}[0-9]+\s+=>\s+~i', '  ', var_export(array_keys($names_by_type['admin']), 1)) . ',' . PHP_EOL
            . ');';
        return '<pre style="color:white;background:black;line-height:1em;font-weight:bold;"><small>' . _prepare_html($export) . '</small></pre>'
            . '<h3>Used assets</h3>' . html()->simple_table($table);
    }


    public function cache_info()
    {
        $assets = clone _class('assets');
        $assets->USE_CACHE = false;

        $cache_dir_tpl = preg_replace('~/+~', '/', str_replace('{project_path}', PROJECT_PATH, $assets->CACHE_DIR_TPL));
        $combined_dir_tpl = str_replace('{asset_name}', 'combined', $cache_dir_tpl) . '_combined.*';
        $contents[] = 'Combined info:' . PHP_EOL . shell_exec('ls -l ' . preg_replace('~\{[^\}]+\}~ims', '*', $combined_dir_tpl));

        $cache_dir = substr($cache_dir_tpl, 0, strpos($cache_dir_tpl, '{'));
        $tmp = [];
        foreach ((array) _class('dir')->rglob($cache_dir) as $path) {
            if (is_dir($path) || substr($path, -5, 5) === '.info') {
                continue;
            }
            $tmp[] = $path;
        }
        $contents[] = 'Cached assets:' . PHP_EOL . implode(PHP_EOL, $tmp);

        $contents[] = PHP_EOL . 'Shared url file cache info:' . PHP_EOL . shell_exec('ls -l /tmp/yf_assets/*');
        return 'Cache info: <pre style="line-height:1em;"><small>' . implode(PHP_EOL, $contents) . '</small></pre>';
    }


    public function cache_purge()
    {
        $assets = clone _class('assets');
        $assets->USE_CACHE = false;
        $cache_dir_tpl = preg_replace('~/+~', '/', str_replace('{project_path}', PROJECT_PATH, $assets->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)) {
            if (is_console()) {
                echo 'Cleaning ' . $cache_dir . PHP_EOL;
            }
            _class('dir')->delete($cache_dir, $and_start_dir = true);
        }
        return 'Done';
    }


    public function cache_fill()
    {
        $this->cache_purge();
        // TODO: use temp dir while caching
        // TODO: verify that all files are available
        $assets = clone _class('assets');
        $assets->ADD_IS_DIRECT_OUT = true;
        $assets->USE_CACHE = true;
        $assets->COMBINE = false;
        $assets->FORCE_LOCAL_STORAGE = false;
        ($cache_dir_tpl = $GLOBALS['PROJECT_CONF']['assets']['CACHE_DIR_TPL']) && $assets->CACHE_DIR_TPL = $cache_dir_tpl;
        $combined_names = $assets->load_combined_config($force = true);

        $cur_lang = conf('language');

        $dir = _class('dir');
        $enabled_langs = main()->get_data('languages');
        $main_types = ['user', 'admin'];
        foreach ((array) $main_types as $main_type) {
            $assets->_override['main_type'] = $main_type;
            foreach ((array) $enabled_langs as $lang) {
                conf('language', $lang);
                $assets->_override['language'] = $lang;
                $assets->load_predefined_assets($force = true);
                foreach ((array) $assets->supported_out_types as $out_type) {
                    foreach ((array) $combined_names[$main_type] as $name) {
                        // echo $main_type.' | '.$lang.' | '.$out_type.' | '.$name.'<br>';
                        $direct_out = $assets->add_asset($name, $out_type);
                    }
                }
            }
        }
        conf('language', $cur_lang);
        return $this->cache_info();
    }

    /**
     * Force combine assets according to config.
     */
    public function combine()
    {
        $assets = clone _class('assets');
        $assets->clean_all();
        $assets->ADD_IS_DIRECT_OUT = false;
        $assets->USE_CACHE = true;
        $assets->COMBINE = true;
        $combined_names = $assets->load_combined_config($force = true);
        $assets->FORCE_LOCAL_STORAGE = false;
        ($cache_dir_tpl = $GLOBALS['PROJECT_CONF']['assets']['CACHE_DIR_TPL']) && $assets->CACHE_DIR_TPL = $cache_dir_tpl;

        $cur_lang = conf('language');

        $dir = _class('dir');
        $enabled_langs = main()->get_data('languages');
        $main_types = ['user', 'admin'];
        foreach ((array) $main_types as $main_type) {
            $assets->_override['main_type'] = $main_type;
            foreach ((array) $enabled_langs as $lang) {
                conf('language', $lang);
                $assets->_override['language'] = $lang;
                $assets->load_predefined_assets($force = true);
                foreach ((array) $assets->supported_out_types as $out_type) {
                    $assets->clean_all();
                    $combined_path = $assets->_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) {
                        $assets->add_asset($name, $out_type);
                    }
                    $out = $assets->show($out_type);
                    $combined_dir = dirname($assets->_get_combined_path($_out_type = ''));
                    $tmp = [];
                    foreach ((array) $dir->rglob($combined_dir) as $path) {
                        if (is_dir($path) || substr($path, -5, 5) === '.info') {
                            continue;
                        }
                        $tmp[] = $path;
                    }
                    $contents[] = implode(PHP_EOL, $tmp);
                }
            }
        }
        conf('language', $cur_lang);
        return 'Combined info: <pre style="line-height:1em;"><small>' . implode(PHP_EOL, $contents) . '</small></pre>';
    }


    public function upload()
    {
        // TODO: upload cache and combined into outer storage (CDN, FTP, S3, ...)
    }


    public function settings()
    {
        $config_path = CONFIG_PATH . 'assets_combine.php';
        $combined_config = file_get_contents($config_path);
        return 'Current assets combine config: <b>' . $config_path . '</b>'
            . '<pre style="color:white;background:black;line-height:1em;font-weight:bold;"><small>' . _prepare_html($combined_config) . '</small></pre>';
        // TODO: pretty show current important assets settings and optionally allow to change them
    }
}