<?php

/**
 * @package SP Page Builder
 * @author JoomShaper http://www.joomshaper.com
 * @copyright Copyright (c) 2010 - 2023 JoomShaper
 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPLv2 or later
 */
//no direct access
defined('_JEXEC') or die('Restricted access');

use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\Filesystem\Folder;
use Joomla\CMS\Filesystem\Path;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Plugin\PluginHelper;

require_once JPATH_ROOT . '/components/com_sppagebuilder/builder/classes/base.php';

class SppagebuilderHelperSite
{
	/**
	 * Addon structures
	 *
	 * @var array
	 */
	private static $addonFieldStructures = [];

	/**
	 * Column settings structure
	 *
	 * @var array
	 */
	private static $columnFieldStructures = [];

	/**
	 * Section/row settings structure
	 *
	 * @var array
	 */
	private static $sectionFieldStructures = [];

	/**
	 * Section field types
	 *
	 * @var array
	 */
	private static $sectionFieldTypes = [];

	/**
	 * Column field types
	 *
	 * @var array
	 */
	private static $columnFieldTypes = [];

	/**
	 * The fields types
	 *
	 * @var array
	 */
	private static $addonFieldTypes = [];


	/**
	 * Check if the data come from the version 3 or bellow.
	 *
	 * @var boolean
	 */
	private static $isLegacyData = false;

	private static function getAddonGlobalFieldStructures()
	{
		$structures = [];

		$globalSettings = SpPgaeBuilderBase::addonOptions();

		foreach ($globalSettings as $setting)
		{
			$structures = array_merge($structures, $setting);
		}

		return $structures;
	}


	/**
	 * Load the addon structure from the admin.php files.
	 *
	 * @return void
	 */
	private static function prepareAddonFieldStructures()
	{
		self::loadLanguage();
		SpPgaeBuilderBase::loadAddons();
		$addons =  SpAddonsConfig::$addons;
		$globalStructures = self::getAddonGlobalFieldStructures();

		foreach ($addons as &$addon)
		{
			$addon = AddonsHelper::modernizeAddonStructure($addon);
			$addon['addon_name'] = preg_replace('/^sp_/i', '', $addon['addon_name']);
			$addon['settings'] = array_merge($addon['settings'], $globalStructures);
		}

		unset($addon);

		self::$addonFieldStructures = $addons;
	}

	private static function prepareSectionFieldStructures()
	{
		$sectionSettings = SpPgaeBuilderBase::getRowGlobalSettings();

		foreach ($sectionSettings as $setting)
		{
			self::$sectionFieldStructures = array_merge(self::$sectionFieldStructures, $setting);
		}
	}

	private static function prepareColumnFieldStructures()
	{
		$columnSettings = SpPgaeBuilderBase::getColumnGlobalSettings();

		foreach ($columnSettings as $setting)
		{
			self::$columnFieldStructures = array_merge(self::$columnFieldStructures, $setting);
		}
	}

	/**
	 * Predict the column's fill style if it is fit in a single line
	 * or multiple line.
	 *
	 * @param     array         $columns     The columns array.
	 *
	 * @return     stdClass     The fit value after prediction.
	 * @since     4.0.0
	 */
	private static function predictColumnFillStyle(array $columns): stdClass
	{
		$fitObject = (object) ['xl' => false, 'lg' => false, 'md' => false, 'sm' => false, 'xs' => false];

		foreach (['xl', 'lg', 'md', 'sm', 'xs'] as $key)
		{
			$total = 0;

			foreach ($columns as $column)
			{
				$width = self::getColumnWidth($column);
				$total += (float) $width->$key;
			}

			if ($total <= 100)
			{
				$fitObject->$key = true;
			}
		}

		return $fitObject;
	}

	/**
	 * Generate the column widths from the columns class_name property.
	 *
	 * @param     stdClass     $column     The column object.
	 *
	 * @return     stdClass                 The generated width object for multiple device.
	 * @since     4.0.0
	 */
	public static function getColumnWidth(stdClass $column): stdClass
	{
		$width = (object) ['xl' => '100%', 'lg' => '100%', 'md' => '100%', 'sm' => '100%', 'xs' => '100%'];
		$size = (int) \substr($column->class_name, 7);
		$value = self::calculateColumnPercent($size);

		$width->xl = $value;

		if (!empty($column->class_name) && empty($column->settings->sm_col))
		{
			$width->lg = $value;
		}

		if (!empty($column->settings->sm_col))
		{
			$smSize = (int) \substr($column->settings->sm_col, 7);
			$width->lg = self::calculateColumnPercent($smSize);
			$width->md = self::calculateColumnPercent($smSize);
		}

		if (!empty($column->settings->xs_col))
		{
			$xsSize = (int) \substr($column->settings->xs_col, 7);
			$width->sm = self::calculateColumnPercent($xsSize);
			$width->xs = self::calculateColumnPercent($xsSize);
		}

		return $width;
	}

	private static function isValidWidthValue($width)
	{
		return preg_match("@^\d+(\.\d+)?%$@", $width);
	}

	public static function purifyColumnWidth($width)
	{
		$defaultDevice = SpPgaeBuilderBase::$defaultDevice;


		$smallDevices = ['sm', 'xs'];

		if (\is_object($width))
		{
			$deviceData = $width->$defaultDevice;

			foreach ($width as $device => $value)
			{
				if (!self::isValidWidthValue($value))
				{
					if (\in_array($device, $smallDevices))
					{
						$width->$device = '100%';
					}
					else
					{
						$width->$device = !empty($deviceData) ? $deviceData : '100%';
					}
				}
			}
		}

		return $width;
	}

	/**
	 * Calculate the column percentage from the column size.
	 *
	 * @param     int     $size     The size value ranged from 1 to 12.
	 *
	 * @return    string            The percentage value w.r.t the the size.
	 * @since     4.0.0
	 */
	private static function calculateColumnPercent(int $size): string
	{
		return ((100 / 12) * (int) $size) . '%';
	}

	// Generate unique id
	public static function nanoid(int $size = 21): string
	{
		$urlAlphabet = "ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW";
		$id = "";
		$i = $size;

		while ($i--)
		{
			$id .= $urlAlphabet[rand(0, 63) | 0];
		}

		return $id;
	}

	// Generate unique id
	public static function numberNanoid(int $size = 21): string
	{
		$urlAlphabet = "0123456789";
		$id = "";
		$i = $size;

		while ($i--)
		{
			$id .= $urlAlphabet[rand(0, 9) | 0];
		}

		return intval($id);
	}

