Your IP : 216.73.216.190


Current Path : /proc/1908984/root/proc/2603263/cwd/
Upload File :
Current File : //proc/1908984/root/proc/2603263/cwd/Helpers.tar

advancedmodulehelper.php000064400000051075152355114540011445 0ustar00<?php
/**
 * @package         Advanced Module Manager
 * @version         9.8.0PRO
 * 
 * @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
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Log\Log as JLog;
use RegularLabs\Library\ArrayHelper as RL_Array;
use RegularLabs\Library\Conditions as RL_Conditions;
use RegularLabs\Library\Document as RL_Document;
use RegularLabs\Library\ParametersNew as RL_Parameters;
use RegularLabs\Library\PluginTag as RL_PluginTag;
use RegularLabs\Library\RegEx as RL_RegEx;
use RegularLabs\Plugin\System\AdvancedModules\Params;

jimport('joomla.filesystem.file');

/*
 * ModuleHelper methods
 */

class AdvancedModules
{
    static $current_module = null;

    public static function getCurrentModule()
    {
        return self::$current_module;
    }

    public static function setCurrentModule($module)
    {
        self::$current_module = $module;
    }
}

class PlgSystemAdvancedModuleHelper
{
    var $mirror_ids = [];

    public function onPrepareModuleList(&$modules)
    {
        // return if is not frontend
        if ( ! RL_Document::isClient('site'))
        {
            return;
        }

        jimport('joomla.filesystem.file');

        $modules = is_null($modules) ? $this->getModuleList() : $modules;

        if (is_array($modules) && empty($modules))
        {
            return;
        }

        [$remove_positions, $remove_modules] = $this->getRemovePositionsAndModules();

        $filtered_modules = [];

        foreach ($modules as $module)
        {
            if (empty($module->id))
            {
                continue;
            }

            if (
                in_array($module->position, $remove_positions)
                || in_array($module->id, $remove_modules)
                || in_array($module->module, $remove_modules)
                || in_array($module->title, $remove_modules)
            )
            {
                continue;
            }

            $module->name = substr($module->module, 4);

            if (JFactory::getApplication()->input->get('option') == 'com_ajax')
            {
                $filtered_modules[] = $module;
                continue;
            }

            $this->setAdvancedParams($module);

            if ($module->advancedparams === 0)
            {
                if (isset($module->published) && ! $module->published)
                {
                    continue;
                }

                $filtered_modules[] = $module;

                continue;
            }

            $this->setExtraParams($module);

            $module->reverse = 0;

            $this->setMirrorParams($module);

            $this->removeDisabledAssignments($module->advancedparams);

            AdvancedModules::setCurrentModule($module);

            $assignments       = RL_Conditions::getConditionsFromParams($module->advancedparams);
            $module->published = RL_Conditions::pass(
                $assignments,
                $module->advancedparams->match_method,
                null,
                $module
            );

            if ($module->reverse)
            {
                $module->published = ! $module->published;
            }

            if (isset($module->published) && ! $module->published)
            {
                continue;
            }

            $filtered_modules[] = $module;
        }

        $modules = array_values($filtered_modules);
        unset($filtered_modules);
    }

    public function onRenderModule(&$module)
    {
        // Module already nulled
        if (is_null($module))
        {
            return;
        }

        // Do nothing if is not frontend
        if ( ! RL_Document::isClient('site'))
        {
            return;
        }

        // return true if module is empty (this will empty the content)
        if ($this->isEmpty($module))
        {
            $module = null;

            return;
        }

        // Add pre and post html
        $this->addHTML($module);
    }

    private function addHTML(&$module)
    {
        $this->setAdvancedParams($module);

        // return false if module params are not found
        if (empty($module->advancedparams))
        {
            return;
        }

        $params = $module->advancedparams;

        // do nothing if no advanced params are set
        if (empty($params))
        {
            return;
        }

        // prepend the pre html
        if (isset($params->pre_html) && $params->pre_html)
        {
            $module->content = $params->pre_html . $module->content;
        }

        // append the post html
        if (isset($params->post_html) && $params->post_html)
        {
            $module->content .= $params->post_html;
        }
    }

    private function getAdvancedParams($module)
    {
        if (empty($module->id))
        {
            return '{}';
        }

        if (isset($module->adv_params))
        {
            return $module->adv_params;
        }

        return $this->getAdvancedParamsById($module->id);
    }

    private function getAdvancedParamsById($id = 0)
    {
        if ( ! $id)
        {
            return '{}';
        }

        $db    = JFactory::getDbo();
        $query = $db->getQuery(true)
            ->select('a.params')
            ->from('#__advancedmodules AS a')
            ->where('a.moduleid = ' . (int) $id);
        $db->setQuery($query);

        $params = $db->loadResult();

        if (empty($params))
        {
            $params = '{}';
        }

        return $params;
    }

    private function getConfig()
    {
        static $instance;

        if (is_object($instance))
        {
            return $instance;
        }

        $instance = RL_Parameters::getComponent('advancedmodules');

        return $instance;
    }

    private function getMirrorModuleId($module)
    {
        if (isset($module->mirror_id))
        {
            return $module->mirror_id;
        }

        return $this->getMirrorModuleIdById($module->id);
    }

    private function getMirrorModuleIdById($id)
    {
        if (isset($this->mirror_ids[$id]))
        {
            return $this->mirror_ids[$id];
        }

        $db    = JFactory::getDbo();
        $query = $db->getQuery(true)
            ->select('a.mirror_id')
            ->from('#__advancedmodules AS a')
            ->where('a.moduleid = ' . (int) $id);
        $db->setQuery($query);

        $this->mirror_ids[$id] = $db->loadResult();

        return $this->mirror_ids[$id];
    }

    private function getModuleList()
    {
        $app      = JFactory::getApplication();
        $user     = JFactory::getApplication()->getIdentity() ?: JFactory::getUser();
        $groups   = implode(',', $user->getAuthorisedViewLevels());
        $lang     = JFactory::getLanguage()->getTag();
        $clientId = (int) $app->getClientId();

        $db = JFactory::getDbo();

        $query = $db->getQuery(true)
            ->select('m.id, m.title, m.module, m.position, m.content, m.showtitle, m.params')
            ->select('am.mirror_id, am.params AS advancedparams, 0 AS menuid, m.publish_up, m.publish_down')
            ->from('#__modules AS m')
            ->join('LEFT', '#__extensions AS e ON e.element = m.module AND e.client_id = m.client_id')
            ->join('LEFT', '#__advancedmodules as am ON am.moduleid = m.id')
            ->where('m.published = 1')
            ->where('e.enabled = 1')
            ->where('m.access IN (' . $groups . ')')
            ->where('m.client_id = ' . $clientId);

        // Filter by language
        if ($app->isClient('site') && $app->getLanguageFilter())
        {
            $query->where('m.language IN (' . $db->quote($lang) . ',' . $db->quote('*') . ')');
        }

        $query->order('m.position, m.ordering');

        // Set the query
        $db->setQuery($query);

        try
        {
            $modules = $db->loadObjectList();
        }
        catch (RuntimeException $e)
        {
            JLog::add(JText::sprintf('JLIB_APPLICATION_ERROR_MODULE_LOAD', $e->getMessage()), JLog::WARNING, 'jerror');

            return [];
        }

        return array_values($modules);
    }

    private function getRemovePositionsAndModules()
    {
        if ( ! $buffer = RL_Document::getComponentBuffer())
        {
            return [[], []];
        }

        $params = Params::get();

        $remove_positions = [];
        $remove_modules   = [];

        RL_RegEx::matchAll(Params::getRegex(), $buffer, $matches);

        foreach ($matches as $match)
        {
            // Get the values from the tag
            $attributes = RL_PluginTag::getAttributesFromString($match['attributes'], 'nokey');

            $key_aliases = [
                'modules'   => ['module'],
                'positions' => ['position'],
            ];

            RL_PluginTag::replaceKeyAliases($attributes, $key_aliases);

            if ( ! empty($attributes->positions))
            {
                $remove_positions = array_merge($remove_positions, RL_Array::toArray($attributes->positions));
            }

            if ( ! empty($attributes->modules))
            {
                $remove_modules = array_merge($remove_modules, RL_Array::toArray($attributes->modules));
            }

            if (empty($attributes->nokey))
            {
                continue;
            }

            if ($match['type'] == $params->tag_remove_modulepos)
            {
                $remove_positions[] = $attributes->nokey;
                continue;
            }

            $remove_modules[] = $attributes->nokey;
        }

        return [array_unique($remove_positions), array_unique($remove_modules)];
    }

    private function isEmpty(&$module)
    {
        if ( ! isset($module->content))
        {
            return true;
        }

        $this->setAdvancedParams($module);

        // return false if module params are not found
        if (empty($module->advancedparams))
        {
            return false;
        }

        $params = $module->advancedparams;

        // return false if hideempty is off in module params
        if (empty($params)
            || (( ! isset($params->hideempty) || ! $params->hideempty) && empty($params->if_empty_html)))
        {
            return false;
        }

        $config = $this->getConfig();

        // return false if use_hideempty is off in main config
        if ( ! $config->use_hideempty)
        {
            return false;
        }

        $content = trim($module->content);

        // remove empty tags to see if module can be considered as empty
        if ( ! empty($content))
        {
            // remove html and hidden whitespace
            $content = str_replace(chr(194) . chr(160), ' ', $content);
            $content = str_replace(['&nbsp;', '&#160;'], ' ', $content);
            // remove comment tags
            $content = RL_RegEx::replace('<\!--.*?-->', '', $content);
            // remove all closing tags
            $content = RL_RegEx::replace('</[^>]+>', '', $content);
            // remove tags to be ignored
            $tags   = 'p|div|span|strong|b|em|i|ul|font|br|h[0-9]|fieldset|label|ul|ol|li|table|thead|tbody|tfoot|tr|th|td|form';
            $search = '<(?:' . $tags . ')(?:\s[^>]*)?>';

            if (RL_RegEx::match($search, $content))
            {
                $content = RL_RegEx::replace($search, '', $content);
            }

            $content = trim($content);
        }

        if ( ! empty($content))
        {
            return false;
        }

        if ( ! empty($params->if_empty_html))
        {
            $module->content = $params->if_empty_html;

            return false;
        }

        return true;
    }

    private function removeDisabledAssignments(&$params)
    {
        $config = $this->getConfig();

        if ( ! $config->show_assignto_homepage)
        {
            $params->assignto_homepage = 0;
        }
        if ( ! $config->show_assignto_usergrouplevels)
        {
            $params->assignto_usergrouplevels = 0;
        }
        if ( ! $config->show_assignto_users)
        {
            $params->assignto_users = 0;
        }
        if ( ! $config->show_assignto_date)
        {
            $params->assignto_date = 0;
            $params->assignto_seasons = 0;
            $params->assignto_months  = 0;
            $params->assignto_days    = 0;
            $params->assignto_time    = 0;
        }
        if ( ! $config->show_assignto_languages)
        {
            $params->assignto_languages = 0;
        }
        if ( ! $config->show_assignto_ips)
        {
            $params->assignto_ips = 0;
        }
        if ( ! $config->show_assignto_geo)
        {
            $params->assignto_geocontinents  = 0;
            $params->assignto_geocountries   = 0;
            $params->assignto_georegions     = 0;
            $params->assignto_geopostalcodes = 0;
        }
        if ( ! $config->show_assignto_templates)
        {
            $params->assignto_templates = 0;
        }
        if ( ! $config->show_assignto_urls)
        {
            $params->assignto_urls = 0;
        }
        if ( ! $config->show_assignto_devices)
        {
            $params->assignto_devices = 0;
        }
        if ( ! $config->show_assignto_os)
        {
            $params->assignto_os = 0;
        }
        if ( ! $config->show_assignto_browsers)
        {
            $params->assignto_browsers = 0;
        }
        if ( ! $config->show_assignto_components)
        {
            $params->assignto_components = 0;
        }
        if ( ! $config->show_assignto_tags)
        {
            $params->show_assignto_tags = 0;
        }
        if ( ! $config->show_assignto_content)
        {
            $params->assignto_contentpagetypes = 0;
            $params->assignto_cats             = 0;
            $params->assignto_articles         = 0;
        }
        if ( ! $config->show_assignto_easyblog)
        {
            $params->assignto_easyblogpagetypes = 0;
            $params->assignto_easyblogcats      = 0;
            $params->assignto_easyblogtags      = 0;
            $params->assignto_easyblogitems     = 0;
        }
        if ( ! $config->show_assignto_flexicontent)
        {
            $params->assignto_flexicontentpagetypes = 0;
            $params->assignto_flexicontenttags      = 0;
            $params->assignto_flexicontenttypes     = 0;
        }
        if ( ! $config->show_assignto_form2content)
        {
            $params->assignto_form2contentprojects = 0;
        }
        if ( ! $config->show_assignto_k2)
        {
            $params->assignto_k2pagetypes = 0;
            $params->assignto_k2cats      = 0;
            $params->assignto_k2tags      = 0;
            $params->assignto_k2items     = 0;
        }
        if ( ! $config->show_assignto_zoo)
        {
            $params->assignto_zoopagetypes = 0;
            $params->assignto_zoocats      = 0;
            $params->assignto_zooitems     = 0;
        }
        if ( ! $config->show_assignto_akeebasubs)
        {
            $params->assignto_akeebasubspagetypes = 0;
            $params->assignto_akeebasubslevels    = 0;
        }
        if ( ! $config->show_assignto_hikashop)
        {
            $params->assignto_hikashoppagetypes = 0;
            $params->assignto_hikashopcats      = 0;
            $params->assignto_hikashopproducts  = 0;
        }
        if ( ! $config->show_assignto_mijoshop)
        {
            $params->assignto_mijoshoppagetypes = 0;
            $params->assignto_mijoshopcats      = 0;
            $params->assignto_mijoshopproducts  = 0;
        }
        if ( ! $config->show_assignto_redshop)
        {
            $params->assignto_redshoppagetypes = 0;
            $params->assignto_redshopcats      = 0;
            $params->assignto_redshopproducts  = 0;
        }
        if ( ! $config->show_assignto_virtuemart)
        {
            $params->assignto_virtuemartpagetypes = 0;
            $params->assignto_virtuemartcats      = 0;
            $params->assignto_virtuemartproducts  = 0;
        }
        if ( ! $config->show_assignto_cookieconfirm)
        {
            $params->assignto_cookieconfirm = 0;
        }
        if ( ! $config->show_assignto_php)
        {
            $params->assignto_php = 0;
        }
    }

    private function setAdvancedParams(&$module)
    {
        if (empty($module->id))
        {
            return;
        }

        if (isset($module->advancedparams) && is_object($module->advancedparams))
        {
            return;
        }

        if ( ! isset($module->advancedparams))
        {
            $module->advancedparams = $this->getAdvancedParams($module);
        }

        $module->advancedparams = json_decode($module->advancedparams);

        if (is_null($module->advancedparams))
        {
            $module->advancedparams = (object) [];
        }

        if (
            ! isset($module->advancedparams->assignto_menuitems)
            || isset($module->advancedparams->assignto_urls_selection_sef)
            || (
                ! is_array($module->advancedparams->assignto_menuitems)
                && strpos($module->advancedparams->assignto_menuitems, '|') !== false
            )
        )
        {
            require_once JPATH_ADMINISTRATOR . '/components/com_advancedmodules/models/module.php';
            $model = new AdvancedModulesModelModule;

            $module->advancedparams = (object) $model->initAssignments($module->id, $module);
        }

        $xmlfile_assignments = JPATH_ADMINISTRATOR . '/components/com_advancedmodules/assignments.xml';

        $use_cache = $module->use_amm_cache ?? true;

        $module->advancedparams = RL_Parameters::getObjectFromRegistry($module->advancedparams, $xmlfile_assignments, '', $use_cache);
    }

