From fa9af99f04e8fbd5d8d74d6817edec51622b1e97 Mon Sep 17 00:00:00 2001 From: Owen Bush <ojb@ukhhf.co.uk> Date: Sat, 2 Mar 2019 09:59:17 -0700 Subject: [PATCH] Added eventinstance entity type. --- src/Entity/EventInstance.php | 380 ++++++++++++++++++ src/Entity/EventSeries.php | 39 +- src/EventInstanceAccessControlHandler.php | 47 +++ ...SeriesInterface.php => EventInterface.php} | 4 +- 4 files changed, 448 insertions(+), 22 deletions(-) create mode 100644 src/Entity/EventInstance.php create mode 100644 src/EventInstanceAccessControlHandler.php rename src/{EventSeriesInterface.php => EventInterface.php} (88%) diff --git a/src/Entity/EventInstance.php b/src/Entity/EventInstance.php new file mode 100644 index 00000000..9a8c4451 --- /dev/null +++ b/src/Entity/EventInstance.php @@ -0,0 +1,380 @@ +<?php + +namespace Drupal\recurring_events\Entity; + +use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\Core\Entity\EditorialContentEntityBase; +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\recurring_events\EventInterface; +use Drupal\user\UserInterface; + +/** + * Defines the Event Instance entity. + * + * @ingroup recurring events + * + * This is the main definition of the entity type. From it, an entityType is + * derived. The most important properties in this example are listed below. + * + * id: The unique identifier of this entityType. It follows the pattern + * 'moduleName_xyz' to avoid naming conflicts. + * + * label: Human readable name of the entity type. + * + * handlers: Handler classes are used for different tasks. You can use + * standard handlers provided by D8 or build your own, most probably derived + * from the standard class. In detail: + * + * - view_builder: we use the standard controller to view an instance. It is + * called when a route lists an '_entity_view' default for the entityType + * (see routing.yml for details. The view can be manipulated by using the + * standard drupal tools in the settings. + * + * - list_builder: We derive our own list builder class from the + * entityListBuilder to control the presentation. + * If there is a view available for this entity from the views module, it + * overrides the list builder. @todo: any view? naming convention? + * + * - form: We derive our own forms to add functionality like additional fields, + * redirects etc. These forms are called when the routing list an + * '_entity_form' default for the entityType. Depending on the suffix + * (.add/.edit/.delete) in the route, the correct form is called. + * + * - access: Our own accessController where we determine access rights based on + * permissions. + * + * More properties: + * + * - base_table: Define the name of the table used to store the data. Make sure + * it is unique. The schema is automatically determined from the + * BaseFieldDefinitions below. The table is automatically created during + * installation. + * + * - fieldable: Can additional fields be added to the entity via the GUI? + * Analog to content types. + * + * - entity_keys: How to access the fields. Analog to 'nid' or 'uid'. + * + * - links: Provide links to do standard tasks. The 'edit-form' and + * 'delete-form' links are added to the list built by the + * entityListController. They will show up as action buttons in an additional + * column. + * + * There are many more properties to be used in an entity type definition. For + * a complete overview, please refer to the '\Drupal\Core\Entity\EntityType' + * class definition. + * + * The following construct is the actual definition of the entity type which + * is read and cached. Don't forget to clear cache after changes. + * + * @ContentEntityType( + * id = "eventinstance", + * label = @Translation("Event Instance entity"), + * handlers = { + * "storage" = "Drupal\Core\Entity\Sql\SqlContentEntityStorage", + * "view_builder" = "Drupal\Core\Entity\EntityViewBuilder", + * "views_data" = "Drupal\views\EntityViewsData", + * "form" = { + * "edit" = "Drupal\recurring_events\Form\EventInstanceForm", + * "delete" = "Drupal\recurring_events\Form\EventInstanceDeleteForm", + * "default" = "Drupal\recurring_events\Form\EventInstanceForm", + * "clone" = "Drupal\recurring_events\Form\EventInstanceCloneForm", + * "contact" = "Drupal\recurring_events\Form\EventInstanceContactRegistrationsForm", + * }, + * "access" = "Drupal\recurring_events\EventInstanceAccessControlHandler", + * }, + * base_table = "eventinstance", + * data_table = "eventinstance_field_data", + * revision_table = "eventinstance_revision", + * revision_data_table = "eventinstance_field_revision", + * show_revision_ui = TRUE, + * translatable = TRUE, + * admin_permission = "administer eventinstance entity", + * fieldable = TRUE, + * entity_keys = { + * "id" = "id", + * "revision" = "vid", + * "published" = "status", + * "langcode" = "langcode", + * "uuid" = "uuid" + * }, + * revision_metadata_keys = { + * "revision_user" = "revision_uid", + * "revision_created" = "revision_timestamp", + * "revision_log_message" = "revision_log" + * }, + * links = { + * "canonical" = "/events/{eventinstance}", + * "edit-form" = "/events/{eventinstance}/edit", + * "delete-form" = "/events/{eventinstance}/delete", + * "collection" = "/admin/content/events/instances", + * "version-history" = "/events/{eventinstance}/revisions", + * "revision" = "/events/{eventinstance}/revisions/{eventinstance_revision}/view", + * "clone-form" = "/events/{eventinstance}/clone", + * "contact-form" = "/events/{eventinstance}/registration/contact", + * }, + * field_ui_base_route = "eventinstance.settings", + * ) + * + * The 'links' above are defined by their path. For core to find the + * corresponding route, the route name must follow the correct pattern: + * + * entity.<entity-name>.<link-name> (replace dashes with underscores) + * Example: 'entity.eventinstance.canonical' + * + * See routing file above for the corresponding implementation + * + * The 'EventInstance' class defines methods and fields for the eventinstance + * entity. + * + * Being derived from the ContentEntityBase class, we can override the methods + * we want. In our case we want to provide access to the standard fields about + * creation and changed time stamps. + * + * Our interface (see EventSeriesInterface) also exposes the + * EntityOwnerInterface. This allows us to provide methods for setting + * and providing ownership information. + * + * The most important part is the definitions of the field properties for this + * entity type. These are of the same type as fields added through the GUI, but + * they can by changed in code. In the definition we can define if the user with + * the rights privileges can influence the presentation (view, edit) of each + * field. + */ +class EventInstance extends EditorialContentEntityBase implements EventInterface { + + /** + * {@inheritdoc} + * + * When a new entity instance is added, set the user_id entity reference to + * the current user as the creator of the instance. + */ + public static function preCreate(EntityStorageInterface $storage_controller, array &$values) { + parent::preCreate($storage_controller, $values); + $values += [ + 'uid' => \Drupal::currentUser()->id(), + ]; + } + + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageInterface $storage) { + parent::preSave($storage); + + foreach (array_keys($this->getTranslationLanguages()) as $langcode) { + $translation = $this->getTranslation($langcode); + + // If no owner has been set explicitly, make the anonymous user the owner. + if (!$translation->getOwner()) { + $translation->setOwnerId(0); + } + } + + // If no revision author has been set explicitly, make the node owner the + // revision author. + if (!$this->getRevisionUser()) { + $this->setRevisionUserId($this->getOwnerId()); + } + } + + /** + * {@inheritdoc} + */ + public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) { + parent::preSaveRevision($storage, $record); + + if (!$this->isNewRevision() && isset($this->original) && (!isset($record->revision_log) || $record->revision_log === '')) { + // If we are updating an existing eventinstance without adding a new + // revision, we need to make sure $entity->revision_log is reset whenever + // it is empty. Therefore, this code allows us to avoid clobbering an + // existing log entry with an empty one. + $record->revision_log = $this->original->revision_log->value; + } + } + + /** + * {@inheritdoc} + */ + public function getRevisionAuthor() { + return $this->getRevisionUser(); + } + + /** + * {@inheritdoc} + */ + public function setRevisionAuthorId($uid) { + $this->setRevisionUserId($uid); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getCreatedTime() { + return $this->get('created')->value; + } + + /** + * {@inheritdoc} + */ + public function getChangedTime() { + return $this->get('changed')->value; + } + + /** + * {@inheritdoc} + */ + public function setChangedTime($timestamp) { + $this->set('changed', $timestamp); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getChangedTimeAcrossTranslations() { + $changed = $this->getUntranslated()->getChangedTime(); + foreach ($this->getTranslationLanguages(FALSE) as $language) { + $translation_changed = $this->getTranslation($language->getId()) + ->getChangedTime(); + $changed = max($translation_changed, $changed); + } + return $changed; + } + + /** + * {@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->setOwnerId($account->id()); + return $this; + } + + /** + * {@inheritdoc} + * + * Define the field properties here. + * + * Field name, type and size determine the table structure. + * + * In addition, we can define how the field and its content can be manipulated + * in the GUI. The behaviour of the widgets used can be determined here. + */ + public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { + $fields = parent::baseFieldDefinitions($entity_type); + + // Standard field, used as unique if primary index. + $fields['id'] = BaseFieldDefinition::create('integer') + ->setLabel(t('ID')) + ->setDescription(t('The ID of the event entity.')) + ->setReadOnly(TRUE); + + $fields['uid'] = BaseFieldDefinition::create('entity_reference') + ->setLabel(t('Authored by')) + ->setDescription(t('The username of the content author.')) + ->setRevisionable(TRUE) + ->setSetting('target_type', 'user') + ->setDefaultValueCallback('Drupal\recurring_events\Entity\EventInstance::getCurrentUserId') + ->setTranslatable(TRUE) + ->setDisplayOptions('form', [ + 'type' => 'entity_reference_autocomplete', + 'weight' => 5, + 'settings' => [ + 'match_operator' => 'CONTAINS', + 'size' => '60', + 'placeholder' => '', + ], + ]) + ->setDisplayConfigurable('form', TRUE); + + // Standard field, unique outside of the scope of the current project. + $fields['uuid'] = BaseFieldDefinition::create('uuid') + ->setLabel(t('UUID')) + ->setDescription(t('The UUID of the event entity.')) + ->setReadOnly(TRUE); + + $fields['body'] = BaseFieldDefinition::create('text_long') + ->setLabel(t('Event Description')) + ->setTranslatable(TRUE) + ->setRevisionable(TRUE) + ->setRequired(TRUE) + ->setDisplayConfigurable('view', TRUE) + ->setDisplayOptions('form', [ + 'type' => 'text_textarea', + 'weight' => -4, + ]) + ->setDisplayConfigurable('form', TRUE); + + $fields['date'] = BaseFieldDefinition::create('daterange') + ->setLabel(t('Event Description')) + ->setTranslatable(FALSE) + ->setRevisionable(TRUE) + ->setRequired(TRUE) + ->setDisplayOptions('view', array( + 'label' => 'above', + 'type' => 'string', + 'weight' => -4, + )) + ->setDisplayOptions('form', array( + 'type' => 'daterange_default', + 'weight' => -4, + )) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['eventseries_id'] = BaseFieldDefinition::create('entity_reference') + ->setLabel(t('Event Series ID')) + ->setDescription(t('The ID of the event series entity.')) + ->setSetting('target_type', 'eventseries'); + + $fields['langcode'] = BaseFieldDefinition::create('language') + ->setLabel(t('Language code')) + ->setDescription(t('The language code of event entity.')); + + $fields['created'] = BaseFieldDefinition::create('created') + ->setLabel(t('Created')) + ->setDescription(t('The time that the entity was created.')); + + $fields['changed'] = BaseFieldDefinition::create('changed') + ->setLabel(t('Changed')) + ->setRevisionable(TRUE) + ->setDescription(t('The time that the entity was last edited.')); + + $fields['status'] + ->setDisplayOptions('form', [ + 'type' => 'boolean_checkbox', + 'settings' => [ + 'display_label' => TRUE, + ], + 'weight' => 120, + ]) + ->setDisplayConfigurable('form', TRUE); + + return $fields; + } + +} diff --git a/src/Entity/EventSeries.php b/src/Entity/EventSeries.php index 1ece4c63..09e63a8e 100644 --- a/src/Entity/EventSeries.php +++ b/src/Entity/EventSeries.php @@ -6,7 +6,7 @@ use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Entity\EditorialContentEntityBase; use Drupal\Core\Entity\EntityTypeInterface; -use Drupal\recurring_events\EventSeriesInterface; +use Drupal\recurring_events\EventInterface; use Drupal\user\UserInterface; /** @@ -105,13 +105,13 @@ use Drupal\user\UserInterface; * "revision_log_message" = "revision_log" * }, * links = { - * "canonical" = "/events/series/{event}", - * "edit-form" = "/events/series/{event}/edit", - * "delete-form" = "/events/series/{event}/delete", + * "canonical" = "/events/series/{eventseries}", + * "edit-form" = "/events/series/{eventseries}/edit", + * "delete-form" = "/events/series/{eventseries}/delete", * "collection" = "/admin/content/events/series", - * "version-history" = "/events/series/{event}/revisions", - * "revision" = "/events/series/{event}/revisions/{event_revision}/view", - * "clone-form" = "/events/series/{event}/clone", + * "version-history" = "/events/series/{eventseries}/revisions", + * "revision" = "/events/series/{eventseries}/revisions/{eventseries_revision}/view", + * "clone-form" = "/events/series/{eventseries}/clone", * }, * field_ui_base_route = "eventseries.settings", * ) @@ -140,18 +140,18 @@ use Drupal\user\UserInterface; * the rights privileges can influence the presentation (view, edit) of each * field. */ -class EventSeries extends EditorialContentEntityBase implements EventSeriesInterface { +class EventSeries extends EditorialContentEntityBase implements EventInterface { /** * {@inheritdoc} * - * When a new entity instance is added, set the user_id entity reference to + * When a new entity instance is added, set the uid entity reference to * the current user as the creator of the instance. */ public static function preCreate(EntityStorageInterface $storage_controller, array &$values) { parent::preCreate($storage_controller, $values); $values += [ - 'user_id' => \Drupal::currentUser()->id(), + 'uid' => \Drupal::currentUser()->id(), ]; } @@ -246,21 +246,21 @@ class EventSeries extends EditorialContentEntityBase implements EventSeriesInter * {@inheritdoc} */ public function getOwner() { - return $this->get('user_id')->entity; + return $this->get('uid')->entity; } /** * {@inheritdoc} */ public function getOwnerId() { - return $this->get('user_id')->target_id; + return $this->get('uid')->target_id; } /** * {@inheritdoc} */ public function setOwnerId($uid) { - $this->set('user_id', $uid); + $this->set('uid', $uid); return $this; } @@ -308,7 +308,6 @@ class EventSeries extends EditorialContentEntityBase implements EventSeriesInter ]) ->setDisplayConfigurable('form', TRUE); - // Standard field, unique outside of the scope of the current project. $fields['uuid'] = BaseFieldDefinition::create('uuid') ->setLabel(t('UUID')) ->setDescription(t('The UUID of the event entity.')) @@ -333,15 +332,15 @@ class EventSeries extends EditorialContentEntityBase implements EventSeriesInter ->setTranslatable(TRUE) ->setRequired(TRUE); - $fields['body'] = BaseFieldDefinition::create('text_with_summary') - ->setLabel(t('Description of the event.')) + $fields['body'] = BaseFieldDefinition::create('text_long') + ->setLabel(t('Event Description')) ->setTranslatable(TRUE) ->setRevisionable(TRUE) ->setRequired(TRUE) ->setDisplayConfigurable('view', TRUE) ->setDisplayOptions('form', [ - 'type' => 'text_textarea_with_summary', - 'weight' => -5, + 'type' => 'text_textarea', + 'weight' => -4, ]) ->setDisplayConfigurable('form', TRUE); @@ -414,11 +413,11 @@ class EventSeries extends EditorialContentEntityBase implements EventSeriesInter 'weight' => 4, ]); - $fields['instances'] = BaseFieldDefinition::create('entity_reference') + $fields['event_instances'] = BaseFieldDefinition::create('entity_reference') ->setLabel(t('Events in this Series')) ->setDescription(t('The events in this series.')) ->setRevisionable(FALSE) - ->setSetting('target_type', 'node') + ->setSetting('target_type', 'eventinstance') ->setTranslatable(FALSE) ->setDisplayOptions('view', [ 'label' => 'above', diff --git a/src/EventInstanceAccessControlHandler.php b/src/EventInstanceAccessControlHandler.php new file mode 100644 index 00000000..23f21b56 --- /dev/null +++ b/src/EventInstanceAccessControlHandler.php @@ -0,0 +1,47 @@ +<?php + +namespace Drupal\recurring_events; + +use Drupal\Core\Access\AccessResult; +use Drupal\Core\Entity\EntityAccessControlHandler; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Session\AccountInterface; + +/** + * Access controller for the event instance entity. + * + * @see \Drupal\recurring_events\Entity\EventInstance + */ +class EventInstanceAccessControlHandler extends EntityAccessControlHandler { + + /** + * {@inheritdoc} + * + * Link the activities to the permissions. checkAccess is called with the + * $operation as defined in the routing.yml file. + */ + protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) { + switch ($operation) { + case 'view': + $status = $entity->isPublished(); + if (!$status) { + return AccessResult::allowedIfHasPermission($account, 'view unpublished eventinstance entity'); + } + return AccessResult::allowedIfHasPermission($account, 'view eventinstance entity'); + + case 'edit': + return AccessResult::allowedIfHasPermission($account, 'edit eventinstance entity'); + + case 'delete': + return AccessResult::allowedIfHasPermission($account, 'delete eventinstance entity'); + + case 'clone': + return AccessResult::allowedIfHasPermission($account, 'clone eventinstance entity'); + + case 'contact': + return AccessResult::allowedIfHasPermission($account, 'contact eventinstance registration entities'); + } + return AccessResult::allowed(); + } + +} diff --git a/src/EventSeriesInterface.php b/src/EventInterface.php similarity index 88% rename from src/EventSeriesInterface.php rename to src/EventInterface.php index cf57f9ce..6644e0c5 100644 --- a/src/EventSeriesInterface.php +++ b/src/EventInterface.php @@ -9,9 +9,9 @@ use Drupal\Core\Entity\EntityChangedInterface; use Drupal\Core\Entity\ContentEntityInterface; /** - * Provides an interface defining an EventSeries entity. + * Provides an interface defining an EventSeries and EventInstance entities. */ -interface EventSeriesInterface extends ContentEntityInterface, EntityChangedInterface, EntityOwnerInterface, RevisionLogInterface, EntityPublishedInterface { +interface EventInterface extends ContentEntityInterface, EntityChangedInterface, EntityOwnerInterface, RevisionLogInterface, EntityPublishedInterface { /** * Denotes that the event/eventinstance is not published. */ -- GitLab