Upgraded drupal core with security updates
[yaffs-website] / web / core / modules / content_moderation / src / EntityTypeInfo.php
1 <?php
2
3 namespace Drupal\content_moderation;
4
5 use Drupal\content_moderation\Plugin\Field\ModerationStateFieldItemList;
6 use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
7 use Drupal\Core\Entity\BundleEntityFormBase;
8 use Drupal\Core\Entity\ContentEntityFormInterface;
9 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
10 use Drupal\Core\Entity\ContentEntityTypeInterface;
11 use Drupal\Core\Entity\EntityInterface;
12 use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
13 use Drupal\Core\Entity\EntityTypeInterface;
14 use Drupal\Core\Entity\EntityTypeManagerInterface;
15 use Drupal\Core\Field\BaseFieldDefinition;
16 use Drupal\Core\Form\FormStateInterface;
17 use Drupal\Core\Session\AccountInterface;
18 use Drupal\Core\StringTranslation\StringTranslationTrait;
19 use Drupal\Core\StringTranslation\TranslationInterface;
20 use Drupal\Core\Url;
21 use Drupal\content_moderation\Entity\Handler\BlockContentModerationHandler;
22 use Drupal\content_moderation\Entity\Handler\ModerationHandler;
23 use Drupal\content_moderation\Entity\Handler\NodeModerationHandler;
24 use Drupal\content_moderation\Form\BundleModerationConfigurationForm;
25 use Drupal\content_moderation\Routing\EntityModerationRouteProvider;
26 use Drupal\content_moderation\Routing\EntityTypeModerationRouteProvider;
27 use Symfony\Component\DependencyInjection\ContainerInterface;
28
29 /**
30  * Manipulates entity type information.
31  *
32  * This class contains primarily bridged hooks for compile-time or
33  * cache-clear-time hooks. Runtime hooks should be placed in EntityOperations.
34  */
35 class EntityTypeInfo implements ContainerInjectionInterface {
36
37   use StringTranslationTrait;
38
39   /**
40    * The moderation information service.
41    *
42    * @var \Drupal\content_moderation\ModerationInformationInterface
43    */
44   protected $moderationInfo;
45
46   /**
47    * The entity type manager.
48    *
49    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
50    */
51   protected $entityTypeManager;
52
53   /**
54    * The bundle information service.
55    *
56    * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
57    */
58   protected $bundleInfo;
59
60   /**
61    * The current user.
62    *
63    * @var \Drupal\Core\Session\AccountInterface
64    */
65   protected $currentUser;
66
67   /**
68    * A keyed array of custom moderation handlers for given entity types.
69    *
70    * Any entity not specified will use a common default.
71    *
72    * @var array
73    */
74   protected $moderationHandlers = [
75     'node' => NodeModerationHandler::class,
76     'block_content' => BlockContentModerationHandler::class,
77   ];
78
79   /**
80    * EntityTypeInfo constructor.
81    *
82    * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
83    *   The translation service. for form alters.
84    * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_information
85    *   The moderation information service.
86    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
87    *   Entity type manager.
88    * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
89    *   Bundle information service.
90    * @param \Drupal\Core\Session\AccountInterface $current_user
91    *   Current user.
92    */
93   public function __construct(TranslationInterface $translation, ModerationInformationInterface $moderation_information, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $bundle_info, AccountInterface $current_user) {
94     $this->stringTranslation = $translation;
95     $this->moderationInfo = $moderation_information;
96     $this->entityTypeManager = $entity_type_manager;
97     $this->bundleInfo = $bundle_info;
98     $this->currentUser = $current_user;
99   }
100
101   /**
102    * {@inheritdoc}
103    */
104   public static function create(ContainerInterface $container) {
105     return new static(
106       $container->get('string_translation'),
107       $container->get('content_moderation.moderation_information'),
108       $container->get('entity_type.manager'),
109       $container->get('entity_type.bundle.info'),
110       $container->get('current_user')
111     );
112   }
113
114
115   /**
116    * Adds Moderation configuration to appropriate entity types.
117    *
118    * This is an alter hook bridge.
119    *
120    * @param EntityTypeInterface[] $entity_types
121    *   The master entity type list to alter.
122    *
123    * @see hook_entity_type_alter()
124    */
125   public function entityTypeAlter(array &$entity_types) {
126     foreach ($entity_types as $entity_type_id => $entity_type) {
127       // The ContentModerationState entity type should never be moderated.
128       if ($entity_type->isRevisionable() && $entity_type_id != 'content_moderation_state') {
129         $entity_types[$entity_type_id] = $this->addModerationToEntityType($entity_type);
130         // Add additional moderation support to entity types whose bundles are
131         // managed by a config entity type.
132         if ($entity_type->getBundleEntityType()) {
133           $entity_types[$entity_type->getBundleEntityType()] = $this->addModerationToBundleEntityType($entity_types[$entity_type->getBundleEntityType()]);
134         }
135       }
136     }
137   }
138
139   /**
140    * Modifies an entity definition to include moderation support.
141    *
142    * This primarily just means an extra handler. A Generic one is provided,
143    * but individual entity types can provide their own as appropriate.
144    *
145    * @param \Drupal\Core\Entity\ContentEntityTypeInterface $type
146    *   The content entity definition to modify.
147    *
148    * @return \Drupal\Core\Entity\ContentEntityTypeInterface
149    *   The modified content entity definition.
150    */
151   protected function addModerationToEntityType(ContentEntityTypeInterface $type) {
152     if (!$type->hasHandlerClass('moderation')) {
153       $handler_class = !empty($this->moderationHandlers[$type->id()]) ? $this->moderationHandlers[$type->id()] : ModerationHandler::class;
154       $type->setHandlerClass('moderation', $handler_class);
155     }
156
157     if (!$type->hasLinkTemplate('latest-version') && $type->hasLinkTemplate('canonical')) {
158       $type->setLinkTemplate('latest-version', $type->getLinkTemplate('canonical') . '/latest');
159     }
160
161     // @todo Core forgot to add a direct way to manipulate route_provider, so
162     // we have to do it the sloppy way for now.
163     $providers = $type->getRouteProviderClasses() ?: [];
164     if (empty($providers['moderation'])) {
165       $providers['moderation'] = EntityModerationRouteProvider::class;
166       $type->setHandlerClass('route_provider', $providers);
167     }
168
169     return $type;
170   }
171
172   /**
173    * Configures moderation configuration support on a entity type definition.
174    *
175    * That "configuration support" includes a configuration form, a hypermedia
176    * link, and a route provider to tie it all together. There's also a
177    * moderation handler for per-entity-type variation.
178    *
179    * @param \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $type
180    *   The config entity definition to modify.
181    *
182    * @return \Drupal\Core\Config\Entity\ConfigEntityTypeInterface
183    *   The modified config entity definition.
184    */
185   protected function addModerationToBundleEntityType(ConfigEntityTypeInterface $type) {
186     if ($type->hasLinkTemplate('edit-form') && !$type->hasLinkTemplate('moderation-form')) {
187       $type->setLinkTemplate('moderation-form', $type->getLinkTemplate('edit-form') . '/moderation');
188     }
189
190     if (!$type->getFormClass('moderation')) {
191       $type->setFormClass('moderation', BundleModerationConfigurationForm::class);
192     }
193
194     // @todo Core forgot to add a direct way to manipulate route_provider, so
195     // we have to do it the sloppy way for now.
196     $providers = $type->getRouteProviderClasses() ?: [];
197     if (empty($providers['moderation'])) {
198       $providers['moderation'] = EntityTypeModerationRouteProvider::class;
199       $type->setHandlerClass('route_provider', $providers);
200     }
201
202     return $type;
203   }
204
205   /**
206    * Adds an operation on bundles that should have a Moderation form.
207    *
208    * @param \Drupal\Core\Entity\EntityInterface $entity
209    *   The entity on which to define an operation.
210    *
211    * @return array
212    *   An array of operation definitions.
213    *
214    * @see hook_entity_operation()
215    */
216   public function entityOperation(EntityInterface $entity) {
217     $operations = [];
218     $type = $entity->getEntityType();
219     $bundle_of = $type->getBundleOf();
220     if ($this->currentUser->hasPermission('administer content moderation') && $bundle_of &&
221       $this->moderationInfo->canModerateEntitiesOfEntityType($this->entityTypeManager->getDefinition($bundle_of))
222     ) {
223       $operations['manage-moderation'] = [
224         'title' => t('Manage moderation'),
225         'weight' => 27,
226         'url' => Url::fromRoute("entity.{$type->id()}.moderation", [$entity->getEntityTypeId() => $entity->id()]),
227       ];
228     }
229
230     return $operations;
231   }
232
233   /**
234    * Gets the "extra fields" for a bundle.
235    *
236    * This is a hook bridge.
237    *
238    * @see hook_entity_extra_field_info()
239    *
240    * @return array
241    *   A nested array of 'pseudo-field' elements. Each list is nested within the
242    *   following keys: entity type, bundle name, context (either 'form' or
243    *   'display'). The keys are the name of the elements as appearing in the
244    *   renderable array (either the entity form or the displayed entity). The
245    *   value is an associative array:
246    *   - label: The human readable name of the element. Make sure you sanitize
247    *     this appropriately.
248    *   - description: A short description of the element contents.
249    *   - weight: The default weight of the element.
250    *   - visible: (optional) The default visibility of the element. Defaults to
251    *     TRUE.
252    *   - edit: (optional) String containing markup (normally a link) used as the
253    *     element's 'edit' operation in the administration interface. Only for
254    *     'form' context.
255    *   - delete: (optional) String containing markup (normally a link) used as
256    *     the element's 'delete' operation in the administration interface. Only
257    *     for 'form' context.
258    */
259   public function entityExtraFieldInfo() {
260     $return = [];
261     foreach ($this->getModeratedBundles() as $bundle) {
262       $return[$bundle['entity']][$bundle['bundle']]['display']['content_moderation_control'] = [
263         'label' => $this->t('Moderation control'),
264         'description' => $this->t("Status listing and form for the entity's moderation state."),
265         'weight' => -20,
266         'visible' => TRUE,
267       ];
268     }
269
270     return $return;
271   }
272
273   /**
274    * Returns an iterable list of entity names and bundle names under moderation.
275    *
276    * That is, this method returns a list of bundles that have Content
277    * Moderation enabled on them.
278    *
279    * @return \Generator
280    *   A generator, yielding a 2 element associative array:
281    *   - entity: The machine name of an entity type, such as "node" or
282    *     "block_content".
283    *   - bundle: The machine name of a bundle, such as "page" or "article".
284    */
285   protected function getModeratedBundles() {
286     $entity_types = array_filter($this->entityTypeManager->getDefinitions(), [$this->moderationInfo, 'canModerateEntitiesOfEntityType']);
287     foreach ($entity_types as $type_name => $type) {
288       foreach ($this->bundleInfo->getBundleInfo($type_name) as $bundle_id => $bundle) {
289         if ($this->moderationInfo->shouldModerateEntitiesOfBundle($type, $bundle_id)) {
290           yield ['entity' => $type_name, 'bundle' => $bundle_id];
291         }
292       }
293     }
294   }
295
296   /**
297    * Adds base field info to an entity type.
298    *
299    * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
300    *   Entity type for adding base fields to.
301    *
302    * @return \Drupal\Core\Field\BaseFieldDefinition[]
303    *   New fields added by moderation state.
304    */
305   public function entityBaseFieldInfo(EntityTypeInterface $entity_type) {
306     if (!$this->moderationInfo->canModerateEntitiesOfEntityType($entity_type)) {
307       return [];
308     }
309
310     $fields = [];
311     $fields['moderation_state'] = BaseFieldDefinition::create('string')
312       ->setLabel(t('Moderation state'))
313       ->setDescription(t('The moderation state of this piece of content.'))
314       ->setComputed(TRUE)
315       ->setClass(ModerationStateFieldItemList::class)
316       ->setSetting('target_type', 'moderation_state')
317       ->setDisplayOptions('view', [
318         'label' => 'hidden',
319         'region' => 'hidden',
320         'weight' => -5,
321       ])
322       ->setDisplayOptions('form', [
323         'type' => 'moderation_state_default',
324         'weight' => 5,
325         'settings' => [],
326       ])
327       ->addConstraint('ModerationState', [])
328       ->setDisplayConfigurable('form', FALSE)
329       ->setDisplayConfigurable('view', FALSE)
330       ->setReadOnly(FALSE)
331       ->setTranslatable(TRUE);
332
333     return $fields;
334   }
335
336   /**
337    * Alters bundle forms to enforce revision handling.
338    *
339    * @param array $form
340    *   An associative array containing the structure of the form.
341    * @param \Drupal\Core\Form\FormStateInterface $form_state
342    *   The current state of the form.
343    * @param string $form_id
344    *   The form id.
345    *
346    * @see hook_form_alter()
347    */
348   public function formAlter(array &$form, FormStateInterface $form_state, $form_id) {
349     $form_object = $form_state->getFormObject();
350     if ($form_object instanceof BundleEntityFormBase) {
351       $type = $form_object->getEntity()->getEntityType();
352       if ($this->moderationInfo->canModerateEntitiesOfEntityType($type)) {
353         $this->entityTypeManager->getHandler($type->getBundleOf(), 'moderation')->enforceRevisionsBundleFormAlter($form, $form_state, $form_id);
354       }
355     }
356     elseif ($form_object instanceof ContentEntityFormInterface) {
357       $entity = $form_object->getEntity();
358       if ($this->moderationInfo->isModeratedEntity($entity)) {
359         $this->entityTypeManager
360           ->getHandler($entity->getEntityTypeId(), 'moderation')
361           ->enforceRevisionsEntityFormAlter($form, $form_state, $form_id);
362         // Submit handler to redirect to the latest version, if available.
363         $form['actions']['submit']['#submit'][] = [EntityTypeInfo::class, 'bundleFormRedirect'];
364       }
365     }
366   }
367
368   /**
369    * Redirect content entity edit forms on save, if there is a forward revision.
370    *
371    * When saving their changes, editors should see those changes displayed on
372    * the next page.
373    *
374    * @param array $form
375    *   An associative array containing the structure of the form.
376    * @param \Drupal\Core\Form\FormStateInterface $form_state
377    *   The current state of the form.
378    */
379   public static function bundleFormRedirect(array &$form, FormStateInterface $form_state) {
380     /* @var \Drupal\Core\Entity\ContentEntityInterface $entity */
381     $entity = $form_state->getFormObject()->getEntity();
382
383     $moderation_info = \Drupal::getContainer()->get('content_moderation.moderation_information');
384     if ($moderation_info->hasForwardRevision($entity) && $entity->hasLinkTemplate('latest-version')) {
385       $entity_type_id = $entity->getEntityTypeId();
386       $form_state->setRedirect("entity.$entity_type_id.latest_version", [$entity_type_id => $entity->id()]);
387     }
388   }
389
390 }