    private function setExtraParams(&$module)
    {
        $extraparams = [];

        for ($e = 1; $e <= 5; $e++)
        {
            $var               = 'extra' . $e;
            $extraparams[$var] = $module->advancedparams->{$var} ?? '';
        }

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

        if (empty($module->params))
        {
            $module->params = json_encode($extraparams);

            return;
        }

        $params = (array) json_decode($module->params, true);
        $params = array_merge($params, $extraparams);

        $module->params = json_encode($params);
    }

    private function setMirrorParams(&$module)
    {
        $module->mirror_id = $this->getMirrorModuleId($module);

        if (empty($module->mirror_id))
        {
            return;
        }

        $mirror_id = $module->mirror_id < 0 ? $module->mirror_id * -1 : $module->mirror_id;

        $count = 0;
        while ($count++ < 10)
        {
            if ( ! $test_mirrorid = $this->getMirrorModuleIdById($mirror_id))
            {
                break;
            }

            $mirror_id = $test_mirrorid;
        }

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

        $xmlfile_assignments = JPATH_ADMINISTRATOR . '/components/com_advancedmodules/assignments.xml';

        $module->reverse = $module->mirror_id < 0;

        if ($mirror_id == $module->id)
        {
            $empty         = (object) [];
            $mirror_params = RL_Parameters::getObjectFromRegistry($empty, $xmlfile_assignments);
        }
        else
        {
            if (isset($modules[$mirror_id]))
            {
                if ( ! isset($modules[$mirror_id]->advancedparams))
                {
                    $modules[$mirror_id]->advancedparams = $this->getAdvancedParamsById($mirror_id);
                    $modules[$mirror_id]->advancedparams = RL_Parameters::getObjectFromRegistry($modules[$mirror_id]->advancedparams, $xmlfile_assignments);
                }

                $mirror_params = $modules[$mirror_id]->advancedparams;
            }
            else
            {
                $mirror_params = $this->getAdvancedParamsById($mirror_id);
                $mirror_params = RL_Parameters::getObjectFromRegistry($mirror_params, $xmlfile_assignments);
            }
        }

        // Keep the advanced settings that shouldn't be mirrored
        $settings_to_keep = [
            'hideempty', 'color',
            'pre_html', 'post_html',
            'extra1', 'extra2', 'extra3', 'extra4', 'extra5'
        ];

        foreach ($settings_to_keep as $key)
        {
            if ( ! isset($module->advancedparams->{$key}))
            {
                continue;
            }

            $mirror_params->{$key} = $module->advancedparams->{$key};
        }

        $module->advancedparams = $mirror_params;
    }
}
Controls/CSS.php000064400000002505152417672540007527 0ustar00<?php

/**
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            http://www.tassos.gr
 * @copyright       Copyright © 2022 Tassos Marinos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 */

namespace NRFramework\Helpers\Controls;

defined('_JEXEC') or die;

class CSS
{
    public static function generateCSS($styles = [])
	{
		if (!$styles || !is_array($styles))
		{
			return;
		}

		$css = '';
		foreach ($styles as $breakpoint => $array)
		{
			if (!$selectors = self::groupCSSBySelectors($array))
			{
				continue;
			}

			$css_tmp = '';

			// Get all the CSS for this breakpoint for all selectors
			foreach ($selectors as $selector => $_styles)
			{
				$css_tmp .= $selector . '{' . implode('', $_styles) . '}';
			}

			// Then enapsulate all the breakpoint CSS in the breakpoint media query
			$css_tmp = \NRFramework\Helpers\Responsive::renderResponsiveCSS([
				$breakpoint => [$css_tmp]
			]);

			if (!$css_tmp)
			{
				continue;
			}

			$css .= $css_tmp;
		}

        return $css;
	}
	
	public static function groupCSSBySelectors($styles = [])
	{
		if (!$styles)
		{
			return;
		}

		$selectors = [];

		foreach ($styles as $style)
		{
			$selectors[$style['selector']][] = $style['css'];
		}

		if (!$selectors)
		{
			return;
		}

		return $selectors;
	}
}Controls/Control.php000064400000015311152417672560010520 0ustar00<?php

/**
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            https://www.tassos.gr
 * @copyright       Copyright © 2024 Tassos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 */

namespace NRFramework\Helpers\Controls;

defined('_JEXEC') or die;

class Control
{
    /**
     * Finds the value and unit in the given subject.
     * 
     * @param   string/array  $subject
     * 
     * @return  array
     */
	public static function findUnitInValue($subject = '')
	{
        if (is_null($subject))
        {
            return;
        }
        
        if (is_string($subject) && $subject === '')
        {
            return;
        }

        if (is_array($subject) && count($subject) === 0)
        {
            return;
        }

        if ($subject === 'auto')
        {
            return [
                'value' => '',
                'unit' => 'auto'
            ];
        }
        
        if (is_array($subject) && isset($subject['value']))
        {
            $return = [
                'value' => $subject['value']
            ];

            if (isset($subject['unit']))
            {
                $return['unit'] = $subject['unit'];
            }

            return $return;
        }

		$pattern = '/^([\d.]+)(\D+)?$/';
		if (is_scalar($subject) && preg_match($pattern, $subject, $matches, PREG_OFFSET_CAPTURE) === 1)
		{
			return [
				'value' => $matches[1][0],
				'unit' => isset($matches[2][0]) ? $matches[2][0] : ''
			];
		}

		return [
            'value' => $subject,
            'unit' => ''
        ];
	}

    /**
     * Parses the given value to a CSS value.
     * 
     * @param   mixed   $value
     * @param   string  $unit
     * 
     * @return  string
     */
    public static function getCSSValue($value = '', $unit = '')
    {
        if (is_null($value) || $value === '')
        {
            return;
        }

        // Is scalar, transform to array
        if (is_scalar($value))
        {
            $value = array_filter(explode(' ', $value), function($value) {
                return $value !== '';
            });
        }

        if (!$value)
        {
            return;
        }
        
        if (is_array($value))
        {
            if (empty($value))
            {
                return;
            }
            
			// If all values are empty, abort
			$isEmptyArray = array_filter($value, function($str) {
                return $str === null || $str === false || $str === '' || (is_array($str) && empty($str));
            });
			if (count($isEmptyArray) === 4)
			{
				return;
			}

			// Apply spacing positions
			if ($positions = self::findSpacingPositions($value))
			{
				$return = [];
				foreach ($positions as $pos)
				{
					$return[$pos] = isset($value[$pos]) && $value[$pos] !== '' ? $value[$pos] : 0;
				}
				if (empty($return))
				{
					return;
				}
				$value = $return;
			}

            /**
             * All values are duplicates, return only 1 number with their unit.
             * 
             * Example: Given [5, 5, 5, 5] to print the margin in pixels, do not return `margin: 5px 5px 5px 5px`.
             * Rather return `margin: 5px`
             */
            if (count($value) === 4 && count(array_unique($value)) === 1)
            {
				$value = reset($value);
                if ($value_data = self::findUnitInValue($value))
                {
                    $value = $value_data['value'];
                    $unit = !empty($value_data['unit']) ? $value_data['unit'] : $unit;
                }

				if (is_array($value))
				{
					return;
				}
				
                return $value . ($value > 0 ? $unit : '');
            }

            /**
             * If we were given 4 values and first/third & second/forth values are the same then return these only.
             * 
             * Example: Given[5, 10, 5, 10] to print the margin in pixels, do not return `margin: 5px 10px 5px 10px`.
             * Rather return `margin: 5px 10px`
             */
            $keys = array_keys($value);
            if (count($value) === 4 && $value[$keys[0]] === $value[$keys[2]] && $value[$keys[1]] === $value[$keys[3]])
            {
                $value1 = $value[$keys[0]];
                $suffix1 = $suffix2 = $unit;
                $value2 = $value[$keys[1]];
                
                if ($value_1 = self::findUnitInValue($value1))
                {
                    $value1 = $value_1['value'];
                    $suffix1 = !empty($value_1['unit']) ? $value_1['unit'] : $unit;
                }
                if ($value_2 = self::findUnitInValue($value2))
                {
                    $value2 = $value_2['value'];
                    $suffix2 = !empty($value_2['unit']) ? $value_2['unit'] : $unit;
                }
                
                return $value1 . ($value1 > 0 ? $suffix1 : '') . ' ' . $value2 . ($value2 > 0 ? $suffix2 : '');
            }

            // Different values
            $data = [];
            foreach ($value as $key => $_value)
            {
                $val = $_value;
                if ($value_data = self::findUnitInValue($val))
                {
                    $val = $value_data['value'];
                    $unit = !empty($value_data['unit']) ? $value_data['unit'] : $unit;
                }
                $data[] = $val . ($val > 0 ? $unit : '');
            }

            return implode(' ', $data);
        }

        return;
    }

    /**
     * Finds an array of positions of the given value that
     * relates to margin/padding or border radius.
     * 
     * @param   array  $value
     * 
     * @return  array
     */
    public static function findSpacingPositions($value = [])
    {
		if (!is_array($value) || !count($value))
		{
			return;
		}
		
		$keys = array_keys($value);
        
		// Is margin/padding
        $margin_padding = self::getPositions();
		if (in_array($keys[0], $margin_padding, true))
		{
			return $margin_padding;
		}

		// Is border radius
        $border_radius = self::getPositions('border_radius');
		if (in_array($keys[0], $border_radius, true))
		{
			return $border_radius;
		}

		return;
    }

    /**
     * Return the position keys based on the control type.
     * 
     * @param   string  $type
     * 
     * @return  array
     */
    public static function getPositions($type = 'margin_padding')
    {
        if (!$type)
        {
            return [];
        }

        $margin_padding = [
            'top',
            'right',
            'bottom',
            'left'
        ];

        $border_radius = [
            'top_left',
            'top_right',
            'bottom_right',
            'bottom_left'
        ];

        return $type === 'margin_padding' ? $margin_padding : $border_radius;
    }
}Controls/Spacing.php000064400000005316152417672610010464 0ustar00<?php

/**
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            https://www.tassos.gr
 * @copyright       Copyright © 2024 Tassos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 */

namespace NRFramework\Helpers\Controls;

defined('_JEXEC') or die;

use NRFramework\Helpers\Controls\Control;

class Spacing
{
    /**
     * Parses the given value and returns the value expected by Spacing Control.
     * 
     * @param   mixed   $value
     * @param   string  $type    This can be margin_padding or border_radius.
     * 
     * @return  array
     */
    public static function parseInputValue($value = '', $type = 'margin_padding')
    {
        if (!$value)
        {
            return [];
        }

        $linked = isset($value['linked']) ? $value['linked'] : '0';
        $unit = isset($value['unit']) ? $value['unit'] : 'px';
        $value = isset($value['value']) ? $value['value'] : $value;

        $positions = Control::getPositions($type);
        
        // If it's a string of values, prepare it to be an array and continue
        if (is_scalar($value))
        {
            $value = array_filter(explode(' ', $value), function($value) {
                return $value !== '';
            });

            // Get the unit from the first found value
            foreach ($value as $val)
            {
                $_value = Control::findUnitInValue($val);
                if (!isset($_value['unit']))
                {
                    continue;
                }

                $unit = !empty($_value['unit']) ? $_value['unit'] : $unit;
                break;
            }
            
            // Ensure only ints are in the array
            $value = array_map('intval', $value);

            // If only a single value is given, apply the value to all positions
            if (count($value) === 1)
            {
                $value = array_merge($value, $value, $value, $value);
            }

            if (count($value) === 2)
            {
                $value = [$value[0], $value[1], $value[0], $value[1]];
            }
            
            $tmp_value = [];

            foreach ($positions as $index => $pos)
            {
                $tmp_value[$pos] = isset($value[$index]) ? $value[$index] : '';
            }

            $value = $tmp_value;
        }

        // Return value
        $return = [];

        foreach ($positions as $pos)
        {
            $return[$pos] = isset($value[$pos]) && $value[$pos] !== '' ? intval($value[$pos]) : '';
        }

        if (empty($return))
        {
            return [];
        }

        $return['linked'] = $linked;
        $return['unit'] = $unit;

        return $return;
    }
}Controls/index.php000064400000000000152417672660010175 0ustar00Widgets/Gallery.php000064400000027555152417672700010313 0ustar00<?php

/**
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            https://www.tassos.gr
 * @copyright       Copyright © 2024 Tassos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 */

namespace NRFramework\Helpers\Widgets;

defined('_JEXEC') or die;

use NRFramework\Mimes;
use Joomla\CMS\Helper\ModuleHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Uri\Uri;

class Gallery
{
	/**
	 * Stores all gallery parsed directories info txt file `*.gallery_info.txt` data in format:
	 * GALLERY DIRECTORY => ARRAY OF `*.gallery_info.txt` file data
	 * 
	 * @var  array
	 */
	static $gallery_directories_info_file = [];

	/**
	 * Stores all galleries info file names in format:
	 * 
	 * GALLERY DIRECTORY => INFO FILE NAME
	 * 
	 * @var  array
	 */
	static $gallery_directories_info_file_names = [];
	
	/**
	 * The directory information file holding all gallery item details.
	 * 
	 * @var  string
	 */
	const directory_gallery_info_file = 'gallery_info.txt';
	
	/**
	 * Parses the given gallery items.
	 * 
	 * @param   mixed  	$input     			 	A string to a directory/path/URL or an array of a URL item containing its information.
	 * @param   array   $allowed_file_types	 	The allowed file types.
	 * 
	 * @return  mixed
	 */
	public static function parseGalleryItems($input, $allowed_file_types = [])
	{
		if (is_string($input))
		{
			$fullpath_input = JPATH_ROOT . DIRECTORY_SEPARATOR . ltrim($input, DIRECTORY_SEPARATOR);

			// Parse Directory
			if (is_dir($fullpath_input))
			{
				return self::parseDirectory($fullpath_input, $allowed_file_types);
			}

			// Skip invalid URLs
			if ($url = self::parseURL($input))
			{
				return [$url];
			}

			// Parse Image
			if ($image_data = self::parseImage($fullpath_input, $allowed_file_types))
			{
				return [$image_data];
			}
		}

		return [self::parseURL($input)];
	}

	/**
	 * Parse the directory by finding all of its images and their information.
	 * 
	 * @param   string  $dir
	 * @param   array   $allowed_file_types
	 * 
	 * @return  mixed
	 */
	public static function parseDirectory($dir, $allowed_file_types = [])
	{
		if (!is_string($dir) || !is_dir($dir) || empty($allowed_file_types))
		{
			return;
		}

		$items = [];

		// Get images
		$files = array_diff(scandir($dir), ['.', '..', '.DS_Store']);

		foreach ($files as $key => $filename)
		{
			// Skip directories
			if (is_dir($dir . DIRECTORY_SEPARATOR . $filename))
			{
				continue;
			}

			$image_path = rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $filename;
			
			if (!$image_data = self::parseImage($image_path, $allowed_file_types))
			{
				continue;
			}

			$items[] = $image_data;
		}

		return $items;
	}

