<?php
/**
 * @package         CDN for Joomla!
 * @version         7.3.6PRO
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            https://regularlabs.com
 * @copyright       Copyright © 2025 Regular Labs All Rights Reserved
 * @license         GNU General Public License version 2 or later
 */

namespace RegularLabs\Plugin\System\CDNForJoomla;

defined('_JEXEC') or die;

use Joomla\CMS\Uri\Uri as JUri;
use RegularLabs\Library\ArrayHelper as RL_Array;
use RegularLabs\Library\Document as RL_Document;
use RegularLabs\Library\Parameters as RL_Parameters;
use RegularLabs\Library\RegEx as RL_RegEx;

class Params
{
    protected static $domain = null;
    protected static $params = null;
    protected static $sets   = null;

    public static function get()
    {
        if ( ! is_null(self::$params))
        {
            return self::$params;
        }

        self::$params = RL_Parameters::getPlugin('cdnforjoomla');

        return self::$params;
    }

    public static function getFileTypes($filetypes)
    {
        return explode(',',
            str_replace(
                ["\n", '\n', ' ', ',.'],
                [',', ',', '', ','],
                trim($filetypes)
            )
        );
    }

    public static function getProtocol($protocol = 'same', $use_relative_protocol = true)
    {
        // Enable HTTPS is switched off
        if ($protocol == 'http' && RL_Document::isHttps())
        {
            return 'http://';
        }

        // Enable HTTPS is forced
        if ($protocol == 'https' && ! RL_Document::isHttps())
        {
            return 'https://';
        }

        if ($use_relative_protocol)
        {
            return '//';
        }

        return RL_Document::isHttps() ? 'https://' : 'http://';
    }

    public static function getSets()
    {
        if ( ! is_null(self::$sets))
        {
            return self::$sets;
        }

        $params = self::get();

        self::$sets = [];

        $sets_params = RL_Array::toArray($params->cdns ?? []);

        foreach ($sets_params as $set_params)
        {
            $set = self::getSet($set_params);

            if ( ! $set)
            {
                continue;
            }

            self::$sets[] = $set;
        }

        return self::$sets;
    }

    public static function passDomains($domains)
    {
        if (empty($domains))
        {
            return false;
        }

        $domains = RL_Array::toArray($domains);
        $domain  = self::getDomain();

        return in_array($domain->fulldomain, $domains);
    }

    public static function passProtocol($protocol)
    {
        // Not enabled for HTTPS
        if ($protocol == 'http' && RL_Document::isHttps())
        {
            return false;
        }

        // Not enabled for HTTP
        if ($protocol == 'https' && ! RL_Document::isHttps())
        {
            return false;
        }

        return true;
    }

    private static function getCdnPaths($cdn)
    {
        $cdns = explode(',', $cdn);

        $paths = [];

        foreach ($cdns as $i => $cdn)
        {
            if (empty($cdn))
            {
                continue;
            }

            $cdn = RL_RegEx::replace('^.*\://', '', trim($cdn));
            self::replaceDomainVars($cdn);

            if (empty($cdn))
            {
                continue;
            }

            $paths[] = $cdn;
        }

        return $paths;
    }

    private static function getDomain()
    {
        if ( ! is_null(self::$domain))
        {
            return self::$domain;
        }

        $domain = (object) [];

        $domain->fulldomain = JUri::getInstance()->getHost();
        $domain->subdomain  = '';
        $domain->domain     = $domain->fulldomain;
        $domain->extension  = '';

        $parts = array_reverse(explode('.', $domain->fulldomain));

        if (count($parts) < 2)
        {
            return $domain;
        }

        $domain->extension = array_shift($parts);

        if (in_array($parts[0], self::getSlds()))
        {
            $domain->extension = array_shift($parts) . '.' . $domain->extension;
        }

        $subs = 0;

        while ( ! empty($parts))
        {
            $domain->{str_repeat('sub', $subs++) . 'domain'} = array_shift($parts);
        }

        $domain->subdomain = $domain->subdomain ?: 'www';

        self::$domain = $domain;

        return self::$domain;
    }

