1<?php 2 3namespace Drupal\content_moderation\Plugin\WorkflowType; 4 5use Drupal\content_moderation\ModerationInformationInterface; 6use Drupal\Core\Entity\ContentEntityInterface; 7use Drupal\Core\Entity\EntityTypeBundleInfoInterface; 8use Drupal\Core\Entity\EntityTypeManagerInterface; 9use Drupal\Core\Entity\EntityPublishedInterface; 10use Drupal\Core\Plugin\ContainerFactoryPluginInterface; 11use Drupal\Core\StringTranslation\StringTranslationTrait; 12use Drupal\content_moderation\ContentModerationState; 13use Drupal\workflows\Plugin\WorkflowTypeBase; 14use Drupal\workflows\StateInterface; 15use Drupal\workflows\WorkflowInterface; 16use Symfony\Component\DependencyInjection\ContainerInterface; 17 18/** 19 * Attaches workflows to content entity types and their bundles. 20 * 21 * @WorkflowType( 22 * id = "content_moderation", 23 * label = @Translation("Content moderation"), 24 * required_states = { 25 * "draft", 26 * "published", 27 * }, 28 * forms = { 29 * "configure" = "\Drupal\content_moderation\Form\ContentModerationConfigureForm", 30 * "state" = "\Drupal\content_moderation\Form\ContentModerationStateForm" 31 * }, 32 * ) 33 */ 34class ContentModeration extends WorkflowTypeBase implements ContentModerationInterface, ContainerFactoryPluginInterface { 35 36 use StringTranslationTrait; 37 38 /** 39 * The entity type manager. 40 * 41 * @var \Drupal\Core\Entity\EntityTypeManagerInterface 42 */ 43 protected $entityTypeManager; 44 45 /** 46 * The entity type bundle info service. 47 * 48 * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface 49 */ 50 protected $entityTypeBundleInfo; 51 52 /** 53 * The moderation information service. 54 * 55 * @var \Drupal\content_moderation\ModerationInformationInterface 56 */ 57 protected $moderationInfo; 58 59 /** 60 * Constructs a ContentModeration object. 61 * 62 * @param array $configuration 63 * A configuration array containing information about the plugin instance. 64 * @param string $plugin_id 65 * The plugin_id for the plugin instance. 66 * @param mixed $plugin_definition 67 * The plugin implementation definition. 68 * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager 69 * The entity type manager. 70 * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info 71 * The entity type bundle info. 72 * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info 73 * Moderation information service. 74 */ 75 public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, ModerationInformationInterface $moderation_info) { 76 parent::__construct($configuration, $plugin_id, $plugin_definition); 77 $this->entityTypeManager = $entity_type_manager; 78 $this->entityTypeBundleInfo = $entity_type_bundle_info; 79 $this->moderationInfo = $moderation_info; 80 } 81 82 /** 83 * {@inheritdoc} 84 */ 85 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { 86 return new static( 87 $configuration, 88 $plugin_id, 89 $plugin_definition, 90 $container->get('entity_type.manager'), 91 $container->get('entity_type.bundle.info'), 92 $container->get('content_moderation.moderation_information') 93 ); 94 } 95 96 /** 97 * {@inheritdoc} 98 */ 99 public function getState($state_id) { 100 $state = parent::getState($state_id); 101 if (isset($this->configuration['states'][$state->id()]['published']) && isset($this->configuration['states'][$state->id()]['default_revision'])) { 102 $state = new ContentModerationState($state, $this->configuration['states'][$state->id()]['published'], $this->configuration['states'][$state->id()]['default_revision']); 103 } 104 else { 105 $state = new ContentModerationState($state); 106 } 107 return $state; 108 } 109 110 /** 111 * {@inheritdoc} 112 */ 113 public function workflowHasData(WorkflowInterface $workflow) { 114 return (bool) $this->entityTypeManager 115 ->getStorage('content_moderation_state') 116 ->getQuery() 117 ->condition('workflow', $workflow->id()) 118 ->count() 119 ->accessCheck(FALSE) 120 ->range(0, 1) 121 ->execute(); 122 } 123 124 /** 125 * {@inheritdoc} 126 */ 127 public function workflowStateHasData(WorkflowInterface $workflow, StateInterface $state) { 128 return (bool) $this->entityTypeManager 129 ->getStorage('content_moderation_state') 130 ->getQuery() 131 ->condition('workflow', $workflow->id()) 132 ->condition('moderation_state', $state->id()) 133 ->count() 134 ->accessCheck(FALSE) 135 ->range(0, 1) 136 ->execute(); 137 } 138 139 /** 140 * {@inheritdoc} 141 */ 142 public function getEntityTypes() { 143 return array_keys($this->configuration['entity_types']); 144 } 145 146 /** 147 * {@inheritdoc} 148 */ 149 public function getBundlesForEntityType($entity_type_id) { 150 return isset($this->configuration['entity_types'][$entity_type_id]) ? $this->configuration['entity_types'][$entity_type_id] : []; 151 } 152 153 /** 154 * {@inheritdoc} 155 */ 156 public function appliesToEntityTypeAndBundle($entity_type_id, $bundle_id) { 157 return in_array($bundle_id, $this->getBundlesForEntityType($entity_type_id), TRUE); 158 } 159 160 /** 161 * {@inheritdoc} 162 */ 163 public function removeEntityTypeAndBundle($entity_type_id, $bundle_id) { 164 if (!isset($this->configuration['entity_types'][$entity_type_id])) { 165 return; 166 } 167 $key = array_search($bundle_id, $this->configuration['entity_types'][$entity_type_id], TRUE); 168 if ($key !== FALSE) { 169 unset($this->configuration['entity_types'][$entity_type_id][$key]); 170 if (empty($this->configuration['entity_types'][$entity_type_id])) { 171 unset($this->configuration['entity_types'][$entity_type_id]); 172 } 173 else { 174 $this->configuration['entity_types'][$entity_type_id] = array_values($this->configuration['entity_types'][$entity_type_id]); 175 } 176 } 177 } 178 179 /** 180 * {@inheritdoc} 181 */ 182 public function addEntityTypeAndBundle($entity_type_id, $bundle_id) { 183 if (!$this->appliesToEntityTypeAndBundle($entity_type_id, $bundle_id)) { 184 $this->configuration['entity_types'][$entity_type_id][] = $bundle_id; 185 sort($this->configuration['entity_types'][$entity_type_id]); 186 ksort($this->configuration['entity_types']); 187 } 188 } 189 190 /** 191 * {@inheritdoc} 192 */ 193 public function defaultConfiguration() { 194 return [ 195 'states' => [ 196 'draft' => [ 197 'label' => 'Draft', 198 'published' => FALSE, 199 'default_revision' => FALSE, 200 'weight' => 0, 201 ], 202 'published' => [ 203 'label' => 'Published', 204 'published' => TRUE, 205 'default_revision' => TRUE, 206 'weight' => 1, 207 ], 208 ], 209 'transitions' => [ 210 'create_new_draft' => [ 211 'label' => 'Create New Draft', 212 'to' => 'draft', 213 'weight' => 0, 214 'from' => [ 215 'draft', 216 'published', 217 ], 218 ], 219 'publish' => [ 220 'label' => 'Publish', 221 'to' => 'published', 222 'weight' => 1, 223 'from' => [ 224 'draft', 225 'published', 226 ], 227 ], 228 ], 229 'entity_types' => [], 230 ]; 231 } 232 233 /** 234 * {@inheritdoc} 235 */ 236 public function calculateDependencies() { 237 $dependencies = parent::calculateDependencies(); 238 foreach ($this->getEntityTypes() as $entity_type_id) { 239 $entity_definition = $this->entityTypeManager->getDefinition($entity_type_id); 240 foreach ($this->getBundlesForEntityType($entity_type_id) as $bundle) { 241 $dependency = $entity_definition->getBundleConfigDependency($bundle); 242 $dependencies[$dependency['type']][] = $dependency['name']; 243 } 244 } 245 return $dependencies; 246 } 247 248 /** 249 * {@inheritdoc} 250 */ 251 public function onDependencyRemoval(array $dependencies) { 252 $changed = parent::onDependencyRemoval($dependencies); 253 254 // When bundle config entities are removed, ensure they are cleaned up from 255 // the workflow. 256 foreach ($dependencies['config'] as $removed_config) { 257 if ($entity_type_id = $removed_config->getEntityType()->getBundleOf()) { 258 $bundle_id = $removed_config->id(); 259 $this->removeEntityTypeAndBundle($entity_type_id, $bundle_id); 260 $changed = TRUE; 261 } 262 } 263 264 // When modules that provide entity types are removed, ensure they are also 265 // removed from the workflow. 266 if (!empty($dependencies['module'])) { 267 // Gather all entity definitions provided by the dependent modules which 268 // are being removed. 269 $module_entity_definitions = []; 270 foreach ($this->entityTypeManager->getDefinitions() as $entity_definition) { 271 if (in_array($entity_definition->getProvider(), $dependencies['module'])) { 272 $module_entity_definitions[] = $entity_definition; 273 } 274 } 275 276 // For all entity types provided by the uninstalled modules, remove any 277 // configuration for those types. 278 foreach ($module_entity_definitions as $module_entity_definition) { 279 foreach ($this->getBundlesForEntityType($module_entity_definition->id()) as $bundle) { 280 $this->removeEntityTypeAndBundle($module_entity_definition->id(), $bundle); 281 $changed = TRUE; 282 } 283 } 284 } 285 286 return $changed; 287 } 288 289 /** 290 * {@inheritdoc} 291 */ 292 public function getConfiguration() { 293 $configuration = parent::getConfiguration(); 294 // Ensure that states and entity types are ordered consistently. 295 ksort($configuration['states']); 296 ksort($configuration['entity_types']); 297 return $configuration; 298 } 299 300 /** 301 * {@inheritdoc} 302 */ 303 public function getInitialState($entity = NULL) { 304 // Workflows are not tied to entities, but Content Moderation adds the 305 // relationship between Workflows and entities. Content Moderation needs the 306 // entity object to be able to determine the initial state based on 307 // publishing status. 308 if (!($entity instanceof ContentEntityInterface)) { 309 throw new \InvalidArgumentException('A content entity object must be supplied.'); 310 } 311 if ($entity instanceof EntityPublishedInterface && !$entity->isNew()) { 312 return $this->getState($entity->isPublished() ? 'published' : 'draft'); 313 } 314 315 return $this->getState(!empty($this->configuration['default_moderation_state']) ? $this->configuration['default_moderation_state'] : 'draft'); 316 } 317 318} 319