<?php

/**
 * @extensione  YRMinifier
 * @author      Lev Milicenco
 * @copyright   (c) Marlev.it - Itroom SRLS - 2021. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Profiler\Profiler;
use Joomla\CMS\Uri\Uri;
//for_use
defined('_JEXEC') or die;


class plgSystemYRminifier extends CMSPlugin
{

    protected $cache;
    protected $cache_id;
    protected $fullstring = array();
    protected $js_inbody;

    public function onAfterRender()
    {
        $app = Factory::getApplication();
        if ($app->isClient('site')) {
            $this->params = $this->get_params();


            $enabled = (int)$this->params->optimizecss . $this->params->optimizejs;
            $itemid = $app->input->getInt('Itemid');
            $option = $app->input->getString('option');
            if ($this->params->mode == false || $enabled == 0 || in_array($itemid,$this->params->escludeonmenu) || in_array($option,$this->params->escludeoncomponents)) {
                return false;
            }

            $html = Factory::getApplication()->getBody();
            $rewrited = preg_replace_callback("#<\s*head[^>]*>.*<\s*/\s*head[^>]*>#si", "self::run_yrminifier", $html);
            if($this->params->append_js == 2){ //js to body
                $rewrited = preg_replace('#(<\s*/\s*body>)#',$this->js_inbody . PHP_EOL.'</body>',$rewrited);
            }
            Factory::getApplication()->setBody($rewrited);
        }
    }

    protected function run_yrminifier($match)
    {


        $html = $match[0];
        //mode 1 - test
        //mode 2 - production
        $mode = (int)$this->params->mode;
        $getcache = $this->prepare_cache();
        if ($mode == 1) {
            $rewrited = $this->rewrite($html);
        } else {
            if ($getcache !== false) {
                $cached = json_decode($getcache);
                $rewrited = $this->build_from_cache($cached, $html);
            } else {

                $rewrited = $this->rewrite($html);
            }
        }

        return $rewrited;
    }

    protected function prepare_cache()
    {
        $cache = Cache::getInstance('output', array('caching' => true, "checkTime" => false, "cachebase" => JPATH_SITE . "/cache/"));
        $this->cache_id = $cache->makeId();
        $getcache = $cache->get($this->cache_id, $this->params->cachegroup);
        return $getcache;

    }

    protected function build_from_cache($getcache, $html)
    {
        if ($this->params->optimizecss > 0) {
            $html = $this->build_from_cache_css($getcache, $html);
        }
        if ($this->params->optimizejs > 0) {
            $html = $this->build_from_cache_js($getcache, $html);
        }
        return $html;
    }

    protected function build_from_cache_js($cached, $html)
    {
        $grep_js = preg_grep("#src=[\"']#", $cached);
        $html = $this->remove_from_html($grep_js, $html);
        $html = $this->remove_script($html);
        $html = $this->add_jstohtml($html, $grep_js);
        return $html;
    }

    protected function build_from_cache_css($cached, $html)
    {
        $grep_css = preg_grep("#href=[\"']#", $cached);
        $html = $this->remove_from_html($grep_css, $html);
        $html = $this->remove_style($html);
        $html = $this->add_csstohtml($html, $grep_css);
        return $html;
    }

    protected function set_cache()
    {
        $options = array('lifetime' => 10080, 'caching' => true, "cachebase" => JPATH_SITE . "/cache/");
        $this->cache = Cache::getInstance('callback', $options);
        #$this->cache->addIncludePath(JPATH_ROOT ."/cache/");
        $this->cache->setCaching(true);
        $links = json_encode($this->fullstring);
        $this->cache->store($links, $this->cache_id, $this->params->cachegroup);
    }

    protected function rewrite($html)
    {
        $set = false;
        if ($this->params->optimizecss > 0) {
            $set = true;
            $html = $this->run_rewrite_css($html);
        }
        if ($this->params->optimizejs > 0) {
            $set = true;
            $html = $this->run_rewrite_js($html);
        }

        if ($set == true) {
            $this->set_cache();
        }
        return $html;
    }

    protected function run_rewrite_css($html)
    {
        $css = '#(?:<link[^>].*href=["|\']([^>]*\.css)(\?[^>]*)?["|\'][^>]*/?>)#iU';
        preg_match_all($css, $html, $match_css);

        $css_convalida = $this->convalida_delete($match_css, $html);

        $html = $this->rewrite_css($css_convalida->fnames, $css_convalida->html);
        return $html;
    }

    protected function run_rewrite_js($html)
    {
        $js = '#(?:<script[^>]*src=["|\']([^>]*\.js)(\?[^>]*)?["|\'][^>]*/?>.*</script>)#isU';
        preg_match_all($js, $html, $match_js);
        $js_convalida = $this->convalida_delete($match_js, $html);
        $html = $this->rewrite_js($js_convalida->fnames, $js_convalida->html);
        return $html;
    }

    protected function append_import_font()
    {
        $get_css = file_get_contents(JPATH_BASE . "/" . $this->params->template_css);
        preg_match("#(@import url.*;)#", $get_css, $match);
        if (isset($match[0])) {
            return $match[0];
        }
        return "";

    }

    protected function css_run_minifier($name, $get_file)
    {
        $img_patch = Uri::base() . $name;
        $unpunto = $this->prepare_url_image_for_css($img_patch, 1);
        $duepunti = $this->prepare_url_image_for_css($unpunto);
        return $this->replace_unused_incss($get_file, array('#\.\./#'), array($duepunti), array("./"), array($unpunto));
    }

    protected function rewrite_css($fnames, $html)
    {
        //self::check_create_folder($this->params->cachegroup);

        if ($this->params->mode == 1) {
            file_put_contents(JPATH_BASE . "/cache/" . $this->params->cachegroup . "/" . $this->cache_id . ".css", '');
            file_put_contents(JPATH_BASE . "/cache/" . $this->params->cachegroup . "/" . $this->cache_id . "-head.css", '');
        }
        if (!file_exists(JPATH_BASE . "/cache/" . $this->params->cachegroup . "/" . $this->cache_id . ".css") || !file_exists(JPATH_BASE . "/cache/" . $this->params->cachegroup . "/" . $this->cache_id . "-head.css") || $this->params->mode == 1) {
            $import_font = $this->append_import_font();
            file_put_contents(JPATH_BASE . "/cache/" . $this->params->cachegroup . "/" . $this->cache_id . "-head.css", $import_font);
            foreach ($fnames as $f => $name) {
                $name = str_replace(Uri::base(true), "", $name);
                $name = preg_replace('&^/&', '', $name);
                if ((file_exists(JPATH_BASE . "/" . $name)) && !in_array($name, $this->params->escludecss) && !in_array($name, $this->params->escludecss_write)) {
                    $get_file = file_get_contents(JPATH_BASE . "/" . $name);

                    if ($name == $this->params->template_css && $import_font != "") {
                        $get_file = preg_replace('#' . preg_quote($import_font) . '#', "", $get_file);
                    }
                    $minify = $this->css_run_minifier($name, $get_file);
                    if (in_array($name, $this->params->css_tohead)) {
                        file_put_contents(JPATH_BASE . "/cache/" . $this->params->cachegroup . "/" . $this->cache_id . "-head.css", $minify, FILE_APPEND);
                    } else {
                        file_put_contents(JPATH_BASE . "/cache/" . $this->params->cachegroup . "/" . $this->cache_id . ".css", $minify, FILE_APPEND);
                    }
                }
            }
            $html = $this->replace_style($html);
        }
        $newhtml = $this->add_csstohtml($html, $this->fullstring);
        return $newhtml;
    }

    protected function rewrite_js($fnames, $html)
    {
        //self::check_create_folder($this->params->cachegroup);
        if ($this->params->mode == 1) {
            file_put_contents(JPATH_BASE . "/cache/" . $this->params->cachegroup . "/" . $this->cache_id . ".js", '');
        }
        if (!file_exists(JPATH_BASE . "/cache/" . $this->params->cachegroup . "/" . $this->cache_id . ".js") || $this->params->mode == 1) {

            foreach ($fnames as $f => $name) {
                $name = str_replace(Uri::base(true), "", $name);
                $name = preg_replace('&^/&', '', $name);
                if (file_exists(JPATH_BASE . "/" . $name) && !in_array($name, $this->params->escludejs)) {
                    $get_file = file_get_contents(JPATH_BASE . "/" . $name);
                    if(preg_match('#\.min\.js#',$name)){
                        $minify = $get_file;
                    }
                    else {
                        $minify = $this->replace_unused_injs($get_file).PHP_EOL;
                    }
                    $filename = ($this->params->mode == 1) ? '/*'.$name.'*/':'';
                    file_put_contents(JPATH_BASE . "/cache/" . $this->params->cachegroup . "/" . $this->cache_id . ".js",$filename .$minify, FILE_APPEND);
                }
            }
            $html = $this->replace_script($html);
        }
        $newhtml = $this->add_jstohtml($html, $this->fullstring);
        return $newhtml;
    }

    protected function replace_unused_injs($get_file)
    {
        if ($this->params->optimizejs == 2) {
            //1.single comment, 2.multiline comment, 3.empty lines, 4.space/tab inizio linea, 5. fine della linea che finisce con simboli
            //6. spazio tra variabile e simbolo uguale,7.spazio tra ) e {
            $preg_search = array('#([^\\\"\':]{1})[/]{2,}.*#', '#/\*.*\*/#sU', '#^\s*[\r\n]#m', '#^(\s*|\t*)#m', '#(;|}|{|,|:)\s*[\r\n]#m', '#(\d|\w)[\s]{0,}(=)[\s]{0,}#m', '#(\))\s*({)#m');
            $preg_replace = array('$1', '', '', '', '$1', '$1$2', '$1$2');
            $get_file = preg_replace($preg_search, $preg_replace, $get_file);

        }
        return $get_file;
    }

    protected function convalida_delete($matches, $html)
    {
        //arrays di local patch

        $base = array(Uri::base());
        if (Uri::base(true) != "/") {
            $base[] = Uri::base(true) . "/";
        }
        $locals = array("/templates", "/components", "/media", "/modules", "/plugins");
        if (!empty(Uri::root(true))) {
            $locals[] = Uri::root(true);
        }
        $fnames = array();
        $hash = "";
        $i = 0;
        $return = new \stdClass();
        $remove = array();
        foreach ($matches[1] as $value) {
            foreach (array_merge($locals, $base) as $key => $local) {
                $countlocal = strlen($local);
                $checklocal = substr($value, 0, $countlocal);
                if ($checklocal == $local) {
                    $filename = (in_array($checklocal, $base)) ? substr($value, $countlocal) : $value;
                    if (!in_array($matches[0][$i], $this->fullstring)) {
                        $this->fullstring[] = $matches[0][$i];
                        $fnames[] = $filename;
                        $remove[] = $matches[0][$i];

                    }
                }
            }
            $i++;
        }
        $html = $this->remove_from_html($remove, $html);
        $return->html = $html;
        $return->fnames = $fnames;


        return $return;


    }

    //rimuove tutti richiami in linkarray css e js da html
    public function remove_from_html($linkarray, $html)
    {
        $prepare = array('#^\s*[\r\n]#m');
        foreach ($linkarray as $key => $value) {
            $prepare[] = '#' . preg_quote($value) . '\s*(\r\n)?#s';
        }
        $html = preg_replace($prepare, '', $html);
        return $html;
    }

    protected function prepare_url_image_for_css($array, $cycle = 0)
    {
        //example ../ or ./
        $prepare_url_image = explode("/", $array);
        $delete = array_pop($prepare_url_image);
        if ($cycle == 1)
            $delete = array_pop($prepare_url_image);

        return implode("/", $prepare_url_image) . "/";
    }

    protected function replace_unused_incss($get_file, $preg_search = array(), $preg_replace = array(), $str_search = array(), $str_replace = array())
    {
        if ($this->params->optimizecss == 2) {
            $preg_search = array_merge(array('/\s\s+/', '/\n/', '#/\*.*?\*/#s'), $preg_search);
            $preg_replace = array_merge(array('', '', ''), $preg_replace);
            $str_search = array_merge(array(": ", "; ", " {", " > ", "> ", " >", ", ", ", .", "\n"), $str_search);
            $str_replace = array_merge(array(":", ";", "{", ">", ">", ">", ",", ",.", ""), $str_replace);
        }
        $compress = preg_replace($preg_search, $preg_replace, $get_file);
        $minify = str_replace($str_search, $str_replace, $compress);
        return $minify;
    }

    protected function replace_style($html)
    {

        $style_html = '#<style.*>(.*)<\/style>#isU';
        return preg_replace_callback($style_html, "self::replacecss_inhtml", $html);
    }

    protected function replace_script($html)
    {
        $script_html = '@<script[a-zA-Z0-9-"_/\s=\'?]{0,}>\s*([^{].*)<\/script>@isU';
        preg_match_all($script_html, $html, $match);
        return preg_replace_callback($script_html, "self::replacejs_inhtml", $html);
    }

    protected function remove_style($html)
    {
        $style_html = '#(?:<style.*>(.*)<\/style>\s*(\n|\r)?)#isU';
        return preg_replace($style_html, "", $html);
    }

    protected function remove_script($html)
    {
        $script_html = '#(?:<script[a-zA-Z0-9-"_/\s=\'?]{0,}>\s*([^{].*)<\/script>\s*(\n|\r)?)#isU';
        return preg_replace($script_html, "", $html);
    }


    protected function add_tohtml($html, $cssorjs, $exluded, $cachedlinks, $js=false)
    {
        foreach ($exluded as $key => $value) {
            if (!empty($value)) {
                $find_escluded = preg_grep("#" . $value . "#", $cachedlinks);
                if (is_array($find_escluded)) {
                    $find_escluded = array_unique($find_escluded);
                }
                foreach ($find_escluded as $f => $esc) {
                        $cssorjs .= $esc . PHP_EOL;
                }
            }
        }

        if($js && $this->params->append_js == 2){ //js to variables
            $this->js_inbody = $cssorjs;
        }
        else {
            $cssorjs .= '</head>'.PHP_EOL;
            $html = preg_replace("#<\s*/\s*head>\s*$#", $cssorjs, $html);
        }
        return $html;
    }

    protected function replacecss_inhtml($code)
    {

        if (!isset($code[1]))
            return;
        $minify = $this->replace_unused_incss($code[1], array("@<br*>(.*)<br*>@isU"), array(""));
        file_put_contents(JPATH_BASE . "/cache/" . $this->params->cachegroup . "/" . $this->cache_id . ".css", $minify, FILE_APPEND);
    }

    protected function replacejs_inhtml($code)
    {
        if (!isset($code[1]))
            return;
        $minify = $this->replace_unused_injs($code[1]);
        file_put_contents(JPATH_BASE . "/cache/" . $this->params->cachegroup . "/" . $this->cache_id . ".js", $minify. PHP_EOL, FILE_APPEND);
    }


    protected function add_csstohtml($html, $cachedlinks)
    {
        $css = "<style>" . PHP_EOL;
        $css .= file_get_contents(JPATH_BASE . "/cache/" . $this->params->cachegroup . "/" . $this->cache_id . "-head.css") . PHP_EOL;
        $css .= "</style>" . PHP_EOL;
        //$css = '<link rel="stylesheet" type="text/css" href="' . Uri::base() . "cache/" . $this->params->cachegroup . "/" . $this->cache_id . '-head.css' . $this->params->query_test . '"  />' . PHP_EOL;
        $css .= '<link rel="preload" as="style" onload="this.onload=null;this.rel=\'stylesheet\'" href="' . Uri::base() . "cache/" . $this->params->cachegroup . "/" . $this->cache_id . '.css' . $this->params->query_test . '"  />' . PHP_EOL;
        $css .= '<noscript><link rel="stylesheet" href="' . Uri::base() . "cache/" . $this->params->cachegroup . "/" . $this->cache_id . '.css' . $this->params->query_test . '"></noscript>' . PHP_EOL;
        $css .= '<link rel="preload" as="style" onload="this.onload=null;this.rel=\'stylesheet\'" href="/media/system/css/joomla-fontawesome.min.css"  />' . PHP_EOL;
        $css .= '<noscript><link rel="stylesheet" href="/media/system/css/joomla-fontawesome.min.css"></noscript>' . PHP_EOL;
        return $this->add_tohtml($html, $css, $this->params->escludecss, $cachedlinks,false);
    }

    protected function add_jstohtml($html, $cachedlinks)
    {
        $js = PHP_EOL;
        $js .= '<script src="' . Uri::base() . "cache/" . $this->params->cachegroup . "/" . $this->cache_id . '.js' . $this->params->query_test . '" type="text/javascript" async ></script>' . PHP_EOL;
        return $this->add_tohtml($html, $js, $this->params->escludejs, $cachedlinks,true);
    }

    protected function get_params()
    {
        $app = Factory::getApplication();
        $template = $app->getTemplate(true);
        $return = new \stdClass();
        $return->optimizecss = $template->params->get('optimize_css', false);
        $return->mode = $template->params->get('yrminifier_mode', false);
        $return->escludecss = $template->params->get('yrminifiercss_esclude', array(null));
        $return->append_js = $template->params->get('append_js', 1); //1 in head 2 in body
        $return->escludeonmenu = $template->params->get('yrminifier_escludeonmenu', array());
        $return->escludeoncomponents = $template->params->get('yrminifiercomponents_esclude', array());
        $return->template_css = 'templates/' . $template->template . "/css/layouts/" . $template->template . "-" . $template->id . '.css';;
        $return->escludecss_write[] = 'media/system/css/joomla-fontawesome.min.css';

        $return->css_tohead = array();
        $return->css_tohead[] = 'templates/' . $template->template . "/css/bootstrap.min.css";
        $return->css_tohead[] = 'templates/' . $template->template . "/css/template.css";
        $return->css_tohead[] = $return->template_css;
        $return->css_tohead[] = 'plugins/content/yrvote/incl/css.css';

        $return->optimizejs = $template->params->get('optimize_js', false);
        $return->escludejs = $template->params->get('yrminifierjs_esclude', array(null));
        $tname = $template->params->get('name', false);
        $return->cachegroup = $tname . "_template";
        $return->query_test = ((int)$return->mode == 1) ? "?" . md5(date("Y-m-d h:i:s")) : "";
        return $return;
    }
//        protected function check_create_folder($folder){
//            if (!file_exists(JPATH_BASE . "/cache/" . $folder)) {
//                mkdir(JPATH_BASE . "/cache/" . $folder, 0755, true);
//            }
//        }

}