    private static function getDomainVar($match)
    {
        $domain = self::getDomain();

        if (isset($domain->{$match[2]}) && $domain->{$match[2]})
        {
            return $match[1] . $domain->{$match[2]} . $match[3];
        }

        if ($match[1] && $match[3])
        {
            return '.';
        }

        return '';
    }

    /**
     * Searches are replaced by:
     * '\1http(s)://' . $this->params->cdn . '/\3\4'
     * \2 is used to reference the possible starting quote
     */
    private static function getFiletypeSearches($settings)
    {
        if (empty($settings->filetypes))
        {
            return [];
        }

        $url = self::getUrlRegex($settings->filetypes, $settings->root);

        return self::getSearchesByUrl($url);
    }

    private static function getUrlRegexBySetting($settings)
    {
        if (empty($settings->filetypes))
        {
            return [];
        }

        $url = self::getUrlRegex($settings->filetypes, $settings->root);

        $url_regex                 = str_replace('LSLASH', '/?', $url);
        $url_regex_no_spaces       = '\s*' . str_replace('QUOTES', '"\'', $url_regex) . '\s*';
        $url_regex_can_have_spaces = str_replace('[^ ', '[^', $url_regex_no_spaces);

        return $url_regex_can_have_spaces;
    }

    /**
     * Searches are replaced by:
     * '\1http(s)://' . $this->params->cdn . '/\3\4'
     * \2 is used to reference the possible starting quote
     */
    private static function getFiletypeSearchesJavascript($settings)
    {
        if (empty($settings->filetypes))
        {
            return [];
        }

        $url = self::getUrlRegex($settings->filetypes, $settings->root);

        return self::getSearchesJavascriptByUrl($url);
    }

    private static function getSearchTagAttributes()
    {
        $attributes = [
            'href=',
            'src=',
            'srcset=',
            'data-[a-z0-9-_]+=',
            'longdesc=',
            'poster=',
            '@import',
            'name="movie" value=',
            'property="[a-z]+:image(?:\:secure_url)?" content=',
            'itemprop="image" content=',
            'TileImage" content=',
            'rel="{\'link\':',
        ];

        return str_replace(['"', '=', ' '], ['["\']?', '\s*=\s*', '\s+'], implode('|', $attributes));
    }

    private static function getSearchesByUrl($url)
    {
        $tag_attribs = self::getSearchTagAttributes();

        $url_regex = str_replace('LSLASH', '/?', $url);

        $url_regex_no_spaces       = '\s*' . str_replace('QUOTES', '"\'', $url_regex) . '\s*';
        $url_regex_no_parentheses  = '\s*' . str_replace('QUOTES', '"\'\)', $url_regex) . '\s*';
        $url_regex_can_have_spaces = str_replace('[^ ', '[^', $url_regex_no_spaces);

        $searches = [];

        // attrib="..."
        $searches[] = '(?<prefix>(?:' . $tag_attribs . ')\s*(?<quote>["\']))' . $url_regex_can_have_spaces . '(?<end>(?: [^"\']*)?\2)';
        // attrib=...
        $searches[] = '(?<prefix>(?:' . $tag_attribs . ')(?<quote>))' . $url_regex_no_spaces . '(?<end>[\s|>])';
        // url("...")
        $searches[] = '(?<prefix>url\(\s*(?<quote>["\']))' . $url_regex_can_have_spaces . '(?<end>\2\s*[,\)])';
        // url(...)
        $searches[] = '(?<prefix>url\(\s*(?<quote>))' . $url_regex_no_parentheses . '(?<end>\s*\))'; // url(...)
        // load...("...")
        $searches[] = '(?<prefix>load[a-z]*\(\s*(?<quote>["\']))' . $url_regex_can_have_spaces . '(?<end>\2\s*[,\)])';
        // load...(...)
        $searches[] = '(?<prefix>load[a-z]*\(\s*(?<quote>))' . $url_regex_no_parentheses . '(?<end>\s*[,\)])';
        // "image" : "..."
        $searches[] = '(?<prefix>(?<quote>["\'])image\2\s*:\s*\2)' . $url_regex_can_have_spaces . '(?<end>\2)';

        return $searches;
    }

