Instance.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Widget\Model\Widget;
  7. use Magento\Framework\App\Filesystem\DirectoryList;
  8. use Magento\Framework\Serialize\Serializer\Json;
  9. /**
  10. * Widget Instance Model
  11. *
  12. * @api
  13. * @method string getTitle()
  14. * @method \Magento\Widget\Model\Widget\Instance setTitle(string $value)
  15. * @method \Magento\Widget\Model\Widget\Instance setStoreIds(string $value)
  16. * @method \Magento\Widget\Model\Widget\Instance setWidgetParameters(string|array $value)
  17. * @method int getSortOrder()
  18. * @method \Magento\Widget\Model\Widget\Instance setSortOrder(int $value)
  19. * @method \Magento\Widget\Model\Widget\Instance setThemeId(int $value)
  20. * @method int getThemeId()
  21. *
  22. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  23. * @SuppressWarnings(PHPMD.TooManyFields)
  24. * @since 100.0.2
  25. */
  26. class Instance extends \Magento\Framework\Model\AbstractModel
  27. {
  28. const SPECIFIC_ENTITIES = 'specific';
  29. const ALL_ENTITIES = 'all';
  30. const DEFAULT_LAYOUT_HANDLE = 'default';
  31. const PRODUCT_LAYOUT_HANDLE = 'catalog_product_view';
  32. /**
  33. * @deprecated see self::SINGLE_PRODUCT_LAYOUT_HANDLE
  34. */
  35. const SINGLE_PRODUCT_LAYOUT_HANLDE = self::SINGLE_PRODUCT_LAYOUT_HANDLE;
  36. const SINGLE_PRODUCT_LAYOUT_HANDLE = 'catalog_product_view_id_{{ID}}';
  37. const PRODUCT_TYPE_LAYOUT_HANDLE = 'catalog_product_view_type_{{TYPE}}';
  38. const ANCHOR_CATEGORY_LAYOUT_HANDLE = 'catalog_category_view_type_layered';
  39. const NOTANCHOR_CATEGORY_LAYOUT_HANDLE = 'catalog_category_view_type_default';
  40. const SINGLE_CATEGORY_LAYOUT_HANDLE = 'catalog_category_view_id_{{ID}}';
  41. /**
  42. * @var array
  43. */
  44. protected $_layoutHandles = [];
  45. /**
  46. * @var array
  47. */
  48. protected $_specificEntitiesLayoutHandles = [];
  49. /**
  50. * @var \Magento\Framework\Simplexml\Element
  51. */
  52. protected $_widgetConfigXml = null;
  53. /**
  54. * Prefix of model events names
  55. *
  56. * @var string
  57. */
  58. protected $_eventPrefix = 'widget_widget_instance';
  59. /**
  60. * @var \Magento\Framework\View\FileSystem
  61. */
  62. protected $_viewFileSystem;
  63. /**
  64. * @var \Magento\Widget\Model\Widget
  65. */
  66. protected $_widgetModel;
  67. /**
  68. * @var \Magento\Widget\Model\NamespaceResolver
  69. */
  70. protected $_namespaceResolver;
  71. /**
  72. * @var \Magento\Framework\App\Cache\TypeListInterface
  73. */
  74. protected $_cacheTypeList;
  75. /**
  76. * @var string[]
  77. */
  78. protected $_relatedCacheTypes;
  79. /**
  80. * @var \Magento\Catalog\Model\Product\Type
  81. * @since 101.0.4
  82. */
  83. protected $_productType;
  84. /**
  85. * @var \Magento\Widget\Model\Config\Reader
  86. * @since 101.0.4
  87. */
  88. protected $_reader;
  89. /**
  90. * @var \Magento\Framework\Escaper
  91. */
  92. protected $_escaper;
  93. /**
  94. * @var \Magento\Framework\Math\Random
  95. */
  96. protected $mathRandom;
  97. /**
  98. * @var \Magento\Framework\Filesystem\Directory\ReadInterface
  99. */
  100. protected $_directory;
  101. /**
  102. * @var \Magento\Widget\Helper\Conditions
  103. */
  104. protected $conditionsHelper;
  105. /**
  106. * @var Json
  107. */
  108. private $serializer;
  109. /**
  110. * @param \Magento\Framework\Model\Context $context
  111. * @param \Magento\Framework\Registry $registry
  112. * @param \Magento\Framework\Escaper $escaper
  113. * @param \Magento\Framework\View\FileSystem $viewFileSystem
  114. * @param \Magento\Framework\App\Cache\TypeListInterface $cacheTypeList
  115. * @param \Magento\Catalog\Model\Product\Type $productType
  116. * @param \Magento\Widget\Model\Config\Reader $reader
  117. * @param \Magento\Widget\Model\Widget $widgetModel
  118. * @param \Magento\Widget\Model\NamespaceResolver $namespaceResolver
  119. * @param \Magento\Framework\Math\Random $mathRandom
  120. * @param \Magento\Framework\Filesystem $filesystem
  121. * @param \Magento\Widget\Helper\Conditions $conditionsHelper
  122. * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
  123. * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
  124. * @param array $relatedCacheTypes
  125. * @param array $data
  126. * @param \Magento\Framework\Serialize\Serializer\Json $serializer
  127. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  128. */
  129. public function __construct(
  130. \Magento\Framework\Model\Context $context,
  131. \Magento\Framework\Registry $registry,
  132. \Magento\Framework\Escaper $escaper,
  133. \Magento\Framework\View\FileSystem $viewFileSystem,
  134. \Magento\Framework\App\Cache\TypeListInterface $cacheTypeList,
  135. \Magento\Catalog\Model\Product\Type $productType,
  136. \Magento\Widget\Model\Config\Reader $reader,
  137. \Magento\Widget\Model\Widget $widgetModel,
  138. \Magento\Widget\Model\NamespaceResolver $namespaceResolver,
  139. \Magento\Framework\Math\Random $mathRandom,
  140. \Magento\Framework\Filesystem $filesystem,
  141. \Magento\Widget\Helper\Conditions $conditionsHelper,
  142. \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
  143. \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
  144. array $relatedCacheTypes = [],
  145. array $data = [],
  146. Json $serializer = null
  147. ) {
  148. $this->_escaper = $escaper;
  149. $this->_viewFileSystem = $viewFileSystem;
  150. $this->_cacheTypeList = $cacheTypeList;
  151. $this->_relatedCacheTypes = $relatedCacheTypes;
  152. $this->_productType = $productType;
  153. $this->_reader = $reader;
  154. $this->_widgetModel = $widgetModel;
  155. $this->mathRandom = $mathRandom;
  156. $this->conditionsHelper = $conditionsHelper;
  157. $this->_directory = $filesystem->getDirectoryRead(DirectoryList::ROOT);
  158. $this->_namespaceResolver = $namespaceResolver;
  159. $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance()->get(Json::class);
  160. parent::__construct($context, $registry, $resource, $resourceCollection, $data);
  161. }
  162. /**
  163. * Internal Constructor
  164. *
  165. * @return void
  166. */
  167. protected function _construct()
  168. {
  169. parent::_construct();
  170. $this->_init(\Magento\Widget\Model\ResourceModel\Widget\Instance::class);
  171. $this->_layoutHandles = [
  172. 'anchor_categories' => self::ANCHOR_CATEGORY_LAYOUT_HANDLE,
  173. 'notanchor_categories' => self::NOTANCHOR_CATEGORY_LAYOUT_HANDLE,
  174. 'all_products' => self::PRODUCT_LAYOUT_HANDLE,
  175. 'all_pages' => self::DEFAULT_LAYOUT_HANDLE,
  176. ];
  177. $this->_specificEntitiesLayoutHandles = [
  178. 'anchor_categories' => self::SINGLE_CATEGORY_LAYOUT_HANDLE,
  179. 'notanchor_categories' => self::SINGLE_CATEGORY_LAYOUT_HANDLE,
  180. 'all_products' => self::SINGLE_PRODUCT_LAYOUT_HANDLE,
  181. ];
  182. foreach (array_keys($this->_productType->getTypes()) as $typeId) {
  183. $layoutHandle = str_replace('{{TYPE}}', $typeId, self::PRODUCT_TYPE_LAYOUT_HANDLE);
  184. $this->_layoutHandles[$typeId . '_products'] = $layoutHandle;
  185. $this->_specificEntitiesLayoutHandles[$typeId . '_products'] = self::SINGLE_PRODUCT_LAYOUT_HANDLE;
  186. }
  187. }
  188. /**
  189. * Processing object before save data
  190. *
  191. * @return $this
  192. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  193. * @SuppressWarnings(PHPMD.NPathComplexity)
  194. */
  195. public function beforeSave()
  196. {
  197. $pageGroupIds = [];
  198. $tmpPageGroups = [];
  199. $pageGroups = $this->getData('page_groups');
  200. if ($pageGroups) {
  201. foreach ($pageGroups as $pageGroup) {
  202. if (isset($pageGroup[$pageGroup['page_group']])) {
  203. $pageGroupData = $pageGroup[$pageGroup['page_group']];
  204. if ($pageGroupData['page_id']) {
  205. $pageGroupIds[] = $pageGroupData['page_id'];
  206. }
  207. if (in_array($pageGroup['page_group'], ['pages', 'page_layouts'])) {
  208. $layoutHandle = $pageGroupData['layout_handle'];
  209. } else {
  210. $layoutHandle = $this->_layoutHandles[$pageGroup['page_group']];
  211. }
  212. if (!isset($pageGroupData['template'])) {
  213. $pageGroupData['template'] = '';
  214. }
  215. $tmpPageGroup = [
  216. 'page_id' => $pageGroupData['page_id'],
  217. 'group' => $pageGroup['page_group'],
  218. 'layout_handle' => $layoutHandle,
  219. 'for' => $pageGroupData['for'],
  220. 'block_reference' => $pageGroupData['block'],
  221. 'entities' => '',
  222. 'layout_handle_updates' => [$layoutHandle],
  223. 'template' => $pageGroupData['template'] ? $pageGroupData['template'] : '',
  224. ];
  225. if ($pageGroupData['for'] == self::SPECIFIC_ENTITIES) {
  226. $layoutHandleUpdates = [];
  227. foreach (explode(',', $pageGroupData['entities']) as $entity) {
  228. $layoutHandleUpdates[] = str_replace(
  229. '{{ID}}',
  230. $entity,
  231. $this->_specificEntitiesLayoutHandles[$pageGroup['page_group']]
  232. );
  233. }
  234. $tmpPageGroup['entities'] = $pageGroupData['entities'];
  235. $tmpPageGroup['layout_handle_updates'] = $layoutHandleUpdates;
  236. }
  237. $tmpPageGroups[] = $tmpPageGroup;
  238. }
  239. }
  240. }
  241. if (is_array($this->getData('store_ids'))) {
  242. $this->setData('store_ids', implode(',', $this->getData('store_ids')));
  243. }
  244. $parameters = $this->getData('widget_parameters');
  245. if (is_array($parameters)) {
  246. if (array_key_exists('show_pager', $parameters) && !array_key_exists('page_var_name', $parameters)) {
  247. $parameters['page_var_name'] = 'p' . $this->mathRandom->getRandomString(
  248. 5,
  249. \Magento\Framework\Math\Random::CHARS_LOWERS
  250. );
  251. }
  252. $this->setData('widget_parameters', $this->serializer->serialize($parameters));
  253. }
  254. $this->setData('page_groups', $tmpPageGroups);
  255. $this->setData('page_group_ids', $pageGroupIds);
  256. return parent::beforeSave();
  257. }
  258. /**
  259. * Validate widget instance data
  260. *
  261. * @return \Magento\Framework\Phrase|bool
  262. */
  263. public function validate()
  264. {
  265. if ($this->isCompleteToCreate()) {
  266. return true;
  267. }
  268. return __('We cannot create the widget instance because it is missing required information.');
  269. }
  270. /**
  271. * Check if widget instance has required data (other data depends on it)
  272. *
  273. * @return boolean
  274. */
  275. public function isCompleteToCreate()
  276. {
  277. return $this->getType() && $this->getThemeId();
  278. }
  279. /**
  280. * Return widget instance code. If not set, derive value from type (namespace\class).
  281. *
  282. * @return string
  283. */
  284. public function getCode()
  285. {
  286. $code = $this->_getData('instance_code');
  287. if ($code == null) {
  288. $code = $this->getWidgetReference('type', $this->getType(), 'code');
  289. $this->setData('instance_code', $code);
  290. }
  291. return $code;
  292. }
  293. /**
  294. * Sets the value this widget instance code.
  295. * The widget code is the 'id' attribute in the widget node.
  296. * 'code' is used in Magento\Widget\Model\Widget->getWidgetsArray when the array of widgets is created.
  297. *
  298. * @param string $code
  299. * @return $this
  300. */
  301. public function setCode($code)
  302. {
  303. $this->setData('instance_code', $code);
  304. return $this;
  305. }
  306. /**
  307. * Setter
  308. *
  309. * Prepare widget type
  310. *
  311. * @param string $type
  312. * @return $this
  313. */
  314. public function setType($type)
  315. {
  316. $this->setData('instance_type', $type);
  317. return $this;
  318. }
  319. /**
  320. * Getter
  321. *
  322. * Prepare widget type
  323. *
  324. * @return string
  325. */
  326. public function getType()
  327. {
  328. return $this->_getData('instance_type');
  329. }
  330. /**
  331. * Getter.
  332. *
  333. * If not set return default
  334. *
  335. * @return string
  336. */
  337. public function getArea()
  338. {
  339. //TODO Shouldn't we get "area" from theme model which we can load using "theme_id"?
  340. if (!$this->_getData('area')) {
  341. return \Magento\Framework\View\DesignInterface::DEFAULT_AREA;
  342. }
  343. return $this->_getData('area');
  344. }
  345. /**
  346. * Getter
  347. *
  348. * Explode to array if string setted
  349. *
  350. * @return array
  351. */
  352. public function getStoreIds()
  353. {
  354. if (is_string($this->getData('store_ids'))) {
  355. return explode(',', $this->getData('store_ids'));
  356. }
  357. return $this->getData('store_ids');
  358. }
  359. /**
  360. * Getter
  361. *
  362. * Unserialize if serialized string setted
  363. *
  364. * @return array
  365. */
  366. public function getWidgetParameters()
  367. {
  368. if (is_string($this->getData('widget_parameters'))) {
  369. return $this->serializer->unserialize($this->getData('widget_parameters'));
  370. } elseif (null === $this->getData('widget_parameters')) {
  371. return [];
  372. }
  373. return is_array($this->getData('widget_parameters')) ? $this->getData('widget_parameters') : [];
  374. }
  375. /**
  376. * Retrieve option array of widget types
  377. *
  378. * @param string $value
  379. * @return array
  380. */
  381. public function getWidgetsOptionArray($value = 'code')
  382. {
  383. $widgets = [];
  384. $widgetsArr = $this->_widgetModel->getWidgetsArray();
  385. foreach ($widgetsArr as $widget) {
  386. $widgets[] = ['value' => $widget[$value], 'label' => $widget['name']];
  387. }
  388. return $widgets;
  389. }
  390. /**
  391. * Get the widget reference (code or namespace\class name) for the passed in type or code.
  392. *
  393. * @param string $matchParam
  394. * @param string $value
  395. * @param string $requestedParam
  396. * @return string|null
  397. */
  398. public function getWidgetReference($matchParam, $value, $requestedParam)
  399. {
  400. $reference = null;
  401. $widgetsArr = $this->_widgetModel->getWidgetsArray();
  402. foreach ($widgetsArr as $widget) {
  403. if ($widget[$matchParam] === $value) {
  404. $reference = $widget[$requestedParam];
  405. break;
  406. }
  407. }
  408. return $reference;
  409. }
  410. /**
  411. * Load widget XML config and merge with theme widget config
  412. *
  413. * @return array|null
  414. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  415. */
  416. public function getWidgetConfigAsArray()
  417. {
  418. if ($this->_widgetConfigXml === null) {
  419. $this->_widgetConfigXml = $this->_widgetModel->getWidgetByClassType($this->getType());
  420. if ($this->_widgetConfigXml) {
  421. $configFile = $this->_viewFileSystem->getFilename(
  422. 'widget.xml',
  423. [
  424. 'area' => $this->getArea(),
  425. 'theme' => $this->getThemeId(),
  426. 'module' => $this->_namespaceResolver->determineOmittedNamespace(
  427. preg_replace('/^(.+?)\/.+$/', '\\1', $this->getType()),
  428. true
  429. )
  430. ]
  431. );
  432. $isReadable = $configFile
  433. && $this->_directory->isReadable($this->_directory->getRelativePath($configFile));
  434. if ($isReadable) {
  435. $config = $this->_reader->readFile($configFile);
  436. $widgetName = isset($this->_widgetConfigXml['name']) ? $this->_widgetConfigXml['name'] : null;
  437. $themeWidgetConfig = null;
  438. if ($widgetName !== null) {
  439. foreach ($config as $widget) {
  440. if (isset($widget['name']) && $widgetName === $widget['name']) {
  441. $themeWidgetConfig = $widget;
  442. break;
  443. }
  444. }
  445. }
  446. if ($themeWidgetConfig) {
  447. $this->_widgetConfigXml = array_replace_recursive($this->_widgetConfigXml, $themeWidgetConfig);
  448. }
  449. }
  450. }
  451. }
  452. return $this->_widgetConfigXml;
  453. }
  454. /**
  455. * Retrieve widget available templates
  456. *
  457. * @return array
  458. */
  459. public function getWidgetTemplates()
  460. {
  461. $templates = [];
  462. $widgetConfig = $this->getWidgetConfigAsArray();
  463. if ($widgetConfig && isset($widgetConfig['parameters']) && isset($widgetConfig['parameters']['template'])) {
  464. $configTemplates = $widgetConfig['parameters']['template'];
  465. if (isset($configTemplates['values'])) {
  466. foreach ($configTemplates['values'] as $name => $template) {
  467. $templates[(string)$name] = [
  468. 'value' => $template['value'],
  469. 'label' => __((string)$template['label']),
  470. ];
  471. }
  472. }
  473. }
  474. return $templates;
  475. }
  476. /**
  477. * Get list of containers that widget is limited to be in
  478. *
  479. * @return array
  480. */
  481. public function getWidgetSupportedContainers()
  482. {
  483. $containers = [];
  484. $widgetConfig = $this->getWidgetConfigAsArray();
  485. if (isset($widgetConfig) && isset($widgetConfig['supported_containers'])) {
  486. $configNodes = $widgetConfig['supported_containers'];
  487. foreach ($configNodes as $node) {
  488. if (isset($node['container_name'])) {
  489. $containers[] = (string)$node['container_name'];
  490. }
  491. }
  492. }
  493. return $containers;
  494. }
  495. /**
  496. * Retrieve widget templates that supported by specified container name
  497. *
  498. * @param string $containerName
  499. * @return array
  500. */
  501. public function getWidgetSupportedTemplatesByContainer($containerName)
  502. {
  503. $templates = [];
  504. $widgetTemplates = $this->getWidgetTemplates();
  505. $widgetConfig = $this->getWidgetConfigAsArray();
  506. if (isset($widgetConfig)) {
  507. if (!isset($widgetConfig['supported_containers'])) {
  508. return $widgetTemplates;
  509. }
  510. $configNodes = $widgetConfig['supported_containers'];
  511. foreach ($configNodes as $node) {
  512. if (isset($node['container_name']) && (string)$node['container_name'] == $containerName) {
  513. if (isset($node['template'])) {
  514. $templateChildren = $node['template'];
  515. foreach ($templateChildren as $template) {
  516. if (isset($widgetTemplates[(string)$template])) {
  517. $templates[] = $widgetTemplates[(string)$template];
  518. }
  519. }
  520. }
  521. }
  522. }
  523. } else {
  524. return $widgetTemplates;
  525. }
  526. return $templates;
  527. }
  528. /**
  529. * Generate layout update xml
  530. *
  531. * @param string $container
  532. * @param string $templatePath
  533. * @return string
  534. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  535. * @SuppressWarnings(PHPMD.NPathComplexity)
  536. */
  537. public function generateLayoutUpdateXml($container, $templatePath = '')
  538. {
  539. $templateFilename = $this->_viewFileSystem->getTemplateFileName(
  540. $templatePath,
  541. [
  542. 'area' => $this->getArea(),
  543. 'themeId' => $this->getThemeId(),
  544. 'module' => \Magento\Framework\View\Element\AbstractBlock::extractModuleName($this->getType())
  545. ]
  546. );
  547. if (!$this->getId() && !$this->isCompleteToCreate() || $templatePath && !is_readable($templateFilename)) {
  548. return '';
  549. }
  550. $parameters = $this->getWidgetParameters();
  551. $xml = '<body><referenceContainer name="' . $container . '">';
  552. $template = '';
  553. if (isset($parameters['template'])) {
  554. unset($parameters['template']);
  555. }
  556. if ($templatePath) {
  557. $template = ' template="' . $templatePath . '"';
  558. }
  559. $hash = $this->mathRandom->getUniqueHash();
  560. $xml .= '<block class="' . $this->getType() . '" name="' . $hash . '"' . $template . '>';
  561. foreach ($parameters as $name => $value) {
  562. if ($name == 'conditions') {
  563. $name = 'conditions_encoded';
  564. $value = $this->conditionsHelper->encode($value);
  565. } elseif (is_array($value)) {
  566. $value = implode(',', $value);
  567. }
  568. if ($name && strlen((string)$value)) {
  569. $value = html_entity_decode($value);
  570. $xml .= '<action method="setData">' .
  571. '<argument name="name" xsi:type="string">' .
  572. $name .
  573. '</argument>' .
  574. '<argument name="value" xsi:type="string">' .
  575. $this->_escaper->escapeHtml(
  576. $value
  577. ) . '</argument>' . '</action>';
  578. }
  579. }
  580. $xml .= '</block></referenceContainer></body>';
  581. return $xml;
  582. }
  583. /**
  584. * Invalidate related cache types
  585. *
  586. * @return $this
  587. */
  588. protected function _invalidateCache()
  589. {
  590. if (count($this->_relatedCacheTypes)) {
  591. $this->_cacheTypeList->invalidate($this->_relatedCacheTypes);
  592. }
  593. return $this;
  594. }
  595. /**
  596. * Invalidate related cache if instance contain layout updates
  597. *
  598. * @return $this
  599. */
  600. public function afterSave()
  601. {
  602. if ($this->dataHasChangedFor('page_groups') || $this->dataHasChangedFor('widget_parameters')) {
  603. $this->_invalidateCache();
  604. }
  605. return parent::afterSave();
  606. }
  607. /**
  608. * Invalidate related cache if instance contain layout updates
  609. *
  610. * @return $this
  611. */
  612. public function beforeDelete()
  613. {
  614. if ($this->getPageGroups()) {
  615. $this->_invalidateCache();
  616. }
  617. return parent::beforeDelete();
  618. }
  619. }