├── COPYING.txt ├── LICENSE.txt ├── Plugin ├── CategorySelectedLayout.php ├── CmsSelectableLayout.php └── ProductSelectedLayout.php ├── README.md ├── composer.json ├── docs └── img.png ├── etc ├── di.xml └── module.xml └── registration.php /COPYING.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2023-present GTStudio 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2023-present GTStudio 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /Plugin/CategorySelectedLayout.php: -------------------------------------------------------------------------------- 1 | themeFactory = $themeFactory; 35 | $this->design = $design; 36 | $this->layoutProcessorFactory = $layoutProcessorFactory; 37 | } 38 | 39 | /** 40 | * Get the processor instance. 41 | * 42 | * @return LayoutProcessor 43 | */ 44 | private function getLayoutProcessor(): LayoutProcessor 45 | { 46 | return $this->layoutProcessorFactory->create( 47 | [ 48 | 'theme' => $this->themeFactory->create( 49 | $this->design->getConfigurationDesignTheme(Area::AREA_FRONTEND) 50 | ) 51 | ] 52 | ); 53 | } 54 | 55 | /** 56 | * @param LayoutUpdateManager $subject 57 | * @param $result 58 | * @param CategoryInterface $category 59 | * @return array 60 | */ 61 | public function afterFetchAvailableFiles(LayoutUpdateManager $subject, $result, CategoryInterface $category) 62 | { 63 | if (!$category->getId()) { 64 | return []; 65 | } 66 | 67 | $handles = $this->getLayoutProcessor()->getAvailableHandles(); 68 | 69 | return array_filter( 70 | array_map( 71 | function (string $handle) use ($category): ?string { 72 | preg_match( 73 | '/^catalog\_category\_view\_selectable\_(' . $category->getId() . '|all)\_([a-z0-9]+)/i', 74 | $handle, 75 | $selectable 76 | ); 77 | if (!empty($selectable[2])) { 78 | return "{$selectable[1]}_{$selectable[2]}"; 79 | } 80 | 81 | return null; 82 | }, 83 | $handles 84 | ) 85 | ); 86 | } 87 | 88 | /** 89 | * Extract custom layout attribute value. 90 | * 91 | * @param CategoryInterface $category 92 | * @return mixed 93 | */ 94 | private function extractAttributeValue(CategoryInterface $category) 95 | { 96 | if ($category instanceof Category && !$category->hasData(CustomAttributesDataInterface::CUSTOM_ATTRIBUTES)) { 97 | return $category->getData('custom_layout_update_file'); 98 | } 99 | if ($attr = $category->getCustomAttribute('custom_layout_update_file')) { 100 | return $attr->getValue(); 101 | } 102 | 103 | return null; 104 | } 105 | 106 | /** 107 | * @param LayoutUpdateManager $subject 108 | * @param null $result 109 | * @param CategoryInterface $category 110 | * @param DataObject $intoSettings 111 | * @return void 112 | */ 113 | public function afterExtractCustomSettings( 114 | LayoutUpdateManager $subject, 115 | $result, 116 | CategoryInterface $category, 117 | DataObject $intoSettings 118 | ): void { 119 | if ($category->getId() && $value = $this->extractAttributeValue($category)) { 120 | $handles = $intoSettings->getPageLayoutHandles() ?? []; 121 | $handles = array_merge( 122 | $handles, 123 | ['selectable' => $value] 124 | ); 125 | $intoSettings->setPageLayoutHandles($handles); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Plugin/CmsSelectableLayout.php: -------------------------------------------------------------------------------- 1 | layoutProcessorFactory = $layoutProcessorFactory; 41 | $this->themeFactory = $themeFactory; 42 | $this->design = $design; 43 | $this->identityMap = $identityMap; 44 | $this->pageRepository = $pageRepository; 45 | } 46 | 47 | /** 48 | * Adopt page's identifier to be used as layout handle. 49 | * 50 | * @param PageInterface $page 51 | * @return string 52 | */ 53 | private function sanitizeIdentifier(PageInterface $page): string 54 | { 55 | return $page->getIdentifier() === null ? '' : str_replace('/', '_', $page->getIdentifier()); 56 | } 57 | 58 | /** 59 | * Get the processor instance. 60 | * 61 | * @return LayoutProcessor 62 | */ 63 | private function getLayoutProcessor(): LayoutProcessor 64 | { 65 | return $this->layoutProcessorFactory->create( 66 | [ 67 | 'theme' => $this->themeFactory->create( 68 | $this->design->getConfigurationDesignTheme(Area::AREA_FRONTEND) 69 | ) 70 | ] 71 | ); 72 | } 73 | 74 | /** 75 | * @param CustomLayoutManager $subject 76 | * @param array $result 77 | * @param PageInterface $page 78 | * @return array 79 | */ 80 | public function afterFetchAvailableFiles(CustomLayoutManager $subject, array $result, PageInterface $page): array 81 | { 82 | $identifier = $this->sanitizeIdentifier($page); 83 | $handles = $this->getLayoutProcessor()->getAvailableHandles(); 84 | 85 | return array_filter( 86 | array_map( 87 | function (string $handle) use ($identifier): ?string { 88 | preg_match( 89 | '/^cms\_page\_view\_selectable\_(' . preg_quote($identifier) . '|all)\_([a-z0-9]+)/i', 90 | $handle, 91 | $selectable 92 | ); 93 | if (!empty($selectable[2])) { 94 | return "{$selectable[1]}_{$selectable[2]}"; 95 | } 96 | 97 | return null; 98 | }, 99 | $handles 100 | ) 101 | ); 102 | } 103 | 104 | /** 105 | * @param CustomLayoutManager $subject 106 | * @param null $result 107 | * @param PageLayout $layout 108 | * @param CustomLayoutSelectedInterface $layoutSelected 109 | * @return void 110 | * @throws LocalizedException 111 | */ 112 | public function afterApplyUpdate( 113 | CustomLayoutManager $subject, 114 | $result, 115 | PageLayout $layout, 116 | CustomLayoutSelectedInterface $layoutSelected 117 | ): void { 118 | $page = $this->identityMap->get($layoutSelected->getPageId()); 119 | if (!$page) { 120 | $page = $this->pageRepository->getById($layoutSelected->getPageId()); 121 | } 122 | 123 | $layout->addPageLayoutHandles( 124 | ['selectable' => $layoutSelected->getLayoutFileId()], 125 | 'cms_page_view' 126 | ); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Plugin/ProductSelectedLayout.php: -------------------------------------------------------------------------------- 1 | themeFactory = $themeFactory; 35 | $this->design = $design; 36 | $this->layoutProcessorFactory = $layoutProcessorFactory; 37 | } 38 | 39 | /** 40 | * @param LayoutUpdateManager $subject 41 | * @param $result 42 | * @param ProductInterface $product 43 | * @return array 44 | */ 45 | public function afterFetchAvailableFiles(LayoutUpdateManager $subject, $result, ProductInterface $product) 46 | { 47 | if (!$product->getSku()) { 48 | return []; 49 | } 50 | 51 | $identifier = $this->sanitizeSku($product); 52 | $handles = $this->getLayoutProcessor()->getAvailableHandles(); 53 | 54 | return array_filter( 55 | array_map( 56 | function (string $handle) use ($identifier): ?string { 57 | preg_match( 58 | '/^catalog\_product\_view\_selectable\_(' . preg_quote($identifier) . '|all)\_([a-z0-9]+)/i', 59 | $handle, 60 | $selectable 61 | ); 62 | if (!empty($selectable[2])) { 63 | return "{$selectable[1]}_{$selectable[2]}"; 64 | } 65 | 66 | return null; 67 | }, 68 | $handles 69 | ) 70 | ); 71 | } 72 | 73 | /** 74 | * Get the processor instance. 75 | * 76 | * @return LayoutProcessor 77 | */ 78 | private function getLayoutProcessor(): LayoutProcessor 79 | { 80 | return $this->layoutProcessorFactory->create( 81 | [ 82 | 'theme' => $this->themeFactory->create( 83 | $this->design->getConfigurationDesignTheme(Area::AREA_FRONTEND) 84 | ) 85 | ] 86 | ); 87 | } 88 | 89 | /** 90 | * Extract custom layout attribute value. 91 | * 92 | * @param ProductInterface $product 93 | * @return mixed 94 | */ 95 | private function extractAttributeValue(ProductInterface $product) 96 | { 97 | if ($product instanceof Product && !$product->hasData(CustomAttributesDataInterface::CUSTOM_ATTRIBUTES)) { 98 | return $product->getData('custom_layout_update_file'); 99 | } 100 | if ($attr = $product->getCustomAttribute('custom_layout_update_file')) { 101 | return $attr->getValue(); 102 | } 103 | 104 | return null; 105 | } 106 | 107 | /** 108 | * Adopt product's SKU to be used as layout handle. 109 | * 110 | * @param ProductInterface $product 111 | * @return string 112 | */ 113 | private function sanitizeSku(ProductInterface $product): string 114 | { 115 | return rawurlencode($product->getSku()); 116 | } 117 | 118 | /** 119 | * @param LayoutUpdateManager $subject 120 | * @param null $result 121 | * @param ProductInterface $product 122 | * @param DataObject $intoSettings 123 | * @return void 124 | */ 125 | public function afterExtractCustomSettings( 126 | LayoutUpdateManager $subject, 127 | $result, 128 | ProductInterface $product, 129 | DataObject $intoSettings 130 | ): void { 131 | if ($product->getSku() && $value = $this->extractAttributeValue($product)) { 132 | $handles = $intoSettings->getPageLayoutHandles() ?? []; 133 | $handles = array_merge( 134 | $handles, 135 | ['selectable' => $value] 136 | ); 137 | $intoSettings->setPageLayoutHandles($handles); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Module Gtstudio Selectable Layout 2 | 3 | gtstudio/module-selected-layout 4 | 5 | - [Main Functionalities](#markdown-header-main-functionalities) 6 | - [Usage](#markdown-header-usage) 7 | - [Installation](#markdown-header-installation) 8 | - [Specifications](#markdown-header-specifications) 9 | 10 | 11 | ## Main Functionalities 12 | This module Make Custom Layout Update file selectable generally available in all categories, products and cms pages. 13 | Based on this request : https://github.com/magento/magento2/issues/26901 14 | 15 | ## Usage 16 | With this module, you will be able to create generals selectable layouts updates like this : 17 | 18 | `catalog_category_view_selectable_all_mycustomLayout` 19 | `catalog_product_view_selectable_all_mycustomLayout` 20 | `cms_page_view_selectable_all_mycustomLayout` 21 | 22 | So this layout update will be available on all cms pages, categories or products on field "Custom Layout Update" 23 | 24 | ![](docs/img.png) 25 | 26 | ## Installation 27 | \* = in production please use the `--keep-generated` option 28 | 29 | ### Type 1: Zip file 30 | 31 | - Unzip the zip file in `app/code/Gtstudio` 32 | - Enable the module by running `php bin/magento module:enable Gtstudio_DarkMode` 33 | - Apply database updates by running `php bin/magento setup:upgrade`\* 34 | - Flush the cache by running `php bin/magento cache:flush` 35 | 36 | ### Type 2: Composer 37 | 38 | - Install the module composer by running `composer require gtstudio/module-selected-layout` 39 | - enable the module by running `php bin/magento module:enable Gtstudio_SelectedLayout` 40 | - apply database updates by running `php bin/magento setup:upgrade`\* 41 | - Flush the cache by running `php bin/magento cache:flush` 42 | 43 | 44 | ## Specifications 45 | 46 | - Plugin `Magento\Cms\Model\Page\CustomLayout\CustomLayoutManager` 47 | - Plugin `Magento\Catalog\Model\Category\Attribute\LayoutUpdateManager` 48 | - Plugin `Magento\Catalog\Model\Product\Attribute\LayoutUpdateManager` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gtstudio/module-selected-layout", 3 | "version": "1.0.3", 4 | "description": "Add general selectable layout functionality on CMS pages,category and products", 5 | "type": "magento2-module", 6 | "require": { 7 | "magento/framework": "*", 8 | "php": ">=7.3" 9 | }, 10 | "license": "MIT", 11 | "autoload": { 12 | "files": [ 13 | "registration.php" 14 | ], 15 | "psr-4": { 16 | "Gtstudio\\SelectedLayout\\": "" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /docs/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielgts/magento2-selected-layout/ba93551a4f63922aa591e0f66ec401ab7e2ef53f/docs/img.png -------------------------------------------------------------------------------- /etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 10 | 13 | 14 | 15 | 18 | 19 | 20 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 |