	/**
	 * Parse the directory image and return its information.
	 * 
	 * @param   string  $image_path
	 * @param   string  $allowed_file_types
	 * 
	 * @return  mixed
	 */
	public static function parseImage($image_path, $allowed_file_types = null)
	{
		if (!is_string($image_path))
		{
			return;
		}

		$data = [
			'path' => $image_path,
			'url' => self::directoryImageToURL($image_path)
		];

		if (!is_file($image_path))
		{
			return array_merge($data, [
				'invalid' => true
			]);
		}

		// Skip not allowed file types
		if (!is_null($allowed_file_types) && !Mimes::check($allowed_file_types, Mimes::detectFileType($image_path)))
		{
			return;
		}

		// Check if there is a `*.gallery_info.txt` helper file and get any information about the image
		$gallery_info_file_data = self::getGalleryInfoFileData(dirname($image_path));
		if (!$gallery_info_file_data)
		{
			return $data;
		}

		$image_filename = pathinfo($image_path, PATHINFO_BASENAME);

		// If no information from the text field about this image was found, stop
		if (!isset($gallery_info_file_data[$image_filename]))
		{
			return $data;
		}

		$image_data = $gallery_info_file_data[$image_filename];

		return array_merge($data, [
			'caption' => isset($image_data['caption']) ? $image_data['caption'] : ''
		]);
	}

	/**
	 * Parses a single URL either as a String or as an Array.
	 * 
	 * @param   mixed  $item
	 * 
	 * @return  mixed
	 */
	public static function parseURL($item)
	{
		// URL is a string
		if (is_string($item))
		{
			if (!filter_var($item, FILTER_VALIDATE_URL))
			{
				return;
			}

			return [
				'url' => $item
			];
		}
		
		// URL is an array
		if (!is_array($item) || !count($item))
		{
			return;
		}

		// If a thumbnail URL is given but no URL, use it as the full image URL
		if (isset($item['thumbnail_url']) && !isset($item['url']))
		{
			$item['url'] = $item['thumbnail_url'];
		}

		if (!isset($item['url']))
		{
			return;
		}

		if (!filter_var($item['url'], FILTER_VALIDATE_URL))
		{
			return;
		}
		
		return $item;
	}

	/**
	 * Loads a module by its ID.
	 * 
	 * @param   string  $id
	 * 
	 * @return  string
	 */
	public static function loadModule($id)
	{
		$module = ModuleHelper::getModuleById($id);
		$params = ['style' => 'none'];

		return $module->id > 0 ? Factory::getDocument()->loadRenderer('module')->render($module, $params) : '';
	}

	/**
	 * Read the `*.gallery_info.txt` file for the given directory.
	 * 
	 * @param   string  $dir
	 * 
	 * @return  mixed
	 */
	public static function getGalleryInfoFileData($dir)
	{
		if (isset(self::$gallery_directories_info_file[$dir]) && !empty(self::$gallery_directories_info_file[$dir]))
		{
			return self::$gallery_directories_info_file[$dir];
		}

		if (!$file = self::findGalleryInfoFile($dir))
		{
			return [];
		}

		// Read file
		if (!$handle = fopen($file, 'r'))
		{
			return [];
		}

		$data = [];

		$line_defaults = ['', '', ''];

		// Loop each line
		while (($line = fgets($handle)) !== false)
		{
			list($filename, $caption, $hash) = explode('|', $line) + $line_defaults;

			// If no filename is given, continue
			if (!$filename)
			{
				continue;
			}

			$data[$filename] = [
				'filename' => $filename,
				'caption' => trim($caption),
				'hash' => trim($hash)
			];
		}
		
		// Close file
		fclose($handle);

		self::$gallery_directories_info_file[$dir] = $data;

		return $data;
	}

	/**
	 * Finds the source image and whether it has been edited.
	 * 
	 * @param   string  $source
	 * @param   string  $destination_folder
	 * 
	 * @return  mixed
	 */
	public static function findSourceImageDetails($source, $destination_folder)
	{
		$source_filename = pathinfo($source, PATHINFO_BASENAME);

		$data = self::getGalleryInfoFileData(dirname($source));

		$image_data = isset($data[$source_filename]) ? $data[$source_filename] : false;
		
		if (!$image_data)
		{
			return false;
		}
		
		if (empty($image_data['hash']))
		{
			return false;
		}

		$sourceHash = self::calculateFileHash($source);

		return [
			'path' => $destination_folder . $image_data['filename'],
			'edited' => $image_data['hash'] !== $sourceHash
		];
	}

	/**
	 * Updates or Inserts the given image information from the gallery info file.
	 * 
	 * @param   string   $source
	 * @param   array    $image_data
	 * 
	 * @return  mixed
	 */
	public static function updateImageDataInGalleryInfoFile($source, $image_data)
	{
		// Source directory
		$source_directory = dirname($source);

		// Check whether the gallery info file exists, if not, create it
		if (!$file = self::findGalleryInfoFile($source_directory))
		{
			$file = self::createGalleryInfoFile($source_directory);
		}

		// Open files
		$reading = fopen($file, 'r');
		$writing = fopen($file . '.tmp', 'w');
	
		$replaced = false;
	
		while (!feof($reading))
		{
			// Get each file line
			$line = fgets($reading);

			// Remove new line at the end
			$line = trim(preg_replace('/\s\s+/', ' ', $line));

			// Skip empty lines
			if (empty($line))
			{
				continue;
			}

			list($filename, $caption, $hash) = explode('|', $line) + ['', '', ''];

			// We need to manipulate current file
			if (strtolower($filename) !== strtolower(basename($image_data['path'])))
			{
				fputs($writing, $line . "\n");
				continue;
			}

			$replaced = true;

			$line = $filename . '|' . $caption . '|' . self::calculateFileHash($source) . "\n";;

			// Write changed line
			fputs($writing, $line);
		}

		// Close files
		fclose($reading);
		fclose($writing);

		// If we replaced a line, update the text file
		if ($replaced) 
		{
			rename($file . '.tmp', $file);
		}
		// No line was replaced, append image details
		else
		{
			unlink($file . '.tmp');
			
			self::appendImageDataToGalleryInfoFile($file, $source, $image_data);
		}
	}

	/**
	 * Removes the image from the gallery info file.
	 * 
	 * @param   string   $source
	 * 
	 * @return  boolean
	 */
	public static function removeImageFromGalleryInfoFile($source)
	{
		// Get the gallery info file from destination folder
		if (!$file = self::findGalleryInfoFile(dirname($source)))
		{
			return false;
		}
		
		// Open files
		$reading = fopen($file, 'r');
		$writing = fopen($file . '.tmp', 'w');
	
		$found = false;
	
		while (!feof($reading))
		{
			// Get each file line
			$line = fgets($reading);

			// Remove new line at the end
			$line = trim(preg_replace('/\s\s+/', ' ', $line));

			// Skip empty lines
			if (empty($line))
			{
				continue;
			}

			list($filename, $caption, $hash) = explode('|', $line) + ['', '', ''];

			// We need to manipulate current file
			if ($filename !== pathinfo($source, PATHINFO_BASENAME))
			{
				$found = true;
				fputs($writing, $line . "\n");
				continue;
			}
		}

		// Close files
		fclose($reading);
		fclose($writing);

		if (!$found)
		{
			return false;
		}

		// Save the changes
		rename($file . '.tmp', $file);

		return true;
	}

	/**
	 * Appends the image data into the info file.
	 * 
	 * @param   string  $dir
	 * 
	 * @return  void
	 */
	public static function createGalleryInfoFile($dir)
	{
		$file = self::getLanguageInfoFileName($dir);
		
		file_put_contents($file, '');

		return $file;
	}

	/**
	 * Appends the image data into the info file.
	 * 
	 * @param   string  $file
	 * @param   string  $source
	 * @param   object  $image_data
	 * 
	 * @return  void
	 */
	public static function appendImageDataToGalleryInfoFile($file, $source, $image_data)
	{
		$caption = isset($image_data['caption']) ? $image_data['caption'] : '';
		
		$hash = self::calculateFileHash($source);
		
		$line = pathinfo($source, PATHINFO_BASENAME) . '|' . $caption . '|' . $hash . "\n";

		file_put_contents($file, $line, FILE_APPEND);
	}

	/**
	 * Finds the `*.gallery_info.txt` file if it exists in the given directory.
	 * 
	 * @param   string  $dir
	 * 
	 * @return  mixed
	 */
	public static function findGalleryInfoFile($dir)
	{
		if (isset(self::$gallery_directories_info_file_names[$dir]))
		{
			return self::$gallery_directories_info_file_names[$dir];
		}
		
		// Method 1: With language prefix
		$file = self::getLanguageInfoFileName($dir);

		// Check if the file exists
		if (file_exists($file))
		{
			self::$gallery_directories_info_file_names[$dir] = $file;
			
			return $file;
		}

		// Method 2: Without the language prefix
		$file = rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . self::directory_gallery_info_file;

		// Check if the file exists
		if (file_exists($file))
		{
			self::$gallery_directories_info_file_names[$dir] = $file;

			return $file;
		}

		return false;
	}

	/**
	 * Returns the info file with the language prefix.
	 * 
	 * @param   string  $dir
	 * 
	 * @return  string
	 */
	public static function getLanguageInfoFileName($dir)
	{
		return rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . Factory::getLanguage()->getTag() . '.' . self::directory_gallery_info_file;
	}

	/**
	 * Calculates the file hash of a file.
	 * 
	 * Hash = md5(file path + last modified date of file)
	 * 
	 * @param   string  $file_path
	 * 
	 * @return  string
	 */
	public static function calculateFileHash($file_path)
	{
		return md5($file_path . filemtime($file_path));
	}

	/**
	 * Transforms an image path to a URL.
	 * 
	 * @param   string  $image_path
	 * 
	 * @return  string
	 */
	public static function directoryImageToURL($image_path)
	{
		return rtrim(Uri::root(), DIRECTORY_SEPARATOR) . mb_substr($image_path, strlen(JPATH_BASE), null);;
	}
}Widgets/GalleryManager.php000064400000060365152417672720011604 0ustar00<?php

/**
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            https://www.tassos.gr
 * @copyright       Copyright © 2024 Tassos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 */

namespace NRFramework\Helpers\Widgets;

defined('_JEXEC') or die;

use NRFramework\File;
use NRFramework\Image;
use NRFramework\Functions;
use Joomla\Registry\Registry;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Factory;
use Joomla\Filesystem\Path;
use Joomla\Filesystem\File as JoomlaCMSFile;

class GalleryManager
{
	/**
	 * How long the files can stay in the temp folder.
	 * 
	 * After each save a clean up is run and all files older
	 * than this value in days are removed.
	 * 
	 * @var  int
	 */
	private static $temp_files_cleanup_days = 1;
	
	/**
	 * Upload file
	 *
	 * @param	array	$file						The request file as posted by form
	 * @param	string	$upload_settings			The upload settings
	 * @param	array	$media_uploader_file_data	Media uploader related file settings
	 * @param   array   $resizeSettings				The resize settings
	 *
	 * @return	mixed	String on success, Null on failure
	 */
	public static function upload($file, $upload_settings, $media_uploader_file_data, $resizeSettings)
	{
		// The source file name
		$source = '';

		// Move the image to the tmp folder
		try {
			$source = File::upload($file, self::getFullTempFolder(), $upload_settings['allowed_types'], $upload_settings['allow_unsafe']);
		} catch (\Throwable $th)
		{
			return false;
		}

		if (!$source)
		{
			return false;
		}

		$source_file_path = $source;
		
		$ds = DIRECTORY_SEPARATOR;
		
		// If the file came from the Media Manager file and we are copying it, fix its filename
		if ($media_uploader_file_data['is_media_uploader_file'])
		{
			$media_uploader_file_data['media_uploader_filename'] = self::getFilePathFromMediaUploaderFile($media_uploader_file_data['media_uploader_filename']);
		}

		$source_image_relative = '';
		$original_image_relative = '';

		// Create source image by cloning the original image
		$original_image_extension = pathinfo($source, PATHINFO_EXTENSION);
		$original_image_destination = str_replace('.' . $original_image_extension, '_original.' . $original_image_extension, $source);

		// Thumbnail file name
		$thumb_image_destination = str_replace('.' . $original_image_extension, '_thumb.' . $original_image_extension, $source);

		// Check whether to copy and resize the original image
		if ($resizeSettings['original_image_resize'])
		{
			if ($resizeSettings['original_image_resize_width'] && $resizeSettings['original_image_resize_height'])
			{
				$original_image_full = Image::resize($source, $resizeSettings['original_image_resize_width'], $resizeSettings['original_image_resize_height'], 70, 'crop', $original_image_destination, true);
			}
			else if ($resizeSettings['original_image_resize_width'])
			{
				$original_image_full = Image::resizeAndKeepAspectRatio($source, $resizeSettings['original_image_resize_width'], 70, $original_image_destination, true);
			}
			else if ($resizeSettings['original_image_resize_height'])
			{
				$original_image_full = Image::resizeByHeight($source, $resizeSettings['original_image_resize_height'], $original_image_destination, 70, true);
			}
			
			$original_image_relative = str_replace(JPATH_ROOT . $ds, '', $original_image_full);

			// Delete raw image as not needed
			JoomlaCMSFile::delete($source);
		}
		else
		{
			// Original image must always be cloned by the resized original image
			$original_image_full = File::move($source, $original_image_destination);
			$original_image_relative = str_replace(JPATH_ROOT . $ds, '', $original_image_full);
		}

		// Generate thumbnails
		if (!$thumb_data = self::generateThumbnail($original_image_full, $thumb_image_destination, $resizeSettings))
		{
			return false;
		}

		// Add watermark image
		if (isset($upload_settings['watermark']['type']) && $upload_settings['watermark']['type'] !== 'disabled')
		{
			// Clone source image from original image and hash it
			$source_image_full = File::copy($original_image_full, $source_file_path, false, true);
			$source_image_relative = str_replace(JPATH_ROOT . $ds, '', $source_image_full);
			
			// Add watermark to original image
			$payload = array_merge($upload_settings['watermark'], [
				'source' => $original_image_full
			]);
			\NRFramework\Image::applyWatermark($payload);

			if (isset($upload_settings['watermark']['apply_on_thumbnails']) && $upload_settings['watermark']['apply_on_thumbnails'])
			{
				// Add watermark to original image
				$payload = array_merge($upload_settings['watermark'], [
					'source' => implode($ds, [self::getFullTempFolder(), $thumb_data['resized_filename']])
				]);
				\NRFramework\Image::applyWatermark($payload);
			}
		}

		$tmp_folder = self::getTempFolder();

		return [
			'source' => $source_image_relative ? $source_image_relative : '',
			'original' => $original_image_relative,
			'thumbnail' => implode($ds, [$tmp_folder, $thumb_data['resized_filename']])
		];
	}