	private static function parseRow(array &$rows, \stdClass &$row)
	{
		if (isset($row->settings))
		{
			$row->settings = self::shiftResponsiveSettings($row->settings);
		}

		if (!empty($row->columns))
		{
			if (!isset($row->settings->fit_columns))
			{
				$row->settings->fit_columns = self::predictColumnFillStyle($row->columns);
			}

			foreach ($row->columns as $i => &$column)
			{
				/** Predict the width from the column class name for the old layouts. */
				if (!isset($column->width))
				{
					$width = self::getColumnWidth($column);
					$column->width = $width;
				}

				if (\is_array($column->settings))
				{
					$column->settings = (object) $column->settings;
				}

				if (!isset($column->settings->width))
				{
					$width = !empty($column->width) ? $column->width : self::getColumnWidth($column);
					$column->settings->width = $width;

					if (isset($column->settings->width->unit))
					{
						unset($column->settings->width->unit);
					}
				}

				$column->settings->width = self::purifyColumnWidth($column->settings->width);

				if (isset($column->settings))
				{
					$column
						->settings = self::shiftResponsiveSettings($column->settings);
				}

				if (!empty($column->addons))
				{
					foreach ($column->addons as $j => &$addon)
					{
						if (isset($addon->settings))
						{
							$addon->settings = self::shiftResponsiveSettings($addon->settings);
						}

						// migrate image layouts addon
						if (isset($addon->name) && $addon->name === 'image_layouts')
						{
							$addonSettings = $addon->settings;

							$isBtnUrlExist = property_exists($addonSettings, 'btn_url');
							$isButtonUrlExist = property_exists($addonSettings, 'button_url');
							$isBtnTargetExist = property_exists($addonSettings, 'btn_target');

							if (!$isButtonUrlExist && $isBtnUrlExist)
							{
								$addonSettings->button_url = [
									'type' => 'url',
									'url' => $addonSettings->btn_url,
									'new_tab' => $isBtnTargetExist ? $addonSettings->btn_target : false,
								];
							}

							$isBtnTextExist = property_exists($addonSettings, 'btn_text');
							$isButtonTextExist = property_exists($addonSettings, 'button_text');

							if (!$isButtonTextExist && $isBtnTextExist)
							{
								$addonSettings->button_text = $addonSettings->btn_text;
							}

							$isBtnTypeExist = property_exists($addonSettings, 'btn_type');
							$isButtonTypeExist = property_exists($addonSettings, 'button_type');

							if (!$isButtonTypeExist && $isBtnTypeExist)
							{
								$addonSettings->button_type = $addonSettings->btn_type;
							}

							$isBtnShapeExist = property_exists($addonSettings, 'btn_shape');
							$isButtonShapeExist = property_exists($addonSettings, 'button_shape');

							if (!$isButtonShapeExist && $isBtnShapeExist)
							{
								$addonSettings->button_shape = $addonSettings->btn_shape;
							}

							$isBtnSizeExist = property_exists($addonSettings, 'btn_size');
							$isButtonSizeExist = property_exists($addonSettings, 'button_size');

							if (!$isButtonSizeExist && $isBtnSizeExist)
							{
								$addonSettings->button_size = $addonSettings->btn_size;
							}

							$isBtnColorExist = property_exists($addonSettings, 'btn_color');
							$isButtonColorExist = property_exists($addonSettings, 'button_color');

							if (!$isButtonColorExist && $isBtnColorExist)
							{
								$addonSettings->button_color = $addonSettings->btn_color;
							}

							$isBtnColorHoverExist = property_exists($addonSettings, 'btn_color_hover');
							$isButtonColorHoverExist = property_exists($addonSettings, 'button_color_hover');

							if (!$isButtonColorHoverExist && $isBtnColorHoverExist)
							{
								$addonSettings->button_color_hover = $addonSettings->btn_color_hover;
							}

							$isBtnAppearanceExist = property_exists($addonSettings, 'btn_appearance');
							$isButtonAppearanceExist = property_exists($addonSettings, 'button_appearance');

							if (!$isButtonAppearanceExist && $isBtnAppearanceExist)
							{
								$addonSettings->button_appearance = $addonSettings->btn_appearance;
							}

							$isBtnBackgroundColorExist = property_exists($addonSettings, 'btn_background_color');
							$isButtonBackgroundColorExist = property_exists($addonSettings, 'button_background_color');

							if (!$isButtonBackgroundColorExist && $isBtnBackgroundColorExist)
							{
								$addonSettings->button_background_color = $addonSettings->btn_background_color;
							}

							$isBtnBackgroundColorHoverExist = property_exists($addonSettings, 'btn_background_color_hover');
							$isButtonBackgroundColorHoverExist = property_exists($addonSettings, 'button_background_color_hover');

							if (!$isButtonBackgroundColorHoverExist && $isBtnBackgroundColorHoverExist)
							{
								$addonSettings->button_background_color_hover = $addonSettings->btn_background_color_hover;
							}

							$isBtnBackgroundGradientExist = property_exists($addonSettings, 'btn_background_gradient');
							$isButtonBackgroundGradientExist = property_exists($addonSettings, 'button_background_gradient');

							if (!$isButtonBackgroundGradientExist && $isBtnBackgroundGradientExist)
							{
								$addonSettings->button_background_gradient = $addonSettings->btn_background_gradient;
							}

							$isBtnBackgroundGradientHoverExist = property_exists($addonSettings, 'btn_background_gradient_hover');
							$isButtonBackgroundGradientHoverExist = property_exists($addonSettings, 'button_background_gradient_hover');

							if (!$isButtonBackgroundGradientHoverExist && $isBtnBackgroundGradientHoverExist)
							{
								$addonSettings->button_background_gradient_hover = $addonSettings->btn_background_gradient_hover;
							}
						}

						/** Migrate the slideshow items. */
						if (isset($addon->name) && $addon->name === 'js_slideshow')
						{
							if (!empty($addon->settings->slideshow_items))
							{
								foreach ($addon->settings->slideshow_items as $x => &$slideshowItem)
								{
									if (isset($slideshowItem->slider_overlay_options) && $slideshowItem->slider_overlay_options === 'gradient_overaly')
									{
										$slideshowItem->slider_overlay_options = 'gradient_overlay';
									}

									if (isset($slideshowItem->slider_bg_gradient_overlay) && !isset($slideshowItem->slider_bg_gradient_overlay->type))
									{
										$slideshowItem->slider_bg_gradient_overlay->type = 'linear';
									}

									$slideshowItem = self::shiftResponsiveSettings($slideshowItem);

									if (!empty($slideshowItem->slideshow_inner_items))
									{
										foreach ($slideshowItem->slideshow_inner_items as $y => &$innerItem)
										{
											$innerItem = self::shiftResponsiveSettings($innerItem);
										}

										unset($innerItem);
									}
								}

								unset($slideshowItem);
							}
						}

						/** Migrate the responsive settings for the repeatable items. */
						if (isset($addon->name) && \in_array($addon->name, ['accordion', 'tab']))
						{
							$repeatableKey = 'sp_' . $addon->name . '_item';

							if (!empty($addon->settings->$repeatableKey))
							{
								foreach ($addon->settings->$repeatableKey as &$itemSetting)
								{
									$itemSetting = self::shiftResponsiveSettings($itemSetting);
								}

								unset($itemSetting);
							}
						}

						if (isset($addon->name) && \in_array($addon->name, ['accordion', 'tab']))
						{
							list($outerRows, $_addon) = self::migrateDeepAddon($addon, 'sp_' . $addon->name . '_item', $row, $column);
							$addon = $_addon;
							array_push($rows, ...$outerRows);
						}

						if (isset($addon->name) && $addon->name === 'table_advanced')
						{
							$nodeId = self::generateUUID();

							if (isset($addon->settings->sp_table_advanced_item))
							{
								foreach ($addon->settings->sp_table_advanced_item as $th => $thead)
								{
									if (isset($thead->content) && !\is_array($thead->content))
									{
										$thead = ['id' => $nodeId++, 'name' => 'text_block', 'visibility' => true, 'reference_id' => $addon->id, 'settings' => ['text' => $thead->content]];
										$thead = \json_decode(\json_encode($thead));
										$addon
											->settings
											->sp_table_advanced_item[$th]
											->content = [];
										$addon
											->settings
											->sp_table_advanced_item[$th]
											->content[] = $thead;
									}
									elseif (isset($thead->content) && \is_array($thead->content))
									{
										$contents = [];

										foreach ($thead->content as $content)
										{
											$content->reference_id = $addon->id;
											$contents[] = $content;
										}

										$addon
											->settings
											->sp_table_advanced_item[$th]
											->content = $contents;
									}
								}
							}

							foreach ($addon->settings->table_advanced_item as $r => $tRow)
							{
								if (isset($tRow->table_advanced_item))
								{
									foreach ($tRow->table_advanced_item as $c => $tCell)
									{
										if (isset($tCell->content) && !\is_array($tCell->content))
										{
											$td = ['id' => $nodeId++, 'name' => 'text_block', 'visibility' => true, 'reference_id' => $addon->id, 'settings' => ['text' => $tCell->content]];
											$td = \json_decode(\json_encode($td));

											$addon
												->settings
												->table_advanced_item[$r]
												->table_advanced_item[$c]
												->content = [];
											$addon
												->settings
												->table_advanced_item[$r]
												->table_advanced_item[$c]
												->content[] = $td;
										}
										elseif (isset($tCell->content) && \is_array($tCell->content))
										{
											$contents = [];

											foreach ($tCell->content as &$content)
											{
												$content->reference_id = $addon->id;
												$contents[] = $content;
											}

											$addon
												->settings
												->table_advanced_item[$r]
												->table_advanced_item[$c]
												->content = $contents;

											unset($content);
										}
									}
								}
							}
						}

						if (isset($addon->type) && $addon->type === 'inner_row')
						{
							$addon->id = self::nanoid();

							$nestedRowAddon = new \stdClass;
							$nestedRowAddon->type = 'nested_row';
							$nestedRowAddon->name = 'row';
							$nestedRowAddon->id = $addon->id;

							$addon->parent = new \stdClass;
							$addon->parent->rowId = $row->id;
							$addon->parent->columnId = $column->id;

							$rows[] = $addon;
							$addon = $nestedRowAddon;
						}
					}

					unset($addon);
				}
			}

			unset($column);
		}
	}

