Skip to content
Snippets Groups Projects
Commit 0b9548e2 authored by Seth Hill's avatar Seth Hill
Browse files

Issue #3500389: Allow restrictions based on content type or field name

parent 885e19f5
Branches
Tags
1 merge request!9Resolve #3500389 "Allow restrictions based"
......@@ -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
......
......@@ -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.
......
......@@ -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'];
}
}
}
......
......@@ -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;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment