Current File : /home/pacjaorg/public_html/kmm/libraries/regularlabs/src/StringReplacer.php
<?php
/**
 * @package         Regular Labs Library
 * @version         23.9.3039
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            https://regularlabs.com
 * @copyright       Copyright © 2023 Regular Labs All Rights Reserved
 * @license         GNU General Public License version 2 or later
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

/**
 * Class StringReplacer
 * Handles string replacement operations with the ability to exclude certain parts of the string
 */
class StringReplacer
{
    private bool  $enable_clean = true;
    private array $parts        = [];

    public function __construct(string $string = '')
    {
        $this->set($string ?? '');
    }

    public function clean(): self
    {
        $this->enable_clean = true;
        $this->cleanParts();

        return $this;
    }

    public function contains(string $string): bool
    {
        return strpos($this->toString(), $string) !== false;
    }

    public function disableCleaning(): self
    {
        $this->enable_clean = false;

        return $this;
    }

    public function excludeExceptHtmlTags(array $tags = ['*']): self
    {
        $regex = $this->getHtmlTagsRegex();

        $this->excludeExceptRegex($regex);

        if (in_array('*', $tags))
        {
            return $this;
        }

        return $this->excludeHtmlTags($tags);
    }

    public function excludeExceptRegex(string $regex): self
    {
        $all_parts = [];

        foreach ($this->parts as $key => &$string)
        {
            if ($this->shouldSkip($key, $string))
            {
                $all_parts[] = $string;

                continue;
            }

            $parts = RegEx::split($regex, $string);

            $parts = ['', ...$parts, ''];

            array_push($all_parts, ...$parts);
        }

        $this->setParts($all_parts);

        return $this;
    }

    public function excludeExceptStrings(array $strings = []): self
    {
        $regex = RegEx::quote($strings);

        return $this->excludeExceptRegex($regex);
    }

    public function excludeForm(array $form_classes = []): self
    {
        // Exclude the complete adminForm (to prevent replacements messing stuff up when editing articles and such)
        $regexes = $this->getFormRegexes($form_classes);

        return $this->excludeRegexBetween($regexes->start, $regexes->end, true);
    }

    public function excludeHtmlTags(array $except_tags = []): self
    {
        $regex = $this->getHtmlTagsRegex();

        if (in_array('*', $except_tags))
        {
            return $this;
        }

        $this->disableCleaning();

        $this->excludeRegex($regex);

        if (empty($except_tags))
        {
            $this->clean();

            return $this;
        }

        $this->includeHtmlTags($except_tags);

        $this->clean();

        return $this;
    }

    public function excludeOutsideStrings(string $start, string $end, $exclude_strings = false): self
    {
        if ($start == '' && $end == '')
        {
            return $this;
        }

        $start = $start ?: '^';
        $end   = $end ?: '$';

        $regex = $exclude_strings
            ? '()(' . RegEx::quote($start) . ')(.*?)(' . RegEx::quote($end) . ')()'
            : '(' . RegEx::quote($start) . '.*?' . RegEx::quote($end) . ')';

        return $this->excludeExceptRegex($regex);
    }

    public function excludeRegex(string $regex): self
    {
        $all_parts = [];

        foreach ($this->parts as $key => &$string)
        {
            if ($this->shouldSkip($key, $string))
            {
                $all_parts[] = $string;

                continue;
            }

            $parts = RegEx::split($regex, $string);

            if (empty($parts))
            {
                $all_parts[] = $string;

                continue;
            }

            array_push($all_parts, ...$parts);
        }

        $this->setParts($all_parts);

        return $this;
    }