	/**
	 * sanitize import json for making the old data valid for the data structure.
	 *
	 * @param    string    $text    The json text of the page builder data.
	 *
	 * @return   string    The sanitized text.
	 * 
	 * @since    4.0.0
	 */
	public static function sanitizeImportJSON(string $text): string
	{
		$rows = json_decode($text);

		if (is_string($rows))
		{
			$rows = json_decode($rows);
		}

		if (!empty($rows))
		{
			foreach ($rows as $key => &$row)
			{
				self::parseRow($rows, $row);
			}

			unset($row);
		}
		else
		{
			return $text;
		}

		return json_encode($rows);
	}

	private static $defaultValueWithUnit = ['value' => '', 'unit' => 'px'];

	private static function getDefaultTypographyData()
	{
		$deviceData = [
			'xl' => self::$defaultValueWithUnit,
			'lg' => self::$defaultValueWithUnit,
			'md' => self::$defaultValueWithUnit,
			'sm' => self::$defaultValueWithUnit,
			'xs' => self::$defaultValueWithUnit,
		];

		return (object) [
			'font' => '',
			'size' => $deviceData,
			'line_height' => $deviceData,
			'letter_spacing' => $deviceData,
			'uppercase' => false,
			'italic' => false,
			'underline' => false,
			'weight' => '',
			'type' => 'google',
		];
	}

	private static function isUnitValue($data)
	{
		return isset($data->value) && isset($data->unit);
	}

	private static function handleDeviceValues($data, $fieldKey)
	{
		$deviceData = [
			'xl' => self::$defaultValueWithUnit,
			'lg' => self::$defaultValueWithUnit,
			'md' => self::$defaultValueWithUnit,
			'sm' => self::$defaultValueWithUnit,
			'xs' => self::$defaultValueWithUnit,
		];
		$responsiveKeys = ['size', 'line_height', 'letter_spacing'];
		$pattern = "@px|rem|em|%@i";

		if (in_array($fieldKey, $responsiveKeys))
		{
			if (\is_object($data))
			{
				$finalValue = self::$defaultValueWithUnit;

				foreach ($data as $device => $value)
				{
					if (self::isUnitValue($value))
					{
						$finalValue = (array) $value;
						$finalValue['value'] = preg_match($pattern, $finalValue['value']) ? (float) $finalValue['value'] : $finalValue['value'];
						$finalValue['value'] = (string) $finalValue['value'];
					}
					else
					{
						$finalValue['value'] = $value ?? '';
						$finalValue['value'] = preg_match($pattern, $finalValue['value']) ? (float) $finalValue['value'] : $finalValue['value'];
						$finalValue['value'] = (string) $finalValue['value'];
					}

					$deviceData[$device] = $finalValue;
				}
			}
			else
			{
				$deviceData['xl']['value'] = preg_match($pattern, $data) ? (float) $data : $data;
				$deviceData['xl']['value'] = (string) $deviceData['xl']['value'];
			}

			if (!empty($deviceData['md']['value']) && empty($deviceData['xl']['value']))
			{
				$deviceData['xl'] = $deviceData['md'];
			}

			return (object) $deviceData;
		}

		return $data;
	}

	private static function getFieldsWithFallbacks($settings)
	{

		$fieldArray = [];

		if (empty($settings))
		{
			return $fieldArray;
		}

		foreach ($settings as $setting)
		{
			if (!empty($setting['fields']))
			{
				foreach ($setting['fields'] as $fieldName => &$field)
				{
					if (isset($field['type']) && $field['type'] === 'repeatable' && !empty($field['attr']))
					{
						$field['attr'] = self::getFieldsWithFallbacks($field['attr'] ?? []);

						if (!empty($field['attr']))
						{
							$fieldArray[$fieldName] = $field;
						}
					}

					if (!empty($field['fallbacks']))
					{
						$fieldArray[$fieldName] = $field;
					}
				}

				unset($field);
			}
		}

		return $fieldArray;
	}

	private static function getFieldStructureForTypography()
	{
		$fallbackFields = [];

		foreach (self::$addonFieldStructures as &$addon)
		{
			$addonName = $addon['addon_name'];
			$fallbackFields[$addonName] = self::getFieldsWithFallbacks($addon['settings'] ?? []);
		}

		unset($addon);

		return $fallbackFields;
	}

	private static function retrieveFieldTypesFromSettings($settings)
	{
		$types = [];

		foreach ($settings as $group)
		{
			if (!empty($group['fields']))
			{
				foreach ($group['fields'] as $key => $field)
				{
					$fieldtype = $field['type'] ?? '';

					if ($fieldtype === 'repeatable')
					{
						$types[$key] = [
							'type' => 'repeatable',
							'fields' => self::retrieveFieldTypesFromSettings($field['attr'])
						];
					}
					else
					{
						$types[$key] = $fieldtype;
					}
				}
			}
		}

		return $types;
	}

	private static function processFieldTypes($type)
	{
		switch ($type)
		{
			case 'section':
				self::$sectionFieldTypes = self::retrieveFieldTypesFromSettings(self::$sectionFieldStructures);
				break;
			case 'column':
				self::$columnFieldTypes = self::retrieveFieldTypesFromSettings(self::$columnFieldStructures);
				break;
			case 'addon':
				foreach (self::$addonFieldStructures as $addon)
				{
					$addonName = $addon['addon_name'];
					$addonFieldTypes[$addonName] = self::retrieveFieldTypesFromSettings($addon['settings']);
				}

				self::$addonFieldTypes = $addonFieldTypes;
				break;
		}
	}

	private static function parseResponsiveTypography($data)
	{
		if (!is_object($data))
		{
			return $data;
		}

		$keys = ['letter_spacing', 'size', 'line_height'];

		foreach ($keys as $key)
		{
			$typographyItem = $data->$key ?? null;

			if (!$typographyItem)
			{
				continue;
			}

			if (!empty($typographyItem->md->value) && empty($typographyItem->xl->value))
			{
				$data->$key->xl = clone $typographyItem->md;
			}
		}

		return $data;
	}

