diff --git a/README.md b/README.md index c777bc253cb595be429f8591da234c88334ed6db..107b7ad3b1b598f79261fa28ddbf5a27856f9c53 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/js/restrictions.js b/js/restrictions.js index 3ba774509141252caaeba5899909bc99ac00df39..6469a6126361f543ced2d7e94a14323fcd386c5f 100644 --- a/js/restrictions.js +++ b/js/restrictions.js @@ -2,47 +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-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; } @@ -71,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 4727c066bad6fe7e60878e7721ca92fd5c070b68..614d767c1db6fe9fc9a8aa04c7ca8c389cd505d7 100644 --- a/layout_paragraphs_restrictions.module +++ b/layout_paragraphs_restrictions.module @@ -15,7 +15,16 @@ function layout_paragraphs_restrictions_preprocess_layout_paragraphs_builder(&$v if (!empty($restrictions)) { $variables['#attached']['drupalSettings']['lpBuilder']['restrictions'][$layout->id()] = array_values($restrictions); } + + // Add the Paragraphs reference field name as a data attribute. + $paragraphsReferenceField = $layout->getParagraphsReferenceField(); + $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(); } /** @@ -25,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 bdedc61d2c23c95fe12c960465fccfca7b7c64b6..69f1a9546a68238a8d4cc668d2a9a0b02659ac17 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. @@ -96,11 +97,25 @@ class LayoutParagraphsRestrictions implements EventSubscriberInterface { $context['region'] = '_root'; } - $include = []; - $exclude = []; - $tests = []; + // @todo These contexts are not returned by Layout Paragraphs. + $paragraphsReferenceField = $event->getLayout()->getParagraphsReferenceField(); + $parentEntity = $paragraphsReferenceField->getEntity(); + + if (empty($context['field_name'])) { + $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(); + } foreach ($this->restrictions as $restriction) { + $tests = []; + $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)) { @@ -109,7 +124,7 @@ class LayoutParagraphsRestrictions implements EventSubscriberInterface { else { $tests[] = $restriction['context']; } - + foreach ($tests as $runtest) { $total_tests = 0; $matched_tests = 0;