association-1.0.0-alpha2/src/Entity/Association.php
src/Entity/Association.php
<?php namespace Drupal\association\Entity; use Drupal\association\Entity\Exception\AlreadyAssociatedException; use Drupal\association\Plugin\BehaviorInterface; use Drupal\association\Plugin\LandingPagePluginInterface; use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException; use Drupal\Component\Plugin\PluginInspectionInterface; use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\ContentEntityBase; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityChangedTrait; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityMalformedException; use Drupal\Core\Entity\EntityPublishedTrait; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\user\UserInterface; /** * Entity which maintains content entity associations. * * This is the entity from which linked content and association page content * is associated to. The removal of this entity will trigger the deletion of * those dependent resources (pages and link entities). * * @ContentEntityType( * id = "association", * label = @Translation("Entity association"), * label_plural = @Translation("Entity associations"), * bundle_label = @Translation("Association type"), * bundle_entity_type = "association_type", * base_table = "association", * data_table = "association_field_data", * token_type = "association", * field_ui_base_route = "entity.association_type.edit_form", * admin_permission = "administer association configurations", * permission_granularity = "bundle", * translatable = TRUE, * fieldable = TRUE, * entity_keys = { * "id" = "id", * "bundle" = "type", * "label" = "name", * "uid" = "uid", * "status" = "status", * "published" = "status", * "langcode" = "langcode", * }, * handlers = { * "access" = "Drupal\association\Entity\Access\AssociationAccessControlHandler", * "list_builder" = "Drupal\association\Entity\Controller\AssociationListBuilder", * "views_data" = "Drupal\association\Entity\Views\AssociationViewsData", * "translation" = "Drupal\content_translation\ContentTranslationHandler", * "storage" = "Drupal\association\Entity\Storage\AssociationStorage", * "form" = { * "default" = "Drupal\association\Entity\Form\AssociationForm", * "edit" = "Drupal\association\Entity\Form\AssociationForm", * "delete" = "Drupal\association\Entity\Form\AssociationDeleteConfirm", * "delete-multiple-confirm" = "Drupal\association\Entity\Form\AssociationDeleteMultipleConfirm", * }, * "route_provider" = { * "html" = "Drupal\association\Entity\Routing\AssociationHtmlRouteProvider", * }, * }, * links = { * "collection" = "/admin/content/association", * "add-page" = "/association/add", * "add-form" = "/association/add/{association_type}", * "canonical" = "/association/{association}/manage", * "manage" = "/association/{association}/manage", * "edit-form" = "/association/{association}/edit", * "delete-form" = "/association/{association}/delete", * "delete-multiple-form" = "/admin/content/association/delete-multiple", * }, * ) */ class Association extends ContentEntityBase implements AssociationInterface { use EntityChangedTrait; use EntityPublishedTrait; /** * The association page belonging to this entity, if landing page is enabled. * * This field is boolean FALSE if there is no companion page available for * this entity. There should only be a single landing page per association. * * @var \Drupal\Core\Entity\EntityInterface|bool */ protected $associationPage; /** * The purging state of this entity association. True during deletion. * * @var bool */ protected $isPurging = FALSE; /** * Default value callback for 'uid' base field definition. * * @return array * An array containing the default author data. */ public static function getCurrentUserId() { return [ \Drupal::currentUser()->id(), ]; } /** * {@inheritdoc} */ public function isPurging(): bool { return $this->isPurging; } /** * {@inheritdoc} */ public function getCacheTags() { if ($this->cacheTags) { return Cache::mergeTags(parent::getCacheTagsToInvalidate(), $this->cacheTags); } return parent::getCacheTagsToInvalidate(); } /** * {@inheritdoc} */ public function getCacheTagsToInvalidate() { if ($this->isNew()) { return []; } // Invalidate the associated content list for the parent association. $tags[] = 'association:links:' . $this->id(); return Cache::mergeTags($tags, parent::getCacheTagsToInvalidate()); } /** * {@inheritdoc} */ public function getType(): AssociationTypeInterface { if ($type = $this->type->entity) { return $type; } $err = sprintf('Association type of %s is missing.', $this->bundle()); throw new EntityMalformedException($err); } /** * {@inheritdoc} */ public function getPlugin(string $plugin_type): ?PluginInspectionInterface { return $this->getType()->getPlugin($plugin_type); } /** * {@inheritdoc} */ public function getBehavior(): ?BehaviorInterface { return $this->getType()->getBehavior(); } /** * {@inheritdoc} */ public function getLandingPageHandler(): LandingPagePluginInterface { return $this->getType()->getLandingPageHandler(); } /** * {@inheritdoc} */ public function getPageSettings(): array { return $this->get('page')->settings ?? []; } /** * {@inheritdoc} */ public function getOwner() { return $this->get('uid')->entity; } /** * {@inheritdoc} */ public function getOwnerId() { return $this->get('uid')->target_id; } /** * {@inheritdoc} */ public function setOwnerId($uid) { $this->set('uid', $uid); return $this; } /** * {@inheritdoc} */ public function setOwner(UserInterface $account) { $this->set('uid', $account->id()); return $this; } /** * {@inheritdoc} */ public function getCreatedTime() { return $this->get('created')->value; } /** * {@inheritdoc} */ public function setCreatedTime($timestamp) { $this->set('created', $timestamp); return $this; } /** * {@inheritdoc} */ public function isActive(): bool { return $this->status->value; } /** * {@inheritdoc} */ public function associateEntity($tag, ContentEntityInterface $entity, $save_link = TRUE): ?AssociationLink { $entityTypeId = $entity->getEntityTypeId(); $bundle = $entity->bundle(); $behavior = $this->getBehavior(); if ($behavior->isValidEntity($tag, $entityTypeId, $bundle)) { $target = ['entity' => $entity]; if (!$entity->isNew() && $entity->id()) { // Ensure this entity isn't already part of the association. /** @var \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem */ foreach ($entity->get('associations') as $item) { /** @var \Drupal\association\Entity\AssociationLink */ $assocLink = $item->entity; if ($assocLink->assocation->target_id == $this->id()) { $error = sprintf('Entity %s of type %s is already associated to "%s (id:%s)"', $entity->id(), $entityTypeId, $this->label(), $this->id()); throw new AlreadyAssociatedException($error); } } $target['target_id'] = $entity->id(); } $link = \Drupal::entityTypeManager() ->getStorage('association_link') ->create([ 'association' => $this, 'tag' => $tag, 'entity_type' => $entityTypeId, 'bundle' => $bundle, 'target' => $target, ]); $itemValue = ['entity' => $link]; // If entity is already saved, save the assocation link as well. if ($save_link && !$entity->isNew() && $entity->id()) { $link->save(); $itemValue['target_id'] = $link->id(); } $entity->get('associations')->appendItem($itemValue); return $link; } return NULL; } /** * {@inheritdoc} */ public function getPage(): ?EntityInterface { if (!isset($this->associationPage)) { $handler = $this->getLandingPageHandler(); // Set the value to FALSE if page entity is NULL so successive calls to // static::getPage() can tell that this value is already set. $this->associationPage = $handler->getPage($this) ?? FALSE; } return $this->associationPage ?: NULL; } /** * {@inheritdoc} */ public function toUrl($rel = 'canonical', array $options = []) { if ($rel === 'canonical') { // If there is a companion page to this association, use the page for the // canonical URL instead of linking to the association directly. $page = $this->getPage(); return $this ->getLandingPageHandler() ->getPageUrl($this, $page); } return parent::toUrl($rel, $options); } /** * {@inheritdoc} */ public function postSave(EntityStorageInterface $storage, $update = FALSE) { parent::postSave($storage, $update); // Ensure that the calculated field is loaded before Pathauto has a // chance to update this data in the database. if ($this->original && $this->original->hasField('path')) { /* @phpstan-ignore-next-line */ $this->original->get('path')->alias; } } /** * {@inheritdoc} */ public static function preDelete(EntityStorageInterface $storage, array $entities) { parent::preDelete($storage, $entities); $ids = []; $byBundle = []; /** @var \Drupal\association\Entity\Association $entity */ foreach ($entities as $entity) { $entity->isPurging = TRUE; $ids[] = $entity->id(); $byBundle[$entity->bundle()][$entity->id()] = $entity; } // @todo Convert this into a worker queue and perform the delete of // associations and possibily the related content off-line and in a batch. if ($ids) { $entityTypeManager = \Drupal::entityTypeManager(); /** @var \Drupal\association\Entity\AssociationTypeInterface[] $types */ $types = $entityTypeManager ->getStorage('association_type') ->loadMultiple(array_keys($byBundle)); // Apply the landing page handler events for associations being deleted. foreach ($types as $typeId => $type) { $type ->getLandingPageHandler() ->onPreDelete($byBundle[$typeId]); } // Delete the links that associate entities to the deleted associations. $linkStorage = $entityTypeManager->getStorage('association_link'); $assocIds = $linkStorage->getQuery() ->accessCheck(FALSE) ->condition('association', $ids, 'IN') ->execute(); if ($links = $linkStorage->loadMultiple($assocIds)) { $linkStorage->delete($links); } } } /** * {@inheritdoc} */ public static function postDelete(EntityStorageInterface $storage, array $entities) { $byBundle = []; foreach ($entities as $entity) { $byBundle[$entity->bundle()][$entity->id()] = $entity; } // @todo Convert this into a worker queue and perform the delete of // associations and possibily the related content off-line and in a batch. if ($byBundle) { $entityTypeManager = \Drupal::entityTypeManager(); /** @var \Drupal\association\Entity\AssociationTypeInterface[] $types */ $types = $entityTypeManager ->getStorage('association_type') ->loadMultiple(array_keys($byBundle)); /** @var \Drupal\association\Entity\AssociationTypeInterface[] $types */ $types = $entityTypeManager ->getStorage('association_type') ->loadMultiple(array_keys($byBundle)); // Apply the landing page handler events for associations being deleted. foreach ($types as $typeId => $type) { $type ->getLandingPageHandler() ->onPostDelete($byBundle[$typeId]); } } parent::postDelete($storage, $entities); } /** * {@inheritdoc} */ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { $fields = parent::baseFieldDefinitions($entity_type); $fields['uid'] = BaseFieldDefinition::create('entity_reference') ->setTargetEntityTypeId($entity_type->id()) ->setLabel(t('Authored by')) ->setDescription(t('The author and owner of this content.')) ->setRevisionable(TRUE) ->setTranslatable(FALSE) ->setRequired(TRUE) ->setSetting('target_type', 'user') ->setSetting('handler', 'default') ->setDefaultValueCallback(static::class . '::getCurrentUserId') ->setDisplayConfigurable('form', TRUE) ->setDisplayConfigurable('view', TRUE) ->setDisplayOptions('form', [ 'type' => 'entity_reference_autocomplete', 'weight' => 5, 'settings' => [ 'match_operator' => 'CONTAINS', 'size' => '60', 'autocomplete_type' => 'tags', 'placeholder' => '', ], ]); $fields['name'] = BaseFieldDefinition::create('string') ->setTargetEntityTypeId($entity_type->id()) ->setLabel(t('Name')) ->setDescription(t('The name of this content association.')) ->setRevisionable(TRUE) ->setTranslatable(TRUE) ->setRequired(TRUE) ->setDefaultValue('') ->setDisplayConfigurable('form', TRUE) ->setDisplayConfigurable('view', TRUE) ->setSettings([ 'max_length' => 255, 'text_processing' => 0, ]) ->setDisplayOptions('view', [ 'label' => 'hidden', 'type' => 'string', 'weight' => -4, ]) ->setDisplayOptions('form', [ 'type' => 'string_textfield', 'weight' => -4, ]); $fields['status'] = BaseFieldDefinition::create('boolean') ->setTargetEntityTypeId($entity_type->id()) ->setLabel(t('Active')) ->setRevisionable(TRUE) ->setTranslatable(TRUE) ->setDefaultValue(TRUE) ->setDisplayConfigurable('view', FALSE) ->setDisplayOptions('form', [ 'type' => 'boolean_checkbox', 'weight' => -3, ]); $fields['created'] = BaseFieldDefinition::create('created') ->setTargetEntityTypeId($entity_type->id()) ->setLabel(t('Created')) ->setRevisionable(FALSE) ->setTranslatable(FALSE) ->setDescription(t('The time of created.')); $fields['changed'] = BaseFieldDefinition::create('changed') ->setTargetEntityTypeId($entity_type->id()) ->setLabel(t('Changed')) ->setRevisionable(TRUE) ->setTranslatable(TRUE) ->setDescription(t('The last updated date.')); $fields['page'] = BaseFieldDefinition::create('association_plugin_settings') ->setTargetEntityTypeId($entity_type->id()) ->setLabel(t('Landing page settings')) ->setRevisionable(FALSE) ->setTranslatable(FALSE) ->setDescription(t('Landing page handler settings for this association.')) ->setSettings([ 'type' => 'landing_page', 'label' => t('Landing page'), ]) ->addConstraint('AssociationPluginFieldSettings') ->setDisplayConfigurable('view', FALSE) ->setDisplayOptions('form', [ 'type' => 'association_plugin_settings_widget', 'weight' => -1, ]); return $fields; } /** * {@inheritdoc} */ public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) { /** @var \Drupal\Core\Field\BaseFieldDefinition[] $base_field_definitions */ $fields = []; try { $bundle = \Drupal::entityTypeManager() ->getStorage($entity_type->getBundleEntityType()) ->load($bundle); if ($bundle) { $fields['name'] = clone $base_field_definitions['name']; $fields['name']->setDescription(t('The name of this @bundle_name.', [ '@bundle_name' => $bundle->label(), ])); $fields['status'] = clone $base_field_definitions['status']; $fields['status']->setDescription(t('Is this @bundle_name enabled and publishing its content.', [ '@bundle_name' => $bundle->label(), ])); } } catch (InvalidPluginDefinitionException $e) { // Unlikely that this would happen, but this would happen when the // association_type entity definition is missing or the storage handler. } return $fields; } }