	private static function getFallbackValues($settings, string $fieldName, $field)
	{
		$fallbacks = $field['fallbacks'] ?? [];

		if (!isset($settings->$fieldName))
		{
			$settings->$fieldName = new stdClass;
			$isTypographyField = isset($field['type']) && $field['type'] === 'typography';

			if ($isTypographyField)
			{
				$settings->$fieldName = self::getDefaultTypographyData();
			}

			foreach ($fallbacks as $fieldKey => $referenceKey)
			{
				$fallbackParts = explode('.', $referenceKey, 2);
				$masterKey = $fallbackParts[0] ?? null;
				$slaveKey = $fallbackParts[1] ?? null;

				if (!empty($masterKey) && !empty($slaveKey))
				{
					$fallbackValue = isset($settings->$masterKey->$slaveKey) ? $settings->$masterKey->$slaveKey : '';
				}
				elseif (!empty($masterKey) && empty($slaveKey))
				{
					$fallbackValue = isset($settings->$masterKey) ? $settings->$masterKey : '';
				}

				if ($isTypographyField)
				{
					$settings->$fieldName->$fieldKey = self::handleDeviceValues($fallbackValue, $fieldKey);
				}
				else
				{
					$settings->$fieldName->$fieldKey = $fallbackValue;
				}
			}
		}
		else
		{
			$settings->$fieldName = self::parseResponsiveTypography($settings->$fieldName);
		}

		return $settings;
	}

	/**
	 * Read the fallback values of the typography fields.
	 *
	 * @param 	stdClass 	$settings
	 * @param 	string 		$addonName
	 *
	 * @return 	stdClass 	The modified settings value.
	 * @since 	5.0.0
	 */
	private static function getTypographyFromFallbacks($settings, $addonName)
	{

		$fieldStructures = self::getFieldStructureForTypography();
		$fieldStructureByAddon = $fieldStructures[$addonName] ?? null;

		if (empty($fieldStructureByAddon))
		{
			return $settings;
		}

		foreach ($settings as $key => &$setting)
		{
			$field = !empty($fieldStructureByAddon[$key]) ? $fieldStructureByAddon[$key] : [];

			if (!empty($field['type']) && $field['type'] === 'repeatable' && !empty($field['attr']))
			{
				$attributes = $field['attr'];

				foreach ($attributes as $fieldName => $attribute)
				{
					$fallbacks = $attribute['fallbacks'] ?? [];

					if (is_array($setting) && !empty($setting))
					{
						foreach ($setting as &$value)
						{
							$value = self::getFallbackValues($value, $fieldName, $attribute);
						}

						unset($value);
					}
				}
			}
		}

		unset($setting);

		foreach ($fieldStructureByAddon as $fieldName => $field)
		{
			$fallbacks = $field['fallbacks'] ?? [];

			if (!empty($fallbacks))
			{
				$settings = self::getFallbackValues($settings, $fieldName, $field);
			}
		}

		return $settings;
	}

	private static function reshapeSpacingValues($spacing, $forceModern = false)
	{
		$isLegacy = self::$isLegacyData && !$forceModern;
		$reshapedValue = $isLegacy ? ['', '', '', ''] : ['0px', '0px', '0px', '0px'];

		$spacing = \ctype_space((string) $spacing) || $spacing === '' ? [] : explode(' ', $spacing);
		$spacing = array_map(function ($value) use ($isLegacy)
		{
			if (!$isLegacy && $value === '')
			{
				return '0px';
			}

			return $value;
		}, $spacing);

		$spacing = array_filter($spacing, function ($value)
		{
			return isset($value) && $value !== '';
		});

		$spacing = array_values($spacing);
		$newSpacing = [];

		switch (count($spacing))
		{
			case 0:
				$newSpacing = [''];
				break;
			case 1:
				$newSpacing = $isLegacy
					? array_fill(0, 4, $spacing[0])
					: array_replace($reshapedValue, $spacing);
				break;
			case 2:
				$newSpacing = $isLegacy
					? [$spacing[0], $spacing[1], $spacing[0], $spacing[1]]
					: array_replace($reshapedValue, $spacing);
				break;
			case 3:
				$newSpacing = $isLegacy
					? [$spacing[0], $spacing[1], $spacing[2], $spacing[1]]
					: array_replace($reshapedValue, $spacing);
				break;
			case 4:
				$newSpacing = $spacing;
				break;
		}

		return implode(" ", $newSpacing);
	}

	private static function transformSpacingValues($value, $forceModern = false)
	{
		if (self::hasMultiDeviceSettings($value))
		{
			foreach ($value as $device => $deviceData)
			{
				$value->$device = !empty($deviceData) ? self::reshapeSpacingValues($deviceData, $forceModern) : $deviceData;
			}
		}
		elseif (is_string($value))
		{
			$value = self::reshapeSpacingValues($value, $forceModern);
		}

		return $value;
	}

	private static function traverseSettingsToFixSpacingAnomalies($settings, $fieldTypes, $forceModern = false)
	{
		if ($fieldTypes === null)
		{
			return $settings;
		}

		if (!empty($settings))
		{
			foreach ($settings as $key => &$value)
			{
				$fieldType = $fieldTypes[$key] ?? null;

				if ($fieldType === null || (is_string($fieldType) && !in_array($fieldType, ['padding', 'margin'])))
				{
					continue;
				}

				if (is_array($fieldType))
				{
					$repeatableTypes = $fieldType['fields'];

					if (is_array($value))
					{
						foreach ($value as &$repeatableValue)
						{
							$repeatableValue = self::traverseSettingsToFixSpacingAnomalies($repeatableValue, $repeatableTypes, false);
						}
					}
				}
				else
				{
					$value = self::transformSpacingValues($value, $forceModern);
				}
			}

			unset($value);
		}

		return $settings;
	}

	private static function parsingSpacingValues($settings, $type, $addonName = '')
	{
		if ($type === 'addon'  && (empty($addonName) || !isset(self::$addonFieldTypes[$addonName])))
		{
			return $settings;
		}

		switch ($type)
		{
			case 'section':
				$fieldTypes = self::$sectionFieldTypes;
				break;
			case 'column':
				$fieldTypes = self::$columnFieldTypes;
				break;
			case 'addon':
				$fieldTypes = self::$addonFieldTypes[$addonName];
				break;
		}

		return self::traverseSettingsToFixSpacingAnomalies($settings, $fieldTypes, in_array($type, ['section', 'column']));
	}

	private static function hasLegacyDataInsideSettings($settings)
	{
		if (!empty($settings))
		{
			foreach ($settings as $setting)
			{
				if (self::hasMultiDeviceSettings($setting) && isset($setting->xl))
				{
					return false;
				}
			}
		}

		return true;
	}

	private static function isLegacyDataStructure($content)
	{
		foreach ($content as $row)
		{
			$settings = $row->settings;

			if (!self::hasLegacyDataInsideSettings($settings))
			{
				return false;
			}
		}

		return true;
	}

	public static function prepareSpacingData($text)
	{
		self::prepareSectionFieldStructures();
		self::prepareColumnFieldStructures();
		self::prepareAddonFieldStructures();
		self::processFieldTypes('section');
		self::processFieldTypes('column');
		self::processFieldTypes('addon');

		$content = is_string($text) ? json_decode($text) : $text;

		self::$isLegacyData = self::isLegacyDataStructure($content);

		if (!empty($content))
		{
			foreach ($content as &$section)
			{
				$section->settings = self::parsingSpacingValues($section->settings, 'section');

				if (!empty($section->columns))
				{
					foreach ($section->columns as &$column)
					{
						$column->settings = self::parsingSpacingValues($column->settings, 'column');

						if (!empty($column->addons))
						{
							foreach ($column->addons as &$addon)
							{
								if (!empty($addon->settings))
								{
									$addon->settings = self::parsingSpacingValues($addon->settings, 'addon', $addon->name ?? '');
								}
							}

							unset($addon);
						}
					}

					unset($column);
				}
			}

			unset($section);
		}

		return json_encode($content);
	}

