Interim commit.
[yaffs-website] / web / modules / contrib / entity_embed / src / Plugin / Filter / EntityEmbedFilter.php
1 <?php
2
3 namespace Drupal\entity_embed\Plugin\Filter;
4
5 use Drupal\Component\Utility\Html;
6 use Drupal\Core\Entity\EntityTypeManagerInterface;
7 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
8 use Drupal\Core\Render\BubbleableMetadata;
9 use Drupal\Core\Render\RenderContext;
10 use Drupal\Core\Render\RendererInterface;
11 use Drupal\entity_embed\EntityEmbedBuilderInterface;
12 use Drupal\entity_embed\Exception\EntityNotFoundException;
13 use Drupal\entity_embed\Exception\RecursiveRenderingException;
14 use Drupal\filter\FilterProcessResult;
15 use Drupal\filter\Plugin\FilterBase;
16 use Symfony\Component\DependencyInjection\ContainerInterface;
17 use Drupal\embed\DomHelperTrait;
18
19 /**
20  * Provides a filter to display embedded entities based on data attributes.
21  *
22  * @Filter(
23  *   id = "entity_embed",
24  *   title = @Translation("Display embedded entities"),
25  *   description = @Translation("Embeds entities using data attributes: data-entity-type, data-entity-uuid, and data-view-mode."),
26  *   type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE
27  * )
28  */
29 class EntityEmbedFilter extends FilterBase implements ContainerFactoryPluginInterface {
30
31   use DomHelperTrait;
32
33   /**
34    * The renderer service.
35    *
36    * @var \Drupal\Core\Render\RendererInterface
37    */
38   protected $renderer;
39
40   /**
41    * The entity type manager.
42    *
43    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
44    */
45   protected $entityTypeManager;
46
47   /**
48    * The entity embed builder service.
49    *
50    * @var \Drupal\entity_embed\EntityEmbedBuilderInterface
51    */
52   protected $builder;
53
54   /**
55    * Constructs a EntityEmbedFilter object.
56    *
57    * @param array $configuration
58    *   A configuration array containing information about the plugin instance.
59    * @param string $plugin_id
60    *   The plugin ID for the plugin instance.
61    * @param mixed $plugin_definition
62    *   The plugin implementation definition.
63    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
64    *   The entity type manager.
65    * @param \Drupal\Core\Render\RendererInterface $renderer
66    *   The renderer.
67    * @param \Drupal\entity_embed\EntityEmbedBuilderInterface $builder
68    *   The entity embed builder service.
69    */
70   public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer, EntityEmbedBuilderInterface $builder) {
71     parent::__construct($configuration, $plugin_id, $plugin_definition);
72     $this->entityTypeManager = $entity_type_manager;
73     $this->renderer = $renderer;
74     $this->builder = $builder;
75   }
76
77   /**
78    * {@inheritdoc}
79    */
80   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
81     return new static(
82       $configuration,
83       $plugin_id,
84       $plugin_definition,
85       $container->get('entity_type.manager'),
86       $container->get('renderer'),
87       $container->get('entity_embed.builder')
88     );
89   }
90
91   /**
92    * {@inheritdoc}
93    */
94   public function process($text, $langcode) {
95     $result = new FilterProcessResult($text);
96
97     if (strpos($text, 'data-entity-type') !== FALSE && (strpos($text, 'data-entity-embed-display') !== FALSE || strpos($text, 'data-view-mode') !== FALSE)) {
98       $dom = Html::load($text);
99       $xpath = new \DOMXPath($dom);
100
101       foreach ($xpath->query('//drupal-entity[@data-entity-type and (@data-entity-uuid or @data-entity-id) and (@data-entity-embed-display or @data-view-mode)]') as $node) {
102         /** @var \DOMElement $node */
103         $entity_type = $node->getAttribute('data-entity-type');
104         $entity = NULL;
105         $entity_output = '';
106
107         // data-entity-embed-settings is deprecated, make sure we convert it to
108         // data-entity-embed-display-settings.
109         if (($settings = $node->getAttribute('data-entity-embed-settings')) && !$node->hasAttribute('data-entity-embed-display-settings')) {
110           $node->setAttribute('data-entity-embed-display-settings', $settings);
111           $node->removeAttribute('data-entity-embed-settings');
112         }
113
114         try {
115           // Load the entity either by UUID (preferred) or ID.
116           $id = NULL;
117           $entity = NULL;
118           if ($id = $node->getAttribute('data-entity-uuid')) {
119             $entity = $this->entityTypeManager->getStorage($entity_type)
120               ->loadByProperties(['uuid' => $id]);
121             $entity = current($entity);
122           }
123           else {
124             $id = $node->getAttribute('data-entity-id');
125             $entity = $this->entityTypeManager->getStorage($entity_type)->load($id);
126           }
127
128           if ($entity) {
129             // Protect ourselves from recursive rendering.
130             static $depth = 0;
131             $depth++;
132             if ($depth > 20) {
133               throw new RecursiveRenderingException(sprintf('Recursive rendering detected when rendering embedded %s entity %s.', $entity_type, $entity->id()));
134             }
135
136             // If a UUID was not used, but is available, add it to the HTML.
137             if (!$node->getAttribute('data-entity-uuid') && $uuid = $entity->uuid()) {
138               $node->setAttribute('data-entity-uuid', $uuid);
139             }
140
141             $context = $this->getNodeAttributesAsArray($node);
142             $context += array('data-langcode' => $langcode);
143             $build = $this->builder->buildEntityEmbed($entity, $context);
144             // We need to render the embedded entity:
145             // - without replacing placeholders, so that the placeholders are
146             //   only replaced at the last possible moment. Hence we cannot use
147             //   either renderPlain() or renderRoot(), so we must use render().
148             // - without bubbling beyond this filter, because filters must
149             //   ensure that the bubbleable metadata for the changes they make
150             //   when filtering text makes it onto the FilterProcessResult
151             //   object that they return ($result). To prevent that bubbling, we
152             //   must wrap the call to render() in a render context.
153             $entity_output = $this->renderer->executeInRenderContext(new RenderContext(), function () use (&$build) {
154               return $this->renderer->render($build);
155             });
156             $result = $result->merge(BubbleableMetadata::createFromRenderArray($build));
157
158             $depth--;
159           }
160           else {
161             throw new EntityNotFoundException(sprintf('Unable to load embedded %s entity %s.', $entity_type, $id));
162           }
163         }
164         catch (\Exception $e) {
165           watchdog_exception('entity_embed', $e);
166         }
167
168         $this->replaceNodeContent($node, $entity_output);
169       }
170
171       $result->setProcessedText(Html::serialize($dom));
172     }
173
174     return $result;
175   }
176
177   /**
178    * {@inheritdoc}
179    */
180   public function tips($long = FALSE) {
181     if ($long) {
182       return $this->t('
183         <p>You can embed entities. Additional properties can be added to the embed tag like data-caption and data-align if supported. Example:</p>
184         <code>&lt;drupal-entity data-entity-type="node" data-entity-uuid="07bf3a2e-1941-4a44-9b02-2d1d7a41ec0e" data-view-mode="teaser" /&gt;</code>');
185     }
186     else {
187       return $this->t('You can embed entities.');
188     }
189   }
190
191 }