	/**
	 * Moves all given `tmp` items over to the destination folder.
	 * 
	 * @param   array   $value
	 * @param   object  $field
	 * @param   string  $destination_folder
	 * 
	 * @return  void
	 */
	public static function moveTempItemsToDestination($value, $field, $destination_folder)
	{
		if (!$destination_folder)
		{
			return;
		}

		// Create destination folder if missing
		if (!File::createDirs($destination_folder))
		{
			return;
		}

		// Make field params use Registry
		if (!$field->fieldparams instanceof Registry)
		{
			$field->fieldparams = new Registry($field->fieldparams);
		}

		/**
		 * Prepare the items for backwards compatibility
		 */
		$items = is_string($value) ? json_decode($value, true) ?? [['value' => $value]] : $value;

		$items = isset($items['items']) ? $items['items'] : $items;

		if (isset($items['value']))
		{
			$items = [$items];
		}

		$limit_files = (int) $field->fieldparams->get('limit_files', 0);

		// Handle single file
		if ($limit_files === 1 && is_array($items))
		{
			$items = [reset($items)];
		}

		$ds = DIRECTORY_SEPARATOR;

		// Move all files from `tmp` folder over to the `upload folder`
		foreach ($items as $key => &$item)
		{
			/**
			 * Skip invalid files.
			 * 
			 * These "files" can appear when we try to move files
			 * over to the destination folder when the gallery manager
			 * is still working to upload queueed files.
			 * 
			 * Also skip any items that have no value.
			 */
			if ($key === 'ITEM_ID' || empty($item['thumbnail']))
			{
				continue;
			}
			
			$moved = false;

			// Ensure thumbnail in temp folder file exists
			$thumbnail_clean = pathinfo($item['thumbnail'], PATHINFO_BASENAME);
			$thumbnail_path = implode($ds, [JPATH_ROOT, $item['thumbnail']]);
			// Move thumbnail image
			if (Functions::startsWith($item['thumbnail'], self::getTempFolder()) && file_exists($thumbnail_path))
			{
				// Move thumbnail
				$thumb = File::move($thumbnail_path, $destination_folder . $thumbnail_clean);

				// Update thumbnail file name
				$item['thumbnail'] = pathinfo($thumb, PATHINFO_BASENAME);

				$moved = true;
			}

			// Check if we have uploaded the full image as well and set it
			$image_clean = pathinfo($item['image'], PATHINFO_BASENAME);
			$image_path = implode($ds, [JPATH_ROOT, $item['image']]);
			// Move original image
			if (Functions::startsWith($item['image'], self::getTempFolder()) && file_exists($image_path))
			{
				// Move image
				$image = File::move($image_path, $destination_folder . $image_clean);

				// Update image file name
				$item['image'] = pathinfo($image, PATHINFO_BASENAME);

				$moved = true;
			}

			// Ensure source in temp folder file exists
			$item['source'] = isset($item['source']) ? $item['source'] : '';

			// If source does not exist, create it from the original image, only if watermark is enabled
			if (!$item['source'] && $field->fieldparams->get('watermark.type', 'disabled') !== 'disabled')
			{
				// Create source from original image
				$source = File::copy($image_path, $image_path, false, true);

				// Update source file name
				$item['source'] = pathinfo($source, PATHINFO_BASENAME);
				
				$moved = true;
			}
			
			// Move source image
			$source_clean = pathinfo($item['source'], PATHINFO_BASENAME);
			$source_path = implode($ds, [JPATH_ROOT, $item['source']]);
			if (Functions::startsWith($item['source'], self::getTempFolder()) && file_exists($source_path))
			{
				// Move source
				$thumb = File::move($source_path, $destination_folder . $source_clean);

				// Update source file name
				$item['source'] = pathinfo($thumb, PATHINFO_BASENAME);

				$moved = true;
			}

			if ($moved)
			{
				// Update destination path
				self::updateDestinationPath($item, $destination_folder);
			}
		}

		return $items;
	}

	/**
	 * Saves the tags for each item.
	 * 
	 * @param   array  $value
	 * 
	 * @return  array
	 */
	public static function saveItemTags($value = [])
	{
		if (!is_array($value))
		{
			return $value;
		}

		foreach ($value as &$item)
		{
			if (!isset($item['tags']) || !is_string($item['tags']))
			{
				$item['tags'] = [];
				continue;
			}

			if (!$itemTags = json_decode($item['tags'], true))
			{
				$item['tags'] = [];
				continue;
			}

			if (!is_array($itemTags))
			{
				$item['tags'] = [];
				continue;
			}

			if (!$itemTags)
			{
				$item['tags'] = [];
				continue;
			}

			// Make $itemTags an array of strings
			$itemTags = array_map(function($tag) {
				return (string) $tag;
			}, $itemTags);

			/**
			 * Creates the new tags in the #__tags table.
			 * 
			 * This returns an array of the new tag ids. If a tag isn't new (doesn't have #new# prefix), it will return 0 as its id.
			 * 
			 * We will now store the IDs returned as the tags for the item.
			 */
			$item['tags'] = self::createTagsFromField($itemTags);
		}

		return $value;
	}

    /**
     * Create any new tags by looking for #new# in the strings
     *
     * @param   array  $tags  Tags text array from the field
     *
     * @return  mixed   If successful, metadata with new tag titles replaced by tag ids. Otherwise false.
     *
     * @since   3.1
     */
    public static function createTagsFromField($tags)
    {
        if (empty($tags) || $tags[0] == '')
		{
            return;
        }

		// We will use the tags table to store them
		if (defined('nrJ4'))
		{
			$tagTable = Factory::getApplication()->bootComponent('com_tags')->getMVCFactory()->createTable('Tag', 'Administrator');
		}
		else
		{
			Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_tags/tables');
			$tagTable = Table::getInstance('Tag', 'TagsTable');
		}

		$newTags = [];

		foreach ($tags as $key => $tag)
		{
			// Remove the #new# prefix that identifies new tags
			$tagText = str_replace('#new#', '', $tag);

			if ($tagText === $tag)
			{
				$newTags[] = (int) $tag;
			}
			else
			{
				// Clear old data if exist
				$tagTable->reset();

				// Try to load the selected tag
				if ($tagTable->load(['title' => $tagText]))
				{
					$newTags[] = (int) $tagTable->id;
				}
				else
				{
					// Prepare tag data
					$tagTable->id          = 0;
					$tagTable->title       = $tagText;
					$tagTable->published   = 1;
					$tagTable->description = '';

					$tagTable->language = '*';
					$tagTable->access   = 1;

					// Make this item a child of the root tag
					$tagTable->setLocation($tagTable->getRootId(), 'last-child');

					// Try to store tag
					if ($tagTable->check())
					{
						// Assign the alias as path (autogenerated tags have always level 1)
						$tagTable->path = $tagTable->alias;

						if ($tagTable->store())
						{
							$newTags[] = (int) $tagTable->id;
						}
					}
				}
			}
		}

		// At this point $newTags is an array of all tag ids
        return $newTags;
    }

	/**
	 * Updates the destination path for the image and its thumbnail to the final destination folder.
	 * 
	 * @param   array   $item
	 * @param   string  $destination_folder
	 * 
	 * @return  mixed
	 */
	private static function updateDestinationPath(&$item, $destination_folder)
	{
		$ds = DIRECTORY_SEPARATOR;

		// Ensure destination folder is a relative path
		$destination_folder = ltrim(rtrim(str_replace(JPATH_ROOT, '', $destination_folder), $ds), $ds);

		$item = array_merge($item, [
			'source' => !empty($item['source']) && !Functions::startsWith($item['source'], $destination_folder) ? implode($ds, [$destination_folder, $item['source']]) : $item['source'],
			'thumbnail' => !Functions::startsWith($item['thumbnail'], $destination_folder) ? implode($ds, [$destination_folder, $item['thumbnail']]) : $item['thumbnail'],
			'image' => !Functions::startsWith($item['image'], $destination_folder) ? implode($ds, [$destination_folder, $item['image']]) : $item['image']
		]);
	}

	/**
	 * Sets the custom field item id > field id value "source" to given source image path for the original image path
	 */
	public static function setItemFieldSource($item_id, $field_id, $sourceImagePath, $originalImagePath)
	{
		// Get "value" column from #__fields_values where item_id = $item_id and $field_id = $field_id
		$db = Factory::getDbo();

		$query = $db->getQuery(true)
			->select($db->qn('value'))
			->from($db->qn('#__fields_values'))
			->where($db->qn('item_id') . ' = ' . $db->q($item_id))
			->where($db->qn('field_id') . ' = ' . $db->q($field_id));

		$db->setQuery($query);

		$value = $db->loadResult();

		// If value is empty, return
		if (!$value)
		{
			return;
		}

		// Decode value
		$value = json_decode($value, true);

		// If value is empty, return
		if (!$value)
		{
			return;
		}

		// If value is not an array, return
		if (!is_array($value))
		{
			return;
		}

		// If value has no items, return
		if (!isset($value['items']))
		{
			return;
		}

		// Loop all items until we find a "image" = $originalImagePath and then set 'source' = $sourceImagePath
		foreach ($value['items'] as $key => &$item)
		{
			if ($item['image'] !== $originalImagePath)
			{
				continue;
			}

			$item['source'] = $sourceImagePath;
		}

		// Update value
		$query = $db->getQuery(true)
			->update($db->qn('#__fields_values'))
			->set($db->qn('value') . ' = ' . $db->q(json_encode($value)))
			->where($db->qn('item_id') . ' = ' . $db->q($item_id))
			->where($db->qn('field_id') . ' = ' . $db->q($field_id));

		$db->setQuery($query);

		$db->execute();
	}
	
	/**
	 * Media Uploader files look like: https://example.com/images/sampledata/parks/banner_cradle.png
	 * We remove the first part (https://example.com/images/) and keep the other part (relative path to image).
	 * 
	 * @param   string  $filename
	 * 
	 * @return  string
	 */
	private static function getFilePathFromMediaUploaderFile($filename)
	{
		$filenameArray = explode('images/', $filename, 2);
		unset($filenameArray[0]);
		$new_filepath = join($filenameArray);
		return 'images/' . $new_filepath;
	}

	/**
	 * Generates thumbnail
	 * 
	 * @param   string   $source			 	 Source image path.
	 * @param   string   $destination		 	 Destination image path.
	 * @param   array    $resizeSettings	 	 Resize Settings.
	 * @param   string   $destination_folder	 Destination folder.
	 * @param   boolean  $unique_filename		 Whether the thumbnails will have a unique filename.
	 * 
	 * @return  array
	 */
	public static function generateThumbnail($source = '', $destination = '', $resizeSettings = [], $destination_folder = null, $unique_filename = true)
	{
		if (!$destination)
		{
			$parts = pathinfo($source);
			$destination_folder = !is_null($destination_folder) ? $destination_folder : $parts['dirname'] . DIRECTORY_SEPARATOR;
			$destination = $destination_folder . $parts['filename'] . '_thumb.' . $parts['extension'];
		}

		$resized_image = null;

		$thumb_width = isset($resizeSettings['thumb_width']) ? (int) $resizeSettings['thumb_width'] : null;
		$thumb_height = isset($resizeSettings['thumb_height']) ? (int) $resizeSettings['thumb_height'] : null;

		// If thumbnail width is null, and we have item height set, we are resizing by height
		if (is_null($thumb_width) && $thumb_height && !is_null($thumb_height))
		{
			$resized_image = Image::resizeByHeight($source, $thumb_height, $destination, 70, $unique_filename, true, 'resize');
		}
		else
		{
			if (is_null($thumb_width) || !$thumb_width)
			{
				return;
			}

			/**
			 * If height is zero, then we suppose we want to keep aspect ratio.
			 * 
			 * Resize with width & height: If thumbnail height is not set
			 * Resize and keep aspect ratio: If thumbnail height is set
			 */
			$resized_image = $thumb_height && !is_null($thumb_height)
				?
				Image::resize($source, $thumb_width, $thumb_height, 70, $resizeSettings['thumb_resize_method'], $destination, $unique_filename, true, 'resize')
				:
				Image::resizeAndKeepAspectRatio($source, $thumb_width, 70, $destination, $unique_filename, true, 'resize');

		}
		
		if (!$resized_image)
		{
			return;
		}

		return [
			'filename' => basename($source),
			'resized_filename' => basename($resized_image)
		];
	}

	/**
	 * Deletes an uploaded files: source, original, and thumbnail.
	 *
	 * @param   string  $source			The source image path.
	 * @param   string  $original		The original image path.
	 * @param   string  $thumbnail		The thumbnail image path.
	 *
	 * @return  bool
	 */
	public static function deleteFile($source = null, $original = null, $thumbnail = null)
	{
		return [
			'deleted_source_image' => self::findAndDeleteFile($source),
			'deleted_original_image' => self::findAndDeleteFile($original),
			'deleted_thumbnail' => self::findAndDeleteFile($thumbnail)
		];
	}

	/**
	 * Deletes the file.
	 * 
	 * @param   string  $filepath
	 * 
	 * @return  mixed
	 */
	private static function findAndDeleteFile($filepath)
	{
		if (!$filepath)
		{
			return;
		}
		
		$file = Path::clean(implode(DIRECTORY_SEPARATOR, [JPATH_ROOT, $filepath]));

		return file_exists($file) ? JoomlaCMSFile::delete($file) : false;
	}

	/**
	 * Cleans the temp folder.
	 * 
	 * Removes any image that is 1 day or older.
	 * 
	 * @return  void
	 */
	public static function clean()
	{
		$temp_folder = self::getFullTempFolder();
		
		if (!is_dir($temp_folder))
		{
			return;
		}

		// Get images
		$files = array_diff(scandir($temp_folder), ['.', '..', '.DS_Store', 'index.html']);

		$found = [];

		foreach ($files as $key => $filename)
		{
			$file_path = implode(DIRECTORY_SEPARATOR, [$temp_folder, $filename]);
			
			// Skip directories
			if (is_dir($file_path))
			{
				continue;
			}

			$diff_in_miliseconds = time() - filemtime($file_path);

			// Skip the file if it's not old enough
			if ($diff_in_miliseconds < (60 * 60 * 24 * self::$temp_files_cleanup_days))
			{
				continue;
			}

			$found[] = $file_path;
		}

		if (!$found)
		{
			return;
		}

		// Delete found old files
		foreach ($found as $file)
		{
			unlink($file);
		}
	}

	/**
	 * Full temp directory where images are uploaded
	 * prior to them being saved in the final directory.
	 * 
	 * @return  string
	 */
	private static function getFullTempFolder()
	{
		return implode(DIRECTORY_SEPARATOR, [JPATH_ROOT, self::getTempFolder()]);
	}

	/**
	* Temp folder where images are uploaded
	* prior to them being saved in the final directory.
	* 
	* @var  string
	*/
	public static function getTempFolder()
	{
		return implode(DIRECTORY_SEPARATOR, ['media', 'tfgallerymanager', 'tmp']);
	}

	/**
	 * Deletes a specific tag from every gallery item.
	 * 
	 * @param   int     $tag_id
	 * @param   string  $context
	 * 
	 * @return  void
	 */
	public static function deleteTagFromFieldsValues($tag_id = null, $context = '')
	{
		if (!$tag_id)
		{
			return;
		}

		if ($context === '')
		{
			self::deleteTagFromCustomFieldsByTagId($tag_id);
			self::deleteTagFromSubformCustomFieldsByTagId($tag_id);
		}
	}

	/**
	 * Deletes a specific tag from every gallery item custom field.
	 * 
	 * @param   int  $tag_id
	 * 
	 * @return  void
	 */
	private static function deleteTagFromCustomFieldsByTagId($tag_id = null)
	{
		if (!$tag_id)
		{
			return;
		}

		$db = Factory::getDbo();
        $query = $db->getQuery(true)
            ->select('f.id as field_id, fv.item_id as item_id, fv.value as value')
            ->from('#__fields as f')
            ->join('LEFT', '#__fields_values AS fv ON fv.field_id = f.id')
            ->where('f.type = ' . $db->quote('acfgallery'));

        $db->setQuery($query);

        $fields = $db->loadAssocList();

		if (!$fields)
		{
			return;
		}

        foreach ($fields as $field)
        {
            if (!$decoded_value = json_decode($field['value'], true))
            {
                continue;
            }

            if (!isset($decoded_value['items']))
            {
                continue;
            }

            $update = false;

            foreach ($decoded_value['items'] as &$item)
            {
                if (!isset($item['tags']))
                {
                    continue;
                }

                if (!is_array($item['tags']))
                {
                    continue;
                }

                if (!count($item['tags']))
                {
                    continue;
                }

                $item['tags'] = array_values($item['tags']);

                if (($key = array_search($tag_id, $item['tags'])) !== false)
                {
                    $update = true;
                    unset($item['tags'][$key]);
                }

                $item['tags'] = array_values($item['tags']);
            }

            if (!$update)
            {
				continue;
			}
		
			$field['value'] = json_encode($decoded_value);

			// Update field value
			$query->clear()
				->update('#__fields_values')
				->set($db->quoteName('value') . ' = ' . $db->quote($field['value']))
				->where($db->quoteName('field_id') . ' = ' . $db->quote($field['field_id']))
				->where($db->quoteName('item_id') . ' = ' . $db->quote($field['item_id']));
			$db->setQuery($query);
			$db->execute();
        }
	}