	/**
	 * sanitize contents for making the old data valid for the data structure.
	 *
	 * @param    string    $text    The json text of the page builder data.
	 *
	 * @return   string    The sanitized text.
	 * 
	 * @since    4.0.0
	 */
	public static function sanitize(string $text): string
	{
		if (empty(self::$addonFieldStructures))
		{
			self::prepareAddonFieldStructures();
		}

		$rows = json_decode($text);

		if (!empty($rows) && $rows !== "[]")
		{
			foreach ($rows as $key => &$row)
			{
				if (isset($rows[$key]->settings))
				{
					$rows[$key]->settings = self::shiftResponsiveSettings($row->settings);
					$rows[$key]->settings = self::fixRowSettings($row->settings);
				}

				if (!empty($row->columns))
				{
					if (!isset($row->settings->fit_columns))
					{
						$row->settings->fit_columns = self::predictColumnFillStyle($row->columns);
					}

					foreach ($row->columns as $i => &$column)
					{
						/** Predict the width from the column class name for the old layouts. */
						if (!isset($column->width))
						{
							$width = self::getColumnWidth($column);
							$column->width = $width;
						}

						if (\is_array($column->settings))
						{
							$column->settings = (object) $column->settings;
						}

						if (!isset($column->settings->width))
						{
							$width = !empty($column->width) ? $column->width : self::getColumnWidth($column);
							$column->settings->width = $width;

							if (isset($column->settings->width->unit))
							{
								unset($column->settings->width->unit);
							}
						}

						$column->settings->width = self::purifyColumnWidth($column->settings->width);

						if (isset($rows[$key]->columns[$i]->settings))
						{
							$rows[$key]
								->columns[$i]
								->settings = self::shiftResponsiveSettings($rows[$key]->columns[$i]->settings);
						}

						if (!empty($column->addons))
						{
							foreach ($column->addons as $j => &$addon)
							{
								if (isset($rows[$key]->columns[$i]->addons[$j]->settings))
								{
									$rows[$key]
										->columns[$i]
										->addons[$j]
										->settings = self::shiftResponsiveSettings($rows[$key]->columns[$i]->addons[$j]->settings);

									if (isset($addon->name) && $addon->name === 'image_layouts')
									{
										$addonSettings = $addon->settings;

										$isBtnUrlExist = property_exists($addonSettings, 'btn_url');
										$isButtonUrlExist = property_exists($addonSettings, 'button_url');
										$isBtnTargetExist = property_exists($addonSettings, 'btn_target');

										if (!$isButtonUrlExist && $isBtnUrlExist)
										{
											$addonSettings->button_url = [
												'type' => 'url',
												'url' => $addonSettings->btn_url,
												'new_tab' => $isBtnTargetExist ? $addonSettings->btn_target : false,
											];
										}

										$isBtnTextExist = property_exists($addonSettings, 'btn_text');
										$isButtonTextExist = property_exists($addonSettings, 'button_text');

										if (!$isButtonTextExist && $isBtnTextExist)
										{
											$addonSettings->button_text = $addonSettings->btn_text;
										}

										$isBtnTypeExist = property_exists($addonSettings, 'btn_type');
										$isButtonTypeExist = property_exists($addonSettings, 'button_type');

										if (!$isButtonTypeExist && $isBtnTypeExist)
										{
											$addonSettings->button_type = $addonSettings->btn_type;
										}

										$isBtnShapeExist = property_exists($addonSettings, 'btn_shape');
										$isButtonShapeExist = property_exists($addonSettings, 'button_shape');

										if (!$isButtonShapeExist && $isBtnShapeExist)
										{
											$addonSettings->button_shape = $addonSettings->btn_shape;
										}

										$isBtnSizeExist = property_exists($addonSettings, 'btn_size');
										$isButtonSizeExist = property_exists($addonSettings, 'button_size');

										if (!$isButtonSizeExist && $isBtnSizeExist)
										{
											$addonSettings->button_size = $addonSettings->btn_size;
										}

										$isBtnColorExist = property_exists($addonSettings, 'btn_color');
										$isButtonColorExist = property_exists($addonSettings, 'button_color');

										if (!$isButtonColorExist && $isBtnColorExist)
										{
											$addonSettings->button_color = $addonSettings->btn_color;
										}

										$isBtnColorHoverExist = property_exists($addonSettings, 'btn_color_hover');
										$isButtonColorHoverExist = property_exists($addonSettings, 'button_color_hover');

										if (!$isButtonColorHoverExist && $isBtnColorHoverExist)
										{
											$addonSettings->button_color_hover = $addonSettings->btn_color_hover;
										}

										$isBtnAppearanceExist = property_exists($addonSettings, 'btn_appearance');
										$isButtonAppearanceExist = property_exists($addonSettings, 'button_appearance');

										if (!$isButtonAppearanceExist && $isBtnAppearanceExist)
										{
											$addonSettings->button_appearance = $addonSettings->btn_appearance;
										}

										$isBtnBackgroundColorExist = property_exists($addonSettings, 'btn_background_color');
										$isButtonBackgroundColorExist = property_exists($addonSettings, 'button_background_color');

										if (!$isButtonBackgroundColorExist && $isBtnBackgroundColorExist)
										{
											$addonSettings->button_background_color = $addonSettings->btn_background_color;
										}

										$isBtnBackgroundColorHoverExist = property_exists($addonSettings, 'btn_background_color_hover');
										$isButtonBackgroundColorHoverExist = property_exists($addonSettings, 'button_background_color_hover');

										if (!$isButtonBackgroundColorHoverExist && $isBtnBackgroundColorHoverExist)
										{
											$addonSettings->button_background_color_hover = $addonSettings->btn_background_color_hover;
										}

										$isBtnBackgroundGradientExist = property_exists($addonSettings, 'btn_background_gradient');
										$isButtonBackgroundGradientExist = property_exists($addonSettings, 'button_background_gradient');

										if (!$isButtonBackgroundGradientExist && $isBtnBackgroundGradientExist)
										{
											$addonSettings->button_background_gradient = $addonSettings->btn_background_gradient;
										}

										$isBtnBackgroundGradientHoverExist = property_exists($addonSettings, 'btn_background_gradient_hover');
										$isButtonBackgroundGradientHoverExist = property_exists($addonSettings, 'button_background_gradient_hover');

										if (!$isButtonBackgroundGradientHoverExist && $isBtnBackgroundGradientHoverExist)
										{
											$addonSettings->button_background_gradient_hover = $addonSettings->btn_background_gradient_hover;
										}
									}
								}

								if (isset($addon->name) && isset($addon->settings))
								{
									$addon->settings = self::getTypographyFromFallbacks($addon->settings, $addon->name);
								}

								/** Migrate the slideshow items. */
								if (isset($addon->name) && $addon->name === 'js_slideshow')
								{
									if (!empty($addon->settings->slideshow_items))
									{
										foreach ($addon->settings->slideshow_items as $x => &$slideshowItem)
										{
											if (isset($slideshowItem->slider_overlay_options) && $slideshowItem->slider_overlay_options === 'gradient_overaly')
											{
												$slideshowItem->slider_overlay_options = 'gradient_overlay';
											}

											if (isset($slideshowItem->slider_bg_gradient_overlay) && !isset($slideshowItem->slider_bg_gradient_overlay->type))
											{
												$slideshowItem->slider_bg_gradient_overlay->type = 'linear';
											}

											$slideshowItem = self::shiftResponsiveSettings($slideshowItem);

											if (!empty($slideshowItem->slideshow_inner_items))
											{
												foreach ($slideshowItem->slideshow_inner_items as $y => &$innerItem)
												{
													$innerItem = self::shiftResponsiveSettings($innerItem);
												}

												unset($innerItem);
											}
										}

										unset($slideshowItem);
									}
								}

								/** Migrate the responsive settings for the repeatable items. */
								if (isset($addon->name) && \in_array($addon->name, ['accordion', 'tab']))
								{
									$repeatableKey = 'sp_' . $addon->name . '_item';

									if (!empty($addon->settings->$repeatableKey))
									{
										foreach ($rows[$key]->columns[$i]->addons[$j]->settings->$repeatableKey as &$itemSetting)
										{
											$itemSetting = self::shiftResponsiveSettings($itemSetting);
										}

										unset($itemSetting);
									}
								}

								if (isset($addon->name) && \in_array($addon->name, ['accordion', 'tab']))
								{
									list($outerRows, $addon) = self::migrateDeepAddon($addon, 'sp_' . $addon->name . '_item', $row, $column);
									$rows[$key]->columns[$i]->addons[$j] = $addon;
									array_push($rows, ...$outerRows);
								}

								if (isset($addon->name) && $addon->name === 'table_advanced')
								{
									$nodeId = self::generateUUID();

									if (isset($addon->settings->sp_table_advanced_item))
									{
										foreach ($addon->settings->sp_table_advanced_item as $th => $thead)
										{
											if (isset($thead->content) && !\is_array($thead->content))
											{
												$thead = ['id' => $nodeId++, 'name' => 'text_block', 'visibility' => true, 'reference_id' => $addon->id, 'settings' => ['text' => $thead->content]];
												$thead = \json_decode(\json_encode($thead));
												$rows[$key]
													->columns[$i]
													->addons[$j]
													->settings
													->sp_table_advanced_item[$th]
													->content = [];
												$rows[$key]
													->columns[$i]
													->addons[$j]
													->settings
													->sp_table_advanced_item[$th]
													->content[] = $thead;
											}
											elseif (isset($thead->content) && \is_array($thead->content))
											{
												$contents = [];

												foreach ($thead->content as $content)
												{
													$content->reference_id = $addon->id;
													$contents[] = $content;
												}

												$rows[$key]
													->columns[$i]
													->addons[$j]
													->settings
													->sp_table_advanced_item[$th]
													->content = $contents;
											}
										}
									}

									foreach ($addon->settings->table_advanced_item as $r => $tRow)
									{
										if (isset($tRow->table_advanced_item))
										{
											foreach ($tRow->table_advanced_item as $c => $tCell)
											{
												if (isset($tCell->content) && !\is_array($tCell->content))
												{
													$td = ['id' => $nodeId++, 'name' => 'text_block', 'visibility' => true, 'reference_id' => $addon->id, 'settings' => ['text' => $tCell->content]];
													$td = \json_decode(\json_encode($td));

													$rows[$key]
														->columns[$i]
														->addons[$j]
														->settings
														->table_advanced_item[$r]
														->table_advanced_item[$c]
														->content = [];
													$rows[$key]
														->columns[$i]
														->addons[$j]
														->settings
														->table_advanced_item[$r]
														->table_advanced_item[$c]
														->content[] = $td;
												}
												elseif (isset($tCell->content) && \is_array($tCell->content))
												{
													$contents = [];

													foreach ($tCell->content as &$content)
													{
														$content->reference_id = $addon->id;
														$contents[] = $content;
													}

													$rows[$key]
														->columns[$i]
														->addons[$j]
														->settings
														->table_advanced_item[$r]
														->table_advanced_item[$c]
														->content = $contents;

													unset($content);
												}
											}
										}
									}
								}

								if (isset($addon->type) && $addon->type === 'inner_row')
								{
									$nestedRowAddon = new \stdClass;
									$nestedRowAddon->type = 'nested_row';
									$nestedRowAddon->name = 'row';
									$nestedRowAddon->id = $addon->id;
									$addon->parent = new \stdClass;
									$addon->parent->rowId = $row->id;
									$addon->parent->columnId = $column->id;

									unset($addon->type);
									$rows[] = $addon;
									$rows[$key]->columns[$i]->addons[$j] = $nestedRowAddon;
								}
							}

							unset($addon);
						}
					}

					unset($column);
				}
			}

			unset($row);
		}
		else
		{
			return $text;
		}

		return json_encode($rows);
	}