    public function excludeRegexBetween(string $start, string $end, $exclude_matches = false): self
    {
        $all_parts = [];

        foreach ($this->parts as $key => &$string)
        {
            if ($this->shouldSkip($key, $string))
            {
                $all_parts[] = $string;

                continue;
            }

            $start_parts = RegEx::split($start, $string);

            if (count($start_parts) < 2)
            {
                $all_parts[] = $string;

                continue;
            }

            $first_part = array_shift($start_parts);

            if ( ! $exclude_matches)
            {
                $first_part .= array_shift($start_parts);
            }

            $search_part = implode($start_parts);

            $end_parts = (new StringReplacer($search_part))->excludeRegex($end)->getParts();

            if (count($end_parts) < 2)
            {
                $all_parts[] = $string;

                continue;
            }

            $protected_part = array_shift($end_parts);

            if ($exclude_matches)
            {
                $protected_part .= array_shift($end_parts);
            }

            $last_part = implode($end_parts);

            array_push($all_parts, $first_part, $protected_part, $last_part);
        }

        $this->setParts($all_parts);

        return $this;
    }

    public function excludeRegexNested(string $regex_outer, string $regex_inner): self
    {
        $all_parts = [];

        foreach ($this->parts as $key => $string)
        {
            if (trim($string) == '' || $this->rowIsExcluded($key))
            {
                $all_parts[] = $string;
                continue;
            }

            if ( ! RegEx::match($regex_outer, $string)
                || ! RegEx::match($regex_inner, $string)
            )
            {
                $all_parts[] = $string;
                continue;
            }

            $nested = (new StringReplacer($string))->excludeRegex($regex_inner);

            array_push($all_parts, ...$nested->getParts());
        }

        $this->setParts($all_parts);

        return $this;
    }

    public function excludeStrings(array $strings = []): self
    {
        $regex = RegEx::quote($strings);

        return $this->excludeRegex($regex);
    }

    public function getHtmlTagsRegex(): string
    {
        return '(</?[a-zA-Z][^>]*>)';
    }

    public function getParts(): array
    {
        return $this->parts;
    }

    public function includeRegex(string $regex): self
    {
        $all_parts = [];

        foreach ($this->parts as $key => $string)
        {
            if (trim($string) == '' || ! $this->rowIsExcluded($key))
            {
                $all_parts[] = $string;
                continue;
            }

            if ( ! RegEx::match($regex, $string))
            {
                $all_parts[] = $string;
                continue;
            }

            $parts = RegEx::split($regex, $string);

            array_push($all_parts, ...$parts);
        }

        $this->setParts($all_parts);

        return $this;
    }

    public function includeRegexNested(string $regex_outer, string $regex_inner): self
    {
        $all_parts = [];

        foreach ($this->parts as $key => $string)
        {
            if (trim($string) == '' || ! $this->rowIsExcluded($key))
            {
                $all_parts[] = $string;
                continue;
            }

            if ( ! RegEx::match($regex_outer, $string)
                || ! RegEx::match($regex_inner, $string)
            )
            {
                $all_parts[] = $string;
                continue;
            }

            // using exclude on this excluded row to get the reverse result
            $nested = (new StringReplacer($string))->excludeRegex($regex_inner);

            array_push($all_parts, ...$nested->getParts());
        }

        $this->setParts($all_parts);

        return $this;
    }

    public function replace($search, $replace): self
    {
        foreach ($this->parts as $key => &$string)
        {
            if ($this->shouldSkip($key, $string))
            {
                continue;
            }

            $string = str_replace($search, $replace, $string);
        }

        return $this;
    }

    public function replaceRegex(string $search, string $replace): self
    {
        foreach ($this->parts as $key => &$string)
        {
            if ($this->shouldSkip($key, $string))
            {
                continue;
            }

            $string = RegEx::replace($search, $replace, $string);
        }

        return $this;
    }

    public function run($callback, $on_excluded = false): self
    {
        foreach ($this->parts as $key => &$string)
        {
            if (
                trim($string) == ''
                || ($this->rowIsExcluded($key) && ! $on_excluded)
            )
            {
                continue;
            }

            $callback($string);
        }

        $this->flattenParts();

        return $this;
    }

    public function set(string $string): void
    {
        $this->parts = [$string];
    }

    public function stillContains(string $string): bool
    {
        foreach ($this->parts as $key => $value)
        {
            if (trim($value) == '' || $this->rowIsExcluded($key))
            {
                continue;
            }

            if (strpos($this->toString(), $string) !== false)
            {
                return true;
            }
        }

        return false;
    }