	/**
	 * Deletes a specific tag from every gallery item that exists in a subform custom field.
	 * 
	 * @param   int  $tag_id
	 * 
	 * @return  void
	 */
	private static function deleteTagFromSubformCustomFieldsByTagId($tag_id = null)
	{
		if (!$tag_id)
		{
			return;
		}

		if (!$tag_id)
		{
			return;
		}

		$db = Factory::getDbo();

		// Get all ACF Gallery custom field IDs
		$query = $db->getQuery(true)
			->select('distinct f.id')
			->from('#__fields as f')
			->join('LEFT', '#__fields_values AS fv ON fv.field_id = f.id')
			->where('f.type = ' . $db->quote('acfgallery'));
			$db->setQuery($query);
		$gallery_field_ids = array_keys($db->loadAssocList('id'));

		if (!$gallery_field_ids)
		{
			return;
		}
		
		// Get all Subform custom fields
        $query->clear()
            ->select('f.id as field_id, fv.item_id as item_id, fv.value as value')
            ->from('#__fields as f')
            ->join('LEFT', '#__fields_values AS fv ON fv.field_id = f.id')
            ->where('f.type = ' . $db->quote('subform'));
        $db->setQuery($query);
        $subform_fields = $db->loadAssocList();

		foreach ($subform_fields as $subform_field)
		{
			if (!$subform_field_items = json_decode($subform_field['value'], true))
			{
				continue;
			}

			$update = false;

			foreach ($subform_field_items as $row => &$row_items)
			{
				if (!is_array($row_items))
				{
					continue;
				}

				foreach ($row_items as $field_name => &$field_value)
				{
					// Get the field id
					$field_id = str_replace('field', '', $field_name);
					
					// Check if its a gallery field
					if (!in_array($field_id, $gallery_field_ids))
					{
						continue;
					}

					if (!isset($field_value['items']))
					{
						continue;
					}

					foreach ($field_value['items'] as &$item)
					{
						if (!isset($item['tags']))
						{
							continue;
						}

						if (!is_array($item['tags']))
						{
							continue;
						}

						if (!count($item['tags']))
						{
							continue;
						}

						$item['tags'] = array_values($item['tags']);

						if (($key = array_search($tag_id, $item['tags'])) !== false)
						{
							$update = true;
							unset($item['tags'][$key]);
						}

						$item['tags'] = array_values($item['tags']);
					}
				}
			}

			if (!$update)
			{
				continue;
			}
			
			$subform_field['value'] = json_encode($subform_field_items);

			// Update subform field value
			$query->clear()
				->update('#__fields_values')
				->set($db->quoteName('value') . ' = ' . $db->quote($subform_field['value']))
				->where($db->quoteName('field_id') . ' = ' . $db->quote($subform_field['field_id']))
				->where($db->quoteName('item_id') . ' = ' . $db->quote($subform_field['item_id']));
			$db->setQuery($query);
			$db->execute();
		}
	}
}Widgets/GalleryManager2.php000064400000077532152417672770011677 0ustar00<?php

/**
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            https://www.tassos.gr
 * @copyright       Copyright © 2024 Tassos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 */

namespace NRFramework\Helpers\Widgets;

defined('_JEXEC') or die;

use NRFramework\File;
use NRFramework\Image;
use NRFramework\Functions;
use Joomla\Registry\Registry;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Factory;
use Joomla\Filesystem\Path;
use Joomla\CMS\Language\Text;
use Joomla\Filesystem\File as JoomlaCMSFile;

class GalleryManager2
{
	/**
	 * How long the files can stay in the temp folder.
	 * 
	 * After each save a clean up is run and all files older
	 * than this value in days are removed.
	 * 
	 * @var  int
	 */
	private static $temp_files_cleanup_days = 1;
	
	/**
	 * Uploads the source image to the temp folder.
	 *
	 * @param	array	$file			  The request file as posted by form
	 * @param	string	$upload_settings  The upload settings
	 *
	 * @return	array|bool The uploaded image paths or false on failure
	 */
	public static function upload($file, $uploadSettings)
	{
		$fullTempFolder = self::getFullTempFolder($uploadSettings['context'], $uploadSettings['field_id'], $uploadSettings['item_id']);

		// Create source folder if not exists
		File::createDirs($fullTempFolder);

		// Move the image to the tmp folder
		try {
			$source = File::upload($file, $fullTempFolder, $uploadSettings['allowed_types'], $uploadSettings['allow_unsafe'], null, $uploadSettings['random_suffix']);
		} catch (\Throwable $th)
		{
			return false;
		}

		if (!$source)
		{
			return false;
		}

		return str_replace(JPATH_ROOT . DIRECTORY_SEPARATOR, '', $source);
	}

	public static function maybeRegenerateImages($context = 'default', $items = [], $field_id = null, $item_id = null, $oldData = [])
	{
		if (!$oldData)
		{
			return false;
		}
		
		if (!$field_data = self::getSettings($context, $field_id, $item_id))
		{
			return false;
		}

		/**
		 * In order to proceed, there must be changes in the following data:
		 * 
		 * - provider
		 * - full size image
		 * - slideshow image
		 * - thumbnails
		 * - watermark
		 */
		$images = self::canRegenerateImages($field_data, $oldData);

		$items = is_string($items) ? json_decode($items, true) : $items;

		foreach ($items as &$item)
		{
			self::generateFromSource($item, $images, $field_data, false);
		}

		return $items;
	}

	/**
	 * Checks if the images need to be regenerated.
	 * 
	 * @param   object  $new_data
	 * @param   object  $old_data
	 * 
	 * @return  array
	 */
	public static function canRegenerateImages($new_data = [], $old_data = [])
	{
		$images = [
			'thumb' => false,
			'slideshow' => false,
			'full' => false
		];

		// On provider change
		if ($old_data->get('provider') != $new_data->get('provider'))
		{
			if ($new_data->get('provider') === 'slideshow')
			{
				$images['slideshow'] = true;
	
				if ($new_data->get('show_thumbnails') === '1')
				{
					$images['thumb'] = true;
				}
			}
			else
			{
				$images['thumb'] = true;
			}
		}
		// On thumbnails dimensions change
		else if ($old_data->get('thumbnail_size') != $new_data->get('thumbnail_size') || $old_data->get('justified_item_height') != $new_data->get('justified_item_height') || $old_data->get('show_thumbnails') != $new_data->get('show_thumbnails') || $old_data->get('masonry_thumbnails_width') != $new_data->get('masonry_thumbnails_width') || $old_data->get('slideshow_thumbnail_size') != $new_data->get('slideshow_thumbnail_size'))
		{
			$images['thumb'] = true;
		}
		
		// Check if full image has changed
		if ($old_data->get('full_image') != $new_data->get('full_image') || ($old_data->get('lightbox') != $new_data->get('lightbox') && $new_data->get('lightbox') === '1'))
		{
			$images['full'] = true;
		}

		// Check if slideshow image has changed
		if ($new_data->get('provider') === 'slideshow' && $old_data->get('slideshow_image') != $new_data->get('slideshow_image'))
		{
			$images['slideshow'] = true;
		}

		// Check if watermark settings have changed
		if ($old_data->get('watermark') != $new_data->get('watermark'))
		{
			if ($new_data->get('lightbox') === '1')
			{
				$images['full'] = true;
			}

			if ($old_data->get('slideshow_image') != $new_data->get('slideshow_image') || $new_data->get('provider') === 'slideshow')
			{
				$images['slideshow'] = true;
			}

			if ($old_data->get('provider') !== 'slideshow' || ($new_data->get('provider') === 'slideshow' && $new_data->get('show_thumbnails') === '1'))
			{
				$images['thumb'] = true;
			}
		}

		return $images;
	}

	/**
	 * Returns the settings for the given context.
	 * 
	 * @param   string  $context
	 * @param   int     $field_id
	 * @param   int     $item_id
	 * 
	 * @return  mixed
	 */
	public static function getSettings($context = 'default', $field_id = null, $item_id = null)
	{
		// Make sure we have a valid context
		if (!$context)
		{
			return false;
		}

		$field_data = [];

		if ($context === 'default')
		{
			// Make sure we have a valid field id
			if (!$field_id)
			{
				return Text::_('NR_GALLERY_MANAGER_FIELD_ID_ERROR');
			}

			if (!$field_data = \NRFramework\Helpers\CustomField::getData($field_id))
			{
				return Text::_('NR_GALLERY_MANAGER_INVALID_FIELD_DATA');
			}
		}
		else if ($context === 'module')
		{
			// Make sure we have a valid item id
			if (!$item_id)
			{
				return Text::_('NR_GALLERY_MANAGER_ITEM_ID_ERROR');
			}
			
			if (!$field_data = \NRFramework\Helpers\Module::getData($item_id))
			{
				return Text::_('NR_GALLERY_MANAGER_INVALID_FIELD_DATA');
			}
		}

		return $field_data;
	}

	/**
	 * Moves all given temp items over to the destination folder.
	 * 
	 * @param   array   $value
	 * @param   object  $field
	 * @param   string  $destination_folder
	 * 
	 * @return  void
	 */
	public static function moveTempItemsToDestination($value, $field, $destination_folder)
	{
		if (!$destination_folder)
		{
			return;
		}

		// Create destination folder if missing
		if (!File::createDirs($destination_folder))
		{
			return;
		}

		// Make field params use Registry
		if (!$field->fieldparams instanceof Registry)
		{
			$field->fieldparams = new Registry($field->fieldparams);
		}

		/**
		 * Prepare the items for backwards compatibility
		 */
		$items = is_string($value) ? json_decode($value, true) ?? [['value' => $value]] : $value;

		$items = isset($items['items']) ? $items['items'] : $items;

		if (isset($items['value']))
		{
			$items = [$items];
		}

		$limit_files = (int) $field->fieldparams->get('limit_files', 0);

		// Handle single file
		if ($limit_files === 1 && is_array($items))
		{
			$items = [reset($items)];
		}

		// Compatibility Start: Migrate old items to new folder structure
		self::maybeMigrateOldItems($items, $field->fieldparams);
		// Compatibility End

		$ds = DIRECTORY_SEPARATOR;

		$images = [
			'thumb' => in_array($field->fieldparams->get('provider', 'grid'), ['grid', 'masonry', 'justified']) || ($field->fieldparams->get('provider', 'grid') === 'slideshow' && $field->fieldparams->get('show_thumbnails', '0') === '1'),
			'slideshow' => $field->fieldparams->get('provider', 'grid') === 'slideshow',
			'full' => $field->fieldparams->get('lightbox', '0') === '1'
		];

		$tmpdir = Factory::getConfig()->get('tmp_path');
		$tmpRelativeDirectory = ltrim(str_replace(JPATH_ROOT, '', $tmpdir), $ds);

		// Move all files from the temp folder over to the `upload folder`
		foreach ($items as $key => &$item)
		{
			/**
			 * Skip invalid files.
			 * 
			 * These "files" can appear when we try to move files
			 * over to the destination folder when the gallery manager
			 * is still working to upload queueed files.
			 */
			if ($key === 'ITEM_ID')
			{
				continue;
			}

			// Skip if source does not start with the temp relative directory
			$testSourcePath = ltrim(rtrim($item['source'], $ds), $ds) . $ds;
			if (!Functions::startsWith($testSourcePath, $tmpRelativeDirectory . $ds))
			{
				continue;
			}

			// Move source image to final directory
			try {
				$source_clean = pathinfo($item['source'], PATHINFO_BASENAME);
				$source_path = implode($ds, [JPATH_ROOT, $item['source']]);

				$new_source_path = implode($ds, [rtrim($destination_folder, $ds), md5('source'), $source_clean]);
				$new_source_path = File::move($source_path, $new_source_path);
				$item['source'] = ltrim(rtrim(str_replace(JPATH_ROOT, '', $new_source_path), $ds), $ds);
			} catch (\Throwable $th) {}

			// Generate the rest of images from the source image
			self::generateFromSource($item, $images, $field->fieldparams);
		}

		return $items;
	}

	/**
	 * Check and migrate old items to the new folder structure.
	 * 
	 * @param   array   $items
	 * @param   object  $field_data
	 * 
	 * @return  void
	 */
	public static function maybeMigrateOldItems(&$items = [], $field_data = [])
	{
		if (!is_array($items) || empty($items))
		{
			return;
		}

		$ds = DIRECTORY_SEPARATOR;

		$tmpdir = Factory::getConfig()->get('tmp_path');

		// Migrate images to new folders and create source images from the full image if source is missing
		foreach ($items as &$value)
		{
			// We skip this process if the "source" directory exists in the uploaded files path.
			// This means that the new structure is present, so don't do anything.
			if (!empty($value['source']))
			{
				$fullSourcePath = implode($ds, [JPATH_ROOT, $value['source']]);
				$checkDirectory = dirname($fullSourcePath);

				if (is_dir($checkDirectory) && (Functions::startsWith($checkDirectory, $tmpdir) || Functions::endsWith($checkDirectory, '/' . md5('source'))))
				{
					continue;
				}
			}

			$sourceNewPath = null;

			$fullImageCurrentPath = implode($ds, [JPATH_ROOT, $value['image']]);
			$fullImageData = pathinfo($fullImageCurrentPath);

			// Create source if missing
			if (empty($value['source']))
			{
				$sourceNewPath = implode($ds, [dirname($fullImageCurrentPath), md5('source'), $fullImageData['basename']]);
				$sourceNewPath = File::copy($fullImageCurrentPath, $sourceNewPath);
				$value['source'] = ltrim(rtrim(str_replace(JPATH_ROOT, '', $sourceNewPath), $ds), $ds);
			}
			else
			{
				// Move source image
				$sourceOldPath = implode($ds, [JPATH_ROOT, $value['source']]);
				$sourceNewPath = implode($ds, [JPATH_ROOT, dirname($value['source']), md5('source'), $fullImageData['basename']]);
				$sourceNewPath = File::move($sourceOldPath, $sourceNewPath);
				$value['source'] = ltrim(rtrim(str_replace(JPATH_ROOT, '', $sourceNewPath), $ds), $ds);
			}

			// Delete thumbnail and recreate from source
			$needThumb = false;
			$thumbOldPath = implode($ds, [JPATH_ROOT, $value['thumbnail']]);
			if (is_file($thumbOldPath))
			{
				$needThumb = true;
				JoomlaCMSFile::delete($thumbOldPath);
				$value['thumbnail'] = '';

				if ($field_data->get('provider', 'grid') === 'slideshow' && $field_data->get('show_thumbnails', '0') === '0')
				{
					$needThumb = false;
				}
			}

			// Delete full image and recreate from source
			$needFull = false;
			$fullImageOldPath = implode($ds, [JPATH_ROOT, $value['image']]);
			if (is_file($fullImageOldPath))
			{
				JoomlaCMSFile::delete($fullImageOldPath);
				$value['image'] = '';

				if ($field_data->get('lightbox', '0') === '1')
				{
					$needFull = true;
				}
			}

			self::generateFromSource($value, [
				'thumb' => $needThumb,
				'slideshow' => $field_data->get('provider', 'grid') === 'slideshow',
				'full' => $needFull
			], $field_data);
		}
	}