	public static function fixRowSettings($settings)
	{
		if (isset($settings->background_type))
		{
			return $settings;
		}

		$settings->background_type = 'none';

		$keyMap = [
			'background_image' => 'image',
			'background_color' => 'color',
			'background_gradient' => 'gradient',
			'background_video' => 'video',
			'background_video_mp4' => 'video',
			'background_video_ogv' => 'video',
			'background_external_video' => 'video'
		];

		foreach ($keyMap as $key => $value)
		{
			if (!empty($settings->$key))
			{
				$settings->background_type = $value;
				break;
			}
		}

		return $settings;
	}

	/**
	 * Migrate the accordion addon to the current structure.
	 *
	 * @param     \stdClass     $addon    The accordion addon object.
	 *
	 * @return     array
	 * @since     4.0.0
	 */
	private static function migrateDeepAddon(\stdClass $addon, $key, $row, $column): array
	{
		$addon = json_decode(json_encode($addon));
		$addonCollection = [];
		$outerRows = [];

		if (!isset($addon->parent) || (isset($addon->parent) && !$addon->parent))
		{
			$addon->id = self::nanoid();
		}

		if (isset($addon->settings->$key))
		{
			foreach ($addon->settings->$key as $itemIndex => $item)
			{
				$addonCollection = [];

				if (isset($item->content) && \is_array($item->content))
				{
					foreach ($item->content as $deepAddon)
					{
						if (isset($deepAddon->type) && $deepAddon->type === 'nested_row')
						{
							continue;
						}

						$addonCollection[] = $deepAddon;
					}

					if (\count($addonCollection) > 0)
					{
						$_parent = ['rowId' => $row->id, 'columnId' => $column->id];
						$_parent = (object) $_parent;
						$row = self::createRow('12', $addonCollection, $_parent);
						$row->parent_addon = $addon->id;

						$outerRows[] = $row;
						$nestedRow = ['type' => 'nested_row', 'id' => $row->id, 'name' => 'row'];
						$nestedRow = (object) $nestedRow;
						$addon->settings->$key[$itemIndex]->content = [];
						$addon->settings->$key[$itemIndex]->content[] = $nestedRow;
					}
				}
				else if (isset($item->content) && \is_string($item->content))
				{
					$textAddon = ['id' => self::nanoid(), 'name' => 'text_block', 'settings' => ['text' => $item->content]];
					$addonCollection[] = $textAddon;

					$_parent = ['rowId' => $row->id, 'columnId' => $column->id];
					$_parent = (object) $_parent;
					$row = self::createRow('12', $addonCollection, $_parent);
					$row->parent_addon = $addon->id;

					$outerRows[] = $row;
					$nestedRow = ['type' => 'nested_row', 'id' => $row->id, 'name' => 'row'];
					$nestedRow = (object) $nestedRow;
					$addon->settings->$key[$itemIndex]->content = [];
					$addon->settings->$key[$itemIndex]->content[] = $nestedRow;
				}
			}
		}

		return [$outerRows, $addon];
	}

