![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/old/vendor/magento/module-widget/Model/Widget/ |
<?php /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Widget\Model\Widget; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\ObjectManager; use Magento\Framework\Config\Dom\ValidationException; use Magento\Framework\Config\Dom\ValidationSchemaException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\Simplexml\Element; use Magento\Framework\View\Model\Layout\Update\ValidatorFactory; /** * Widget Instance Model * * @api * @method string getTitle() * @method \Magento\Widget\Model\Widget\Instance setTitle(string $value) * @method \Magento\Widget\Model\Widget\Instance setStoreIds(string $value) * @method \Magento\Widget\Model\Widget\Instance setWidgetParameters(string|array $value) * @method int getSortOrder() * @method \Magento\Widget\Model\Widget\Instance setSortOrder(int $value) * @method \Magento\Widget\Model\Widget\Instance setThemeId(int $value) * @method int getThemeId() * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.TooManyFields) * @since 100.0.2 */ class Instance extends \Magento\Framework\Model\AbstractModel { public const SPECIFIC_ENTITIES = 'specific'; public const ALL_ENTITIES = 'all'; public const DEFAULT_LAYOUT_HANDLE = 'default'; public const PRODUCT_LAYOUT_HANDLE = 'catalog_product_view'; /** * @deprecated see self::SINGLE_PRODUCT_LAYOUT_HANDLE */ public const SINGLE_PRODUCT_LAYOUT_HANLDE = self::SINGLE_PRODUCT_LAYOUT_HANDLE; public const SINGLE_PRODUCT_LAYOUT_HANDLE = 'catalog_product_view_id_{{ID}}'; public const PRODUCT_TYPE_LAYOUT_HANDLE = 'catalog_product_view_type_{{TYPE}}'; public const ANCHOR_CATEGORY_LAYOUT_HANDLE = 'catalog_category_view_type_layered'; public const NOTANCHOR_CATEGORY_LAYOUT_HANDLE = 'catalog_category_view_type_default'; public const SINGLE_CATEGORY_LAYOUT_HANDLE = 'catalog_category_view_id_{{ID}}'; /** * @var array */ protected $_layoutHandles = []; /** * @var array */ protected $_specificEntitiesLayoutHandles = []; /** * @var Element */ protected $_widgetConfigXml = null; /** * Prefix of model events names * * @var string */ protected $_eventPrefix = 'widget_widget_instance'; /** * @var \Magento\Framework\View\FileSystem */ protected $_viewFileSystem; /** * @var \Magento\Widget\Model\Widget */ protected $_widgetModel; /** * @var \Magento\Widget\Model\NamespaceResolver */ protected $_namespaceResolver; /** * @var \Magento\Framework\App\Cache\TypeListInterface */ protected $_cacheTypeList; /** * @var string[] */ protected $_relatedCacheTypes; /** * @var \Magento\Catalog\Model\Product\Type * @since 101.0.4 */ protected $_productType; /** * @var \Magento\Widget\Model\Config\Reader * @since 101.0.4 */ protected $_reader; /** * @var \Magento\Framework\Escaper */ protected $_escaper; /** * @var \Magento\Framework\Math\Random */ protected $mathRandom; /** * @var \Magento\Framework\Filesystem\Directory\ReadInterface */ protected $_directory; /** * @var \Magento\Widget\Helper\Conditions */ protected $conditionsHelper; /** * @var Json */ private $serializer; /** * @var ValidatorFactory */ private $xmlValidatorFactory; /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry * @param \Magento\Framework\Escaper $escaper * @param \Magento\Framework\View\FileSystem $viewFileSystem * @param \Magento\Framework\App\Cache\TypeListInterface $cacheTypeList * @param \Magento\Catalog\Model\Product\Type $productType * @param \Magento\Widget\Model\Config\Reader $reader * @param \Magento\Widget\Model\Widget $widgetModel * @param \Magento\Widget\Model\NamespaceResolver $namespaceResolver * @param \Magento\Framework\Math\Random $mathRandom * @param \Magento\Framework\Filesystem $filesystem * @param \Magento\Widget\Helper\Conditions $conditionsHelper * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $relatedCacheTypes * @param array $data * @param \Magento\Framework\Serialize\Serializer\Json $serializer * @param ValidatorFactory|null $xmlValidatorFactory * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Framework\Model\Context $context, \Magento\Framework\Registry $registry, \Magento\Framework\Escaper $escaper, \Magento\Framework\View\FileSystem $viewFileSystem, \Magento\Framework\App\Cache\TypeListInterface $cacheTypeList, \Magento\Catalog\Model\Product\Type $productType, \Magento\Widget\Model\Config\Reader $reader, \Magento\Widget\Model\Widget $widgetModel, \Magento\Widget\Model\NamespaceResolver $namespaceResolver, \Magento\Framework\Math\Random $mathRandom, \Magento\Framework\Filesystem $filesystem, \Magento\Widget\Helper\Conditions $conditionsHelper, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $relatedCacheTypes = [], array $data = [], Json $serializer = null, ValidatorFactory $xmlValidatorFactory = null ) { $this->_escaper = $escaper; $this->_viewFileSystem = $viewFileSystem; $this->_cacheTypeList = $cacheTypeList; $this->_relatedCacheTypes = $relatedCacheTypes; $this->_productType = $productType; $this->_reader = $reader; $this->_widgetModel = $widgetModel; $this->mathRandom = $mathRandom; $this->conditionsHelper = $conditionsHelper; $this->_directory = $filesystem->getDirectoryRead(DirectoryList::ROOT); $this->_namespaceResolver = $namespaceResolver; $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class); $this->xmlValidatorFactory = $xmlValidatorFactory ?? ObjectManager::getInstance()->get(ValidatorFactory::class); parent::__construct($context, $registry, $resource, $resourceCollection, $data); } /** * Internal Constructor * * @return void */ protected function _construct() { parent::_construct(); $this->_init(\Magento\Widget\Model\ResourceModel\Widget\Instance::class); $this->_layoutHandles = [ 'anchor_categories' => self::ANCHOR_CATEGORY_LAYOUT_HANDLE, 'notanchor_categories' => self::NOTANCHOR_CATEGORY_LAYOUT_HANDLE, 'all_products' => self::PRODUCT_LAYOUT_HANDLE, 'all_pages' => self::DEFAULT_LAYOUT_HANDLE, ]; $this->_specificEntitiesLayoutHandles = [ 'anchor_categories' => self::SINGLE_CATEGORY_LAYOUT_HANDLE, 'notanchor_categories' => self::SINGLE_CATEGORY_LAYOUT_HANDLE, 'all_products' => self::SINGLE_PRODUCT_LAYOUT_HANDLE, ]; foreach (array_keys($this->_productType->getTypes()) as $typeId) { $layoutHandle = str_replace('{{TYPE}}', $typeId, self::PRODUCT_TYPE_LAYOUT_HANDLE); $this->_layoutHandles[$typeId . '_products'] = $layoutHandle; $this->_specificEntitiesLayoutHandles[$typeId . '_products'] = self::SINGLE_PRODUCT_LAYOUT_HANDLE; } } /** * Processing object before save data * * @return $this * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ public function beforeSave() { $pageGroupIds = []; $tmpPageGroups = []; $pageGroups = $this->getData('page_groups'); if ($pageGroups) { foreach ($pageGroups as $pageGroup) { if (isset($pageGroup[$pageGroup['page_group']])) { $pageGroupData = $pageGroup[$pageGroup['page_group']]; if ($pageGroupData['page_id']) { $pageGroupIds[] = $pageGroupData['page_id']; } if (in_array($pageGroup['page_group'], ['pages', 'page_layouts'])) { $layoutHandle = $pageGroupData['layout_handle']; } else { $layoutHandle = $this->_layoutHandles[$pageGroup['page_group']]; } if (!isset($pageGroupData['template'])) { $pageGroupData['template'] = ''; } $tmpPageGroup = [ 'page_id' => $pageGroupData['page_id'], 'group' => $pageGroup['page_group'], 'layout_handle' => $layoutHandle, 'for' => $pageGroupData['for'], 'block_reference' => $pageGroupData['block'], 'entities' => '', 'layout_handle_updates' => [$layoutHandle], 'template' => $pageGroupData['template'] ? $pageGroupData['template'] : '', ]; if ($pageGroupData['for'] == self::SPECIFIC_ENTITIES) { $layoutHandleUpdates = []; foreach (explode(',', $pageGroupData['entities'] ?? '') as $entity) { $layoutHandleUpdates[] = str_replace( '{{ID}}', $entity, $this->_specificEntitiesLayoutHandles[$pageGroup['page_group']] ); } $tmpPageGroup['entities'] = $pageGroupData['entities']; $tmpPageGroup['layout_handle_updates'] = $layoutHandleUpdates; } $tmpPageGroups[] = $tmpPageGroup; } } } if (is_array($this->getData('store_ids'))) { $this->setData('store_ids', implode(',', $this->getData('store_ids'))); } $parameters = $this->getData('widget_parameters'); if (is_array($parameters)) { if (array_key_exists('show_pager', $parameters) && !array_key_exists('page_var_name', $parameters)) { $parameters['page_var_name'] = 'p' . $this->mathRandom->getRandomString( 5, \Magento\Framework\Math\Random::CHARS_LOWERS ); } $this->setData('widget_parameters', $this->serializer->serialize($parameters)); } $this->setData('page_groups', $tmpPageGroups); $this->setData('page_group_ids', $pageGroupIds); return parent::beforeSave(); } /** * Validate widget instance data * * @return \Magento\Framework\Phrase|bool */ public function validate() { if ($this->isCompleteToCreate()) { return true; } return __('We cannot create the widget instance because it is missing required information.'); } /** * Check if widget instance has required data (other data depends on it) * * @return boolean */ public function isCompleteToCreate() { return $this->getType() && $this->getThemeId(); } /** * Return widget instance code. If not set, derive value from type (namespace\class). * * @return string */ public function getCode() { $code = $this->_getData('instance_code'); if ($code == null) { $code = $this->getWidgetReference('type', $this->getType(), 'code'); $this->setData('instance_code', $code); } return $code; } /** * Sets the value this widget instance code. * The widget code is the 'id' attribute in the widget node. * 'code' is used in Magento\Widget\Model\Widget->getWidgetsArray when the array of widgets is created. * * @param string $code * @return $this */ public function setCode($code) { $this->setData('instance_code', $code); return $this; } /** * Setter * * Prepare widget type * * @param string $type * @return $this */ public function setType($type) { $this->setData('instance_type', $type); return $this; } /** * Getter * * Prepare widget type * * @return string */ public function getType() { return (string) $this->_getData('instance_type'); } /** * Getter. * * If not set return default * * @return string */ public function getArea() { //TODO Shouldn't we get "area" from theme model which we can load using "theme_id"? if (!$this->_getData('area')) { return \Magento\Framework\View\DesignInterface::DEFAULT_AREA; } return $this->_getData('area'); } /** * Getter * * Explode to array if string setted * * @return array */ public function getStoreIds() { if (is_string($this->getData('store_ids'))) { return explode(',', $this->getData('store_ids')); } return $this->getData('store_ids'); } /** * Getter * * Unserialize if serialized string setted * * @return array */ public function getWidgetParameters() { if (is_string($this->getData('widget_parameters'))) { return $this->serializer->unserialize($this->getData('widget_parameters')); } elseif (null === $this->getData('widget_parameters')) { return []; } return is_array($this->getData('widget_parameters')) ? $this->getData('widget_parameters') : []; } /** * Retrieve option array of widget types * * @param string $value * @return array */ public function getWidgetsOptionArray($value = 'code') { $widgets = []; $widgetsArr = $this->_widgetModel->getWidgetsArray(); foreach ($widgetsArr as $widget) { $widgets[] = ['value' => $widget[$value], 'label' => $widget['name']]; } return $widgets; } /** * Get the widget reference (code or namespace\class name) for the passed in type or code. * * @param string $matchParam * @param string $value * @param string $requestedParam * @return string|null */ public function getWidgetReference($matchParam, $value, $requestedParam) { $reference = null; $widgetsArr = $this->_widgetModel->getWidgetsArray(); foreach ($widgetsArr as $widget) { if ($widget[$matchParam] === $value) { $reference = $widget[$requestedParam]; break; } } return $reference; } /** * Load widget XML config and merge with theme widget config * * @return array|null * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function getWidgetConfigAsArray() { if ($this->_widgetConfigXml === null) { $this->_widgetConfigXml = $this->_widgetModel->getWidgetByClassType($this->getType()); if ($this->_widgetConfigXml) { $configFile = $this->_viewFileSystem->getFilename( 'widget.xml', [ 'area' => $this->getArea(), 'theme' => $this->getThemeId(), 'module' => $this->_namespaceResolver->determineOmittedNamespace( preg_replace('/^(.+?)\/.+$/', '\\1', $this->getType()), true ) ] ); $isReadable = $configFile && $this->_directory->isReadable($this->_directory->getRelativePath($configFile)); if ($isReadable) { $this->addThemeWidgetConfig($configFile); } } } return $this->_widgetConfigXml; } /** * Add config data from theme config xml. * * @param string $configFile */ private function addThemeWidgetConfig(string $configFile): void { $config = $this->_reader->readFile($configFile); $widgetName = isset($this->_widgetConfigXml['name']) ? $this->_widgetConfigXml['name'] : null; $themeWidgetConfig = null; if ($widgetName !== null) { foreach ($config as $widget) { if (isset($widget['name']) && $widgetName === $widget['name']) { $themeWidgetConfig = $widget; break; } } } if ($themeWidgetConfig) { $this->_widgetConfigXml = array_replace_recursive($this->_widgetConfigXml, $themeWidgetConfig); } } /** * Retrieve widget available templates * * @return array */ public function getWidgetTemplates() { $templates = []; $widgetConfig = $this->getWidgetConfigAsArray(); if ($widgetConfig && isset($widgetConfig['parameters']) && isset($widgetConfig['parameters']['template'])) { $configTemplates = $widgetConfig['parameters']['template']; if (isset($configTemplates['values'])) { foreach ($configTemplates['values'] as $name => $template) { $templates[(string)$name] = [ 'value' => $template['value'], 'label' => __((string)$template['label']), ]; } } } return $templates; } /** * Get list of containers that widget is limited to be in * * @return array */ public function getWidgetSupportedContainers() { $containers = []; $widgetConfig = $this->getWidgetConfigAsArray(); if (isset($widgetConfig) && isset($widgetConfig['supported_containers'])) { $configNodes = $widgetConfig['supported_containers']; foreach ($configNodes as $node) { if (isset($node['container_name'])) { $containers[] = (string)$node['container_name']; } } } return $containers; } /** * Retrieve widget templates that supported by specified container name * * @param string $containerName * @return array */ public function getWidgetSupportedTemplatesByContainer($containerName) { $widgetTemplates = $this->getWidgetTemplates(); $widgetConfig = $this->getWidgetConfigAsArray(); if (isset($widgetConfig)) { return $this->getWidgetTemplatesFromConfig($widgetConfig, $widgetTemplates, $containerName); } else { return $widgetTemplates; } } /** * Return widget templates from widget config. * * @param array $widgetConfig * @param array $widgetTemplates * @param string $containerName * @return array */ private function getWidgetTemplatesFromConfig( array $widgetConfig, array $widgetTemplates, string $containerName ): array { $templates = []; if (!isset($widgetConfig['supported_containers'])) { return $widgetTemplates; } $configNodes = $widgetConfig['supported_containers']; foreach ($configNodes as $node) { if (isset($node['container_name']) && (string)$node['container_name'] == $containerName) { if (isset($node['template'])) { $templateChildren = $node['template']; foreach ($templateChildren as $template) { if (isset($widgetTemplates[(string)$template])) { $templates[] = $widgetTemplates[(string)$template]; } } } } } return $templates; } /** * Generate layout update xml * * @param string $container * @param string $templatePath * @return string * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ public function generateLayoutUpdateXml($container, $templatePath = '') { $templateFilename = $this->_viewFileSystem->getTemplateFileName( $templatePath, [ 'area' => $this->getArea(), 'themeId' => $this->getThemeId(), 'module' => \Magento\Framework\View\Element\AbstractBlock::extractModuleName($this->getType()) ] ); // phpcs:ignore Magento2.Functions.DiscouragedFunction if (!$this->getId() && !$this->isCompleteToCreate() || $templatePath && !is_readable($templateFilename)) { return ''; } $parameters = $this->getWidgetParameters(); $xml = '<body><referenceContainer name="' . $this->_escaper->escapeHtmlAttr($container) . '">'; $template = ''; if (isset($parameters['template'])) { unset($parameters['template']); } if ($templatePath) { $template = ' template="' . $templatePath . '"'; } $hash = $this->mathRandom->getUniqueHash(); $xml .= '<block class="' . $this->getType() . '" name="' . $hash . '"' . $template . '>'; foreach ($parameters as $name => $value) { if ($name == 'conditions') { $name = 'conditions_encoded'; $value = $this->conditionsHelper->encode($value); } elseif (is_array($value)) { $value = implode(',', $value); } $this->validateWidgetParameters($name); if ($name && strlen((string)$value)) { // phpcs:ignore Magento2.Functions.DiscouragedFunction $value = html_entity_decode($value); $xml .= '<action method="setData">' . '<argument name="name" xsi:type="string">' . $name . '</argument>' . '<argument name="value" xsi:type="string">' . $this->_escaper->escapeHtml( $value ) . '</argument>' . '</action>'; } } $xml .= '</block></referenceContainer></body>'; $this->validateLayoutUpdateXml($xml); return $xml; } /** * Check if generated layout update xml is valid. * * @param string $xml * @return void * @throws LocalizedException */ private function validateLayoutUpdateXml(string $xml): void { $xmlValidator = $this->xmlValidatorFactory->create(); try { if (!$xmlValidator->isValid($xml)) { throw new LocalizedException(__('Layout update is invalid')); } } catch (ValidationException|ValidationSchemaException $e) { throw new LocalizedException(__('Layout update is invalid')); } } /** * Check if widget parameter doesn't contains payload * * @param string $param * @throws LocalizedException */ private function validateWidgetParameters(string $param): void { try { if (!preg_match('/^\w+$/', $param)) { throw new LocalizedException(__('Layout update is invalid')); } } catch (ValidationException|ValidationSchemaException $e) { throw new LocalizedException(__('Layout update is invalid')); } } /** * Invalidate related cache types * * @return $this */ protected function _invalidateCache() { if (count($this->_relatedCacheTypes)) { $this->_cacheTypeList->invalidate($this->_relatedCacheTypes); } return $this; } /** * Invalidate related cache if instance contain layout updates * * @return $this */ public function afterSave() { if ($this->dataHasChangedFor('page_groups') || $this->dataHasChangedFor('widget_parameters')) { $this->_invalidateCache(); } return parent::afterSave(); } /** * Invalidate related cache if instance contain layout updates * * @return $this */ public function beforeDelete() { if ($this->getPageGroups()) { $this->_invalidateCache(); } return parent::beforeDelete(); } }