PageRenderTime 44ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/src/EntityOperations.php

https://gitlab.com/Drulenium-bot/workbench_moderation
PHP | 267 lines | 101 code | 35 blank | 131 comment | 17 complexity | c624827152da05687dd637dabcbc803a MD5 | raw file
  1. <?php
  2. namespace Drupal\workbench_moderation;
  3. use Drupal\Core\Entity\ContentEntityInterface;
  4. use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
  5. use Drupal\Core\Entity\EntityInterface;
  6. use Drupal\Core\Entity\EntityTypeManagerInterface;
  7. use Drupal\Core\Form\FormBuilderInterface;
  8. use Drupal\Core\TypedData\TranslatableInterface;
  9. use Drupal\workbench_moderation\Event\WorkbenchModerationEvents;
  10. use Drupal\workbench_moderation\Event\WorkbenchModerationTransitionEvent;
  11. use Drupal\workbench_moderation\Form\EntityModerationForm;
  12. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  13. /**
  14. * Defines a class for reacting to entity events.
  15. */
  16. class EntityOperations {
  17. /**
  18. * @var \Drupal\workbench_moderation\ModerationInformationInterface
  19. */
  20. protected $moderationInfo;
  21. /**
  22. * @var \Drupal\Core\Entity\EntityTypeManagerInterface
  23. */
  24. protected $entityTypeManager;
  25. /**
  26. * The event dispatcher.
  27. *
  28. * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
  29. */
  30. protected $eventDispatcher;
  31. /**
  32. * @var \Drupal\Core\Form\FormBuilderInterface
  33. */
  34. protected $formBuilder;
  35. /**
  36. * @var \Drupal\workbench_moderation\RevisionTrackerInterface
  37. */
  38. protected $tracker;
  39. /**
  40. * Constructs a new EntityOperations object.
  41. *
  42. * @param \Drupal\workbench_moderation\ModerationInformationInterface $moderation_info
  43. * Moderation information service.
  44. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
  45. * Entity type manager service.
  46. * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
  47. * The form builder.
  48. * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
  49. * The event dispatcher.
  50. * @param \Drupal\workbench_moderation\RevisionTrackerInterface $tracker
  51. * The revision tracker.
  52. */
  53. public function __construct(ModerationInformationInterface $moderation_info, EntityTypeManagerInterface $entity_type_manager, FormBuilderInterface $form_builder, EventDispatcherInterface $event_dispatcher, RevisionTrackerInterface $tracker) {
  54. $this->moderationInfo = $moderation_info;
  55. $this->entityTypeManager = $entity_type_manager;
  56. $this->eventDispatcher = $event_dispatcher;
  57. $this->formBuilder = $form_builder;
  58. $this->tracker = $tracker;
  59. }
  60. /**
  61. * Hook bridge.
  62. *
  63. * @see hook_entity_storage_load().
  64. *
  65. * @param EntityInterface[] $entities
  66. * An array of entity objects that have just been loaded.
  67. * @param string $entity_type_id
  68. * The type of entity being loaded, such as "node" or "user".
  69. */
  70. public function entityStorageLoad(array $entities, $entity_type_id) {
  71. // Ensure that all moderatable entities always have a moderation_state field
  72. // with data, in all translations. That avoids us needing to have a thousand
  73. // NULL checks elsewhere in the code.
  74. // Quickly exclude any non-moderatable entities.
  75. $to_check = array_filter($entities, [$this->moderationInfo, 'isModeratableEntity']);
  76. if (!$to_check) {
  77. return;
  78. }
  79. // @todo make this more functional, less iterative.
  80. foreach ($to_check as $entity) {
  81. foreach ($entity->getTranslationLanguages() as $language) {
  82. $translation = $entity->getTranslation($language->getId());
  83. if ($translation->moderation_state->target_id == NULL) {
  84. $translation->moderation_state->target_id = $this->getDefaultLoadStateId($translation);
  85. }
  86. }
  87. }
  88. }
  89. /**
  90. * Determines the default moderation state on load for an entity.
  91. *
  92. * This method is only applicable when an entity is loaded that has
  93. * no moderation state on it, but should. In those cases, failing to set
  94. * one may result in NULL references elsewhere when other code tries to check
  95. * the moderation state of the entity.
  96. *
  97. * The amount of indirection here makes performance a concern, but
  98. * given how Entity API works I don't know how else to do it.
  99. * This reliably gets us *A* valid state. However, that state may be
  100. * not the ideal one. Suggestions on how to better select the default
  101. * state here are welcome.
  102. *
  103. * @param \Drupal\Core\Entity\ContentEntityInterface $entity
  104. * The entity for which we want a default state.
  105. *
  106. * @return string
  107. * The default state for the given entity.
  108. */
  109. protected function getDefaultLoadStateId(ContentEntityInterface $entity) {
  110. return $this->moderationInfo
  111. ->loadBundleEntity($entity->getEntityType()->getBundleEntityType(), $entity->bundle())
  112. ->getThirdPartySetting('workbench_moderation', 'default_moderation_state');
  113. }
  114. /**
  115. * Acts on an entity and set published status based on the moderation state.
  116. *
  117. * @param \Drupal\Core\Entity\EntityInterface $entity
  118. * The entity being saved.
  119. */
  120. public function entityPresave(EntityInterface $entity) {
  121. if (!$this->moderationInfo->isModeratableEntity($entity)) {
  122. return;
  123. }
  124. if ($entity->moderation_state->entity) {
  125. $published_state = $entity->moderation_state->entity->isPublishedState();
  126. // This entity is default if it is new, the default revision, or the
  127. // default revision is not published.
  128. $update_default_revision = $entity->isNew()
  129. || $entity->moderation_state->entity->isDefaultRevisionState()
  130. || !$this->isDefaultRevisionPublished($entity);
  131. // Fire per-entity-type logic for handling the save process.
  132. $this->entityTypeManager->getHandler($entity->getEntityTypeId(), 'moderation')->onPresave($entity, $update_default_revision, $published_state);
  133. // There's currently a bug in core where $entity->original always points
  134. // to the default revision, for now work around this by loading the latest
  135. // revision.
  136. $latest_revision = $this->moderationInfo->getLatestRevision($entity->getEntityTypeId(), $entity->id());
  137. $state_before = !empty($latest_revision) ? $latest_revision->moderation_state->target_id : NULL;
  138. // @todo: Revert to this simpler version when https://www.drupal.org/node/2700747 is fixed.
  139. // $state_before = isset($entity->original) ? $entity->original->moderation_state->target_id : NULL;
  140. $state_after = $entity->moderation_state->target_id;
  141. // Allow other modules to respond to the transition. Note that this
  142. // does not provide any mechanism to cancel the transition, since
  143. // Entity API doesn't allow hook_entity_presave to short-circuit a save.
  144. $event = new WorkbenchModerationTransitionEvent($entity, $state_before, $state_after);
  145. $this->eventDispatcher->dispatch(WorkbenchModerationEvents::STATE_TRANSITION, $event);
  146. }
  147. }
  148. /**
  149. * Hook bridge.
  150. *
  151. * @see hook_entity_insert().
  152. *
  153. * @param \Drupal\Core\Entity\EntityInterface $entity
  154. * The entity that was just saved.
  155. */
  156. public function entityInsert(EntityInterface $entity) {
  157. if (!$this->moderationInfo->isModeratableEntity($entity)) {
  158. return;
  159. }
  160. /** ContentEntityInterface $entity */
  161. // Update our own record keeping.
  162. $this->tracker->setLatestRevision($entity->getEntityTypeId(), $entity->id(), $entity->language()->getId(), $entity->getRevisionId());
  163. }
  164. /**
  165. * Hook bridge.
  166. *
  167. * @see hook_entity_update().
  168. *
  169. * @param \Drupal\Core\Entity\EntityInterface $entity
  170. * The entity that was just saved.
  171. */
  172. public function entityUpdate(EntityInterface $entity) {
  173. if (!$this->moderationInfo->isModeratableEntity($entity)) {
  174. return;
  175. }
  176. /** ContentEntityInterface $entity */
  177. // Update our own record keeping.
  178. $this->tracker->setLatestRevision($entity->getEntityTypeId(), $entity->id(), $entity->language()->getId(), $entity->getRevisionId());
  179. }
  180. /**
  181. * Act on entities being assembled before rendering.
  182. *
  183. * This is a hook bridge.
  184. *
  185. * @see hook_entity_view()
  186. * @see EntityFieldManagerInterface::getExtraFields()
  187. */
  188. public function entityView(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
  189. if (!$this->moderationInfo->isModeratableEntity($entity)) {
  190. return;
  191. }
  192. if (!$this->moderationInfo->isLatestRevision($entity)) {
  193. return;
  194. }
  195. /** @var ContentEntityInterface $entity */
  196. if ($entity->isDefaultRevision()) {
  197. return;
  198. }
  199. $component = $display->getComponent('workbench_moderation_control');
  200. if ($component) {
  201. $build['workbench_moderation_control'] = $this->formBuilder->getForm(EntityModerationForm::class, $entity);
  202. $build['workbench_moderation_control']['#weight'] = $component['weight'];
  203. }
  204. }
  205. /**
  206. * Check if the default revision for the given entity is published.
  207. *
  208. * The default revision is the same as the entity retrieved by "default" from
  209. * the storage handler. If the entity is translated, use the default revision
  210. * of the same language as the given entity.
  211. *
  212. * @param \Drupal\Core\Entity\EntityInterface $entity
  213. * The entity being saved.
  214. *
  215. * @return bool
  216. * TRUE if the default revision is published. FALSE otherwise.
  217. */
  218. protected function isDefaultRevisionPublished(EntityInterface $entity) {
  219. $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
  220. $default_revision = $storage->load($entity->id());
  221. // Ensure we are comparing the same translation as the current entity.
  222. if ($default_revision instanceof TranslatableInterface && $default_revision->isTranslatable()) {
  223. // If there is no translation, then there is no default revision and is
  224. // therefore not published.
  225. if (!$default_revision->hasTranslation($entity->language()->getId())) {
  226. return FALSE;
  227. }
  228. $default_revision = $default_revision->getTranslation($entity->language()->getId());
  229. }
  230. return $default_revision && $default_revision->moderation_state->entity->isPublishedState();
  231. }
  232. }