	/**
	 * Clean up unneeded images.
	 * 
	 * @param   array   $item
	 * @param   object  $field_data
	 */
	public static function cleanUpImages(&$item = [], $field_data = [])
	{
		$ds = DIRECTORY_SEPARATOR;
		$provider = $field_data->get('provider', 'grid');

		/**
		 * Clean up of unneeded images.
		 */
		/**
		 * Delete thumbs if:
		 * 1) We're using the slideshow provider and show_thumbnails is disabled and the item has a thumbnail
		 * 2) We're not using the slideshow provider and the item has a thumbnail and a slideshow image
		 */
		if (($provider === 'slideshow' && $field_data->get('show_thumbnails', '0') === '0' && $item['thumbnail']) || ($provider !== 'slideshow' && $item['thumbnail'] && $item['slideshow']))
		{
			$thumbnails_path = implode($ds, [JPATH_ROOT, $item['thumbnail']]);
			if (is_file($thumbnails_path))
			{
				JoomlaCMSFile::delete($thumbnails_path);
			}
			$item['thumbnail'] = null;
		}

		// Delete full image if lightbox is disabled and the item has a full image
		if ($field_data->get('lightbox') === '0' && $item['image'])
		{
			$full_image_path = implode($ds, [JPATH_ROOT, $item['image']]);
			if (is_file($full_image_path))
			{
				JoomlaCMSFile::delete($full_image_path);
			}
			$item['image'] = null;
		}

		// Delete slideshow image if exists and not in slideshow provider
		if ($provider !== 'slideshow' && $item['slideshow'])
		{
			$slideshow_image_path = implode($ds, [JPATH_ROOT, $item['slideshow']]);
			if (is_file($slideshow_image_path))
			{
				JoomlaCMSFile::delete($slideshow_image_path);
			}
			$item['slideshow'] = null;
		}
	}

	/**
	 * This method generates the full image, thumbnail, and slideshow images from the sources.
	 * 
	 * @param   array   $item
	 * @param   array   $images
	 * @param   object  $field_data
	 * @param   bool    $unique_filename
	 * 
	 * @return  void
	 */
	public static function generateFromSource(&$item, $images = [], $field_data = [], $unique_filename = true)
	{
		$ds = DIRECTORY_SEPARATOR;
		$provider = $field_data->get('provider', 'grid');

		/**
		 * Clean up of unneeded images.
		 */
		self::cleanUpImages($item, $field_data);

		$full_source_path = implode($ds, [JPATH_ROOT, $item['source']]);

		// Create full image
		if ($images['full'])
		{
			$full_image_path = implode($ds, [JPATH_ROOT, dirname(dirname($item['source'])), 'full', basename($item['source'])]);
			$full_image_resizing_dimensions = self::getImageResizingDimensions('full_image', $field_data);
			if (!empty(array_filter($full_image_resizing_dimensions)))
			{
				// Create the full image directory if not exists
				File::createDirs(dirname($full_image_path));
		
				// Resize the full image
				$full_image_path = Image::resizeByWidthOrHeight($full_source_path, $full_image_resizing_dimensions['width'], $full_image_resizing_dimensions['height'], 70, $full_image_path, 'crop', $unique_filename);
	
				$item['image'] = ltrim(rtrim(str_replace(JPATH_ROOT, '', $full_image_path), $ds), $ds);
			}
			else if ($field_data->get('full_image.by', '') === 'disabled')
			{
				// Create the full image directory if not exists
				File::createDirs(dirname($full_image_path));
		
				// Copy image to full folder
				$full_image_path = File::copy($full_source_path, $full_image_path, !$unique_filename);

				$item['image'] = ltrim(rtrim(str_replace(JPATH_ROOT, '', $full_image_path), $ds), $ds);
			}
		}

		// Create thumbnail image
		if ($images['thumb'])
		{
			$thumbs_folder_name = $provider === 'slideshow' ? 'thumb' : '';
			$thumbnail_image_path = implode($ds, array_filter([JPATH_ROOT, dirname(dirname($item['source'])), $thumbs_folder_name, basename($item['source'])]));
			$thumbnail_resizing_dimensions = [
				'width' => null,
				'height' => null
			];
	
			if ($provider === 'justified')
			{
				$thumbnail_resizing_dimensions['height'] = $field_data->get('justified_item_height', 200);
			}
			else if ($provider === 'masonry')
			{
				$thumbnail_resizing_dimensions['width'] = $field_data->get('masonry_thumbnails_width', 200);
			}
			else
			{
				$thumbnail_size = $provider === 'slideshow' ? $field_data->get('slideshow_thumbnail_size', 200) : $field_data->get('thumbnail_size', 200);
	
				$thumbnail_resizing_dimensions = [
					'width' => $thumbnail_size,
					'height' => $thumbnail_size
				];
	
				// If slideshow, require show_thumbnails to be enabled
				if ($provider === 'slideshow' && $field_data->get('show_thumbnails', '0') === '0')
				{
					$thumbnail_resizing_dimensions = null;
				}
			}

			if ($thumbnail_resizing_dimensions)
			{
				// Create the full image directory if not exists
				File::createDirs(dirname($thumbnail_image_path));
		
				// Resize the full image
				$thumbnail_image_path = Image::resizeByWidthOrHeight($full_source_path, $thumbnail_resizing_dimensions['width'], $thumbnail_resizing_dimensions['height'], 70, $thumbnail_image_path, 'crop', $unique_filename);
				
				$item['thumbnail'] = ltrim(rtrim(str_replace(JPATH_ROOT, '', $thumbnail_image_path), $ds), $ds);
			}
		}

		// Create slideshow image
		if ($images['slideshow'] && $provider === 'slideshow')
		{
			$slideshow_image_path = implode($ds, [JPATH_ROOT, dirname(dirname($item['source'])), basename($item['source'])]);
			if ($slideshow_resizing_dimensions = self::getImageResizingDimensions('slideshow_image', $field_data))
			{
				// Create the full image directory if not exists
				File::createDirs(dirname($slideshow_image_path));
		
				// Resize the full image
				$slideshow_image_path = Image::resizeByWidthOrHeight($full_source_path, $slideshow_resizing_dimensions['width'], $slideshow_resizing_dimensions['height'], 70, $slideshow_image_path, 'crop', $unique_filename);

				$item['slideshow'] = ltrim(rtrim(str_replace(JPATH_ROOT, '', $slideshow_image_path), $ds), $ds);
			}
		}

		self::applyWatermarkOnImages($item, $images, $field_data);
	}

	/**
	 * Applies the watermark on the images.
	 * 
	 * @param   array   $payload
	 * @param   array   $images
	 * @param   object  $field_data
	 * 
	 * @return  void
	 */
	public static function applyWatermarkOnImages($payload = [], $images = [], $field_data = [])
	{
		if ($field_data->get('watermark.type', 'disabled') === 'disabled')
		{
			return;
		}

		$ds = DIRECTORY_SEPARATOR;

		$watermarkSettings = (array) $field_data->get('watermark', []);

		$apply_on_thumbnails = $watermarkSettings['apply_on_thumbnails'] === '1';

		$watermarkSettings = array_merge($watermarkSettings, [
			'image' => !empty($watermarkSettings['image']) ? explode('#', JPATH_SITE . DIRECTORY_SEPARATOR . $watermarkSettings['image'])[0] : null,
		]);

		if ($images['full'])
		{
			// Add watermark to full image
			$watermarkPayload = array_merge($watermarkSettings, [
				'source' => implode($ds, [JPATH_ROOT, $payload['image']])
			]);
			\NRFramework\Image::applyWatermark($watermarkPayload);
		}

		if ($images['slideshow'])
		{
			// Add watermark to slideshow image
			if ($field_data->get('provider', 'grid') === 'slideshow')
			{
				$watermarkPayload = array_merge($watermarkSettings, [
					'source' => implode($ds, [JPATH_ROOT, $payload['slideshow']])
				]);
				\NRFramework\Image::applyWatermark($watermarkPayload);
			}
		}

		if ($apply_on_thumbnails && $images['thumb'])
		{
			// Add watermark to thumbnail
			$watermarkPayload = array_merge($watermarkSettings, [
				'source' => implode($ds, [JPATH_ROOT, $payload['thumbnail']])
			]);
			\NRFramework\Image::applyWatermark($watermarkPayload);
		}
	}

	public static function getImageResizingDimensions($key, $field_data)
	{
		$width = $height = null;

		if (in_array($field_data->get($key . '.by', ''), ['width', 'custom']))
		{
			$width = $field_data->get($key . '.width', null);
		}
		if (in_array($field_data->get($key . '.by', ''), ['height', 'custom']))
		{
			$height = $field_data->get($key . '.height', null);
		}

		$data = array_filter([
			'width' => $width,
			'height' => $height
		]);

		if (!isset($data['width']))
		{
			$data['width'] = null;
		}

		if (!isset($data['height']))
		{
			$data['height'] = null;
		}
		
		return $data;
	}

	/**
	 * Saves the tags for each item.
	 * 
	 * @param   array  $value
	 * 
	 * @return  array
	 */
	public static function saveItemTags($value = [])
	{
		if (!is_array($value))
		{
			return $value;
		}

		foreach ($value as &$item)
		{
			if (!isset($item['tags']) || !is_string($item['tags']))
			{
				$item['tags'] = [];
				continue;
			}

			if (!$itemTags = json_decode($item['tags'], true))
			{
				$item['tags'] = [];
				continue;
			}

			if (!is_array($itemTags))
			{
				$item['tags'] = [];
				continue;
			}

			if (!$itemTags)
			{
				$item['tags'] = [];
				continue;
			}

			// Make $itemTags an array of strings
			$itemTags = array_map(function($tag) {
				return (string) $tag;
			}, $itemTags);

			/**
			 * Creates the new tags in the #__tags table.
			 * 
			 * This returns an array of the new tag ids. If a tag isn't new (doesn't have #new# prefix), it will return 0 as its id.
			 * 
			 * We will now store the IDs returned as the tags for the item.
			 */
			$item['tags'] = self::createTagsFromField($itemTags);
		}

		return $value;
	}

    /**
     * Create any new tags by looking for #new# in the strings
     *
     * @param   array  $tags  Tags text array from the field
     *
     * @return  mixed   If successful, metadata with new tag titles replaced by tag ids. Otherwise false.
     *
     * @since   3.1
     */
    public static function createTagsFromField($tags)
    {
        if (empty($tags) || $tags[0] == '')
		{
            return;
        }

		// We will use the tags table to store them
		if (defined('nrJ4'))
		{
			$tagTable = Factory::getApplication()->bootComponent('com_tags')->getMVCFactory()->createTable('Tag', 'Administrator');
		}
		else
		{
			Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_tags/tables');
			$tagTable = Table::getInstance('Tag', 'TagsTable');
		}

		$newTags = [];

		foreach ($tags as $key => $tag)
		{
			// Remove the #new# prefix that identifies new tags
			$tagText = str_replace('#new#', '', $tag);

			if ($tagText === $tag)
			{
				$newTags[] = (int) $tag;
			}
			else
			{
				// Clear old data if exist
				$tagTable->reset();

				// Try to load the selected tag
				if ($tagTable->load(['title' => $tagText]))
				{
					$newTags[] = (int) $tagTable->id;
				}
				else
				{
					// Prepare tag data
					$tagTable->id          = 0;
					$tagTable->title       = $tagText;
					$tagTable->published   = 1;
					$tagTable->description = '';

					$tagTable->language = '*';
					$tagTable->access   = 1;

					// Make this item a child of the root tag
					$tagTable->setLocation($tagTable->getRootId(), 'last-child');

					// Try to store tag
					if ($tagTable->check())
					{
						// Assign the alias as path (autogenerated tags have always level 1)
						$tagTable->path = $tagTable->alias;

						if ($tagTable->store())
						{
							$newTags[] = (int) $tagTable->id;
						}
					}
				}
			}
		}

		// At this point $newTags is an array of all tag ids
        return $newTags;
    }

	/**
	 * Sets the custom field item id > field id value "source" to given source image path for the original image path
	 */
	public static function setItemFieldSource($item_id, $field_id, $sourceImagePath, $originalImagePath)
	{
		// Get "value" column from #__fields_values where item_id = $item_id and $field_id = $field_id
		$db = Factory::getDbo();

		$query = $db->getQuery(true)
			->select($db->qn('value'))
			->from($db->qn('#__fields_values'))
			->where($db->qn('item_id') . ' = ' . $db->q($item_id))
			->where($db->qn('field_id') . ' = ' . $db->q($field_id));

		$db->setQuery($query);

		$value = $db->loadResult();

		// If value is empty, return
		if (!$value)
		{
			return;
		}

		// Decode value
		$value = json_decode($value, true);

		// If value is empty, return
		if (!$value)
		{
			return;
		}

		// If value is not an array, return
		if (!is_array($value))
		{
			return;
		}

		// If value has no items, return
		if (!isset($value['items']))
		{
			return;
		}

		foreach ($value['items'] as $key => &$item)
		{
			if ($item['image'] !== $originalImagePath)
			{
				continue;
			}

			$item['source'] = $sourceImagePath;
		}

		// Update value
		$query = $db->getQuery(true)
			->update($db->qn('#__fields_values'))
			->set($db->qn('value') . ' = ' . $db->q(json_encode($value)))
			->where($db->qn('item_id') . ' = ' . $db->q($item_id))
			->where($db->qn('field_id') . ' = ' . $db->q($field_id));

		$db->setQuery($query);

		$db->execute();
	}
	
	/**
	 * Media Uploader files look like: https://example.com/images/sampledata/parks/banner_cradle.png
	 * We remove the first part (https://example.com/images/) and keep the other part (relative path to image).
	 * 
	 * @param   string  $filename
	 * 
	 * @return  string
	 */
	private static function getFilePathFromMediaUploaderFile($filename)
	{
		$filenameArray = explode('images/', $filename, 2);
		unset($filenameArray[0]);
		$new_filepath = join($filenameArray);
		return 'images/' . $new_filepath;
	}

	/**
	 * Deletes an uploaded files: source, slideshow, original, and thumbnail.
	 *
	 * @param   string  $source			The source image path.
	 * @param   string  $slideshow		The slideshow image path.
	 * @param   string  $original		The original image path.
	 * @param   string  $thumbnail		The thumbnail image path.
	 *
	 * @return  bool
	 */
	public static function deleteFile($source = null, $slideshow = null, $original = null, $thumbnail = null)
	{
		return [
			'deleted_source_image' => self::findAndDeleteFile($source),
			'deleted_slideshow_image' => self::findAndDeleteFile($slideshow),
			'deleted_full_image' => self::findAndDeleteFile($original),
			'deleted_thumbnail' => self::findAndDeleteFile($thumbnail)
		];
	}

	/**
	 * Deletes the file.
	 * 
	 * @param   string  $filepath
	 * 
	 * @return  mixed
	 */
	private static function findAndDeleteFile($filepath)
	{
		if (!$filepath)
		{
			return;
		}
		
		$file = Path::clean(implode(DIRECTORY_SEPARATOR, [JPATH_ROOT, $filepath]));

		return is_file($file) ? JoomlaCMSFile::delete($file) : false;
	}

