From 95ff6afb18803167be4c2ff09ae78f844587cc87 Mon Sep 17 00:00:00 2001 From: Seth Hill <seth@atendesigngroup.com> Date: Thu, 16 Jan 2025 17:28:19 -0600 Subject: [PATCH 1/4] Add restriction based on field_name. --- js/restrictions.js | 3 +++ layout_paragraphs_restrictions.module | 4 ++++ src/EventSubscriber/LayoutParagraphsRestrictions.php | 6 +++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/js/restrictions.js b/js/restrictions.js index 3ba7745..b301943 100644 --- a/js/restrictions.js +++ b/js/restrictions.js @@ -12,6 +12,9 @@ sibling_type: el.previousElementSibling ? el.previousElementSibling.getAttribute('data-me-component-type') : null, + field_name: el.closest('[data-me-paragraphs-reference-field]') + ? el.closest('[data-me-paragraphs-reference-field]').getAttribute('data-me-paragraphs-reference-field') + : null, }; } diff --git a/layout_paragraphs_restrictions.module b/layout_paragraphs_restrictions.module index 4727c06..cad9057 100644 --- a/layout_paragraphs_restrictions.module +++ b/layout_paragraphs_restrictions.module @@ -15,6 +15,10 @@ function layout_paragraphs_restrictions_preprocess_layout_paragraphs_builder(&$v if (!empty($restrictions)) { $variables['#attached']['drupalSettings']['lpBuilder']['restrictions'][$layout->id()] = array_values($restrictions); } + + // Add the paragraphsReferenceField name as a data attribute. + $paragraphsReferenceField = $layout->getParagraphsReferenceField(); + $variables['attributes']['data-me-paragraphs-reference-field'] = $paragraphsReferenceField->getName(); $variables['#attached']['library'][] = 'layout_paragraphs_restrictions/restrictions'; } diff --git a/src/EventSubscriber/LayoutParagraphsRestrictions.php b/src/EventSubscriber/LayoutParagraphsRestrictions.php index bdedc61..81ea215 100644 --- a/src/EventSubscriber/LayoutParagraphsRestrictions.php +++ b/src/EventSubscriber/LayoutParagraphsRestrictions.php @@ -75,6 +75,7 @@ class LayoutParagraphsRestrictions implements EventSubscriberInterface { * - region: The region name (_root if no region is set). * - layout: The layout plugin ID. * - placement: The placement of the component (before or after). + * - field_name: The field name. * * @param \Drupal\layout_paragraphs\Event\LayoutParagraphsAllowedTypesEvent $event * The allowed types event. @@ -95,6 +96,9 @@ class LayoutParagraphsRestrictions implements EventSubscriberInterface { if (empty($context['region'])) { $context['region'] = '_root'; } + if (empty($context['field_name'])) { + $context['field_name'] = $event->getLayout()->getParagraphsReferenceField()->getName(); + } $include = []; $exclude = []; @@ -109,7 +113,7 @@ class LayoutParagraphsRestrictions implements EventSubscriberInterface { else { $tests[] = $restriction['context']; } - + foreach ($tests as $runtest) { $total_tests = 0; $matched_tests = 0; -- GitLab From 1364c9c7e9a6c1e3b48e0986aa7dd71c6ee99a49 Mon Sep 17 00:00:00 2001 From: Seth Hill <seth@atendesigngroup.com> Date: Fri, 17 Jan 2025 10:46:14 -0600 Subject: [PATCH 2/4] Add restriction based on entity_type and entity_bundle. --- js/restrictions.js | 108 ++++++++++-------- layout_paragraphs_restrictions.module | 13 ++- .../LayoutParagraphsRestrictions.php | 13 ++- 3 files changed, 83 insertions(+), 51 deletions(-) diff --git a/js/restrictions.js b/js/restrictions.js index b301943..6469a61 100644 --- a/js/restrictions.js +++ b/js/restrictions.js @@ -2,50 +2,56 @@ function getContext(el) { return { - parent_type: el.closest('[data-me-component-type]') - ? el.closest('[data-me-component-type]').getAttribute('data-me-component-type') + parent_type: el.closest('[data-lp-component-type]') + ? el.closest('[data-lp-component-type]').getAttribute('data-lp-component-type') : null, region: el.getAttribute('data-region') || '_root', layout: el.closest('[data-layout]') ? el.closest('[data-layout]').getAttribute('data-layout') : null, sibling_type: el.previousElementSibling - ? el.previousElementSibling.getAttribute('data-me-component-type') + ? el.previousElementSibling.getAttribute('data-lp-component-type') : null, - field_name: el.closest('[data-me-paragraphs-reference-field]') - ? el.closest('[data-me-paragraphs-reference-field]').getAttribute('data-me-paragraphs-reference-field') + field_name: el.closest('[data-lp-reference-field]') + ? el.closest('[data-lp-reference-field]').getAttribute('data-lp-reference-field') + : null, + entity_type: el.closest('[data-lp-entity-type]') + ? el.closest('[data-lp-entity-type]').getAttribute('data-lp-entity-type') + : null, + entity_bundle: el.closest('[data-lp-entity-bundle]') + ? el.closest('[data-lp-entity-bundle]').getAttribute('data-lp-entity-bundle') : null, }; } function applicableRestrictions(restrictions, targetContext) { - // Filters the list of restrictions to only those that apply to the current context. return restrictions.filter((restriction) => { - for (let key in restriction.context) { - const value = restriction.context[key]; - if (value.indexOf('!') === 0) { - if (targetContext[key] && targetContext[key] !== value.substring(1)) { - return true; - } + // Check if all keys in restriction.context have matching values in targetContext + return Object.entries(restriction.context).every(([key, value]) => { + // Skip if key doesn't exist in target context. + if (!targetContext[key]) { + return false; } - else { - if (targetContext[key] && targetContext[key] === value) { - return true; - } + + // Handle negation cases. + if (value.startsWith('!')) { + return targetContext[key] !== value.substring(1); } - } - return false; + + // Direct value comparison. + return targetContext[key] === value; + }); }); } $(document).on('lpb-component:move lpb-component:drop', (e, uuid) => { setTimeout(() => { - const el = document.querySelector(`[data-me-transform]`); + const el = document.querySelector(`[data-lp-transform]`); if (!el) { return; } const layoutId = el.closest('[data-lpb-id]').getAttribute('data-lpb-id'); - const transform = el.getAttribute('data-me-transform'); + const transform = el.getAttribute('data-lp-transform'); if (!transform) { return; } @@ -74,42 +80,52 @@ const appliedRestrictions = applicableRestrictions(allRestrictions, moveContext); // Build a list of transformations that apply. - const type = el.getAttribute('data-me-component-type') || []; + const type = el.getAttribute('data-lp-component-type') || []; + // Build an exclude list from restrictions that apply. - const exclude = appliedRestrictions.reduce((exclude, restriction) => { - return [...exclude, ...restriction.exclude_components || []]; - }, []); + let exclude = []; + if (appliedRestrictions.length > 0) { + exclude = appliedRestrictions.reduce((exclude, restriction) => { + return [...exclude, ...restriction.exclude_components || []]; + }, []); + } // Build an exclude list from restrictions that apply. - const transformations = appliedRestrictions.reduce((transformations, restriction) => { - if (restriction.transform) { - for (let src in restriction.transform) { - const transformTo = restriction.transform[src]; - // If the transformation is not in the exclude list, add it. - if (exclude.indexOf(transformTo) == -1) { - if (src.indexOf('*')) { - if (type.indexOf(src.replace('*', '')) === 0) { - return [...transformations, transformTo]; + let transformations = []; + if (appliedRestrictions.length > 0) { + transformations = appliedRestrictions.reduce((transformations, restriction) => { + if (restriction.transform) { + for (let src in restriction.transform) { + const transformTo = restriction.transform[src]; + // If the transformation is not in the exclude list, add it. + if (exclude.indexOf(transformTo) == -1) { + if (src.indexOf('*')) { + if (type.indexOf(src.replace('*', '')) === 0) { + return [...transformations, transformTo]; + } } - } - else { - if (type === src) { - return [...transformations, transformTo]; + else { + if (type === src) { + return [...transformations, transformTo]; + } } } } } - } - return transformations; - }, []); + return transformations; + }, []); + } // Build an include list from restrictions that apply. - const include = appliedRestrictions.reduce((include, restriction) => { - return [...include, ...restriction.components || []]; - }, []); - if (transformations.length > 0) { - el.setAttribute('data-me-transform', transformations[0]); - return; + let include = []; + if (appliedRestrictions.length > 0) { + include = appliedRestrictions.reduce((include, restriction) => { + return [...include, ...restriction.components || []]; + }, []); + if (transformations.length > 0) { + el.setAttribute('data-lp-transform', transformations[0]); + return; + } } // Compare the type of the element to the include/exclude lists. diff --git a/layout_paragraphs_restrictions.module b/layout_paragraphs_restrictions.module index cad9057..614d767 100644 --- a/layout_paragraphs_restrictions.module +++ b/layout_paragraphs_restrictions.module @@ -16,10 +16,15 @@ function layout_paragraphs_restrictions_preprocess_layout_paragraphs_builder(&$v $variables['#attached']['drupalSettings']['lpBuilder']['restrictions'][$layout->id()] = array_values($restrictions); } - // Add the paragraphsReferenceField name as a data attribute. + // Add the Paragraphs reference field name as a data attribute. $paragraphsReferenceField = $layout->getParagraphsReferenceField(); - $variables['attributes']['data-me-paragraphs-reference-field'] = $paragraphsReferenceField->getName(); + $variables['attributes']['data-lp-reference-field'] = $paragraphsReferenceField->getName(); $variables['#attached']['library'][] = 'layout_paragraphs_restrictions/restrictions'; + + // Add the parent entity type and bundle as data attributes. + $parentEntity = $paragraphsReferenceField->getEntity(); + $variables['attributes']['data-lp-entity-type'] = $parentEntity->getEntityTypeId(); + $variables['attributes']['data-lp-entity-bundle'] = $parentEntity->bundle(); } /** @@ -29,11 +34,11 @@ function layout_paragraphs_restrictions_preprocess_paragraph(&$variables) { if (!empty($variables['paragraph']->_layoutParagraphsBuilder)) { /** @var \Drupal\paragraphs\Entity\Paragraph $paragraph */ $paragraph = $variables['paragraph']; - $variables['attributes']['data-me-component-type'] = $paragraph->bundle(); + $variables['attributes']['data-lp-component-type'] = $paragraph->bundle(); $behaviorSettings = $paragraph->getAllBehaviorSettings(); foreach ($behaviorSettings['style_options'] ?? [] as $style_option) { if (!empty($style_option['component_variation'])) { - $variables['attributes']['data-me-component-type'] .= '__' . $style_option['component_variation']; + $variables['attributes']['data-lp-component-type'] .= '__' . $style_option['component_variation']; } } } diff --git a/src/EventSubscriber/LayoutParagraphsRestrictions.php b/src/EventSubscriber/LayoutParagraphsRestrictions.php index 81ea215..d1c0337 100644 --- a/src/EventSubscriber/LayoutParagraphsRestrictions.php +++ b/src/EventSubscriber/LayoutParagraphsRestrictions.php @@ -96,8 +96,19 @@ class LayoutParagraphsRestrictions implements EventSubscriberInterface { if (empty($context['region'])) { $context['region'] = '_root'; } + + // @todo These contexts are not returned by Layout Paragraphs. + $paragraphsReferenceField = $event->getLayout()->getParagraphsReferenceField(); + $parentEntity = $paragraphsReferenceField->getEntity(); + if (empty($context['field_name'])) { - $context['field_name'] = $event->getLayout()->getParagraphsReferenceField()->getName(); + $context['field_name'] = $paragraphsReferenceField->getName(); + } + if (empty($context['entity_type'])) { + $context['entity_type'] = $parentEntity->getEntityTypeId(); + } + if (empty($context['entity_bundle'])) { + $context['entity_bundle'] = $parentEntity->bundle(); } $include = []; -- GitLab From cd651b71ad11218ae7cb533ce1b4b497ba60dee8 Mon Sep 17 00:00:00 2001 From: Justin Toupin <justin@atendesigngroup.com> Date: Wed, 22 Jan 2025 10:46:26 -0700 Subject: [PATCH 3/4] Move include/exclude into restrictions loop --- src/EventSubscriber/LayoutParagraphsRestrictions.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/EventSubscriber/LayoutParagraphsRestrictions.php b/src/EventSubscriber/LayoutParagraphsRestrictions.php index d1c0337..1f0816f 100644 --- a/src/EventSubscriber/LayoutParagraphsRestrictions.php +++ b/src/EventSubscriber/LayoutParagraphsRestrictions.php @@ -111,11 +111,12 @@ class LayoutParagraphsRestrictions implements EventSubscriberInterface { $context['entity_bundle'] = $parentEntity->bundle(); } - $include = []; - $exclude = []; $tests = []; - foreach ($this->restrictions as $restriction) { + + $include = []; + $exclude = []; + // If context is a numerically indexed array, treat each item as a separate test context. // Otherwise treat it as a single test context. if (is_array($restriction['context']) && array_keys($restriction['context']) === range(0, count($restriction['context']) - 1)) { -- GitLab From cfc095e3af37657da3cf5e4b4543468d2a15f819 Mon Sep 17 00:00:00 2001 From: Seth Hill <seth@atendesigngroup.com> Date: Thu, 23 Jan 2025 07:15:25 -0600 Subject: [PATCH 4/4] Update README, move tests initialization into loop. --- README.md | 59 +++++++++++++++---- .../LayoutParagraphsRestrictions.php | 3 +- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index c777bc2..107b7ad 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,16 @@ See `example.layout_paragraphs_restrictions.yml` for the correct syntax. Contexts are defined as a list of context variables that must be matched in order for the restriction to apply. Valid values include the following: -- `parent_uuid`: The UUID of the parent component. -- `parent_type`: The bundle of the parent component. -- `sibling_uuid`: The UUID of the sibling component. -- `sibling_type`: The bundle of the sibling component. -- `region`: The region name (_root if no region is set). -- `layout`: The layout plugin ID. -- `placement`: The placement of the component (before or after). +- `parent_uuid`: the UUID of the parent component +- `parent_type`: the bundle of the parent component +- `sibling_uuid`: the UUID of the sibling component +- `sibling_type`: the bundle of the sibling component +- `region`: the region name (_root if no region is set) +- `layout`: the layout plugin ID +- `field_name`: the Layout Paragraphs field name +- `entity_type`: the entity type the Paragraphs field is attached to +- `entity_bundle`: the entity bundle name +- `placement`: the placement of the component (before or after) ## Components @@ -32,10 +35,10 @@ prohibited in the context. my_restriction_rule: # The name of the restriction rule. context: parent_type: section # The parent Layout Paragraph type. In this case, a "section". - components: # The list of components that are allowed in the context. - - rich_text - - image - - call_to_action + components: # The list of components that are allowed in the context. + - rich_text + - image + - call_to_action ``` In this example, the `parent_type` context is set to a Layout Paragraph called `section`. The components `rich_text`, `image`, and `call_to_action` are allowed to be placed within a `section` Layout Paragraph. @@ -58,6 +61,40 @@ full_width_twocol: In this example, a `hero` component is allowed to be placed within the content region of a one-column layout and the top region of a two-column layout. +### Restricting by field name, entity type or bundle + +Restrictions can be placed on specific fields, entity types or bundles. These restrictions may be used individually or in +conjunction with others. In this example, the context is set to the field_paragraphs field only on nodes of type blog. +```yaml +restrict_blog: + context: + entity_type: node + entity_bundle: blog + field_name: field_paragraphs +``` + +### Setting multiple contexts on a single rule +Rules may be applied to multiple contexts by passing them as an array: +```yaml +last_column: + context: + - + layout: twocol + region: second + - + layout: threecol + region: third +``` + +### Restricting Mercury Editor templates + +If using the [Mercury Editor](https://drupal.org/project/mercury_editor) module, templates may be placed in inclusion/exclusion lists by appending their ID to `me_template_`. Note that Layout Paragraphs Restrictions will not evaluate the Paragraph types _within_ a template for inclusion/exclusion, so it may be possible to place forbidden types into a context via a Mercury Editor template. +```yaml +components: + - me_template_3 + - me_template_6 +``` + ### Excluding components instead of including ```yaml diff --git a/src/EventSubscriber/LayoutParagraphsRestrictions.php b/src/EventSubscriber/LayoutParagraphsRestrictions.php index 1f0816f..69f1a95 100644 --- a/src/EventSubscriber/LayoutParagraphsRestrictions.php +++ b/src/EventSubscriber/LayoutParagraphsRestrictions.php @@ -111,9 +111,8 @@ class LayoutParagraphsRestrictions implements EventSubscriberInterface { $context['entity_bundle'] = $parentEntity->bundle(); } - $tests = []; foreach ($this->restrictions as $restriction) { - + $tests = []; $include = []; $exclude = []; -- GitLab