    private static function getSearchesJavascriptByUrl($url)
    {
        $url_regex = '\s*' . str_replace('QUOTES', '"\'', $url) . '\s*';
        $url_regex = str_replace('LSLASH', '', $url_regex);
        $url_regex = str_replace('/|)(?<root>\/?)', ')(?<root>\/)', $url_regex);

        $searches = [];

        $searches[] = '((?<prefix>["\']))' . $url_regex . '(?<end>["\'])'; // "..."

        return $searches;
    }

    private static function getSet($params)
    {
        if (empty($params))
        {
            return false;
        }

        if (($params->domains ?? '') === 'specific' && ! self::passDomains($params->domains_list ?? []))
        {
            return false;
        }

        if ( ! self::passProtocol($params->web_protocol ?? ''))
        {
            return false;
        }

        $filetypes = self::getFileTypes($params->filetypes ?? '');

        if (empty($filetypes))
        {
            return false;
        }

        $set = (object) [];

        $set->cdn = rtrim($params->cdn ?? '', '/');

        if (empty($set->cdn))
        {
            return false;
        }

        $set->protocol = self::getProtocol($params->protocol ?? '', $params->use_relative_protocol ?? true);

        $set->filetypes         = $filetypes;
        $set->ignorefiles       = self::getFileTypes($params->ignorefiles ?? []);
        $set->enable_in_scripts = $params->enable_in_scripts ?? true;

        $set->enable_versioning    = $params->enable_versioning ?? false;
        $set->versioning_filetypes = self::getFileTypes($params->versioning_filetypes ?? []);

        $set->root = trim($params->root, '/');

        $set->url_regex   = self::getUrlRegexBySetting($set);
        $set->searches    = self::getFiletypeSearches($set);
        $set->js_searches = self::getFiletypeSearchesJavascript($set);
        $set->cdns        = self::getCdnPaths($set->cdn);

        return $set;
    }