	/**
	 * Cleans the temp folder.
	 * 
	 * Removes any image that is 1 day or older.
	 * 
	 * @return  void
	 */
	public static function clean()
	{
		$temp_folder = self::getFullTempFolder();
		
		if (!is_dir($temp_folder))
		{
			return;
		}

		// Get images
		$files = array_diff(scandir($temp_folder), ['.', '..', '.DS_Store', 'index.html']);

		$found = [];

		foreach ($files as $key => $filename)
		{
			$file_path = implode(DIRECTORY_SEPARATOR, [$temp_folder, $filename]);
			
			// Skip directories
			if (is_dir($file_path))
			{
				continue;
			}

			$diff_in_miliseconds = time() - filemtime($file_path);

			// Skip the file if it's not old enough
			if ($diff_in_miliseconds < (60 * 60 * 24 * self::$temp_files_cleanup_days))
			{
				continue;
			}

			$found[] = $file_path;
		}

		if (!$found)
		{
			return;
		}

		// Delete found old files
		foreach ($found as $file)
		{
			unlink($file);
		}
	}

	/**
	 * Full temp directory where images are uploaded
	 * prior to them being saved in the final directory.
	 * 
	 * @param   string  $context
	 * @param   string  $field_id
	 * @param   string  $item_id
	 * 
	 * @return  string
	 */
	public static function getFullTempFolder($context = 'default', $field_id = '', $item_id = '')
	{
		$tmpdir = Factory::getConfig()->get('tmp_path');

		$paths = [
			$tmpdir,
			'tassos',
			\NRFramework\VisitorToken::getInstance()->get(),
			$context === 'module' ? 'smilepack' : 'acf',
			'gallery',
			$item_id,
			$field_id
		];

		$paths = array_filter($paths);
		
		return implode(DIRECTORY_SEPARATOR, $paths);
	}

	/**
	 * Deletes a specific tag from every gallery item.
	 * 
	 * @param   int     $tag_id
	 * @param   string  $context
	 * 
	 * @return  void
	 */
	public static function deleteTagFromFieldsValues($tag_id = null, $context = '')
	{
		if (!$tag_id)
		{
			return;
		}

		if ($context === '')
		{
			self::deleteTagFromCustomFieldsByTagId($tag_id);
			self::deleteTagFromSubformCustomFieldsByTagId($tag_id);
		}
	}

	/**
	 * Deletes a specific tag from every gallery item custom field.
	 * 
	 * @param   int  $tag_id
	 * 
	 * @return  void
	 */
	private static function deleteTagFromCustomFieldsByTagId($tag_id = null)
	{
		if (!$tag_id)
		{
			return;
		}

		$db = Factory::getDbo();
        $query = $db->getQuery(true)
            ->select('f.id as field_id, fv.item_id as item_id, fv.value as value')
            ->from('#__fields as f')
            ->join('LEFT', '#__fields_values AS fv ON fv.field_id = f.id')
            ->where('f.type = ' . $db->quote('acfgallery'));

        $db->setQuery($query);

        $fields = $db->loadAssocList();

		if (!$fields)
		{
			return;
		}

        foreach ($fields as $field)
        {
            if (!$decoded_value = json_decode($field['value'], true))
            {
                continue;
            }

            if (!isset($decoded_value['items']))
            {
                continue;
            }

            $update = false;

            foreach ($decoded_value['items'] as &$item)
            {
                if (!isset($item['tags']))
                {
                    continue;
                }

                if (!is_array($item['tags']))
                {
                    continue;
                }

                if (!count($item['tags']))
                {
                    continue;
                }

                $item['tags'] = array_values($item['tags']);

                if (($key = array_search($tag_id, $item['tags'])) !== false)
                {
                    $update = true;
                    unset($item['tags'][$key]);
                }

                $item['tags'] = array_values($item['tags']);
            }

            if (!$update)
            {
				continue;
			}
		
			$field['value'] = json_encode($decoded_value);

			// Update field value
			$query->clear()
				->update('#__fields_values')
				->set($db->quoteName('value') . ' = ' . $db->quote($field['value']))
				->where($db->quoteName('field_id') . ' = ' . $db->quote($field['field_id']))
				->where($db->quoteName('item_id') . ' = ' . $db->quote($field['item_id']));
			$db->setQuery($query);
			$db->execute();
        }
	}

	/**
	 * Deletes a specific tag from every gallery item that exists in a subform custom field.
	 * 
	 * @param   int  $tag_id
	 * 
	 * @return  void
	 */
	private static function deleteTagFromSubformCustomFieldsByTagId($tag_id = null)
	{
		if (!$tag_id)
		{
			return;
		}

		if (!$tag_id)
		{
			return;
		}

		$db = Factory::getDbo();

		// Get all ACF Gallery custom field IDs
		$query = $db->getQuery(true)
			->select('distinct f.id')
			->from('#__fields as f')
			->join('LEFT', '#__fields_values AS fv ON fv.field_id = f.id')
			->where('f.type = ' . $db->quote('acfgallery'));
			$db->setQuery($query);
		$gallery_field_ids = array_keys($db->loadAssocList('id'));

		if (!$gallery_field_ids)
		{
			return;
		}
		
		// Get all Subform custom fields
        $query->clear()
            ->select('f.id as field_id, fv.item_id as item_id, fv.value as value')
            ->from('#__fields as f')
            ->join('LEFT', '#__fields_values AS fv ON fv.field_id = f.id')
            ->where('f.type = ' . $db->quote('subform'));
        $db->setQuery($query);
        $subform_fields = $db->loadAssocList();

		foreach ($subform_fields as $subform_field)
		{
			if (!$subform_field_items = json_decode($subform_field['value'], true))
			{
				continue;
			}

			$update = false;

			foreach ($subform_field_items as $row => &$row_items)
			{
				if (!is_array($row_items))
				{
					continue;
				}

				foreach ($row_items as $field_name => &$field_value)
				{
					// Get the field id
					$field_id = str_replace('field', '', $field_name);
					
					// Check if its a gallery field
					if (!in_array($field_id, $gallery_field_ids))
					{
						continue;
					}

					if (!isset($field_value['items']))
					{
						continue;
					}

					foreach ($field_value['items'] as &$item)
					{
						if (!isset($item['tags']))
						{
							continue;
						}

						if (!is_array($item['tags']))
						{
							continue;
						}

						if (!count($item['tags']))
						{
							continue;
						}

						$item['tags'] = array_values($item['tags']);

						if (($key = array_search($tag_id, $item['tags'])) !== false)
						{
							$update = true;
							unset($item['tags'][$key]);
						}

						$item['tags'] = array_values($item['tags']);
					}
				}
			}

			if (!$update)
			{
				continue;
			}
			
			$subform_field['value'] = json_encode($subform_field_items);

			// Update subform field value
			$query->clear()
				->update('#__fields_values')
				->set($db->quoteName('value') . ' = ' . $db->quote($subform_field['value']))
				->where($db->quoteName('field_id') . ' = ' . $db->quote($subform_field['field_id']))
				->where($db->quoteName('item_id') . ' = ' . $db->quote($subform_field['item_id']));
			$db->setQuery($query);
			$db->execute();
		}
	}
}Widgets/MapAddress.php000064400000002056152417673120010721 0ustar00<?php

/**
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            https://www.tassos.gr
 * @copyright       Copyright © 2024 Tassos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 */

namespace NRFramework\Helpers\Widgets;

defined('_JEXEC') or die;

use Joomla\CMS\Language\Text;

class MapAddress
{
	/**
	 * Returns the default address details layout.
	 * 
	 * @param   array  $address
	 * @param   array  $showAddressDetails
	 * 
	 * @return  string
	 */
	public static function getDefaultAddressDetailsLayout($address = [], $showAddressDetails = [])
	{
		if (empty($address) || empty($showAddressDetails))
		{
			return;
		}
		
		$html = '';

		$template = '<div class="nrf-mapaddress-field-address-detail-item"><strong>%s</strong>: %s</div>';

		foreach ($showAddressDetails as $key)
		{
			$value = isset($address[$key]) ? $address[$key] : '';

			if (empty($value))
			{
				continue;
			}
			
			$html .= sprintf($template, Text::_('NR_' . strtoupper($key)), $value);
		}

		return $html;
	}
}CSS.php000064400000001504152417673170005722 0ustar00<?php

/**
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            https://www.tassos.gr
 * @copyright       Copyright © 2024 Tassos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 */

namespace NRFramework\Helpers;

defined('_JEXEC') or die;

class CSS
{
	/**
	 * Transforms an array of CSS variables (key, value) to
	 * a CSS output.
	 * 
	 * @param   array   $cssVars
	 * @param   string  $namespace
	 * 
	 * @return  string
	 */
    public static function cssVarsToString($cssVars, $namespace)
    {
        $output = '';

        foreach (array_filter($cssVars) as $key => $value)
        {
            $output .= '--' . $key . ': ' . $value . ';' . "\n";
        }

        return $namespace . ' {
                ' . $output . '
            }
        ';
    }
}ChainedFields.php000064400000006124152417673200007751 0ustar00<?php
/**
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            https://www.tassos.gr
 * @copyright       Copyright © 2024 Tassos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 */

namespace NRFramework\Helpers;

defined('_JEXEC') or die;

class ChainedFields
{
	/**
	 * Loads a combined array of the inputs and choices of the CSV file.
	 * 
	 * @param   string  $path
	 * @param   string  $data_source
	 * @param   string  $separator
	 * @param   string  $id_prefix
	 * @param   string  $name_prefix
	 * 
	 * @return  array
	 */
	public static function loadCSV($input, $data_source = 'custom', $separator = ',', $id_prefix = '', $name_prefix = '')
	{
		if (!$separator)
		{
			return [];
		}

		if ($data_source === 'csv_file')
		{
			if (!file_exists($input))
			{
				return [];
			}
	
			if (!$input = file_get_contents($input))
			{
				return [];
			}
		}

		if (!$data = self::getData($input, $separator, $id_prefix, $name_prefix))
		{
			return [];
		}

		return $data;
	}

	/**
	 * Iterates over the given data and returns the inputs and choices.
	 * 
	 * @param   string  $data
	 * @param   string  $separator
	 * @param   string  $id_prefix
	 * @param   string  $name_prefix
	 * 
	 * @return  array
	 */
	public static function getData($data = '', $separator = ',', $id_prefix = '', $name_prefix = '')
	{
		if (!$data || !is_string($data))
		{
			return;
		}
		
		if (!$rows = explode(PHP_EOL, $data))
		{
			return;
		}

		$choices = [];
		$inputs = [];

		foreach ($rows as $row)
		{
			$row = explode($separator, $row);

			$row = array_filter($row, 'strlen');

			// if an empty row was found, skip it
			if (empty($row))
			{
				continue;
			}

			if (empty($inputs))
			{
				$i = 1;
				
				foreach ($row as $index => $item)
				{
					if ($i % 10 == 0)
					{
						$i++;
					}

					$inputs[] = [
						'id'    => $id_prefix . $i,
						'name'  => $name_prefix . '[' . $i . ']',
						'label' => trim($item),
					];

					$i++;
				}

				continue;
			}

			$parent = null;

			foreach($row as $item)
			{
				$item = trim($item);
				
				if ($parent === null)
				{
					$parent = &$choices;
				}

				if (!isset($parent[$item]))
				{
					$item = trim($item);

					$parent[$item] = [
						'text'       => $item,
						'value'      => $item,
						'isSelected' => false,
						'choices'    => []
					];
				}

				$parent = &$parent[$item]['choices'];
			}
		}

		self::array_values_recursive($choices);

		if (!isset($inputs) || !isset($choices))
		{
			return;
		}

		return compact('inputs', 'choices');
	}

	/**
	 * Transforms an array to using as key an index value instead of a alphanumeric.
	 * 
	 * @param   array   $choices
	 * @param   string  $property
	 * 
	 * @return  array
	 */
	public static function array_values_recursive(&$choices, $property = 'choices')
	{
		$choices = array_values($choices);

		for($i = 0; $i <= count($choices); $i++)
		{
			if(empty($choices[$i][$property]))
			{
				continue;
			}

			$choices[$i][$property] = self::array_values_recursive($choices[$i][$property], $property);
        }

		return $choices;
	}
}CustomField.php000064400000002113152417673210007500 0ustar00<?php

/**
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            https://www.tassos.gr
 * @copyright       Copyright © 2024 Tassos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 */

namespace NRFramework\Helpers;

defined('_JEXEC') or die;

use Joomla\CMS\Factory;
use \Joomla\Registry\Registry;

class CustomField
{
	/**
	 * Get a custom field's data.
	 *
	 * @param  integer  $value
	 * @param  string   $selector
	 *
	 * @return object
	 */
    public static function getData($value, $selector = 'id')
    {
		if (!$value)
		{
			return;
		}

        $db = Factory::getDbo();

        $query = $db->getQuery(true);

        $query
            ->select($db->quoteName(['fieldparams']))
            ->from($db->quoteName('#__fields'))
            ->where($db->quoteName($selector) . ' = ' . $db->quote($value))
            ->where($db->quoteName('state') . ' = 1');

        $db->setQuery($query);

        if (!$result = $db->loadResult())
        {
            return;
        }

        return new Registry($result);
    }
}File.php000064400000002622152417673210006146 0ustar00<?php

/**
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            https://www.tassos.gr
 * @copyright       Copyright © 2024 Tassos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 */

namespace NRFramework\Helpers;

defined('_JEXEC') or die;

use Joomla\CMS\Uri\Uri;

class File
{
	public static function getFileSources($sources, $allowedExtensions = null)
    {
        if (!$sources)
        {
            return;
        }

        // Support comma separated values
        $sources = is_array($sources) ? $sources : explode(',', $sources);
        $result  = [];
		$ds 	 = DIRECTORY_SEPARATOR;

        foreach ($sources as $source)
        {
            if (!$pathinfo = pathinfo($source))
            {
                continue;
            }

            if (!isset($pathinfo['extension']))
            {
                continue;
            }
            
            if ($allowedExtensions && !in_array($pathinfo['extension'], $allowedExtensions))
            {
                continue;
            }

            // Add root path to local source
            if (strpos($source, 'http') === false)
            {
                $source = Uri::root() . ltrim($source, '/');
            }

            $result[] = [
                'ext'  => $pathinfo['extension'],
                'file' => $source
			];
        }

        return $result;
    }
}Geo.php000064400000001516152417673210006002 0ustar00<?php

/**
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            https://www.tassos.gr
 * @copyright       Copyright © 2024 Tassos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 */

namespace NRFramework\Helpers;

defined('_JEXEC') or die;

class Geo
{
    /**
     * Detect and return the visitor's country.
     *
     * @return  string   The visitor's country code (GR)
     */
    public static function getVisitorCountryCode()
    {
    	$path = JPATH_PLUGINS . '/system/tgeoip/';

    	if (!is_dir($path))
    	{
    		return '';
    	}

    	if (!class_exists('TGeoIP'))
    	{
        	@include_once $path . 'vendor/autoload.php';
        	@include_once $path . 'helper/tgeoip.php';
    	}

        $geo = new \TGeoIP();
        return $geo->getCountryCode();
    }
}License.php000064400000001614152417673230006653 0ustar00<?php

/**
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            https://www.tassos.gr
 * @copyright       Copyright © 2024 Tassos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 */

namespace NRFramework\Helpers;

defined('_JEXEC') or die;

use Joomla\CMS\Http\HttpFactory;

class License
{
	/**
	 * Returns the remote license data from the server for the given download key.
	 * 
	 * @return  array
	 */
	public static function getRemoteLicenseData($download_key = null)
	{
		if (!$download_key)
		{
			return;
		}
		
		// License Check Endpoint
		$url = TF_CHECK_LICENSE;
		// Set Download Key
		$url = str_replace('{{DOWNLOAD_KEY}}', $download_key, $url);
		
		$response = HttpFactory::getHttp()->get($url);
		
		// No response, abort
		if (!$response = $response->body)
		{
			return;
		}

		return json_decode($response, true);
	}
}Module.php000064400000002055152417673250006520 0ustar00<?php