	/**
	 * Create Row function
	 *
	 * @param string $layout Default layout size
	 * @param array $addons Addons
	 * @param mixed $parent Parent row.
	 *   
	 * @return object
	 * 
	 * @since 4.0.0
	 */
	public static function createRow(string $layout = '12', array $addons = [], $parent = null)
	{
		$rowId = self::nanoid();
		$layouts = explode('+', $layout);

		$rowDefaultValues = EditorUtils::getSectionSettingsDefaultValues();
		$columnDefaultValues = EditorUtils::getColumnSettingsDefaultValues();

		$rowDefaultValues = json_decode(json_encode($rowDefaultValues));
		$columnDefaultValues = json_decode(json_encode($columnDefaultValues));

		$columns = array_map(function ($col) use ($columnDefaultValues, $addons)
		{
			$width = (float) ((100 / (12 / (int) $col))) . '%';
			$widthObject = ['xl' => $width, 'lg' => $width, 'md' => $width, 'sm' => '100%', 'xs' => '100%'];
			$widthObject = (object) $widthObject;

			$columnObject = [
				'id' => self::nanoid(),
				'class_name' => 'row-column',
				'visibility' => true,
				'settings' => $columnDefaultValues,
				'addons' => $addons,
				'width' => $widthObject,
			];

			return (object) $columnObject;
		}, $layouts);

		$rowDefaultValues->padding = '5px 0px 5px 0px';
		$rowDefaultValues->margin = '0px 0px 0px 0px';

		$rowObject = [
			'id' => $rowId,
			'visibility' => true,
			'collapse' => false,
			'settings' => $rowDefaultValues,
			'layout' => $layout,
			'columns' => $columns,
			'parent' => $parent ? $parent : false,
		];

		return (object) $rowObject;
	}

	/**
	 * Generate a unique ID by using microtime.
	 *
	 * @return     integer
	 * @since     4.0.0
	 */
	public static function generateUUID(): int
	{
		return (int) (microtime(true) * 1000);
	}

	/**
	 * Shift responsive device settings for with the new device structure.
	 *
	 * @param     \stdClass     $settings    The settings value.
	 *
	 * @return     \stdClass | null
	 * @since     4.0.0
	 */
	public static function shiftResponsiveSettings($settings)
	{
		if (!empty($settings))
		{
			foreach ($settings as $key => $setting)
			{
				if (\is_object($setting) && isset($setting->md) && !isset($setting->xl))
				{
					$tmp = (object) ['xl' => '', 'lg' => '', 'md' => '', 'sm' => '', 'xs' => ''];

					if (isset($setting->md))
					{
						$tmp->xl = $setting->md;
					}

					if (isset($setting->sm))
					{
						$tmp->lg = $setting->sm;
						$tmp->md = $setting->sm;
					}

					if (isset($setting->xs))
					{
						$tmp->sm = $setting->xs;
						$tmp->xs = $setting->xs;
					}

					if (isset($setting->unit))
					{
						$tmp->unit = $setting->unit;
					}

					$settings->$key = $tmp;
				}
			}

			if (isset($settings->hidden_md) && !isset($settings->hidden_xl))
			{
				if (isset($settings->hidden_md))
				{
					$settings->hidden_xl = $settings->hidden_md;
				}

				if (isset($settings->hidden_sm))
				{
					$settings->hidden_lg = $settings->hidden_sm;
					$settings->hidden_md = $settings->hidden_sm;
				}

				if (isset($settings->hidden_xs))
				{
					$settings->hidden_sm = $settings->hidden_xs;
					$settings->hidden_xs = $settings->hidden_xs;
				}
			}
		}

		return $settings;
	}


	/**
	 * Remove sp_ from the addon name
	 *
	 * @return    void
	 * @since    4.0.0
	 */
	public static function sanitize_addon_name($addon_name)
	{
		$from = '/' . preg_quote('sp_', '/') . '/';
		return preg_replace($from, '', $addon_name, 1);
	}

	/**
	 * Load Language File
	 *
	 * @param boolean $forceLoad
	 * @return void
	 */
	public static function loadLanguage($forceLoad = false)
	{
		$lang = Factory::getLanguage();

		/** @var CMSApplication */
		$app = Factory::getApplication();
		$template = $app->getTemplate();

		if ($app->isClient('administrator'))
		{
			$template = self::getTemplate();
		}

		$com_option = $app->input->get('option', '', 'STR');
		$com_view = $app->input->get('view', '', 'STR');
		$com_id = $app->input->get('id', 0, 'INT');

		if (($com_option == 'com_sppagebuilder' && $com_view == 'form' && $com_id) || $forceLoad)
		{
			$lang->load('com_sppagebuilder', JPATH_ADMINISTRATOR, null, true);
		}

		// Load template language file
		$lang->load('tpl_' . $template, JPATH_SITE, null, true);

		self::setPluginsAddonsLanguage();

		if (ApplicationHelper::isEasyStoreInstalled() && ApplicationHelper::isProVersion())
		{
			$lang->load('com_easystore', JPATH_BASE, null, true);
			$lang->load('com_easystore', JPATH_ADMINISTRATOR, null, true);
		}

		if (($com_option === 'com_sppagebuilder' && $com_view !== 'page'))
		{
			require_once JPATH_ROOT . '/administrator/components/com_sppagebuilder/helpers/language.php';
		}
	}



	private static function getTemplate()
	{
		$db = Factory::getDbo();
		$query = $db->getQuery(true);

		$query->select('template')
			->from($db->quoteName('#__template_styles'))
			->where($db->quoteName('client_id') . ' = 0')
			->where($db->quoteName('home') . ' = 1');

		$db->setQuery($query);

		return $db->loadResult();
	}

	/**
	 * Load Plugin addons language files.
	 *
	 * @return void
	 */
	private static function setPluginsAddonsLanguage()
	{
		$path = JPATH_PLUGINS . '/sppagebuilder';
		if (!Folder::exists($path)) return;

		$plugins = Folder::folders($path);
		if (!count((array) $plugins)) return;

		foreach ($plugins as $plugin)
		{
			if (PluginHelper::isEnabled('sppagebuilder', $plugin))
			{
				$lang = Factory::getLanguage();
				$lang->load('plg_' . $plugin, JPATH_ADMINISTRATOR, null, true);
			}
		}
	}

	/**
	 * Convert Padding Margin Value.
	 *
	 * @param string $main_value CSS value
	 * @param string $type  CSS property
	 * 
	 * @return string
	 * 
	 * @since 4.0.0
	 */
	public static function getPaddingMargin($main_value, $type): string
	{
		$css = '';
		$pos = array('top', 'right', 'bottom', 'left');
		if (is_string($main_value) && trim($main_value) != "")
		{
			$values = explode(' ', $main_value);

			foreach ($values as $key => $value)
			{
				$value = preg_replace('@/s@', '', $value);

				if ($value !== "")
				{
					$css .= $type . '-' . $pos[$key] . ': ' . $value . ';';
				}
			}
		}

		return $css;
	}

	public static function getSvgShapes()
	{
		$shape_path = JPATH_ROOT . '/components/com_sppagebuilder/assets/shapes';
		$shapes = Folder::files($shape_path, '.svg');

		$shapeArray = array();

		if (count((array) $shapes))
		{
			foreach ($shapes as $shape)
			{
				$shapeArray[str_replace('.svg', '', $shape)] = base64_encode(file_get_contents($shape_path . '/' . $shape));
			}
		}

		return $shapeArray;
	}

	public static function getSvgShapeCode($shapeName, $invert)
	{
		if ($invert)
		{
			$shape_path = JPATH_ROOT . '/components/com_sppagebuilder/assets/shapes/' . $shapeName . '-invert.svg';
		}
		else
		{
			$shape_path = JPATH_ROOT . '/components/com_sppagebuilder/assets/shapes/' . $shapeName . '.svg';
		}

		$shapeCode = '';

		if (file_exists($shape_path))
		{
			$shapeCode = file_get_contents($shape_path);
		}

		return is_string($shapeCode) ? $shapeCode : '';
	}