    public function toString(): string
    {
        return implode('', $this->parts);
    }

    private static function includeHtmlTagsOnString(string &$string, array $tags): void
    {
        $replacer = new StringReplacer($string);

        foreach ($tags as $tag_name => $tag_params)
        {
            self::includeSingleHtmlTag($replacer, $tag_name, $tag_params);
        }

        $string = $replacer->getParts();
    }

    private static function includeSingleHtmlTag(StringReplacer &$replacer, $tag_name, $tag_params): void
    {
        if ($tag_name == '*')
        {
            $tag_name = '[a-zA-Z][^> ]*';
        }

        $regex_tag = '(</?' . $tag_name . '(?: [^>]*)?>)';

        if ( ! count($tag_params))
        {
            // include the whole tag (exclude on an excluded row)
            $replacer->excludeRegex($regex_tag);

            return;
        }

        // only include the parameter values
        $regex_params = '()(' . RegEx::quote($tag_params) . '=")([^"]*)';
        $replacer->excludeRegexNested($regex_tag, $regex_params);
    }

    private function cleanParts(): void
    {
        if ( ! $this->enable_clean)
        {
            return;
        }

        $delimiter   = '<!-- ___RL_DELIMITER___ -->';
        $temp_string = implode($delimiter, $this->parts);
        $temp_string = str_replace($delimiter . $delimiter, '', $temp_string);
        $this->parts = explode($delimiter, $temp_string);
    }

    private function flattenParts(): void
    {
        // move any nested parts to the parent
        $all_parts = [];

        foreach ($this->parts as $string)
        {
            if ( ! is_array($string))
            {
                $all_parts[] = $string;
                continue;
            }

            array_push($all_parts, ...$string);
        }

        $this->setParts($all_parts);
    }

    private function getFormRegexes(array $form_classes = []): object
    {
        $form_classes = ArrayHelper::toArray($form_classes);

        $start = '(<form\s[^>]*(?:'
            . '(?:id|name)="(?:adminForm|postform|submissionForm|default_action_user|seblod_form|spEntryForm)"'
            . '|action="[^"]*option=com_myjspace&(?:amp;)?view=see"'
            . (! empty($form_classes) ? '|class="(?:[^"]* )?(?:' . implode('|', $form_classes) . ')(?: [^"]*)?"' : '')
            . '))';
        $end   = '(</form>)';

        return (object) compact('start', 'end');
    }

    private function getHtmlTagArray(array $tags = []): array
    {
        $search_tags = [];

        foreach ($tags as $tag)
        {
            if ( ! strlen($tag))
            {
                continue;
            }

            $tag       = trim($tag, ']');
            $tag_parts = explode('[', $tag);
            $tag_name  = trim($tag_parts[0]);

            if ($tag_name == '*')
            {
                return [];
            }

            if (count($tag_parts) < 2)
            {
                $search_tags[$tag_name] = [];
                continue;
            }

            $tag_params = $tag_parts[1];
            // Trim and remove empty values
            $tag_params = array_diff(array_map('trim', explode(',', $tag_params)), ['']);

            if (in_array('*', $tag_params))
            {
                // Make array empty if asterisk is found
                // (the whole tag should be allowed)
                $search_tags[$tag_name] = [];
                continue;
            }

            $search_tags[$tag_name] = $tag_params;
        }

        return $search_tags;
    }

    private function includeHtmlTags(array $tags = []): void
    {
        $tags = $this->getHtmlTagArray($tags);

        if ( ! count($tags))
        {
            return;
        }

        $this->run(function (&$string) use ($tags) {
            self::includeHtmlTagsOnString($string, $tags);
        }, true);
    }

    private function rowIsExcluded(int $key): bool
    {
        // uneven count = excluded
        return fmod($key, 2);
    }

    private function setParts(array $parts): void
    {
        $this->parts = $parts;
        $this->cleanParts();
    }

    private function shouldSkip(int $key, string $string): bool
    {
        return (trim($string) == '' || $this->rowIsExcluded($key));
    }
}
Site is undergoing maintenance

PACJA Events

Maintenance mode is on

Site will be available soon. Thank you for your patience!