/**
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            https://www.tassos.gr
 * @copyright       Copyright © 2023 Tassos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 */

namespace NRFramework\Helpers;

defined('_JEXEC') or die;

use Joomla\CMS\Factory;

class Module
{
	/**
	 * Get a module data.
	 *
	 * @param  integer  $value
	 * @param  string   $selector
	 *
	 * @return object
	 */
    public static function getData($value, $selector = 'id')
    {
		if (!$value)
		{
			return;
		}

        $db = Factory::getDbo();

        $query = $db->getQuery(true);

        $query
            ->select($db->quoteName(['params']))
            ->from($db->quoteName('#__modules'))
            ->where($db->quoteName($selector) . ' = ' . $db->quote($value))
            ->where($db->quoteName('access') . ' = 1');

        $db->setQuery($query);

        if (!$result = $db->loadResult())
        {
            return;
        }

        return new \Joomla\Registry\Registry($result);
    }
}Number.php000064400000002746152417673270006534 0ustar00<?php

/**
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            https://www.tassos.gr
 * @copyright       Copyright © 2024 Tassos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 */

namespace NRFramework\Helpers;

defined('_JEXEC') or die;

class Number
{
	/**
	 * Converts a number into a short version, eg: 1000 -> 1k
	 *
	 * @param	Number		$n			The number to create the shorter version
	 * @param	integer		$precision	
	 * 
	 * @return	string		The shorter version of the given number
	 */
	public static function toShortFormat($n, $precision = 1)
	{
		if ($n < 900)
		{
			// 0 - 900
			$n_format = number_format($n, $precision);
			$suffix = '';
		} else if ($n < 900000)
		{
			// 0.9k-850k
			$n_format = number_format($n / 1000, $precision);
			$suffix = 'K';
		} else if ($n < 900000000)
		{
			// 0.9m-850m
			$n_format = number_format($n / 1000000, $precision);
			$suffix = 'M';
		} else if ($n < 900000000000)
		{
			// 0.9b-850b
			$n_format = number_format($n / 1000000000, $precision);
			$suffix = 'B';
		} else
		{
			// 0.9t+
			$n_format = number_format($n / 1000000000000, $precision);
			$suffix = 'T';
		}
	
		// Remove unecessary zeroes after decimal. "1.0" -> "1"; "1.00" -> "1"
		// Intentionally does not affect partials, eg "1.50" -> "1.50"
		if ($precision > 0)
		{
			$dotzero = '.' . str_repeat( '0', $precision );
			$n_format = str_replace( $dotzero, '', $n_format );
		}
	
		return $n_format . $suffix;
	}
}Responsive.php000064400000017262152417673310007433 0ustar00<?php

/**
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            https://www.tassos.gr
 * @copyright       Copyright © 2024 Tassos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 */

namespace NRFramework\Helpers;

defined('_JEXEC') or die;

use NRFramework\Cache;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Plugin\PluginHelper;

class Responsive
{
	/**
	 * Renders the given CSS.
	 * 
	 * @param   array   $css
	 * @param   string  $selector
	 * 
	 * @return  string
	 */
	public static function renderResponsiveCSS($css, $selector = '')
	{
		if (!$css || !is_array($css))
		{
			return;
		}
		
		$output = '';

		foreach (self::getBreakpoints() as $breakpoint => $breakpoint_data)
		{
			if (!isset($css[$breakpoint]) || empty($css[$breakpoint]))
			{
				continue;
			}

			/**
			 * If we were given an array of strings of CSS, transform them to a string so we can output it.
			 * 
			 * i.e. transform
			 * [
			 * 	 'color: #fff;',
			 * 	 'background: #000;'
			 * ]
			 * 
			 * to:
			 * 
			 * 'color: #fff;background: #000;'
			 */
			if (!is_string($css[$breakpoint]))
			{
				$css[$breakpoint] = implode('', $css[$breakpoint]);
			}

			$max_width = isset($breakpoint_data['max_width']) ? $breakpoint_data['max_width'] : '';
			
			$output .= self::getGenericTemplate($css[$breakpoint], $max_width, $selector);
		}

		return $output;
	}

	/**
	 * Returns the responsive output of a specific media query size.
	 * 
	 * @param   string  $css		 The Custom CSS
	 * @param   int		$size	     This is the max-width in pixels
	 * @param   string  $selector    The CSS Selector to apply the CSS
	 * 
	 * @return  string
	 */
	public static function getGenericTemplate($css, $size = '', $selector = '')
	{
		if (!is_string($css) || !is_scalar($size) || !is_string($selector))
		{
			return '';
		}
		
		$selector_prefix = $selector_suffix = $size_prefix = $size_suffix = '';
		
		if (!empty($size))
		{
			$size_prefix = '@media screen and (max-width: ' . $size . 'px){';
			$size_suffix = '}';
		}

		if (!empty($selector))
		{
			$selector_prefix = $selector . '{';
			$selector_suffix = '}';
		}
		
		return $size_prefix . $selector_prefix . $css . $selector_suffix . $size_suffix;
	}

    /**
     * Returns all breakpoints.
     * 
     * @return  array
     */
    public static function getBreakpoints()
    {
		$breakpointsSettings = self::getBreakpointsSettings();

		$tablet_max_width = isset($breakpointsSettings['tablet']) && !empty($breakpointsSettings['tablet']) ? $breakpointsSettings['tablet'] : 1024;
		$mobile_max_width = isset($breakpointsSettings['mobile']) && !empty($breakpointsSettings['mobile']) ? $breakpointsSettings['mobile'] : 575;
		
        return [
            'desktop' => [
                'icon' => '<svg width="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><mask id="mask0_112_458" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="24"><rect width="24" height="24" fill="#D9D9D9"/></mask><g mask="url(#mask0_112_458)"><path d="M8.5 20.5V19H10.5V17H4.3077C3.80257 17 3.375 16.825 3.025 16.475C2.675 16.125 2.5 15.6974 2.5 15.1923V5.3077C2.5 4.80257 2.675 4.375 3.025 4.025C3.375 3.675 3.80257 3.5 4.3077 3.5H19.6923C20.1974 3.5 20.625 3.675 20.975 4.025C21.325 4.375 21.5 4.80257 21.5 5.3077V15.1923C21.5 15.6974 21.325 16.125 20.975 16.475C20.625 16.825 20.1974 17 19.6923 17H13.5V19H15.5V20.5H8.5ZM4.3077 15.5H19.6923C19.7692 15.5 19.8397 15.468 19.9038 15.4039C19.9679 15.3398 20 15.2692 20 15.1923V5.3077C20 5.23077 19.9679 5.16024 19.9038 5.09613C19.8397 5.03203 19.7692 4.99998 19.6923 4.99998H4.3077C4.23077 4.99998 4.16024 5.03203 4.09613 5.09613C4.03202 5.16024 3.99998 5.23077 3.99998 5.3077V15.1923C3.99998 15.2692 4.03202 15.3398 4.09613 15.4039C4.16024 15.468 4.23077 15.5 4.3077 15.5Z" fill="currentColor"/></g></svg>',
                'label' => Text::_('NR_DESKTOP'),
                'desc' => Text::_('NR_DESKTOPS_WITH_BREAKPOINT_INFO')
            ],
            'tablet' => [
                'icon' => '<svg width="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><mask id="mask0_112_446" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="24"><rect width="24" height="24" fill="#D9D9D9"/></mask><g mask="url(#mask0_112_446)"><path d="M12 20.2692C12.2448 20.2692 12.4535 20.183 12.6259 20.0105C12.7984 19.8381 12.8846 19.6294 12.8846 19.3846C12.8846 19.1397 12.7984 18.9311 12.6259 18.7586C12.4535 18.5862 12.2448 18.5 12 18.5C11.7551 18.5 11.5465 18.5862 11.374 18.7586C11.2016 18.9311 11.1154 19.1397 11.1154 19.3846C11.1154 19.6294 11.2016 19.8381 11.374 20.0105C11.5465 20.183 11.7551 20.2692 12 20.2692ZM5.3077 22.5C4.80898 22.5 4.38302 22.3233 4.02982 21.9701C3.67661 21.6169 3.5 21.191 3.5 20.6923V3.3077C3.5 2.80898 3.67661 2.38302 4.02982 2.02982C4.38302 1.67661 4.80898 1.5 5.3077 1.5H18.6923C19.191 1.5 19.6169 1.67661 19.9701 2.02982C20.3233 2.38302 20.5 2.80898 20.5 3.3077V20.6923C20.5 21.191 20.3233 21.6169 19.9701 21.9701C19.6169 22.3234 19.191 22.5 18.6923 22.5L5.3077 22.5ZM4.99997 17.7692V20.6923C4.99997 20.782 5.02883 20.8557 5.08653 20.9134C5.14423 20.9711 5.21795 21 5.3077 21H18.6923C18.782 21 18.8557 20.9711 18.9134 20.9134C18.9711 20.8557 19 20.782 19 20.6923V17.7692H4.99997ZM4.99997 16.2692H19V5.74995H4.99997V16.2692ZM4.99997 4.25H19V3.3077C19 3.21795 18.9711 3.14423 18.9134 3.08652C18.8557 3.02882 18.782 2.99998 18.6923 2.99998H5.3077C5.21795 2.99998 5.14423 3.02882 5.08653 3.08652C5.02883 3.14423 4.99997 3.21795 4.99997 3.3077V4.25Z" fill="currentColor"/></g></svg>',
                'label' => Text::_('NR_TABLET'),
                'desc' => Text::sprintf('NR_TABLETS_WITH_BREAKPOINT_INFO', $tablet_max_width),
				'max_width' => $tablet_max_width
            ],
            'mobile' => [
                'icon' => '<svg width="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><mask id="mask0_112_452" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="24"><rect width="24" height="24" fill="#D9D9D9"/></mask><g mask="url(#mask0_112_452)"><path d="M7.3077 22.4999C6.80257 22.4999 6.375 22.3249 6.025 21.9749C5.675 21.6249 5.5 21.1974 5.5 20.6923V3.3077C5.5 2.80257 5.675 2.375 6.025 2.025C6.375 1.675 6.80257 1.5 7.3077 1.5H16.6922C17.1974 1.5 17.625 1.675 17.975 2.025C18.325 2.375 18.5 2.80257 18.5 3.3077V20.6923C18.5 21.1974 18.325 21.625 17.975 21.975C17.625 22.325 17.1974 22.5 16.6922 22.5L7.3077 22.4999ZM6.99997 19.75V20.6923C6.99997 20.7692 7.03202 20.8397 7.09613 20.9039C7.16024 20.968 7.23077 21 7.3077 21H16.6922C16.7692 21 16.8397 20.968 16.9038 20.9039C16.9679 20.8397 17 20.7692 17 20.6923V19.75H6.99997ZM6.99997 18.25H17V5.74998H6.99997V18.25ZM6.99997 4.25003H17V3.30773C17 3.23079 16.9679 3.16027 16.9038 3.09615C16.8397 3.03205 16.7692 3 16.6922 3H7.3077C7.23077 3 7.16024 3.03205 7.09613 3.09615C7.03202 3.16027 6.99997 3.23079 6.99997 3.30773V4.25003Z" fill="currentColor"/></g></svg>',
                'label' => Text::_('NR_MOBILE'),
                'desc' => Text::sprintf('NR_MOBILES_WITH_BREAKPOINT_INFO', $mobile_max_width),
				'max_width' => $mobile_max_width
            ]
        ];
    }

	public static function getBreakpointsSettings()
	{
        $hash = 'tassosResponsiveBreakpoints';

        if (Cache::has($hash))
        {
            return Cache::get($hash);
        }

		$settings = PluginHelper::getPlugin('system', 'nrframework');

		$default = [
			'desktop' => 'any',
			'tablet' => 1024,
			'mobile' => 575
		];
		
		if (!isset($settings->params))
		{
			return $default;
		}

		if (!$params = json_decode($settings->params, true))
		{
			return $default;
		}

		$data = isset($params['breakpoints']) ? $params['breakpoints'] : [];

        return Cache::set($hash, $data);
	}
}Template.php000064400000002152152417673330007043 0ustar00<?php

/**
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            https://www.tassos.gr
 * @copyright       Copyright © 2024 Tassos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 */

namespace NRFramework\Helpers;

defined('_JEXEC') or die;

use NRFramework\Cache;
use Joomla\CMS\Factory;

class Template
{
	/**
	 * Returns the current template name.
	 * 
	 * @return  string
	 */
	public static function getTemplateName()
	{
        $hash = 'TFGetTemplateName';

        if (Cache::has($hash))
        {
            return Cache::get($hash);
        }

		$template = null;
		
		if (Factory::getApplication()->isClient('site'))
		{
			$template = Factory::getApplication()->getTemplate();
		}
		else
		{
			$db = Factory::getDbo();
			$query = $db->getQuery(true)
				->select($db->quoteName('template'))
				->from($db->quoteName('#__template_styles'))
				->where($db->quoteName('client_id') . ' = 0')
				->where($db->quoteName('home') . ' = 1');
			$db->setQuery($query);
			$template = $db->loadResult();
		}
		
		return Cache::set($hash, $template);
	}
}Video.php000064400000003756152417673350006353 0ustar00<?php

/**
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            https://www.tassos.gr
 * @copyright       Copyright © 2024 Tassos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 */

namespace NRFramework\Helpers;

defined('_JEXEC') or die;

class Video
{
	/**
	 * Returns the Video URL details.
	 * 
	 * Supported platforms:
	 * - YouTube
	 * - Vimeo
	 * 
	 * @param   string  $url
	 * 
	 * @return  array
	 */
	public static function getDetails($url)
	{
		$id = '';
		$provider = '';

		if (preg_match(self::getYouTubePattern(), $url, $matches))
		{
			$id = !empty($matches[1]) ? $matches[1] : $matches[2];
			$provider = 'youtube';
		}
		else if (preg_match(self::getVimeoPattern(), $url, $matches))
		{
			$id = !empty($matches[1]) ? $matches[1] : null;
			$provider = 'vimeo';
		}
		else if (preg_match(self::getFacebookVideoPattern(), $url))
		{
			$id = $url;
			$provider = 'facebookvideo';
		}
		else if (preg_match(self::getDailymotionPattern(), $url, $matches))
		{
			$id = end($matches);
			$provider = 'dailymotion';
		}
		
		return [
			'id' => $id,
			'provider' => $provider
		];
	}

	/**
	 * Get YouTube Pattern.
	 * 
	 * @return  string
	 */
	public static function getYouTubePattern()
	{
		return '/^https?:\/\/(?:m\.|www\.)?youtube\.com\/(?:watch\?v=|embed\/)?([a-zA-Z0-9_-]{11})|^https?:\/\/youtu\.be\/([a-zA-Z0-9_-]{11})/';
	}

	/**
	 * Get Vimeo Pattern.
	 * 
	 * @return  string
	 */
	public static function getVimeoPattern()
	{
		return '/^https?:\/\/(?:www\.)?(?:player\.)?vimeo\.com\/(\d+)/';
	}

	/**
	 * Get Facebook Video Pattern.
	 * 
	 * @return  string
	 */
	public static function getFacebookVideoPattern()
	{
		return '/^(?:(?:https?:)?\/\/)?(?:www\.)?facebook\.com\/(?:watch\/\?v=|[\w\.]+\/videos\/(?:[\w\.]+\/)?)?(\d+)/';
	}

	/**
	 * Get Dailymotion Pattern.
	 * 
	 * @return  string
	 */
	public static function getDailymotionPattern()
	{
		return '/(?:dailymotion\.com\/(?:video|hub)\/|dai\.ly\/)([a-zA-Z0-9]+)/';
	}
}