    private static function getSlds()
    {
        return [
            'a', 'ab', 'abo', 'ac', 'ad', 'adm', 'adv', 'aero', 'agr', 'ah', 'alt', 'am', 'amur', 'arq', 'art', 'arts', 'asn', 'assn', 'asso', 'ato', 'av',
            'b', 'bbs', 'bc', 'bd', 'bel', 'bio', 'bir', 'biz', 'bj', 'bl', 'blog', 'bmd', 'c', 'cat', 'cbg', 'cc', 'chel', 'cim', 'city', 'ck', 'club', 'cn',
            'cng', 'cnt', 'co', 'com', 'conf', 'coop', 'cq', 'cr', 'cri', 'cv', 'cym', 'd', 'db', 'de', 'dn', 'dni', 'dp', 'dr', 'du', 'e', 'ebiz', 'ecn', 'ed',
            'edu', 'eng', 'ens', 'es', 'esp', 'est', 'et', 'etc', 'eti', 'eun', 'f', 'fam', 'far', 'fed', 'fi', 'fin', 'firm', 'fj', 'flog', 'fm', 'fnd', 'fot',
            'fr', 'fs', 'fst', 'g', 'g12', 'game', 'gd', 'gda', 'geek', 'gen', 'ggf', 'go', 'gob', 'gok', 'gon', 'gop', 'gos', 'gouv', 'gov', 'govt', 'gp',
            'gr', 'grp', 'gs', 'gub', 'gv', 'gx', 'gz', 'h', 'ha', 'hb', 'he', 'hi', 'hl', 'hn', 'hs', 'i', 'id', 'idf', 'idn', 'idv', 'if', 'imb', 'imt', 'in',
            'inca', 'ind', 'inf', 'info', 'ing', 'int', 'intl', 'iq', 'ir', 'isa', 'isla', 'it', 'its', 'iwi', 'jar', 'jeju', 'jet', 'jl', 'jobs', 'jor', 'js',
            'jus', 'jx', 'k', 'k12', 'kchr', 'kg', 'kh', 'khv', 'kids', 'kiev', 'km', 'komi', 'kr', 'ks', 'kv', 'kzn', 'l', 'law', 'lea', 'lel', 'lg', 'ln',
            'lodz', 'lp', 'ltd', 'lviv', 'm', 'ma', 'mari', 'mat', 'mb', 'me', 'med', 'mi', 'mil', 'mk', 'mob', 'mobi', 'mod', 'mpm', 'ms', 'msk', 'muni',
            'mus', 'n', 'name', 'nat', 'nb', 'ne', 'nel', 'net', 'news', 'nf', 'ngo', 'nhs', 'nic', 'nis', 'nl', 'nls', 'nm', 'nnov', 'nom', 'nome', 'not',
            'nov', 'ns', 'nsk', 'nsn', 'nt', 'ntr', 'nu', 'nw', 'nx', 'o', 'od', 'odo', 'og', 'om', 'omsk', 'on', 'or', 'org', 'orgn', 'ov', 'p', 'pb', 'pe',
            'per', 'perm', 'pix', 'pl', 'plc', 'plo', 'pol', 'pp', 'ppg', 'prd', 'priv', 'pro', 'prof', 'psc', 'psi', 'ptz', 'pub', 'publ', 'pwr', 'qc', 'qh',
            'qsl', 'r', 're', 'rec', 'red', 'res', 'rg', 'rnd', 'rnrt', 'rns', 'rnu', 'rs', 'rv', 's', 'sa', 'sc', 'sch', 'sci', 'scot', 'sd', 'sec', 'sh',
            'sk', 'sld', 'slg', 'sn', 'soc', 'spb', 'srv', 'stv', 'sumy', 'sx', 't', 'te', 'tel', 'test', 'tj', 'tm', 'tmp', 'tom', 'trd', 'tsk', 'tula', 'tur',
            'tuva', 'tv', 'tver', 'tw', 'u', 'udm', 'unbi', 'univ', 'unmo', 'unsa', 'untz', 'unze', 'vet', 'vlog', 'vn', 'vrn', 'w', 'war', 'waw', 'web',
            'wiki', 'wroc', 'www', 'x', 'xj', 'xz', 'y', 'yk', 'yn', 'z', 'zj', 'zlg', 'zp', 'zt',
        ];
    }

    /**
     * Searches are replaced by:
     * '\1http(s)://' . [cdn] . '/\3\4'
     * \2 is used to reference the possible starting quote
     */
    private static function getUrlRegex($filetypes, $root)
    {
        // Domain url or root path
        $roots = [
            str_replace(['http\\://', 'https\\://'], '(?:https?\:)?//', RL_RegEx::quote(JUri::root())),
        ];

        if (JUri::root(1))
        {
            $roots[] = RL_RegEx::quote(JUri::root(1) . '/');
        }

        $roots[] = 'LSLASH';

        $filetypes = implode('|', $filetypes);
        $root      = RL_RegEx::quote($root);

        return
            '(?:' . implode('|', $roots) . ')(?<root>' . $root . '\/?)'
            . '(?<url>[a-z0-9-_]+(?:/[^ \?QUOTES]+|[^ \?\/QUOTES]+)\.(?:' . $filetypes . ')(?:\?[^QUOTES]*)?(?:\#[^QUOTES]*)?)';
    }

    private static function replaceDomainVars(&$cdn)
    {
        RL_RegEx::matchAll('(\.?)\{([^\}]*)\}(\.?)', $cdn, $matches);

        if (empty($matches))
        {
            return;
        }

        foreach ($matches as $match)
        {
            $cdn = str_replace($match[0], self::getDomainVar($match), $cdn);
        }
    }
}