	// Convert json code to plain text
	public static function getPrettyText($sections)
	{
		if (!class_exists('AddonParser'))
		{
			require_once JPATH_ROOT . '/components/com_sppagebuilder/parser/addon-parser.php';
		}
		if (!class_exists('SpPageBuilderAddonHelper'))
		{
			require_once JPATH_ROOT . '/components/com_sppagebuilder/builder/classes/addon.php';
		}

		$content = ApplicationHelper::sanitizePageText($sections);
		$htmlContent = AddonParser::viewAddons($content);
		$htmlContent = str_replace('><', '> <', $htmlContent);

		return trim(strip_tags($htmlContent));
	}

	public static function addScript($script, $client = 'site', $version = true)
	{
		$doc = Factory::getDocument();

		$script_url = Uri::base(true) . ($client == 'admin' ? '/administrator' : '') . '/components/com_sppagebuilder/assets/js/' . $script;

		if ($version)
		{
			$script_url .= '?' . self::getVersion(true);
		}

		$doc->addScript($script_url);
	}

	public static function addStylesheet($stylesheet, $client = 'site', $version = true)
	{
		$doc = Factory::getDocument();

		$stylesheet_url = Uri::base(true) . ($client == 'admin' ? '/administrator' : '') . '/components/com_sppagebuilder/assets/css/' . $stylesheet;

		if ($version)
		{
			$stylesheet_url .= '?' . self::getVersion(true);
		}

		$doc->addStylesheet($stylesheet_url);
	}

	public static function addContainerMaxWidth()
	{
		$doc = Factory::getDocument();
		$params = ComponentHelper::getParams('com_sppagebuilder');
		$containerMaxWidth = $params->get('container_max_width', 1320);
		$doc->addStyleDeclaration("@media(min-width: 1400px) {.sppb-row-container { max-width: " . $containerMaxWidth . "px; }}");
	}

	public static function getVersion($md5 = false)
	{
		$db = Factory::getDbo();
		$query = $db->getQuery(true)
			->select('e.manifest_cache')
			->select($db->quoteName('e.manifest_cache'))
			->from($db->quoteName('#__extensions', 'e'))
			->where($db->quoteName('e.element') . ' = ' . $db->quote('com_sppagebuilder'));

		$db->setQuery($query);
		$manifest_cache = json_decode($db->loadResult());

		if (isset($manifest_cache->version) && $manifest_cache->version)
		{

			if ($md5)
			{
				return md5($manifest_cache->version);
			}

			return $manifest_cache->version;
		}

		return '1.0';
	}

	/**
	 * Load Assets form database table.
	 *
	 * @return void
	 */
	public static function loadAssets()
	{
		$doc = Factory::getDocument();
		$db = Factory::getDbo();
		$query = $db->getQuery(true)
			->select($db->quoteName(array('a.name', 'a.css_path')))
			->from($db->quoteName('#__sppagebuilder_assets', 'a'))
			->where($db->quoteName('a.published') . ' = 1');

		$db->setQuery($query);
		$assets = $db->loadObjectList();

		if (!empty($assets))
		{
			foreach ($assets as $asset)
			{
				$asset_url = Uri::base(true) . '/' . $asset->css_path . '?' . self::getVersion(true);
				$doc->addStylesheet($asset_url);
			}
		}
	}

	/**
	 * Get the current template name form database.
	 *
	 * @return void
	 */
	public static function getTemplateName()
	{
		$db = Factory::getDbo();
		$query = $db->getQuery(true);
		$query->select($db->quoteName(['template']))
			->from($db->quoteName('#__template_styles'))
			->where($db->quoteName('client_id') . ' = 0')
			->where($db->quoteName('home') . ' = 1');
		$db->setQuery($query);

		return $db->loadObject()->template;
	}

	/**
	 * Get installed google fonts from database.
	 * 
	 * @return array
	 */
	public static function getInstalledGoogleFonts()
	{
		$db = Factory::getDbo();
		$query = $db->getQuery(true);

		$query->select('family_name')
			->from($db->quoteName('#__sppagebuilder_fonts'))
			->where($db->quoteName('type') . ' = ' . $db->quote('google'));

		$db->setQuery($query);

		try
		{
			return $db->loadColumn() ?? [];
		}
		catch (Exception $e)
		{
			return [];
		}
	}

	/**
	 * Checking multi device settings
	 *
	 * @param mixed $value
	 * @return boolean
	 * 
	 * @since 5.0.0
	 */
	public static function hasMultiDeviceSettings($value): bool
	{
		return isset($value->xl)
			|| isset($value->lg)
			|| isset($value->md)
			|| isset($value->sm)
			|| isset($value->xs);
	}

	/**
	 * Checking media item
	 *
	 * @param mixed $value
	 * @return boolean
	 * 
	 * @since 5.0.0
	 */
	public static function isMediaItemData($value): bool
	{
		return isset($value->src);
	}

	/**
	 * Clean media path
	 *
	 * @param string $path
	 * @return string
	 * 
	 * @since 5.0.3
	 */
	public static function cleanPath($path): string
	{
		$cleanedPath = Path::clean($path);
		$cleanedPath = str_replace("/\\", "\\", $cleanedPath);
		$cleanedPath = str_replace("\\", "/", $cleanedPath);
		return $cleanedPath;
	}

	public static function classes(array $classNames): string
	{
		$filteredClassNames = array_filter($classNames, function ($value)
		{
			return (bool) trim(strval($value));
		});

		return implode(" ", $filteredClassNames);
	}

	/**
	 * Initialize the basic settings for the page builder page view
	 *
	 * @param $data object
	 * @return object
	 */
	public static function initView($data)
	{
		/** @var CMSApplication */
		$app = Factory::getApplication();
		$doc = $app->getDocument();

		$params = ComponentHelper::getParams('com_sppagebuilder');

		if ($params->get('fontawesome', 1))
		{
			SppagebuilderHelperSite::addStylesheet('font-awesome-5.min.css');
			SppagebuilderHelperSite::addStylesheet('font-awesome-v4-shims.css');
		}

		if (!$params->get('disableanimatecss', 0))
		{
			SppagebuilderHelperSite::addStylesheet('animate.min.css');
		}

		if (!$params->get('disablecss', 0))
		{
			SppagebuilderHelperSite::addStylesheet('sppagebuilder.css');
			SppagebuilderHelperSite::addStylesheet('animate.min.css');
			SppagebuilderHelperSite::addContainerMaxWidth();
		}

		// load font assets form database
		SppagebuilderHelperSite::loadAssets();

		HTMLHelper::_('jquery.framework');
		HTMLHelper::_('script', 'components/com_sppagebuilder/assets/js/jquery.parallax.js', ['version' => SppagebuilderHelperSite::getVersion(true)]);

		HTMLHelper::_('script', 'components/com_sppagebuilder/assets/js/es5_interaction.js', ['version' => SppagebuilderHelperSite::getVersion(true)], ['defer' => true]);
		HTMLHelper::_('script', 'components/com_sppagebuilder/assets/js/sppagebuilder.js', ['version' => SppagebuilderHelperSite::getVersion(true)], ['defer' => true]);

		require_once JPATH_ROOT . '/components/com_sppagebuilder/parser/addon-parser.php';
		require_once JPATH_ROOT . '/components/com_sppagebuilder/builder/classes/addon.php';

		// Add page css
		if (isset($data->css) && $data->css)
		{
			$doc->addStyledeclaration($data->css);
		}

		$content = $data->content;

		return is_string($content) ? json_decode($content) : $content;
	}
}
