diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php index a25f72fbff1..e35a533213d 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php @@ -278,6 +278,7 @@ class ConfigStorageController extends EntityStorageControllerBase { */ public function create(array $values) { $class = $this->entityInfo['class']; + $class::preCreate($this, $values); // Set default language to site default if not provided. $values += array('langcode' => language_default()->langcode); @@ -292,6 +293,7 @@ class ConfigStorageController extends EntityStorageControllerBase { $uuid = new Uuid(); $entity->{$this->uuidKey} = $uuid->generate(); } + $entity->postCreate($this); // Modules might need to add or change the data initially held by the new // entity object, for instance to fill-in default values. @@ -314,7 +316,8 @@ class ConfigStorageController extends EntityStorageControllerBase { return; } - $this->preDelete($entities); + $entity_class = $this->entityInfo['class']; + $entity_class::preDelete($this, $entities); foreach ($entities as $id => $entity) { $this->invokeHook('predelete', $entity); } @@ -324,7 +327,7 @@ class ConfigStorageController extends EntityStorageControllerBase { $config->delete(); } - $this->postDelete($entities); + $entity_class::postDelete($this, $entities); foreach ($entities as $id => $entity) { $this->invokeHook('delete', $entity); } @@ -367,7 +370,7 @@ class ConfigStorageController extends EntityStorageControllerBase { $this->configFactory->rename($prefix . $id, $prefix . $entity->id()); } - $this->preSave($entity); + $entity->preSave($this); $this->invokeHook('presave', $entity); // Retrieve the desired properties and set them in config. @@ -378,7 +381,7 @@ class ConfigStorageController extends EntityStorageControllerBase { if (!$is_new) { $return = SAVED_UPDATED; $config->save(); - $this->postSave($entity, TRUE); + $entity->postSave($this, TRUE); $this->invokeHook('update', $entity); // Immediately update the original ID. @@ -388,7 +391,7 @@ class ConfigStorageController extends EntityStorageControllerBase { $return = SAVED_NEW; $config->save(); $entity->enforceIsNew(FALSE); - $this->postSave($entity, FALSE); + $entity->postSave($this, FALSE); $this->invokeHook('insert', $entity); } @@ -397,45 +400,6 @@ class ConfigStorageController extends EntityStorageControllerBase { return $return; } - /** - * Acts on an entity before the presave hook is invoked. - * - * Used before the entity is saved and before invoking the presave hook. - */ - protected function preSave(EntityInterface $entity) { - } - - /** - * Acts on a saved entity before the insert or update hook is invoked. - * - * Used after the entity is saved, but before invoking the insert or update - * hook. - * - * @param EntityInterface $entity - * The entity to act on. - * @param $update - * (bool) TRUE if the entity has been updated, or FALSE if it has been - * inserted. - */ - protected function postSave(EntityInterface $entity, $update) { - } - - /** - * Acts on entities before they are deleted. - * - * Used before the entities are deleted and before invoking the delete hook. - */ - protected function preDelete($entities) { - } - - /** - * Acts on deleted entities before the delete hook is invoked. - * - * Used after the entities are deleted but before invoking the delete hook. - */ - protected function postDelete($entities) { - } - /** * Implements Drupal\Core\Entity\EntityStorageControllerInterface::getFieldDefinitions(). */ diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php index fe746f4dd95..a3cd9df00c1 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php @@ -370,15 +370,17 @@ class DatabaseStorageController extends EntityStorageControllerBase { * Implements \Drupal\Core\Entity\EntityStorageControllerInterface::create(). */ public function create(array $values) { - $class = $this->entityInfo['class']; + $entity_class = $this->entityInfo['class']; + $entity_class::preCreate($this, $values); - $entity = new $class($values, $this->entityType); + $entity = new $entity_class($values, $this->entityType); // Assign a new UUID if there is none yet. if ($this->uuidKey && !isset($entity->{$this->uuidKey})) { $uuid = new Uuid(); $entity->{$this->uuidKey} = $uuid->generate(); } + $entity->postCreate($this); // Modules might need to add or change the data initially held by the new // entity object, for instance to fill-in default values. @@ -398,7 +400,8 @@ class DatabaseStorageController extends EntityStorageControllerBase { $transaction = $this->database->startTransaction(); try { - $this->preDelete($entities); + $entity_class = $this->entityInfo['class']; + $entity_class::preDelete($this, $entities); foreach ($entities as $id => $entity) { $this->invokeHook('predelete', $entity); } @@ -417,7 +420,7 @@ class DatabaseStorageController extends EntityStorageControllerBase { // Reset the cache as soon as the changes have been applied. $this->resetCache($ids); - $this->postDelete($entities); + $entity_class::postDelete($this, $entities); foreach ($entities as $id => $entity) { $this->invokeHook('delete', $entity); } @@ -442,7 +445,7 @@ class DatabaseStorageController extends EntityStorageControllerBase { $entity->original = entity_load_unchanged($this->entityType, $entity->id()); } - $this->preSave($entity); + $entity->preSave($this); $this->invokeHook('presave', $entity); if (!$entity->isNew()) { @@ -458,7 +461,7 @@ class DatabaseStorageController extends EntityStorageControllerBase { $this->saveRevision($entity); } $this->resetCache(array($entity->id())); - $this->postSave($entity, TRUE); + $entity->postSave($this, TRUE); $this->invokeHook('update', $entity); } else { @@ -470,7 +473,7 @@ class DatabaseStorageController extends EntityStorageControllerBase { $this->resetCache(array()); $entity->enforceIsNew(FALSE); - $this->postSave($entity, FALSE); + $entity->postSave($this, FALSE); $this->invokeHook('insert', $entity); } @@ -507,7 +510,7 @@ class DatabaseStorageController extends EntityStorageControllerBase { // Cast to object as preSaveRevision() expects one to be compatible with the // upcoming NG storage controller. $record = (object) $record; - $this->preSaveRevision($record, $entity); + $entity->preSaveRevision($this, $record); $record = (array) $record; if ($entity->isNewRevision()) { @@ -527,49 +530,6 @@ class DatabaseStorageController extends EntityStorageControllerBase { $entity->{$this->revisionKey} = $record[$this->revisionKey]; } - /** - * Acts on an entity before the presave hook is invoked. - * - * Used before the entity is saved and before invoking the presave hook. - */ - protected function preSave(EntityInterface $entity) { } - - /** - * Acts on a saved entity before the insert or update hook is invoked. - * - * Used after the entity is saved, but before invoking the insert or update - * hook. - * - * @param $update - * (bool) TRUE if the entity has been updated, or FALSE if it has been - * inserted. - */ - protected function postSave(EntityInterface $entity, $update) { } - - /** - * Acts on entities before they are deleted. - * - * Used before the entities are deleted and before invoking the delete hook. - */ - protected function preDelete($entities) { } - - /** - * Acts on deleted entities before the delete hook is invoked. - * - * Used after the entities are deleted but before invoking the delete hook. - */ - protected function postDelete($entities) { } - - /** - * Act on a revision before being saved. - * - * @param \stdClass $record - * The revision object. - * @param Drupal\Core\Entity\EntityInterface $entity - * The entity object. - */ - protected function preSaveRevision(\stdClass $record, EntityInterface $entity) { } - /** * Invokes a hook on behalf of the entity. * diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php index 91ec62d616b..c3a76e2d5d4 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php @@ -98,6 +98,9 @@ class DatabaseStorageControllerNG extends DatabaseStorageController { * A new entity object. */ public function create(array $values) { + $entity_class = $this->entityClass; + $entity_class::preCreate($this, $values); + // We have to determine the bundle first. $bundle = FALSE; if ($this->bundleKey) { @@ -118,6 +121,7 @@ class DatabaseStorageControllerNG extends DatabaseStorageController { $uuid = new Uuid(); $entity->{$this->uuidKey} = $uuid->generate(); } + $entity->postCreate($this); // Modules might need to add or change the data initially held by the new // entity object, for instance to fill-in default values. @@ -354,7 +358,7 @@ class DatabaseStorageControllerNG extends DatabaseStorageController { $entity->original = entity_load_unchanged($this->entityType, $entity->id()); } - $this->preSave($entity); + $entity->preSave($this); $this->invokeHook('presave', $entity); // Create the storage record to be saved. @@ -376,7 +380,7 @@ class DatabaseStorageControllerNG extends DatabaseStorageController { $this->savePropertyData($entity); } $this->resetCache(array($entity->id())); - $this->postSave($entity, TRUE); + $entity->postSave($this, TRUE); $this->invokeHook('update', $entity); } else { @@ -394,7 +398,7 @@ class DatabaseStorageControllerNG extends DatabaseStorageController { $this->resetCache(array()); $entity->enforceIsNew(FALSE); - $this->postSave($entity, FALSE); + $entity->postSave($this, FALSE); $this->invokeHook('insert', $entity); } @@ -445,7 +449,7 @@ class DatabaseStorageControllerNG extends DatabaseStorageController { $record->{$this->revisionKey} = NULL; } - $this->preSaveRevision($record, $entity); + $entity->preSaveRevision($this, $record); if ($entity->isNewRevision()) { drupal_write_record($this->revisionTable, $record); @@ -596,12 +600,14 @@ class DatabaseStorageControllerNG extends DatabaseStorageController { $transaction = $this->database->startTransaction(); try { + $entity_class = $this->entityClass; + $entity_class::preDelete($this, $entities); + // Ensure we are dealing with the actual entities. foreach ($entities as $id => $entity) { $entities[$id] = $entity->getNGEntity(); } - $this->preDelete($entities); foreach ($entities as $id => $entity) { $this->invokeHook('predelete', $entity); } @@ -626,7 +632,7 @@ class DatabaseStorageControllerNG extends DatabaseStorageController { // Reset the cache as soon as the changes have been applied. $this->resetCache($ids); - $this->postDelete($entities); + $entity_class::postDelete($this, $entities); foreach ($entities as $id => $entity) { $this->invokeHook('delete', $entity); } diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php index 9e95a0b0f4d..f8f8eb1e898 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -531,4 +531,52 @@ class Entity implements IteratorAggregate, EntityInterface { return !empty($bundles[$this->bundle()]['translatable']); } + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageControllerInterface $storage_controller) { + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { + } + + /** + * {@inheritdoc} + */ + public static function preCreate(EntityStorageControllerInterface $storage_controller, array &$values) { + } + + /** + * {@inheritdoc} + */ + public function postCreate(EntityStorageControllerInterface $storage_controller) { + } + + /** + * {@inheritdoc} + */ + public static function preDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + } + + /** + * {@inheritdoc} + */ + public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + } + + /** + * {@inheritdoc} + */ + public static function postLoad(EntityStorageControllerInterface $storage_controller, array $entities) { + } + + /** + * {@inheritdoc} + */ + public function preSaveRevision(EntityStorageControllerInterface $storage_controller, \stdClass $record) { + } + } diff --git a/core/lib/Drupal/Core/Entity/EntityBCDecorator.php b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php index 475f7b34c60..93bafb4a230 100644 --- a/core/lib/Drupal/Core/Entity/EntityBCDecorator.php +++ b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php @@ -533,4 +533,53 @@ class EntityBCDecorator implements IteratorAggregate, EntityInterface { public function isTranslatable() { return $this->decorated->isTranslatable(); } + + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageControllerInterface $storage_controller) { + $this->decorated->preSave($storage_controller); + } + + /** + * {@inheritdoc} + */ + public function preSaveRevision(EntityStorageControllerInterface $storage_controller, \stdClass $record) { + $this->decorated->preSave($storage_controller, $record); + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { + $this->decorated->postSave($storage_controller, $update); + } + + /** + * {@inheritdoc} + */ + public static function preCreate(EntityStorageControllerInterface $storage_controller, array &$values) { + } + + public function postCreate(EntityStorageControllerInterface $storage_controller) { + $this->decorated->postCreate($storage_controller); + } + + /** + * {@inheritdoc} + */ + public static function preDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + } + + /** + * {@inheritdoc} + */ + public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + } + + /** + * {@inheritdoc} + */ + public static function postLoad(EntityStorageControllerInterface $storage_controller, array $entities) { + } } diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php index 692c3291dab..b8fe82bb679 100644 --- a/core/lib/Drupal/Core/Entity/EntityInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityInterface.php @@ -162,6 +162,94 @@ interface EntityInterface extends ComplexDataInterface, AccessibleInterface, Tra */ public function delete(); + /** + * Acts on an entity before the presave hook is invoked. + * + * Used before the entity is saved and before invoking the presave hook. + * + * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage_controller + * The entity storage controller object. + */ + public function preSave(EntityStorageControllerInterface $storage_controller); + + /** + * Acts on a revision before it gets saved. + * + * @param EntityStorageControllerInterface $storage_controller + * The entity storage controller object. + * @param \stdClass $record + * The revision object. + */ + public function preSaveRevision(EntityStorageControllerInterface $storage_controller, \stdClass $record); + + /** + * Acts on a saved entity before the insert or update hook is invoked. + * + * Used after the entity is saved, but before invoking the insert or update + * hook. + * + * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage_controller + * The entity storage controller object. + * @param bool $update + * TRUE if the entity has been updated, or FALSE if it has been inserted. + */ + public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE); + + /** + * Changes the values of an entity before it is created. + * + * Load defaults for example. + * + * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage_controller + * The entity storage controller object. + * @param array $values + * An array of values to set, keyed by property name. If the entity type has + * bundles the bundle key has to be specified. + */ + public static function preCreate(EntityStorageControllerInterface $storage_controller, array &$values); + + /** + * Acts on an entity after it is created but before hooks are invoked. + * + * @param EntityStorageControllerInterface $storage_controller + * The entity storage controller object. + */ + public function postCreate(EntityStorageControllerInterface $storage_controller); + + /** + * Acts on entities before they are deleted and before hooks are invoked. + * + * Used before the entities are deleted and before invoking the delete hook. + * + * @param EntityStorageControllerInterface $storage_controller + * The entity storage controller object. + * @param array $entities + * An array of entities. + */ + public static function preDelete(EntityStorageControllerInterface $storage_controller, array $entities); + + /** + * Acts on deleted entities before the delete hook is invoked. + * + * Used after the entities are deleted but before invoking the delete hook. + * + * @param EntityStorageControllerInterface $storage_controller + * The entity storage controller object. + * @param array $entities + * An array of entities. + */ + public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities); + + /** + * Acts on loaded entities before the load hook is invoked. + * + * @param EntityStorageControllerInterface $storage_controller + * The entity storage controller object. + * @param array $entities + * An array of entities. + */ + public static function postLoad(EntityStorageControllerInterface $storage_controller, array $entities); + /** * Creates a duplicate of the entity. * diff --git a/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php index 610cb4d82f1..e533dd701fb 100644 --- a/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php @@ -33,7 +33,7 @@ interface EntityStorageControllerInterface { * Loads one or more entities. * * @param $ids - * An array of entity IDs, or FALSE to load all entities. + * An array of entity IDs, or NULL to load all entities. * * @return * An array of entity objects indexed by their ids. diff --git a/core/modules/aggregator/lib/Drupal/aggregator/FeedStorageController.php b/core/modules/aggregator/lib/Drupal/aggregator/FeedStorageController.php index 82a5643d943..36d87eb25f9 100644 --- a/core/modules/aggregator/lib/Drupal/aggregator/FeedStorageController.php +++ b/core/modules/aggregator/lib/Drupal/aggregator/FeedStorageController.php @@ -8,6 +8,7 @@ namespace Drupal\aggregator; use Drupal\Core\Entity\DatabaseStorageControllerNG; +use Drupal\aggregator\Plugin\Core\Entity\Feed; use Drupal\Core\Entity\EntityInterface; /** @@ -16,101 +17,14 @@ use Drupal\Core\Entity\EntityInterface; * This extends the Drupal\Core\Entity\DatabaseStorageController class, adding * required special handling for feed entities. */ -class FeedStorageController extends DatabaseStorageControllerNG { - - /** - * Overrides Drupal\Core\Entity\DataBaseStorageController::create(). - */ - public function create(array $values) { - $values += array( - 'link' => '', - 'description' => '', - 'image' => '', - ); - return parent::create($values); - } +class FeedStorageController extends DatabaseStorageControllerNG implements FeedStorageControllerInterface { /** * Overrides Drupal\Core\Entity\DataBaseStorageController::attachLoad(). */ protected function attachLoad(&$queried_entities, $load_revision = FALSE) { parent::attachLoad($queried_entities, $load_revision); - foreach ($queried_entities as $item) { - $item->categories = db_query('SELECT c.cid, c.title FROM {aggregator_category} c JOIN {aggregator_category_feed} f ON c.cid = f.cid AND f.fid = :fid ORDER BY title', array(':fid' => $item->id()))->fetchAllKeyed(); - } - } - - /** - * Overrides Drupal\Core\Entity\DataBaseStorageController::preDelete(). - */ - protected function preDelete($entities) { - parent::preDelete($entities); - - // Invalidate the block cache to update aggregator feed-based derivatives. - if (module_exists('block')) { - \Drupal::service('plugin.manager.block')->clearCachedDefinitions(); - } - foreach ($entities as $entity) { - // Notify processors to remove stored items. - $manager = \Drupal::service('plugin.manager.aggregator.processor'); - foreach ($manager->getDefinitions() as $id => $definition) { - $manager->createInstance($id)->remove($entity); - } - } - } - - /** - * Overrides Drupal\Core\Entity\DataBaseStorageController::postDelete(). - */ - protected function postDelete($entities) { - parent::postDelete($entities); - - foreach ($entities as $entity) { - // Make sure there is no active block for this feed. - $block_configs = config_get_storage_names_with_prefix('plugin.core.block'); - foreach ($block_configs as $config_id) { - $config = config($config_id); - if ($config->get('id') == 'aggregator_feed_block:' . $entity->id()) { - $config->delete(); - } - } - } - } - - /** - * Overrides Drupal\Core\Entity\DataBaseStorageController::preSave(). - */ - protected function preSave(EntityInterface $entity) { - parent::preSave($entity); - - // Invalidate the block cache to update aggregator feed-based derivatives. - if (module_exists('block')) { - drupal_container()->get('plugin.manager.block')->clearCachedDefinitions(); - } - // An existing feed is being modified, delete the category listings. - db_delete('aggregator_category_feed') - ->condition('fid', $entity->id()) - ->execute(); - } - - /** - * Overrides Drupal\Core\Entity\DataBaseStorageController::postSave(). - */ - protected function postSave(EntityInterface $entity, $update) { - parent::postSave($entity, $update); - - if (!empty($entity->categories)) { - foreach ($entity->categories as $cid => $value) { - if ($value) { - db_insert('aggregator_category_feed') - ->fields(array( - 'fid' => $entity->id(), - 'cid' => $cid, - )) - ->execute(); - } - } - } + $this->loadCategories($queried_entities); } /** @@ -191,4 +105,39 @@ class FeedStorageController extends DatabaseStorageControllerNG { return $fields; } + /** + * {@inheritdoc} + */ + public function loadCategories(array $feeds) { + foreach ($feeds as $feed) { + $feed->categories = $this->database->query('SELECT c.cid, c.title FROM {aggregator_category} c JOIN {aggregator_category_feed} f ON c.cid = f.cid AND f.fid = :fid ORDER BY title', array(':fid' => $feed->id()))->fetchAllKeyed(); + } + } + + /** + * {@inheritdoc} + */ + public function saveCategories(Feed $feed, array $categories) { + foreach ($categories as $cid => $value) { + if ($value) { + $this->database->insert('aggregator_category_feed') + ->fields(array( + 'fid' => $feed->id(), + 'cid' => $cid, + )) + ->execute(); + } + } + } + + /** + * {@inheritdoc} + */ + public function deleteCategories(array $feeds) { + // An existing feed is being modified, delete the category listings. + $this->database->delete('aggregator_category_feed') + ->condition('fid', array_keys($feeds)) + ->execute(); + } + } diff --git a/core/modules/aggregator/lib/Drupal/aggregator/FeedStorageControllerInterface.php b/core/modules/aggregator/lib/Drupal/aggregator/FeedStorageControllerInterface.php new file mode 100644 index 00000000000..24545ca4ad2 --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/FeedStorageControllerInterface.php @@ -0,0 +1,45 @@ +timestamp->value = REQUEST_TIME; - return $entity; - } +class ItemStorageController extends DatabaseStorageControllerNG implements ItemStorageControllerInterface { /** * Overrides Drupal\Core\Entity\DataBaseStorageController::attachLoad(). */ protected function attachLoad(&$queried_entities, $load_revision = FALSE) { parent::attachLoad($queried_entities, $load_revision); - foreach ($queried_entities as $item) { - $item->categories = db_query('SELECT c.title, c.cid FROM {aggregator_category_item} ci LEFT JOIN {aggregator_category} c ON ci.cid = c.cid WHERE ci.iid = :iid ORDER BY c.title', array(':iid' => $item->id()))->fetchAll(); - } - } - - /** - * Overrides Drupal\Core\Entity\DataBaseStorageController::preDelete(). - */ - protected function preDelete($entities) { - parent::preDelete($entities); - - db_delete('aggregator_category_item') - ->condition('iid', array_keys($entities), 'IN') - ->execute(); - } - - /** - * Overrides Drupal\Core\Entity\DataBaseStorageController::postSave(). - */ - protected function postSave(EntityInterface $entity, $update) { - parent::postSave($entity, $update); - - $result = db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = :fid', array(':fid' => $entity->fid->value)); - foreach ($result as $category) { - db_merge('aggregator_category_item') - ->key(array( - 'iid' => $entity->id(), - 'cid' => $category->cid, - )) - ->execute(); - } + $this->loadCategories($queried_entities); } /** @@ -120,4 +80,36 @@ class ItemStorageController extends DatabaseStorageControllerNG { return $fields; } + /** + * {@inheritdoc} + */ + public function loadCategories(array $entities) { + foreach ($entities as $item) { + $item->categories = db_query('SELECT c.title, c.cid FROM {aggregator_category_item} ci LEFT JOIN {aggregator_category} c ON ci.cid = c.cid WHERE ci.iid = :iid ORDER BY c.title', array(':iid' => $item->id()))->fetchAll(); + } + } + + /** + * {@inheritdoc} + */ + public function deleteCategories(array $entities) { + $this->database->delete('aggregator_category_item') + ->condition('iid', array_keys($entities)) + ->execute(); + } + + /** + * {@inheritdoc} + */ + public function saveCategories(Item $item) { + $result = $this->database->query('SELECT cid FROM {aggregator_category_feed} WHERE fid = :fid', array(':fid' => $item->fid->value)); + foreach ($result as $category) { + $this->database->merge('aggregator_category_item') + ->key(array( + 'iid' => $item->id(), + 'cid' => $category->cid, + )) + ->execute(); + } + } } diff --git a/core/modules/aggregator/lib/Drupal/aggregator/ItemStorageControllerInterface.php b/core/modules/aggregator/lib/Drupal/aggregator/ItemStorageControllerInterface.php new file mode 100644 index 00000000000..5c00345f764 --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/ItemStorageControllerInterface.php @@ -0,0 +1,44 @@ +etag); unset($this->modified); unset($this->block); - } /** @@ -173,4 +174,75 @@ class Feed extends EntityNG implements FeedInterface { public function label($langcode = NULL) { return $this->get('title')->value; } + + /** + * {@inheritdoc} + */ + public static function preCreate(EntityStorageControllerInterface $storage_controller, array &$values) { + $values += array( + 'link' => '', + 'description' => '', + 'image' => '', + ); + } + + /** + * {@inheritdoc} + */ + public static function preDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + // Invalidate the block cache to update aggregator feed-based derivatives. + if (module_exists('block')) { + \Drupal::service('plugin.manager.block')->clearCachedDefinitions(); + } + $storage_controller->deleteCategories($entities); + foreach ($entities as $entity) { + // Notify processors to remove stored items. + $manager = \Drupal::service('plugin.manager.aggregator.processor'); + foreach ($manager->getDefinitions() as $id => $definition) { + $manager->createInstance($id)->remove($entity); + } + } + } + + /** + * {@inheritdoc} + */ + public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + foreach ($entities as $entity) { + // Make sure there is no active block for this feed. + $block_configs = config_get_storage_names_with_prefix('plugin.core.block'); + foreach ($block_configs as $config_id) { + $config = config($config_id); + if ($config->get('id') == 'aggregator_feed_block:' . $entity->id()) { + $config->delete(); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageControllerInterface $storage_controller) { + $this->clearBlockCacheDefinitions(); + $storage_controller->deleteCategories(array($this->id() => $this)); + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageControllerInterface $storage_controller, $update = FALSE) { + if (!empty($this->categories)) { + $storage_controller->saveCategories($this, $this->categories); + } + } + + /** + * Invalidate the block cache to update aggregator feed-based derivatives. + */ + protected function clearBlockCacheDefinitions() { + if ($block_manager = \Drupal::getContainer()->get('plugin.manager.block', Container::NULL_ON_INVALID_REFERENCE)) { + $block_manager->clearCachedDefinitions(); + } + } } diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Core/Entity/Item.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Core/Entity/Item.php index 51f150a5884..b5277525f41 100644 --- a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Core/Entity/Item.php +++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Core/Entity/Item.php @@ -8,6 +8,7 @@ namespace Drupal\aggregator\Plugin\Core\Entity; use Drupal\Core\Entity\EntityNG; +use Drupal\Core\Entity\EntityStorageControllerInterface; use Drupal\Core\Entity\Annotation\EntityType; use Drupal\Core\Annotation\Translation; use Drupal\aggregator\ItemInterface; @@ -130,4 +131,25 @@ class Item extends EntityNG implements ItemInterface { public function label($langcode = NULL) { return $this->get('title')->value; } + + /** + * {@inheritdoc} + */ + public function postCreate(EntityStorageControllerInterface $storage_controller) { + $this->timestamp->value = REQUEST_TIME; + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { + $storage_controller->saveCategories($this); + } + + /** + * {@inheritdoc} + */ + public static function preDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + $storage_controller->deleteCategories($entities); + } } diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageController.php index 046b065b5bf..755a91a435b 100644 --- a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageController.php +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageController.php @@ -8,41 +8,15 @@ namespace Drupal\custom_block; use Drupal\Core\Entity\DatabaseStorageControllerNG; -use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityStorageControllerInterface; /** * Controller class for custom blocks. * - * This extends the Drupal\Core\Entity\DatabaseStorageController class, adding - * required special handling for custom block entities. + * This extends the Drupal\Core\Entity\DatabaseStorageControllerNG class, + * adding required special handling for custom block entities. */ -class CustomBlockStorageController extends DatabaseStorageControllerNG { - - /** - * Overrides \Drupal\Core\Entity\DatabaseStorageController::preSaveRevision(). - */ - protected function preSaveRevision(\stdClass $record, EntityInterface $entity) { - if ($entity->isNewRevision()) { - // When inserting either a new custom block or a new custom_block - // revision, $entity->log must be set because {block_custom_revision}.log - // is a text column and therefore cannot have a default value. However, - // it might not be set at this point (for example, if the user submitting - // the form does not have permission to create revisions), so we ensure - // that it is at least an empty string in that case. - // @todo: Make the {block_custom_revision}.log column nullable so that we - // can remove this check. - if (!isset($record->log)) { - $record->log = ''; - } - } - elseif (isset($entity->original) && (!isset($record->log) || $record->log === '')) { - // If we are updating an existing custom_block without adding a new - // revision, we need to make sure $entity->log is reset whenever it is - // empty. Therefore, this code allows us to avoid clobbering an existing - // log entry with an empty one. - $record->log = $entity->original->log->value; - } - } +class CustomBlockStorageController extends DatabaseStorageControllerNG implements EntityStorageControllerInterface { /** * Overrides \Drupal\Core\Entity\DatabaseStorageController::attachLoad(). @@ -62,14 +36,6 @@ class CustomBlockStorageController extends DatabaseStorageControllerNG { parent::attachLoad($blocks, $load_revision); } - /** - * Overrides \Drupal\Core\Entity\DatabaseStorageController::postSave(). - */ - protected function postSave(EntityInterface $block, $update) { - // Invalidate the block cache to update custom block-based derivatives. - drupal_container()->get('plugin.manager.block')->clearCachedDefinitions(); - } - /** * Implements \Drupal\Core\Entity\DataBaseStorageControllerNG::basePropertyDefinitions(). */ diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeStorageController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeStorageController.php deleted file mode 100644 index 17f7b4d1d9d..00000000000 --- a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeStorageController.php +++ /dev/null @@ -1,44 +0,0 @@ -id()); - custom_block_add_body_field($entity->id()); - } - elseif ($entity->original->id() != $entity->id()) { - entity_invoke_bundle_hook('rename', 'custom_block', $entity->original->id(), $entity->id()); - } - } - - /** - * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::postDelete(). - */ - protected function postDelete($entities) { - parent::postDelete($entities); - - foreach ($entities as $entity) { - entity_invoke_bundle_hook('delete', 'custom_block', $entity->id()); - } - } - -} diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlock.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlock.php index f799e75913c..74dfc25d049 100644 --- a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlock.php +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlock.php @@ -8,6 +8,7 @@ namespace Drupal\custom_block\Plugin\Core\Entity; use Drupal\Core\Entity\EntityNG; +use Drupal\Core\Entity\EntityStorageControllerInterface; use Drupal\Core\Entity\Annotation\EntityType; use Drupal\Core\Annotation\Translation; use Drupal\custom_block\CustomBlockInterface; @@ -181,6 +182,14 @@ class CustomBlock extends EntityNG implements CustomBlockInterface { ); } + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { + // Invalidate the block cache to update custom block-based derivatives. + \Drupal::service('plugin.manager.block')->clearCachedDefinitions(); + } + /** * {@inheritdoc} */ @@ -188,6 +197,30 @@ class CustomBlock extends EntityNG implements CustomBlockInterface { return entity_load_multiple_by_properties('block', array('plugin' => 'custom_block:' . $this->uuid->value)); } + /** + * {@inheritdoc} + */ + public function preSaveRevision(EntityStorageControllerInterface $storage_controller, \stdClass $record) { + if ($this->isNewRevision()) { + // When inserting either a new custom block or a new custom_block + // revision, $entity->log must be set because {block_custom_revision}.log + // is a text column and therefore cannot have a default value. However, + // it might not be set at this point (for example, if the user submitting + // the form does not have permission to create revisions), so we ensure + // that it is at least an empty string in that case. + // @todo: Make the {block_custom_revision}.log column nullable so that we + // can remove this check. + if (!isset($record->log)) { + $record->log = ''; + } + } + elseif (isset($this->original) && (!isset($record->log) || $record->log === '')) { + // If we are updating an existing custom_block without adding a new + // revision and the user did not supply a log, keep the existing one. + $record->log = $this->original->log->value; + } + } + /** * {@inheritdoc} */ diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlockType.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlockType.php index e4d3ab2d767..6f90e568928 100644 --- a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlockType.php +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlockType.php @@ -10,6 +10,7 @@ namespace Drupal\custom_block\Plugin\Core\Entity; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\Annotation\EntityType; use Drupal\Core\Annotation\Translation; +use Drupal\Core\Entity\EntityStorageControllerInterface; use Drupal\custom_block\CustomBlockTypeInterface; /** @@ -20,7 +21,7 @@ use Drupal\custom_block\CustomBlockTypeInterface; * label = @Translation("Custom block type"), * module = "custom_block", * controllers = { - * "storage" = "Drupal\custom_block\CustomBlockTypeStorageController", + * "storage" = "Drupal\Core\Config\Entity\ConfigStorageController", * "form" = { * "default" = "Drupal\custom_block\CustomBlockTypeFormController" * }, @@ -83,4 +84,26 @@ class CustomBlockType extends ConfigEntityBase implements CustomBlockTypeInterfa ) ); } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { + if (!$update) { + entity_invoke_bundle_hook('create', 'custom_block', $this->id()); + custom_block_add_body_field($this->id); + } + elseif ($this->originalID != $this->id) { + entity_invoke_bundle_hook('rename', 'custom_block', $this->originalID, $this->id); + } + } + + /** + * {@inheritdoc} + */ + public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + foreach ($entities as $entity) { + entity_invoke_bundle_hook('delete', 'custom_block', $entity->id()); + } + } } diff --git a/core/modules/block/lib/Drupal/block/BlockStorageController.php b/core/modules/block/lib/Drupal/block/BlockStorageController.php index d6c547db679..747aa8743ce 100644 --- a/core/modules/block/lib/Drupal/block/BlockStorageController.php +++ b/core/modules/block/lib/Drupal/block/BlockStorageController.php @@ -26,13 +26,4 @@ class BlockStorageController extends ConfigStorageController { }); } - /** - * {@inheritdoc} - */ - protected function preSave(EntityInterface $entity) { - parent::preSave($entity); - - $entity->set('settings', $entity->getPlugin()->getConfig()); - } - } diff --git a/core/modules/block/lib/Drupal/block/Plugin/Core/Entity/Block.php b/core/modules/block/lib/Drupal/block/Plugin/Core/Entity/Block.php index 3c8b14c171e..eb9b8a7d82f 100644 --- a/core/modules/block/lib/Drupal/block/Plugin/Core/Entity/Block.php +++ b/core/modules/block/lib/Drupal/block/Plugin/Core/Entity/Block.php @@ -12,6 +12,7 @@ use Drupal\Core\Entity\Annotation\EntityType; use Drupal\Core\Annotation\Translation; use Drupal\block\BlockPluginBag; use Drupal\block\BlockInterface; +use Drupal\Core\Entity\EntityStorageControllerInterface; /** * Defines a Block configuration entity class. @@ -163,4 +164,11 @@ class Block extends ConfigEntityBase implements BlockInterface { return $properties; } + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageControllerInterface $storage_controller) { + $this->set('settings', $this->getPlugin()->getConfig()); + } + } diff --git a/core/modules/comment/lib/Drupal/comment/CommentStorageController.php b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php index 3231d4ba8f1..435a9607a88 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentStorageController.php +++ b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php @@ -18,14 +18,15 @@ use LogicException; * This extends the Drupal\Core\Entity\DatabaseStorageController class, adding * required special handling for comment entities. */ -class CommentStorageController extends DatabaseStorageControllerNG { +class CommentStorageController extends DatabaseStorageControllerNG implements CommentStorageControllerInterface { + /** * The thread for which a lock was acquired. */ protected $threadLock = ''; /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::buildQuery(). + * {@inheritdoc} */ protected function buildQuery($ids, $revision_id = FALSE) { $query = parent::buildQuery($ids, $revision_id); @@ -39,7 +40,7 @@ class CommentStorageController extends DatabaseStorageControllerNG { } /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::attachLoad(). + * {@inheritdoc} */ protected function attachLoad(&$records, $load_revision = FALSE) { // Prepare standard comment fields. @@ -52,156 +53,9 @@ class CommentStorageController extends DatabaseStorageControllerNG { } /** - * Overrides Drupal\Core\Entity\DatabaseStorageControllerNG::create(). + * {@inheritdoc} */ - public function create(array $values) { - if (empty($values['node_type']) && !empty($values['nid'])) { - $node = node_load(is_object($values['nid']) ? $values['nid']->value : $values['nid']); - $values['node_type'] = 'comment_node_' . $node->type; - } - return parent::create($values); - } - - /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::preSave(). - * - * @see comment_int_to_alphadecimal() - * @see comment_alphadecimal_to_int() - */ - protected function preSave(EntityInterface $comment) { - global $user; - - if (!isset($comment->status->value)) { - $comment->status->value = user_access('skip comment approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED; - } - // Make sure we have a proper bundle name. - if (!isset($comment->node_type->value)) { - $comment->node_type->value = 'comment_node_' . $comment->nid->entity->type; - } - if ($comment->isNew()) { - // Add the comment to database. This next section builds the thread field. - // Also see the documentation for comment_view(). - if (!empty($comment->thread->value)) { - // Allow calling code to set thread itself. - $thread = $comment->thread->value; - } - else { - if ($this->threadLock) { - // As preSave() is protected, this can only happen when this class - // is extended in a faulty manner. - throw new LogicException('preSave is called again without calling postSave() or releaseThreadLock()'); - } - if ($comment->pid->target_id == 0) { - // This is a comment with no parent comment (depth 0): we start - // by retrieving the maximum thread level. - $max = db_query('SELECT MAX(thread) FROM {comment} WHERE nid = :nid', array(':nid' => $comment->nid->target_id))->fetchField(); - // Strip the "/" from the end of the thread. - $max = rtrim($max, '/'); - // We need to get the value at the correct depth. - $parts = explode('.', $max); - $n = comment_alphadecimal_to_int($parts[0]); - $prefix = ''; - } - else { - // This is a comment with a parent comment, so increase the part of - // the thread value at the proper depth. - - // Get the parent comment: - $parent = $comment->pid->entity; - // Strip the "/" from the end of the parent thread. - $parent->thread->value = (string) rtrim((string) $parent->thread->value, '/'); - $prefix = $parent->thread->value . '.'; - // Get the max value in *this* thread. - $max = db_query("SELECT MAX(thread) FROM {comment} WHERE thread LIKE :thread AND nid = :nid", array( - ':thread' => $parent->thread->value . '.%', - ':nid' => $comment->nid->target_id, - ))->fetchField(); - - if ($max == '') { - // First child of this parent. As the other two cases do an - // increment of the thread number before creating the thread - // string set this to -1 so it requires an increment too. - $n = -1; - } - else { - // Strip the "/" at the end of the thread. - $max = rtrim($max, '/'); - // Get the value at the correct depth. - $parts = explode('.', $max); - $parent_depth = count(explode('.', $parent->thread->value)); - $n = comment_alphadecimal_to_int($parts[$parent_depth]); - } - } - // Finally, build the thread field for this new comment. To avoid - // race conditions, get a lock on the thread. If aother process already - // has the lock, just move to the next integer. - do { - $thread = $prefix . comment_int_to_alphadecimal(++$n) . '/'; - } while (!lock()->acquire("comment:{$comment->nid->target_id}:$thread")); - $this->threadLock = $thread; - } - if (empty($comment->created->value)) { - $comment->created->value = REQUEST_TIME; - } - if (empty($comment->changed->value)) { - $comment->changed->value = $comment->created->value; - } - // We test the value with '===' because we need to modify anonymous - // users as well. - if ($comment->uid->target_id === $user->uid && $user->uid) { - $comment->name->value = $user->name; - } - // Add the values which aren't passed into the function. - $comment->thread->value = $thread; - $comment->hostname->value = \Drupal::request()->getClientIP(); - } - } - - /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::postSave(). - */ - protected function postSave(EntityInterface $comment, $update) { - $this->releaseThreadLock(); - // Update the {node_comment_statistics} table prior to executing the hook. - $this->updateNodeStatistics($comment->nid->target_id); - if ($comment->status->value == COMMENT_PUBLISHED) { - module_invoke_all('comment_publish', $comment); - } - } - - /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::postDelete(). - */ - protected function postDelete($comments) { - // Delete the comments' replies. - $query = db_select('comment', 'c') - ->fields('c', array('cid')) - ->condition('pid', array(array_keys($comments)), 'IN'); - $child_cids = $query->execute()->fetchCol(); - entity_delete_multiple('comment', $child_cids); - - foreach ($comments as $comment) { - $this->updateNodeStatistics($comment->nid->target_id); - } - } - - /** - * Updates the comment statistics for a given node. - * - * The {node_comment_statistics} table has the following fields: - * - last_comment_timestamp: The timestamp of the last comment for this node, - * or the node created timestamp if no comments exist for the node. - * - last_comment_name: The name of the anonymous poster for the last comment. - * - last_comment_uid: The user ID of the poster for the last comment for - * this node, or the node author's user ID if no comments exist for the - * node. - * - comment_count: The total number of approved/published comments on this - * node. - * - * @param $nid - * The node ID. - */ - protected function updateNodeStatistics($nid) { + public function updateNodeStatistics($nid) { // Allow bulk updates and inserts to temporarily disable the // maintenance of the {node_comment_statistics} table. if (!variable_get('comment_maintain_node_statistics', TRUE)) { @@ -247,17 +101,7 @@ class CommentStorageController extends DatabaseStorageControllerNG { } /** - * Release the lock acquired for the thread in preSave(). - */ - protected function releaseThreadLock() { - if ($this->threadLock) { - lock()->release($this->threadLock); - $this->threadLock = ''; - } - } - - /** - * Implements \Drupal\Core\Entity\DataBaseStorageControllerNG::basePropertyDefinitions(). + * {@inheritdoc} */ public function baseFieldDefinitions() { $properties['cid'] = array( @@ -356,4 +200,32 @@ class CommentStorageController extends DatabaseStorageControllerNG { ); return $properties; } + + /** + * {@inheritdoc} + */ + public function getMaxThread(EntityInterface $comment) { + return db_query('SELECT MAX(thread) FROM {comment} WHERE nid = :nid', array(':nid' => $comment->nid->target_id))->fetchField(); + } + + /** + * {@inheritdoc} + */ + public function getMaxThreadPerThread(EntityInterface $comment) { + return $this->database->query("SELECT MAX(thread) FROM {comment} WHERE thread LIKE :thread AND nid = :nid", array( + ':thread' => rtrim($comment->pid->entity->thread->value, '/') . '.%', + ':nid' => $comment->nid->target_id, + ))->fetchField(); + } + + /** + * {@inheritdoc} + */ + public function getChildCids(array $comments) { + return $this->database->select('comment', 'c') + ->fields('c', array('cid')) + ->condition('pid', array_keys($comments)) + ->execute() + ->fetchCol(); + } } diff --git a/core/modules/comment/lib/Drupal/comment/CommentStorageControllerInterface.php b/core/modules/comment/lib/Drupal/comment/CommentStorageControllerInterface.php new file mode 100644 index 00000000000..00a6edbb439 --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/CommentStorageControllerInterface.php @@ -0,0 +1,69 @@ +get('cid')->value; } + /** + * {@inheritdoc} + */ + public static function preCreate(EntityStorageControllerInterface $storage_controller, array &$values) { + if (empty($values['node_type']) && !empty($values['nid'])) { + $node = node_load(is_object($values['nid']) ? $values['nid']->value : $values['nid']); + $values['node_type'] = 'comment_node_' . $node->type; + } + } + + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageControllerInterface $storage_controller) { + global $user; + + if (!isset($this->status->value)) { + $this->status->value = user_access('skip comment approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED; + } + // Make sure we have a proper bundle name. + if (!isset($this->node_type->value)) { + $this->node_type->value = 'comment_node_' . $this->nid->entity->type; + } + if ($this->isNew()) { + // Add the comment to database. This next section builds the thread field. + // Also see the documentation for comment_view(). + if (!empty($this->thread->value)) { + // Allow calling code to set thread itself. + $thread = $this->thread->value; + } + else { + if ($this->threadLock) { + // As preSave() is protected, this can only happen when this class + // is extended in a faulty manner. + throw new \LogicException('preSave is called again without calling postSave() or releaseThreadLock()'); + } + if ($this->pid->target_id == 0) { + // This is a comment with no parent comment (depth 0): we start + // by retrieving the maximum thread level. + $max = $storage_controller->getMaxThread($this); + // Strip the "/" from the end of the thread. + $max = rtrim($max, '/'); + // We need to get the value at the correct depth. + $parts = explode('.', $max); + $n = comment_alphadecimal_to_int($parts[0]); + $prefix = ''; + } + else { + // This is a comment with a parent comment, so increase the part of + // the thread value at the proper depth. + + // Get the parent comment: + $parent = $this->pid->entity; + // Strip the "/" from the end of the parent thread. + $parent->thread->value = (string) rtrim((string) $parent->thread->value, '/'); + $prefix = $parent->thread->value . '.'; + // Get the max value in *this* thread. + $max = $storage_controller->getMaxThreadPerThread($this); + + if ($max == '') { + // First child of this parent. As the other two cases do an + // increment of the thread number before creating the thread + // string set this to -1 so it requires an increment too. + $n = -1; + } + else { + // Strip the "/" at the end of the thread. + $max = rtrim($max, '/'); + // Get the value at the correct depth. + $parts = explode('.', $max); + $parent_depth = count(explode('.', $parent->thread->value)); + $n = comment_alphadecimal_to_int($parts[$parent_depth]); + } + } + // Finally, build the thread field for this new comment. To avoid + // race conditions, get a lock on the thread. If aother process already + // has the lock, just move to the next integer. + do { + $thread = $prefix . comment_int_to_alphadecimal(++$n) . '/'; + } while (!lock()->acquire("comment:{$this->nid->target_id}:$thread")); + $this->threadLock = $thread; + } + if (empty($this->created->value)) { + $this->created->value = REQUEST_TIME; + } + if (empty($this->changed->value)) { + $this->changed->value = $this->created->value; + } + // We test the value with '===' because we need to modify anonymous + // users as well. + if ($this->uid->target_id === $user->uid && $user->uid) { + $this->name->value = $user->name; + } + // Add the values which aren't passed into the function. + $this->thread->value = $thread; + $this->hostname->value = \Drupal::request()->getClientIP(); + } + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { + $this->releaseThreadLock(); + // Update the {node_comment_statistics} table prior to executing the hook. + $storage_controller->updateNodeStatistics($this->nid->target_id); + if ($this->status->value == COMMENT_PUBLISHED) { + module_invoke_all('comment_publish', $this); + } + } + + /** + * Release the lock acquired for the thread in preSave(). + */ + protected function releaseThreadLock() { + if ($this->threadLock) { + lock()->release($this->threadLock); + $this->threadLock = ''; + } + } + + /** + * {@inheritdoc} + */ + public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + $child_cids = $storage_controller->getChildCids($entities); + entity_delete_multiple('comment', $child_cids); + + foreach ($entities as $id => $entity) { + $storage_controller->updateNodeStatistics($entity->nid->target_id); + } + } + /** * {@inheritdoc} */ diff --git a/core/modules/contact/lib/Drupal/contact/CategoryStorageController.php b/core/modules/contact/lib/Drupal/contact/CategoryStorageController.php index 93506130fcd..90b74db37a9 100644 --- a/core/modules/contact/lib/Drupal/contact/CategoryStorageController.php +++ b/core/modules/contact/lib/Drupal/contact/CategoryStorageController.php @@ -8,36 +8,10 @@ namespace Drupal\contact; use Drupal\Core\Config\Entity\ConfigStorageController; -use Drupal\Core\Entity\EntityInterface; /** * Controller class for contact categories. */ class CategoryStorageController extends ConfigStorageController { - /** - * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::postSave(). - */ - protected function postSave(EntityInterface $entity, $update) { - parent::postSave($entity, $update); - - if (!$update) { - entity_invoke_bundle_hook('create', 'contact_message', $entity->id()); - } - elseif ($entity->original->id() != $entity->id()) { - entity_invoke_bundle_hook('rename', 'contact_message', $entity->original->id(), $entity->id()); - } - } - - /** - * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::postDelete(). - */ - protected function postDelete($entities) { - parent::postDelete($entities); - - foreach ($entities as $entity) { - entity_invoke_bundle_hook('delete', 'contact_message', $entity->id()); - } - } - } diff --git a/core/modules/contact/lib/Drupal/contact/Plugin/Core/Entity/Category.php b/core/modules/contact/lib/Drupal/contact/Plugin/Core/Entity/Category.php index 12a9fc058f1..f1aca0d870a 100644 --- a/core/modules/contact/lib/Drupal/contact/Plugin/Core/Entity/Category.php +++ b/core/modules/contact/lib/Drupal/contact/Plugin/Core/Entity/Category.php @@ -8,6 +8,7 @@ namespace Drupal\contact\Plugin\Core\Entity; use Drupal\Core\Config\Entity\ConfigEntityBase; +use Drupal\Core\Entity\EntityStorageControllerInterface; use Drupal\Core\Entity\Annotation\EntityType; use Drupal\Core\Annotation\Translation; use Drupal\contact\CategoryInterface; @@ -81,4 +82,25 @@ class Category extends ConfigEntityBase implements CategoryInterface { */ public $weight = 0; + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { + if (!$update) { + entity_invoke_bundle_hook('create', 'contact_message', $this->id()); + } + elseif ($this->original->id() != $this->id()) { + entity_invoke_bundle_hook('rename', 'contact_message', $this->original->id(), $this->id()); + } + } + + /** + * {@inheritdoc} + */ + public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + foreach ($entities as $entity) { + entity_invoke_bundle_hook('delete', 'contact_message', $entity->id()); + } + } + } diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Core/Entity/BundleKeyTestEntity.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Core/Entity/BundleKeyTestEntity.php index 7f524d664f0..d0e715a532a 100644 --- a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Core/Entity/BundleKeyTestEntity.php +++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Core/Entity/BundleKeyTestEntity.php @@ -18,7 +18,7 @@ use Drupal\Core\Annotation\Translation; * label = @Translation("Test Entity with a bundle key"), * module = "field_test", * controllers = { - * "storage" = "Drupal\field_test\TestEntityController", + * "storage" = "Drupal\Core\Entity\DatabaseStorageController", * "form" = { * "default" = "Drupal\field_test\TestEntityFormController" * } diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Core/Entity/BundleTestEntity.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Core/Entity/BundleTestEntity.php index 7875bc49d10..5ad577724c0 100644 --- a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Core/Entity/BundleTestEntity.php +++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Core/Entity/BundleTestEntity.php @@ -18,7 +18,7 @@ use Drupal\Core\Annotation\Translation; * label = @Translation("Test Entity with a specified bundle"), * module = "field_test", * controllers = { - * "storage" = "Drupal\field_test\TestEntityController", + * "storage" = "Drupal\Core\Entity\DatabaseStorageController", * "form" = { * "default" = "Drupal\field_test\TestEntityFormController" * } diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Core/Entity/CacheableTestEntity.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Core/Entity/CacheableTestEntity.php index 976690ae981..351d7c0d22d 100644 --- a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Core/Entity/CacheableTestEntity.php +++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Core/Entity/CacheableTestEntity.php @@ -18,7 +18,7 @@ use Drupal\Core\Annotation\Translation; * label = @Translation("Test Entity, cacheable"), * module = "field_test", * controllers = { - * "storage" = "Drupal\field_test\TestEntityController" + * "storage" = "Drupal\Core\Entity\DatabaseStorageController" * }, * field_cache = TRUE, * base_table = "test_entity", diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Core/Entity/TestEntity.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Core/Entity/TestEntity.php index 112ba6cbf98..1d8d9d0222a 100644 --- a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Core/Entity/TestEntity.php +++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Core/Entity/TestEntity.php @@ -10,6 +10,7 @@ namespace Drupal\field_test\Plugin\Core\Entity; use Drupal\Core\Entity\Entity; use Drupal\Core\Entity\Annotation\EntityType; use Drupal\Core\Annotation\Translation; +use Drupal\Core\Entity\EntityStorageControllerInterface; /** * Test entity class. @@ -19,7 +20,7 @@ use Drupal\Core\Annotation\Translation; * label = @Translation("Test Entity"), * module = "field_test", * controllers = { - * "storage" = "Drupal\field_test\TestEntityController", + * "storage" = "Drupal\Core\Entity\DatabaseStorageController", * "render" = "Drupal\Core\Entity\EntityRenderController", * "form" = { * "default" = "Drupal\field_test\TestEntityFormController" @@ -86,5 +87,16 @@ class TestEntity extends Entity { public function bundle() { return !empty($this->fttype) ? $this->fttype : $this->entityType(); } + + /** + * {@inheritdoc} + */ + public function preSaveRevision(EntityStorageControllerInterface $storage_controller, \stdClass $record) { + // Allow for predefined revision ids. + if (!empty($record->use_provided_revision_id)) { + $record->ftvid = $record->use_provided_revision_id; + } + } + } diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityController.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityController.php deleted file mode 100644 index 0fe760226b6..00000000000 --- a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityController.php +++ /dev/null @@ -1,28 +0,0 @@ -use_provided_revision_id)) { - $record->ftvid = $record->use_provided_revision_id; - } - } - -} diff --git a/core/modules/file/lib/Drupal/file/FileStorageController.php b/core/modules/file/lib/Drupal/file/FileStorageController.php index dd65e6f6be4..10ba8d37bdc 100644 --- a/core/modules/file/lib/Drupal/file/FileStorageController.php +++ b/core/modules/file/lib/Drupal/file/FileStorageController.php @@ -8,76 +8,17 @@ namespace Drupal\file; use Drupal\Core\Entity\DatabaseStorageControllerNG; -use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Language\Language; /** * File storage controller for files. */ -class FileStorageController extends DatabaseStorageControllerNG { +class FileStorageController extends DatabaseStorageControllerNG implements FileStorageControllerInterface { /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::create(). - */ - public function create(array $values) { - // Automatically detect filename if not set. - if (!isset($values['filename']) && isset($values['uri'])) { - $values['filename'] = drupal_basename($values['uri']); - } - - // Automatically detect filemime if not set. - if (!isset($values['filemime']) && isset($values['uri'])) { - $values['filemime'] = file_get_mimetype($values['uri']); - } - return parent::create($values); - } - - /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::presave(). - */ - protected function preSave(EntityInterface $entity) { - $entity->timestamp = REQUEST_TIME; - $entity->setSize(filesize($entity->getFileUri())); - if (!$entity->langcode->value) { - // Default the file's language code to none, because files are language - // neutral more often than language dependent. Until we have better - // flexible settings. - // @todo See http://drupal.org/node/258785 and followups. - $entity->langcode = Language::LANGCODE_NOT_SPECIFIED; - } - } - - /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::preDelete(). - */ - public function preDelete($entities) { - foreach ($entities as $entity) { - // Delete the actual file. Failures due to invalid files and files that - // were already deleted are logged to watchdog but ignored, the - // corresponding file entity will be deleted. - file_unmanaged_delete($entity->getFileUri()); - } - // Delete corresponding file usage entries. - db_delete('file_usage') - ->condition('fid', array_keys($entities), 'IN') - ->execute(); - } - - /** - * Determines total disk space used by a single user or the whole filesystem. - * - * @param int $uid - * Optional. A user id, specifying NULL returns the total space used by all - * non-temporary files. - * @param $status - * Optional. The file status to consider. The default is to only - * consider files in status FILE_STATUS_PERMANENT. - * - * @return int - * An integer containing the number of bytes used. + * {@inheritdoc} */ public function spaceUsed($uid = NULL, $status = FILE_STATUS_PERMANENT) { - $query = db_select($this->entityInfo['base_table'], 'f') + $query = $this->database->select($this->entityInfo['base_table'], 'f') ->condition('f.status', $status); $query->addExpression('SUM(f.filesize)', 'filesize'); if (isset($uid)) { @@ -87,15 +28,12 @@ class FileStorageController extends DatabaseStorageControllerNG { } /** - * Retrieve temporary files that are older than DRUPAL_MAXIMUM_TEMP_FILE_AGE. - * - * @return - * A list of files to be deleted. + * {@inheritdoc} */ public function retrieveTemporaryFiles() { // Use separate placeholders for the status to avoid a bug in some versions // of PHP. See http://drupal.org/node/352956. - return db_query('SELECT fid FROM {' . $this->entityInfo['base_table'] . '} WHERE status <> :permanent AND timestamp < :timestamp', array( + return $this->database->query('SELECT fid FROM {' . $this->entityInfo['base_table'] . '} WHERE status <> :permanent AND timestamp < :timestamp', array( ':permanent' => FILE_STATUS_PERMANENT, ':timestamp' => REQUEST_TIME - DRUPAL_MAXIMUM_TEMP_FILE_AGE )); diff --git a/core/modules/file/lib/Drupal/file/FileStorageControllerInterface.php b/core/modules/file/lib/Drupal/file/FileStorageControllerInterface.php new file mode 100644 index 00000000000..913d172b997 --- /dev/null +++ b/core/modules/file/lib/Drupal/file/FileStorageControllerInterface.php @@ -0,0 +1,40 @@ +get('status')->value = 0; } + /** + * {@inheritdoc} + */ + public static function preCreate(EntityStorageControllerInterface $storage_controller, array &$values) { + // Automatically detect filename if not set. + if (!isset($values['filename']) && isset($values['uri'])) { + $values['filename'] = drupal_basename($values['uri']); + } + + // Automatically detect filemime if not set. + if (!isset($values['filemime']) && isset($values['uri'])) { + $values['filemime'] = file_get_mimetype($values['uri']); + } + } + + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageControllerInterface $storage_controller) { + $this->timestamp = REQUEST_TIME; + $this->setSize(filesize($this->getFileUri())); + if (!isset($this->langcode->value)) { + // Default the file's language code to none, because files are language + // neutral more often than language dependent. Until we have better + // flexible settings. + // @todo See http://drupal.org/node/258785 and followups. + $this->langcode = Language::LANGCODE_NOT_SPECIFIED; + } + } + + /** + * {@inheritdoc} + */ + public static function preDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + foreach ($entities as $entity) { + // Delete all remaining references to this file. + $file_usage = file_usage()->listUsage($entity); + if (!empty($file_usage)) { + foreach ($file_usage as $module => $usage) { + file_usage()->delete($entity, $module); + } + } + // Delete the actual file. Failures due to invalid files and files that + // were already deleted are logged to watchdog but ignored, the + // corresponding file entity will be deleted. + file_unmanaged_delete($entity->getFileUri()); + } + } + } diff --git a/core/modules/filter/lib/Drupal/filter/FilterFormatStorageController.php b/core/modules/filter/lib/Drupal/filter/FilterFormatStorageController.php deleted file mode 100644 index 4c19d8b1324..00000000000 --- a/core/modules/filter/lib/Drupal/filter/FilterFormatStorageController.php +++ /dev/null @@ -1,69 +0,0 @@ -name = trim($entity->label()); - - // @todo Do not save disabled filters whose properties are identical to - // all default properties. - - // Determine whether the format can be cached. - // @todo This is a derived/computed definition, not configuration. - $entity->cache = TRUE; - foreach ($entity->filters() as $filter) { - if ($filter->status && !$filter->cache) { - $entity->cache = FALSE; - } - } - } - - /** - * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::postSave(). - */ - protected function postSave(EntityInterface $entity, $update) { - parent::postSave($entity, $update); - - // Clear the static caches of filter_formats() and others. - filter_formats_reset(); - - if ($update) { - // Clear the filter cache whenever a text format is updated. - cache('filter')->deleteTags(array('filter_format' => $entity->id())); - } - else { - // Default configuration of modules and installation profiles is allowed - // to specify a list of user roles to grant access to for the new format; - // apply the defined user role permissions when a new format is inserted - // and has a non-empty $roles property. - // Note: user_role_change_permissions() triggers a call chain back into - // filter_permission() and lastly filter_formats(), so its cache must be - // reset upfront. - if (($roles = $entity->get('roles')) && $permission = filter_permission_name($entity)) { - foreach (user_roles() as $rid => $name) { - $enabled = in_array($rid, $roles, TRUE); - user_role_change_permissions($rid, array($permission => $enabled)); - } - } - } - } - -} diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/Core/Entity/FilterFormat.php b/core/modules/filter/lib/Drupal/filter/Plugin/Core/Entity/FilterFormat.php index d6b1fe64a01..09b4df999c1 100644 --- a/core/modules/filter/lib/Drupal/filter/Plugin/Core/Entity/FilterFormat.php +++ b/core/modules/filter/lib/Drupal/filter/Plugin/Core/Entity/FilterFormat.php @@ -10,6 +10,7 @@ namespace Drupal\filter\Plugin\Core\Entity; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\Annotation\EntityType; use Drupal\Core\Annotation\Translation; +use Drupal\Core\Entity\EntityStorageControllerInterface; use Drupal\filter\FilterFormatInterface; use Drupal\filter\FilterBag; @@ -21,7 +22,7 @@ use Drupal\filter\FilterBag; * label = @Translation("Text format"), * module = "filter", * controllers = { - * "storage" = "Drupal\filter\FilterFormatStorageController" + * "storage" = "Drupal\Core\Config\Entity\ConfigStorageController" * }, * config_prefix = "filter.format", * entity_keys = { @@ -179,4 +180,50 @@ class FilterFormat extends ConfigEntityBase implements FilterFormatInterface { return $this; } + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageControllerInterface $storage_controller) { + $this->name = trim($this->label()); + + // @todo Do not save disabled filters whose properties are identical to + // all default properties. + + // Determine whether the format can be cached. + // @todo This is a derived/computed definition, not configuration. + $this->cache = TRUE; + foreach ($this->filters() as $filter) { + if ($filter->status && !$filter->cache) { + $this->cache = FALSE; + } + } + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { + // Clear the static caches of filter_formats() and others. + filter_formats_reset(); + + if ($update) { + // Clear the filter cache whenever a text format is updated. + cache('filter')->deleteTags(array('filter_format' => $this->id())); + } + else { + // Default configuration of modules and installation profiles is allowed + // to specify a list of user roles to grant access to for the new format; + // apply the defined user role permissions when a new format is inserted + // and has a non-empty $roles property. + // Note: user_role_change_permissions() triggers a call chain back into + // filter_permission() and lastly filter_formats(), so its cache must be + // reset upfront. + if (($roles = $this->get('roles')) && $permission = filter_permission_name($this)) { + foreach (user_roles() as $rid => $name) { + $enabled = in_array($rid, $roles, TRUE); + user_role_change_permissions($rid, array($permission => $enabled)); + } + } + } + } } diff --git a/core/modules/image/lib/Drupal/image/ImageStyleStorageController.php b/core/modules/image/lib/Drupal/image/ImageStyleStorageController.php index 97766b520b2..3f35f5b4e42 100644 --- a/core/modules/image/lib/Drupal/image/ImageStyleStorageController.php +++ b/core/modules/image/lib/Drupal/image/ImageStyleStorageController.php @@ -9,8 +9,6 @@ namespace Drupal\image; use Drupal\Core\Config\Entity\ConfigStorageController; use Drupal\Core\Config\Config; -use Drupal\Core\Entity\EntityInterface; -use Drupal\image\Plugin\Core\Entity\ImageStyle; /** * Defines a controller class for image styles. @@ -35,73 +33,4 @@ class ImageStyleStorageController extends ConfigStorageController { parent::attachLoad($queried_entities, $revision_id); } - /** - * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::postSave(). - */ - protected function postSave(EntityInterface $entity, $update) { - if ($update && !empty($entity->original) && $entity->{$this->idKey} !== $entity->original->{$this->idKey}) { - // The old image style name needs flushing after a rename. - image_style_flush($entity->original); - // Update field instance settings if necessary. - $this->replaceImageStyle($entity); - } - } - - /** - * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::postDelete(). - */ - protected function postDelete($entities) { - foreach ($entities as $style) { - // Flush cached media for the deleted style. - image_style_flush($style); - // Check whether field instance settings need to be updated. - // In case no replacement style was specified, all image fields that are - // using the deleted style are left in a broken state. - if ($new_id = $style->get('replacementID')) { - // The deleted ID is still set as originalID. - $style->set('name', $new_id); - $this->replaceImageStyle($style); - } - } - } - - /** - * Update field instance settings if the image style name is changed. - * - * @param ImageStyle $style - * The image style. - */ - protected function replaceImageStyle(ImageStyle $style) { - if ($style->id() != $style->getOriginalID()) { - $instances = field_read_instances(); - // Loop through all fields searching for image fields. - foreach ($instances as $instance) { - if ($instance->getField()->type == 'image') { - $view_modes = entity_get_view_modes($instance['entity_type']); - $view_modes = array('default') + array_keys($view_modes); - foreach ($view_modes as $view_mode) { - $display = entity_get_display($instance['entity_type'], $instance['bundle'], $view_mode); - $display_options = $display->getComponent($instance['field_name']); - - // Check if the formatter involves an image style. - if ($display_options && $display_options['type'] == 'image' && $display_options['settings']['image_style'] == $style->getOriginalID()) { - // Update display information for any instance using the image - // style that was just deleted. - $display_options['settings']['image_style'] = $style->id(); - $display->setComponent($instance['field_name'], $display_options) - ->save(); - } - } - $entity_form_display = entity_get_form_display($instance['entity_type'], $instance['bundle'], 'default'); - $widget_configuration = $entity_form_display->getComponent($instance['field_name']); - if ($widget_configuration['settings']['preview_image_style'] == $style->getOriginalID()) { - $widget_options['settings']['preview_image_style'] = $style->id(); - $entity_form_display->setComponent($instance['field_name'], $widget_options) - ->save(); - } - } - } - } - } - } diff --git a/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php b/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php index 60f020e6d25..1145c1b0540 100644 --- a/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php +++ b/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php @@ -10,6 +10,7 @@ namespace Drupal\image\Plugin\Core\Entity; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\Annotation\EntityType; use Drupal\Core\Annotation\Translation; +use Drupal\Core\Entity\EntityStorageControllerInterface; use Drupal\image\ImageStyleInterface; /** @@ -68,4 +69,73 @@ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface { return $this->name; } + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { + if ($update && !empty($this->original) && $this->id() !== $this->original->id()) { + // The old image style name needs flushing after a rename. + image_style_flush($this->original); + // Update field instance settings if necessary. + static::replaceImageStyle($this); + } + } + + /** + * {@inheritdoc} + */ + public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + foreach ($entities as $style) { + // Flush cached media for the deleted style. + image_style_flush($style); + // Check whether field instance settings need to be updated. + // In case no replacement style was specified, all image fields that are + // using the deleted style are left in a broken state. + if ($new_id = $style->get('replacementID')) { + // The deleted ID is still set as originalID. + $style->set('name', $new_id); + static::replaceImageStyle($style); + } + } + } + + /** + * Update field instance settings if the image style name is changed. + * + * @param \Drupal\image\Plugin\Core\Entity\ImageStyle $style + * The image style. + */ + protected static function replaceImageStyle(ImageStyle $style) { + if ($style->id() != $style->getOriginalID()) { + $instances = field_read_instances(); + // Loop through all fields searching for image fields. + foreach ($instances as $instance) { + if ($instance->getField()->type == 'image') { + $view_modes = entity_get_view_modes($instance['entity_type']); + $view_modes = array('default') + array_keys($view_modes); + foreach ($view_modes as $view_mode) { + $display = entity_get_display($instance['entity_type'], $instance['bundle'], $view_mode); + $display_options = $display->getComponent($instance['field_name']); + + // Check if the formatter involves an image style. + if ($display_options && $display_options['type'] == 'image' && $display_options['settings']['image_style'] == $style->getOriginalID()) { + // Update display information for any instance using the image + // style that was just deleted. + $display_options['settings']['image_style'] = $style->id(); + $display->setComponent($instance['field_name'], $display_options) + ->save(); + } + } + $entity_form_display = entity_get_form_display($instance['entity_type'], $instance['bundle'], 'default'); + $widget_configuration = $entity_form_display->getComponent($instance['field_name']); + if ($widget_configuration['settings']['preview_image_style'] == $style->getOriginalID()) { + $widget_options['settings']['preview_image_style'] = $style->id(); + $entity_form_display->setComponent($instance['field_name'], $widget_options) + ->save(); + } + } + } + } + } + } diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkInterface.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkInterface.php index 3d74bdea1b2..c185dc4e9b7 100644 --- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkInterface.php +++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkInterface.php @@ -9,6 +9,8 @@ namespace Drupal\menu_link; use Symfony\Component\Routing\Route; use Drupal\Core\Entity\ContentEntityInterface; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityStorageControllerInterface; /** * Provides an interface defining a menu link entity. @@ -53,4 +55,44 @@ interface MenuLinkInterface extends ContentEntityInterface { */ public static function buildFromRouterItem(array $item); + /** + * Returns the route_name matching a URL. + * + * @param string $link_path + * The link path to find a route name for. + * + * @return string + * The route name. + */ + public static function findRouteName($link_path); + + /** + * Sets the p1 through p9 properties for a menu link entity being saved. + * + * @param \Drupal\Core\Entity\EntityInterface $parent + * A menu link entity. + */ + public function setParents(EntityInterface $parent); + + /** + * Finds a possible parent for a given menu link entity. + * + * Because the parent of a given link might not exist anymore in the database, + * we apply a set of heuristics to determine a proper parent: + * + * - use the passed parent link if specified and existing. + * - else, use the first existing link down the previous link hierarchy + * - else, for system menu links (derived from hook_menu()), reparent + * based on the path hierarchy. + * + * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage_controller + * Storage controller object. + * @param array $parent_candidates + * An array of menu link entities keyed by mlid. + * + * @return \Drupal\Core\Entity\EntityInterface|false + * A menu link entity structure of the possible parent or FALSE if no valid + * parent has been found. + */ + public function findParent(EntityStorageControllerInterface $storage_controller, array $parent_candidates = array()); } diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php index 229892af752..493b9397dd1 100644 --- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php +++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php @@ -13,7 +13,6 @@ use Drupal\Core\Entity\EntityStorageException; use Drupal\Core\Database\Connection; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Cmf\Component\Routing\RouteProviderInterface; -use Symfony\Component\HttpFoundation\Request; /** * Controller class for menu links. @@ -21,7 +20,7 @@ use Symfony\Component\HttpFoundation\Request; * This extends the Drupal\entity\DatabaseStorageController class, adding * required special handling for menu_link entities. */ -class MenuLinkStorageController extends DatabaseStorageController { +class MenuLinkStorageController extends DatabaseStorageController implements MenuLinkStorageControllerInterface { /** * Indicates whether the delete operation should re-parent children items. @@ -144,6 +143,8 @@ class MenuLinkStorageController extends DatabaseStorageController { * Overrides DatabaseStorageController::save(). */ public function save(EntityInterface $entity) { + $entity_class = $this->entityInfo['class']; + // We return SAVED_UPDATED by default because the logic below might not // update the entity if its values haven't changed, so returning FALSE // would be confusing in that situation. @@ -165,7 +166,7 @@ class MenuLinkStorageController extends DatabaseStorageController { // 'presave' hook first because we want to allow modules to alter the // entity before all the logic from our preSave() method. $this->invokeHook('presave', $entity); - $this->preSave($entity); + $entity->preSave($this); // If every value in $entity->original is the same in the $entity, there // is no reason to run the update queries or clear the caches. We use @@ -178,7 +179,7 @@ class MenuLinkStorageController extends DatabaseStorageController { if ($return) { if (!$entity->isNew()) { $this->resetCache(array($entity->{$this->idKey})); - $this->postSave($entity, TRUE); + $entity->postSave($this, TRUE); $this->invokeHook('update', $entity); } else { @@ -186,7 +187,7 @@ class MenuLinkStorageController extends DatabaseStorageController { $this->resetCache(); $entity->enforceIsNew(FALSE); - $this->postSave($entity, FALSE); + $entity->postSave($this, FALSE); $this->invokeHook('insert', $entity); } } @@ -206,178 +207,21 @@ class MenuLinkStorageController extends DatabaseStorageController { } /** - * Overrides DatabaseStorageController::preSave(). + * {@inheritdoc} */ - protected function preSave(EntityInterface $entity) { - // This is the easiest way to handle the unique internal path '', - // since a path marked as external does not need to match a router path. - $entity->external = (url_is_external($entity->link_path) || $entity->link_path == '') ? 1 : 0; - - // Try to find a parent link. If found, assign it and derive its menu. - $parent_candidates = !empty($entity->parentCandidates) ? $entity->parentCandidates : array(); - $parent = $this->findParent($entity, $parent_candidates); - if ($parent) { - $entity->plid = $parent->id(); - $entity->menu_name = $parent->menu_name; - } - // If no corresponding parent link was found, move the link to the top-level. - else { - $entity->plid = 0; - } - - // Directly fill parents for top-level links. - if ($entity->plid == 0) { - $entity->p1 = $entity->id(); - for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) { - $parent_property = "p$i"; - $entity->$parent_property = 0; - } - $entity->depth = 1; - } - // Otherwise, ensure that this link's depth is not beyond the maximum depth - // and fill parents based on the parent link. - else { - if ($entity->has_children && $entity->original) { - $limit = MENU_MAX_DEPTH - $this->findChildrenRelativeDepth($entity->original) - 1; - } - else { - $limit = MENU_MAX_DEPTH - 1; - } - if ($parent->depth > $limit) { - return FALSE; - } - $entity->depth = $parent->depth + 1; - $this->setParents($entity, $parent); - } - - // Need to check both plid and menu_name, since plid can be 0 in any menu. - if (isset($entity->original) && ($entity->plid != $entity->original->plid || $entity->menu_name != $entity->original->menu_name)) { - $this->moveChildren($entity, $entity->original); - } - // Find the router_path. - if (empty($entity->router_path) || empty($entity->original) || (isset($entity->original) && $entity->original->link_path != $entity->link_path)) { - if ($entity->external) { - $entity->router_path = ''; - } - else { - // Find the router path which will serve this path. - $entity->parts = explode('/', $entity->link_path, MENU_MAX_PARTS); - $entity->router_path = _menu_find_router_path($entity->link_path); - } - } - // Find the route_name. - if (!isset($entity->route_name)) { - $entity->route_name = $this->findRouteName($entity->link_path); - } - } - - /** - * Returns the route_name matching a URL. - * - * @param string $link_path - * The link path to find a route name for. - * - * @return string - * The route name. - */ - protected function findRouteName($link_path) { - // Look up the route_name used for the given path. - $request = Request::create('/' . $link_path); - $request->attributes->set('system_path', $link_path); - try { - // Use router.dynamic instead of router, because router will call the - // legacy router which will call hook_menu() and you will get back to - // this method. - $result = \Drupal::service('router.dynamic')->matchRequest($request); - return isset($result['_route']) ? $result['_route'] : ''; - } - catch (\Exception $e) { - return ''; - } - - } - - /** - * DatabaseStorageController::postSave(). - */ - function postSave(EntityInterface $entity, $update) { - // Check the has_children status of the parent. - $this->updateParentalStatus($entity); - - menu_cache_clear($entity->menu_name); - if (isset($entity->original) && $entity->menu_name != $entity->original->menu_name) { - menu_cache_clear($entity->original->menu_name); - } - - // Now clear the cache. - _menu_clear_page_cache(); - } - - /** - * Sets an internal flag that allows us to prevent the reparenting operations - * executed during deletion. - * - * @param bool $value - */ - public function preventReparenting($value = FALSE) { + public function setPreventReparenting($value = FALSE) { $this->preventReparenting = $value; } /** - * Overrides DatabaseStorageController::preDelete(). + * {@inheritdoc} */ - protected function preDelete($entities) { - // Nothing to do if we don't want to reparent children. - if ($this->preventReparenting) { - return; - } - - foreach ($entities as $entity) { - // Children get re-attached to the item's parent. - if ($entity->has_children) { - $children = $this->loadByProperties(array('plid' => $entity->plid)); - foreach ($children as $child) { - $child->plid = $entity->plid; - $this->save($child); - } - } - } + public function getPreventReparenting() { + return $this->preventReparenting; } /** - * Overrides DatabaseStorageController::postDelete(). - */ - protected function postDelete($entities) { - $affected_menus = array(); - // Update the has_children status of the parent. - foreach ($entities as $entity) { - if (!$this->preventReparenting) { - $this->updateParentalStatus($entity); - } - - // Store all menu names for which we need to clear the cache. - if (!isset($affected_menus[$entity->menu_name])) { - $affected_menus[$entity->menu_name] = $entity->menu_name; - } - } - - foreach ($affected_menus as $menu_name) { - menu_cache_clear($menu_name); - } - _menu_clear_page_cache(); - } - - /** - * Loads updated and customized menu links for specific router paths. - * - * Note that this is a low-level method and it doesn't return fully populated - * menu link entities. (e.g. no fields are attached) - * - * @param array $router_paths - * An array of router paths. - * - * @return array - * An array of menu link objects indexed by their ids. + * {@inheritdoc} */ public function loadUpdatedCustomized(array $router_paths) { $query = parent::buildQuery(NULL); @@ -403,10 +247,7 @@ class MenuLinkStorageController extends DatabaseStorageController { } /** - * Loads system menu link as needed by system_get_module_admin_tasks(). - * - * @return array - * An array of menu link entities indexed by their IDs. + * {@inheritdoc} */ public function loadModuleAdminTasks() { $query = $this->buildQuery(NULL); @@ -422,12 +263,9 @@ class MenuLinkStorageController extends DatabaseStorageController { } /** - * Checks and updates the 'has_children' property for the parent of a link. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * A menu link entity. + * {@inheritdoc} */ - protected function updateParentalStatus(EntityInterface $entity, $exclude = FALSE) { + public function updateParentalStatus(EntityInterface $entity, $exclude = FALSE) { // If plid == 0, there is nothing to update. if ($entity->plid) { // Check if at least one visible child exists in the table. @@ -451,125 +289,7 @@ class MenuLinkStorageController extends DatabaseStorageController { } /** - * Finds a possible parent for a given menu link entity. - * - * Because the parent of a given link might not exist anymore in the database, - * we apply a set of heuristics to determine a proper parent: - * - * - use the passed parent link if specified and existing. - * - else, use the first existing link down the previous link hierarchy - * - else, for system menu links (derived from hook_menu()), reparent - * based on the path hierarchy. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * A menu link entity. - * @param array $parent_candidates - * An array of menu link entities keyed by mlid. - * - * @return \Drupal\Core\Entity\EntityInterface|false - * A menu link entity structure of the possible parent or FALSE if no valid - * parent has been found. - */ - protected function findParent(EntityInterface $entity, array $parent_candidates = array()) { - $parent = FALSE; - - // This item is explicitely top-level, skip the rest of the parenting. - if (isset($entity->plid) && empty($entity->plid)) { - return $parent; - } - - // If we have a parent link ID, try to use that. - $candidates = array(); - if (isset($entity->plid)) { - $candidates[] = $entity->plid; - } - - // Else, if we have a link hierarchy try to find a valid parent in there. - if (!empty($entity->depth) && $entity->depth > 1) { - for ($depth = $entity->depth - 1; $depth >= 1; $depth--) { - $parent_property = "p$depth"; - $candidates[] = $entity->$parent_property; - } - } - - foreach ($candidates as $mlid) { - if (isset($parent_candidates[$mlid])) { - $parent = $parent_candidates[$mlid]; - } - else { - $parent = $this->load(array($mlid)); - $parent = reset($parent); - } - if ($parent) { - return $parent; - } - } - - // If everything else failed, try to derive the parent from the path - // hierarchy. This only makes sense for links derived from menu router - // items (ie. from hook_menu()). - if ($entity->module == 'system') { - // Find the parent - it must be unique. - $parent_path = $entity->link_path; - do { - $parent = FALSE; - $parent_path = substr($parent_path, 0, strrpos($parent_path, '/')); - - $query = \Drupal::entityQuery($this->entityType); - $query - ->condition('mlid', $entity->id(), '<>') - ->condition('module', 'system') - // We always respect the link's 'menu_name'; inheritance for router - // items is ensured in _menu_router_build(). - ->condition('menu_name', $entity->menu_name) - ->condition('link_path', $parent_path); - - $result = $query->execute(); - // Only valid if we get a unique result. - if (count($result) == 1) { - $parent = $this->load($result); - $parent = reset($parent); - } - } while ($parent === FALSE && $parent_path); - } - - return $parent; - } - - /** - * Sets the p1 through p9 properties for a menu link entity being saved. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * A menu link entity. - * @param \Drupal\Core\Entity\EntityInterface $parent - * A menu link entity. - */ - protected function setParents(EntityInterface $entity, EntityInterface $parent) { - $i = 1; - while ($i < $entity->depth) { - $p = 'p' . $i++; - $entity->{$p} = $parent->{$p}; - } - $p = 'p' . $i++; - // The parent (p1 - p9) corresponding to the depth always equals the mlid. - $entity->{$p} = $entity->id(); - while ($i <= MENU_MAX_DEPTH) { - $p = 'p' . $i++; - $entity->{$p} = 0; - } - } - - /** - * Finds the depth of an item's children relative to its depth. - * - * For example, if the item has a depth of 2 and the maximum of any child in - * the menu link tree is 5, the relative depth is 3. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * A menu link entity. - * - * @return int - * The relative depth, or zero. + * {@inheritdoc} */ public function findChildrenRelativeDepth(EntityInterface $entity) { // @todo Since all we need is a specific field from the base table, does it @@ -593,15 +313,9 @@ class MenuLinkStorageController extends DatabaseStorageController { } /** - * Updates the children of a menu link that is being moved. - * - * The menu name, parents (p1 - p6), and depth are updated for all children of - * the link, and the has_children status of the previous parent is updated. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * A menu link entity. + * {@inheritdoc} */ - protected function moveChildren(EntityInterface $entity) { + public function moveChildren(EntityInterface $entity) { $query = $this->database->update($this->entityInfo['base_table']); $query->fields(array('menu_name' => $entity->menu_name)); @@ -645,10 +359,7 @@ class MenuLinkStorageController extends DatabaseStorageController { } /** - * Returns the number of menu links from a menu. - * - * @param string $menu_name - * The unique name of a menu. + * {@inheritdoc} */ public function countMenuLinks($menu_name) { $query = \Drupal::entityQuery($this->entityType); @@ -657,4 +368,34 @@ class MenuLinkStorageController extends DatabaseStorageController { ->count(); return $query->execute(); } + + /** + * {@inheritdoc} + */ + public function getParentFromHierarchy(EntityInterface $entity) { + $parent_path = $entity->link_path; + do { + $parent = FALSE; + $parent_path = substr($parent_path, 0, strrpos($parent_path, '/')); + + $query = \Drupal::entityQuery($this->entityType); + $query + ->condition('mlid', $entity->id(), '<>') + ->condition('module', 'system') + // We always respect the link's 'menu_name'; inheritance for router + // items is ensured in _menu_router_build(). + ->condition('menu_name', $entity->menu_name) + ->condition('link_path', $parent_path); + + $result = $query->execute(); + // Only valid if we get a unique result. + if (count($result) == 1) { + $parent = $this->load($result); + $parent = reset($parent); + } + } while ($parent === FALSE && $parent_path); + + return $parent; + } + } diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageControllerInterface.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageControllerInterface.php new file mode 100644 index 00000000000..3fb408f4739 --- /dev/null +++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageControllerInterface.php @@ -0,0 +1,110 @@ +{$offset}); } + + /** + * {@inheritdoc} + */ + public static function preDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + // Nothing to do if we don't want to reparent children. + if ($storage_controller->getPreventReparenting()) { + return; + } + + foreach ($entities as $entity) { + // Children get re-attached to the item's parent. + if ($entity->has_children) { + $children = $storage_controller->loadByProperties(array('plid' => $entity->plid)); + foreach ($children as $child) { + $child->plid = $entity->plid; + $storage_controller->save($child); + } + } + } + } + + /** + * {@inheritdoc} + */ + public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + $affected_menus = array(); + // Update the has_children status of the parent. + foreach ($entities as $entity) { + if (!$storage_controller->getPreventReparenting()) { + $storage_controller->updateParentalStatus($entity); + } + + // Store all menu names for which we need to clear the cache. + if (!isset($affected_menus[$entity->menu_name])) { + $affected_menus[$entity->menu_name] = $entity->menu_name; + } + } + + foreach ($affected_menus as $menu_name) { + menu_cache_clear($menu_name); + } + _menu_clear_page_cache(); + } + + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageControllerInterface $storage_controller) { + // This is the easiest way to handle the unique internal path '', + // since a path marked as external does not need to match a router path. + $this->external = (url_is_external($this->link_path) || $this->link_path == '') ? 1 : 0; + + // Try to find a parent link. If found, assign it and derive its menu. + $parent_candidates = !empty($this->parentCandidates) ? $this->parentCandidates : array(); + $parent = $this->findParent($storage_controller, $parent_candidates); + if ($parent) { + $this->plid = $parent->id(); + $this->menu_name = $parent->menu_name; + } + // If no corresponding parent link was found, move the link to the top-level. + else { + $this->plid = 0; + } + + // Directly fill parents for top-level links. + if ($this->plid == 0) { + $this->p1 = $this->id(); + for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) { + $parent_property = "p$i"; + $this->{$parent_property} = 0; + } + $this->depth = 1; + } + // Otherwise, ensure that this link's depth is not beyond the maximum depth + // and fill parents based on the parent link. + else { + if ($this->has_children && $this->original) { + $limit = MENU_MAX_DEPTH - $storage_controller->findChildrenRelativeDepth($this->original) - 1; + } + else { + $limit = MENU_MAX_DEPTH - 1; + } + if ($parent->depth > $limit) { + return FALSE; + } + $this->depth = $parent->depth + 1; + $this->setParents($parent); + } + + // Need to check both plid and menu_name, since plid can be 0 in any menu. + if (isset($this->original) && ($this->plid != $this->original->plid || $this->menu_name != $this->original->menu_name)) { + $storage_controller->moveChildren($this, $this->original); + } + // Find the router_path. + if (empty($this->router_path) || empty($this->original) || (isset($this->original) && $this->original->link_path != $this->link_path)) { + if ($this->external) { + $this->router_path = ''; + } + else { + // Find the router path which will serve this path. + $this->parts = explode('/', $this->link_path, MENU_MAX_PARTS); + $this->router_path = _menu_find_router_path($this->link_path); + } + } + // Find the route_name. + if (!isset($this->route_name)) { + $this->route_name = $this::findRouteName($this->link_path); + } + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { + // Check the has_children status of the parent. + $storage_controller->updateParentalStatus($this); + + menu_cache_clear($this->menu_name); + if (isset($this->original) && $this->menu_name != $this->original->menu_name) { + menu_cache_clear($this->original->menu_name); + } + + // Now clear the cache. + _menu_clear_page_cache(); + } + + /** + * {@inheritdoc} + */ + public static function findRouteName($link_path) { + // Look up the route_name used for the given path. + $request = Request::create('/' . $link_path); + $request->attributes->set('system_path', $link_path); + try { + // Use router.dynamic instead of router, because router will call the + // legacy router which will call hook_menu() and you will get back to + // this method. + $result = \Drupal::service('router.dynamic')->matchRequest($request); + return isset($result['_route']) ? $result['_route'] : ''; + } + catch (\Exception $e) { + return ''; + } + } + + /** + * {@inheritdoc} + */ + public function setParents(EntityInterface $parent) { + $i = 1; + while ($i < $this->depth) { + $p = 'p' . $i++; + $this->{$p} = $parent->{$p}; + } + $p = 'p' . $i++; + // The parent (p1 - p9) corresponding to the depth always equals the mlid. + $this->{$p} = $this->id(); + while ($i <= MENU_MAX_DEPTH) { + $p = 'p' . $i++; + $this->{$p} = 0; + } + } + + /** + * {@inheritdoc} + */ + public function findParent(EntityStorageControllerInterface $storage_controller, array $parent_candidates = array()) { + $parent = FALSE; + + // This item is explicitely top-level, skip the rest of the parenting. + if (isset($this->plid) && empty($this->plid)) { + return $parent; + } + + // If we have a parent link ID, try to use that. + $candidates = array(); + if (isset($this->plid)) { + $candidates[] = $this->plid; + } + + // Else, if we have a link hierarchy try to find a valid parent in there. + if (!empty($this->depth) && $this->depth > 1) { + for ($depth = $this->depth - 1; $depth >= 1; $depth--) { + $parent_property = "p$depth"; + $candidates[] = $this->$parent_property; + } + } + + foreach ($candidates as $mlid) { + if (isset($parent_candidates[$mlid])) { + $parent = $parent_candidates[$mlid]; + } + else { + $parent = $storage_controller->load(array($mlid)); + $parent = reset($parent); + } + if ($parent) { + return $parent; + } + } + + // If everything else failed, try to derive the parent from the path + // hierarchy. This only makes sense for links derived from menu router + // items (ie. from hook_menu()). + if ($this->module == 'system') { + $parent = $storage_controller->getParentFromHierarchy($this); + } + + return $parent; + } + + } diff --git a/core/modules/menu_link/menu_link.module b/core/modules/menu_link/menu_link.module index 45e1923a45a..80785f91a9a 100644 --- a/core/modules/menu_link/menu_link.module +++ b/core/modules/menu_link/menu_link.module @@ -103,7 +103,7 @@ function menu_link_delete_multiple(array $mlids, $force = FALSE, $prevent_repare else { $entities = $controller->load($mlids); } - $controller->preventReparenting($prevent_reparenting); + $controller->setPreventReparenting($prevent_reparenting); $controller->delete($entities); } diff --git a/core/modules/node/lib/Drupal/node/NodeStorageController.php b/core/modules/node/lib/Drupal/node/NodeStorageController.php index db3ba75a015..ce2d20933b7 100644 --- a/core/modules/node/lib/Drupal/node/NodeStorageController.php +++ b/core/modules/node/lib/Drupal/node/NodeStorageController.php @@ -123,63 +123,6 @@ class NodeStorageController extends DatabaseStorageControllerNG { return $record; } - /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::preSave(). - */ - protected function preSave(EntityInterface $node) { - // Before saving the node, set changed and revision times. - $node->changed->value = REQUEST_TIME; - } - - /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::preSaveRevision(). - */ - protected function preSaveRevision(\stdClass $record, EntityInterface $entity) { - if ($entity->isNewRevision()) { - // When inserting either a new node or a new node revision, $node->log - // must be set because {node_field_revision}.log is a text column and - // therefore cannot have a default value. However, it might not be set at - // this point (for example, if the user submitting a node form does not - // have permission to create revisions), so we ensure that it is at least - // an empty string in that case. - // @todo Make the {node_field_revision}.log column nullable so that we - // can remove this check. - if (!isset($record->log)) { - $record->log = ''; - } - } - elseif (isset($entity->original) && (!isset($record->log) || $record->log === '')) { - // If we are updating an existing node without adding a new revision, we - // need to make sure $entity->log is reset whenever it is empty. - // Therefore, this code allows us to avoid clobbering an existing log - // entry with an empty one. - $record->log = $entity->original->log; - } - } - - /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::postSave(). - */ - public function postSave(EntityInterface $node, $update) { - // Update the node access table for this node, but only if it is the - // default revision. There's no need to delete existing records if the node - // is new. - if ($node->isDefaultRevision()) { - node_access_acquire_grants($node->getBCEntity(), $update); - } - } - - /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::preDelete(). - */ - public function preDelete($entities) { - if (module_exists('search')) { - foreach ($entities as $id => $entity) { - search_reindex($entity->nid->value, 'node'); - } - } - } - /** * Overrides Drupal\Core\Entity\DatabaseStorageController::postDelete(). */ diff --git a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php index 3343da49e05..0791287725e 100644 --- a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php +++ b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php @@ -8,6 +8,7 @@ namespace Drupal\node\Plugin\Core\Entity; use Drupal\Core\Entity\EntityNG; +use Drupal\Core\Entity\EntityStorageControllerInterface; use Drupal\Core\Entity\Annotation\EntityType; use Drupal\Core\Annotation\Translation; use Drupal\node\NodeInterface; @@ -241,6 +242,52 @@ class Node extends EntityNG implements NodeInterface { return $this->get('vid')->value; } + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageControllerInterface $storage_controller) { + // Before saving the node, set changed and revision times. + $this->changed->value = REQUEST_TIME; + } + + /** + * {@inheritdoc} + */ + public function preSaveRevision(EntityStorageControllerInterface $storage_controller, \stdClass $record) { + if ($this->newRevision) { + // When inserting either a new node or a new node revision, $node->log + // must be set because {node_field_revision}.log is a text column and + // therefore cannot have a default value. However, it might not be set at + // this point (for example, if the user submitting a node form does not + // have permission to create revisions), so we ensure that it is at least + // an empty string in that case. + // @todo Make the {node_field_revision}.log column nullable so that we + // can remove this check. + if (!isset($record->log)) { + $record->log = ''; + } + } + elseif (isset($this->original) && (!isset($record->log) || $record->log === '')) { + // If we are updating an existing node without adding a new revision, we + // need to make sure $entity->log is reset whenever it is empty. + // Therefore, this code allows us to avoid clobbering an existing log + // entry with an empty one. + $record->log = $this->original->log; + } + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { + // Update the node access table for this node, but only if it is the + // default revision. There's no need to delete existing records if the node + // is new. + if ($this->isDefaultRevision()) { + node_access_acquire_grants($this->getBCEntity(), $update); + } + } + /** * {@inheritdoc} */ @@ -252,4 +299,16 @@ class Node extends EntityNG implements NodeInterface { return $this->bcEntity; } + /** + * {@inheritdoc} + */ + public static function preDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + if (module_exists('search')) { + foreach ($entities as $id => $entity) { + search_reindex($entity->nid->value, 'node'); + } + } + } + + } diff --git a/core/modules/node/tests/modules/node_test/lib/Drupal/node_test/NodeTest.php b/core/modules/node/tests/modules/node_test/lib/Drupal/node_test/NodeTest.php new file mode 100644 index 00000000000..a77ca7674f2 --- /dev/null +++ b/core/modules/node/tests/modules/node_test/lib/Drupal/node_test/NodeTest.php @@ -0,0 +1,25 @@ +get('node_test.storage_controller')) { - $entity_info['node']['controllers']['storage'] = 'Drupal\node_test\NodeTestStorageController'; + $entity_info['node']['class'] = 'Drupal\node_test\NodeTest'; } } diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Form/SetDelete.php b/core/modules/shortcut/lib/Drupal/shortcut/Form/SetDelete.php index 2b245740753..105b382a32d 100644 --- a/core/modules/shortcut/lib/Drupal/shortcut/Form/SetDelete.php +++ b/core/modules/shortcut/lib/Drupal/shortcut/Form/SetDelete.php @@ -95,7 +95,7 @@ class SetDelete extends ConfirmFormBase implements ControllerInterface { // Find out how many users are directly assigned to this shortcut set, and // make a message. - $number = $this->database->query('SELECT COUNT(*) FROM {shortcut_set_users} WHERE set_name = :name', array(':name' => $this->shortcut->id()))->fetchField(); + $number = \Drupal::entityManager()->getStorageController('shortcut')->countAssignedUsers($shortcut); $info = ''; if ($number) { $info .= '

' . format_plural($number, diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Plugin/Core/Entity/Shortcut.php b/core/modules/shortcut/lib/Drupal/shortcut/Plugin/Core/Entity/Shortcut.php index 95802d81b9d..5f599e453ff 100644 --- a/core/modules/shortcut/lib/Drupal/shortcut/Plugin/Core/Entity/Shortcut.php +++ b/core/modules/shortcut/lib/Drupal/shortcut/Plugin/Core/Entity/Shortcut.php @@ -10,6 +10,7 @@ namespace Drupal\shortcut\Plugin\Core\Entity; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\Annotation\EntityType; use Drupal\Core\Annotation\Translation; +use Drupal\Core\Entity\EntityStorageControllerInterface; use Drupal\shortcut\ShortcutInterface; /** @@ -79,4 +80,65 @@ class Shortcut extends ConfigEntityBase implements ShortcutInterface { ); } + /** + * {@inheritdoc} + */ + public function postCreate(EntityStorageControllerInterface $storage_controller) { + // Generate menu-compatible set name. + if (!$this->getOriginalID()) { + // Save a new shortcut set with links copied from the user's default set. + $default_set = shortcut_default_set(); + // Generate a name to have no collisions with menu. + // Size of menu_name is 32 so id could be 23 = 32 - strlen('shortcut-'). + $id = substr($this->id(), 0, 23); + $this->set('id', $id); + if ($default_set->id() != $id) { + foreach ($default_set->links as $link) { + $link = $link->createDuplicate(); + $link->enforceIsNew(); + $link->menu_name = $id; + $link->save(); + $this->links[$link->uuid()] = $link; + } + } + } + } + + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageControllerInterface $storage_controller) { + // Just store the UUIDs. + foreach ($this->links as $uuid => $link) { + $this->links[$uuid] = $uuid; + } + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { + foreach ($this->links as $uuid) { + if ($menu_link = entity_load_by_uuid('menu_link', $uuid)) { + // Do not specifically associate these links with the shortcut module, + // since other modules may make them editable via the menu system. + // However, we do need to specify the correct menu name. + $menu_link->menu_name = 'shortcut-' . $this->id(); + $menu_link->plid = 0; + $menu_link->save(); + } + } + } + + /** + * {@inheritdoc} + */ + public static function preDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + foreach ($entities as $entity) { + $storage_controller->deleteAssignedShortcutSets($entity); + // Next, delete the menu links for this set. + menu_delete_links('shortcut-' . $entity->id()); + + } + } } diff --git a/core/modules/shortcut/lib/Drupal/shortcut/ShortcutStorageController.php b/core/modules/shortcut/lib/Drupal/shortcut/ShortcutStorageController.php index 4463a863b85..a7150b10a28 100644 --- a/core/modules/shortcut/lib/Drupal/shortcut/ShortcutStorageController.php +++ b/core/modules/shortcut/lib/Drupal/shortcut/ShortcutStorageController.php @@ -8,12 +8,12 @@ namespace Drupal\shortcut; use Drupal\Core\Config\Entity\ConfigStorageController; -use Drupal\Core\Entity\EntityInterface; +use Drupal\shortcut\Plugin\Core\Entity\Shortcut; /** * Defines a storage controller for shortcut entities. */ -class ShortcutStorageController extends ConfigStorageController { +class ShortcutStorageController extends ConfigStorageController implements ShortcutStorageControllerInterface { /** * Overrides \Drupal\config\ConfigStorageController::attachLoad(). @@ -30,80 +30,51 @@ class ShortcutStorageController extends ConfigStorageController { } /** - * Overrides \Drupal\config\ConfigStorageController::create(). + * {@inheritdoc} */ - public function create(array $values) { - $entity = parent::create($values); - - // Generate menu-compatible set name. - if (!$entity->getOriginalID()) { - // Save a new shortcut set with links copied from the user's default set. - $default_set = shortcut_default_set(); - // Generate a name to have no collisions with menu. - // Size of menu_name is 32 so id could be 23 = 32 - strlen('shortcut-'). - $id = substr($entity->id(), 0, 23); - $entity->set('id', $id); - if ($default_set->id() != $id) { - foreach ($default_set->links as $link) { - $link = $link->createDuplicate(); - $link->enforceIsNew(); - $link->menu_name = $id; - $link->save(); - $entity->links[$link->uuid()] = $link; - } - } - } - - return $entity; + public function deleteAssignedShortcutSets(Shortcut $entity) { + // First, delete any user assignments for this set, so that each of these + // users will go back to using whatever default set applies. + db_delete('shortcut_set_users') + ->condition('set_name', $entity->id()) + ->execute(); } /** - * Overrides \Drupal\config\ConfigStorageController::preSave(). + * {@inheritdoc} */ - public function preSave(EntityInterface $entity) { - // Just store the UUIDs. - foreach ($entity->links as $uuid => $link) { - $entity->links[$uuid] = $uuid; - } - - parent::preSave($entity); + public function assignUser($shortcut_set, $account) { + db_merge('shortcut_set_users') + ->key(array('uid' => $account->uid)) + ->fields(array('set_name' => $shortcut_set->id())) + ->execute(); + drupal_static_reset('shortcut_current_displayed_set'); } /** - * Overrides \Drupal\config\ConfigStorageController::postSave(). + * {@inheritdoc} */ - function postSave(EntityInterface $entity, $update) { - // Process links in shortcut set. - foreach ($entity->links as $uuid) { - if ($menu_link = entity_load_by_uuid('menu_link', $uuid)) { - // Do not specifically associate these links with the shortcut module, - // since other modules may make them editable via the menu system. - // However, we do need to specify the correct menu name. - $menu_link->menu_name = 'shortcut-' . $entity->id(); - $menu_link->plid = 0; - $menu_link->save(); - } - } - - parent::postSave($entity, $update); + public function unassignUser($account) { + $deleted = db_delete('shortcut_set_users') + ->condition('uid', $account->uid) + ->execute(); + return (bool) $deleted; } /** - * Overrides \Drupal\Core\Entity\ConfigStorageController::preDelete(). + * {@inheritdoc} */ - protected function preDelete($entities) { - foreach ($entities as $entity) { - // First, delete any user assignments for this set, so that each of these - // users will go back to using whatever default set applies. - db_delete('shortcut_set_users') - ->condition('set_name', $entity->id()) - ->execute(); - - // Next, delete the menu links for this set. - menu_delete_links('shortcut-' . $entity->id()); - } - - parent::preDelete($entities); + public function getAssignedToUser($account) { + $query = db_select('shortcut_set_users', 'ssu'); + $query->fields('ssu', array('set_name')); + $query->condition('ssu.uid', $account->uid); + return $query->execute()->fetchField(); } + /** + * {@inheritdoc} + */ + public function countAssignedUsers(Shortcut $shortcut) { + return db_query('SELECT COUNT(*) FROM {shortcut_set_users} WHERE set_name = :name', array(':name' => $shortcut->id()))->fetchField(); + } } diff --git a/core/modules/shortcut/lib/Drupal/shortcut/ShortcutStorageControllerInterface.php b/core/modules/shortcut/lib/Drupal/shortcut/ShortcutStorageControllerInterface.php new file mode 100644 index 00000000000..e951620760f --- /dev/null +++ b/core/modules/shortcut/lib/Drupal/shortcut/ShortcutStorageControllerInterface.php @@ -0,0 +1,72 @@ +key(array('uid' => $account->uid)) - ->fields(array('set_name' => $shortcut_set->id())) - ->execute(); - drupal_static_reset('shortcut_current_displayed_set'); + Drupal::entityManager() + ->getStorageController('shortcut') + ->assignUser($shortcut_set, $account); } /** @@ -332,10 +330,9 @@ function shortcut_set_assign_user($shortcut_set, $account) { * to any set. */ function shortcut_set_unassign_user($account) { - $deleted = db_delete('shortcut_set_users') - ->condition('uid', $account->uid) - ->execute(); - return (bool) $deleted; + return (bool) Drupal::entityManager() + ->getStorageController('shortcut') + ->unassignUser($account); } /** @@ -362,10 +359,9 @@ function shortcut_current_displayed_set($account = NULL) { } // If none was found, try to find a shortcut set that is explicitly assigned // to this user. - $query = db_select('shortcut_set_users', 'ssu'); - $query->fields('ssu', array('set_name')); - $query->condition('ssu.uid', $account->uid); - $shortcut_set_name = $query->execute()->fetchField(); + $shortcut_set_name = Drupal::entityManager() + ->getStorageController('shortcut') + ->getAssignedToUser($account); if ($shortcut_set_name) { $shortcut_set = shortcut_set_load($shortcut_set_name); } diff --git a/core/modules/system/lib/Drupal/system/ActionStorageController.php b/core/modules/system/lib/Drupal/system/ActionStorageController.php deleted file mode 100644 index 9fa0e0fea23..00000000000 --- a/core/modules/system/lib/Drupal/system/ActionStorageController.php +++ /dev/null @@ -1,32 +0,0 @@ -getPlugin(); - // If this plugin has any configuration, ensure that it is set. - if ($plugin instanceof ConfigurableActionInterface) { - $entity->set('configuration', $plugin->getConfiguration()); - } - } - -} diff --git a/core/modules/system/lib/Drupal/system/Plugin/Core/Entity/Action.php b/core/modules/system/lib/Drupal/system/Plugin/Core/Entity/Action.php index 6086102a081..dfdf37973c3 100644 --- a/core/modules/system/lib/Drupal/system/Plugin/Core/Entity/Action.php +++ b/core/modules/system/lib/Drupal/system/Plugin/Core/Entity/Action.php @@ -10,6 +10,7 @@ namespace Drupal\system\Plugin\Core\Entity; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\Annotation\EntityType; use Drupal\Core\Annotation\Translation; +use Drupal\Core\Entity\EntityStorageControllerInterface; use Drupal\system\ActionConfigEntityInterface; use Drupal\Core\Action\ActionBag; use Drupal\Core\Action\ConfigurableActionInterface; @@ -22,7 +23,7 @@ use Drupal\Core\Action\ConfigurableActionInterface; * label = @Translation("Action"), * module = "system", * controllers = { - * "storage" = "Drupal\system\ActionStorageController" + * "storage" = "Drupal\Core\Config\Entity\ConfigStorageController" * }, * config_prefix = "action.action", * entity_keys = { @@ -176,4 +177,15 @@ class Action extends ConfigEntityBase implements ActionConfigEntityInterface { return $properties; } + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageControllerInterface $storage_controller) { + $plugin = $this->getPlugin(); + // If this plugin has any configuration, ensure that it is set. + if ($plugin instanceof ConfigurableActionInterface) { + $this->set('configuration', $plugin->getConfiguration()); + } + } + } diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php index 5f3dc785f3c..0bf0aacf75a 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php @@ -9,6 +9,7 @@ namespace Drupal\taxonomy\Plugin\Core\Entity; use Drupal\Core\Entity\EntityNG; use Drupal\Core\Entity\Annotation\EntityType; +use Drupal\Core\Entity\EntityStorageControllerInterface; use Drupal\Core\Annotation\Translation; use Drupal\Core\Language\Language; use Drupal\taxonomy\TermInterface; @@ -151,4 +152,46 @@ class Term extends EntityNG implements TermInterface { unset($this->description); unset($this->parent); } + + /** + * {@inheritdoc} + */ + public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + // See if any of the term's children are about to be become orphans. + $orphans = array(); + foreach (array_keys($entities) as $tid) { + if ($children = taxonomy_term_load_children($tid)) { + foreach ($children as $child) { + // If the term has multiple parents, we don't delete it. + $parents = taxonomy_term_load_parents($child->id()); + // Because the parent has already been deleted, the parent count might + // be 0. + if (count($parents) <= 1) { + $orphans[] = $child->id(); + } + } + } + } + + // Delete term hierarchy information after looking up orphans but before + // deleting them so that their children/parent information is consistent. + $storage_controller->deleteTermHierarchy(array_keys($entities)); + + if (!empty($orphans)) { + entity_delete_multiple('taxonomy_term', $orphans); + } + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { + // Only change the parents if a value is set, keep the existing values if + // not. + if (isset($this->parent->value)) { + $storage_controller->deleteTermHierarchy(array($this->id())); + $storage_controller->updateTermHierarchy($this); + } + } + } diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Vocabulary.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Vocabulary.php index 31d2e2fa41b..dab74c601d2 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Vocabulary.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Vocabulary.php @@ -8,6 +8,7 @@ namespace Drupal\taxonomy\Plugin\Core\Entity; use Drupal\Core\Config\Entity\ConfigEntityBase; +use Drupal\Core\Entity\EntityStorageControllerInterface; use Drupal\Core\Entity\Annotation\EntityType; use Drupal\Core\Annotation\Translation; use Drupal\taxonomy\VocabularyInterface; @@ -104,4 +105,78 @@ class Vocabulary extends ConfigEntityBase implements VocabularyInterface { ); } + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { + if (!$update) { + entity_invoke_bundle_hook('create', 'taxonomy_term', $this->id()); + } + elseif ($this->getOriginalID() != $this->id()) { + // Reflect machine name changes in the definitions of existing 'taxonomy' + // fields. + $fields = field_read_fields(); + foreach ($fields as $field_name => $field) { + $update_field = FALSE; + if ($field['type'] == 'taxonomy_term_reference') { + foreach ($field['settings']['allowed_values'] as $key => &$value) { + if ($value['vocabulary'] == $this->getOriginalID()) { + $value['vocabulary'] = $this->id(); + $update_field = TRUE; + } + } + if ($update_field) { + field_update_field($field); + } + } + } + // Update bundles. + entity_invoke_bundle_hook('rename', 'taxonomy_term', $this->getOriginalID(), $this->id()); + } + $storage_controller->resetCache($update ? array($this->getOriginalID()) : array()); + } + + /** + * {@inheritdoc} + */ + public static function preDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + // Only load terms without a parent, child terms will get deleted too. + entity_delete_multiple('taxonomy_term', $storage_controller->getToplevelTids(array_keys($entities))); + } + + /** + * {@inheritdoc} + */ + public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + $vocabularies = array(); + foreach ($entities as $vocabulary) { + $vocabularies[$vocabulary->id()] = $vocabulary->id(); + } + // Load all Taxonomy module fields and delete those which use only this + // vocabulary. + $taxonomy_fields = field_read_fields(array('module' => 'taxonomy')); + foreach ($taxonomy_fields as $field_name => $taxonomy_field) { + $modified_field = FALSE; + // Term reference fields may reference terms from more than one + // vocabulary. + foreach ($taxonomy_field['settings']['allowed_values'] as $key => $allowed_value) { + if (isset($vocabularies[$allowed_value['vocabulary']])) { + unset($taxonomy_field['settings']['allowed_values'][$key]); + $modified_field = TRUE; + } + } + if ($modified_field) { + if (empty($taxonomy_field['settings']['allowed_values'])) { + field_delete_field($field_name); + } + else { + // Update the field definition with the new allowed values. + field_update_field($taxonomy_field); + } + } + } + // Reset caches. + $storage_controller->resetCache(array_keys($vocabularies)); + } + } diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php index 9bb08c0b5b2..58860a0f5ab 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php @@ -14,7 +14,7 @@ use Drupal\Core\Entity\DatabaseStorageControllerNG; /** * Defines a Controller class for taxonomy terms. */ -class TermStorageController extends DatabaseStorageControllerNG { +class TermStorageController extends DatabaseStorageControllerNG implements TermStorageControllerInterface { /** * Overrides Drupal\Core\Entity\DatabaseStorageController::create(). @@ -43,61 +43,6 @@ class TermStorageController extends DatabaseStorageControllerNG { parent::buildPropertyQuery($entity_query, $values); } - /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::postDelete(). - */ - protected function postDelete($entities) { - // See if any of the term's children are about to be become orphans. - $orphans = array(); - foreach (array_keys($entities) as $tid) { - if ($children = taxonomy_term_load_children($tid)) { - foreach ($children as $child) { - // If the term has multiple parents, we don't delete it. - $parents = taxonomy_term_load_parents($child->id()); - // Because the parent has already been deleted, the parent count might - // be 0. - if (count($parents) <= 1) { - $orphans[] = $child->id(); - } - } - } - } - - // Delete term hierarchy information after looking up orphans but before - // deleting them so that their children/parent information is consistent. - db_delete('taxonomy_term_hierarchy') - ->condition('tid', array_keys($entities)) - ->execute(); - - if (!empty($orphans)) { - entity_delete_multiple('taxonomy_term', $orphans); - } - } - - /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::postSave(). - */ - protected function postSave(EntityInterface $entity, $update) { - // Only change the parents if a value is set, keep the existing values if - // not. - if (isset($entity->parent->value)) { - db_delete('taxonomy_term_hierarchy') - ->condition('tid', $entity->id()) - ->execute(); - - $query = db_insert('taxonomy_term_hierarchy') - ->fields(array('tid', 'parent')); - - foreach ($entity->parent as $parent) { - $query->values(array( - 'tid' => $entity->id(), - 'parent' => (int) $parent->value, - )); - } - $query->execute(); - } - } - /** * Overrides Drupal\Core\Entity\DatabaseStorageController::resetCache(). */ @@ -167,4 +112,30 @@ class TermStorageController extends DatabaseStorageControllerNG { ); return $properties; } + + /** + * {@inheritdoc} + */ + public function deleteTermHierarchy($tids) { + $this->database->delete('taxonomy_term_hierarchy') + ->condition('tid', $tids) + ->execute(); + } + + /** + * {@inheritdoc} + */ + public function updateTermHierarchy(EntityInterface $term) { + $query = $this->database->insert('taxonomy_term_hierarchy') + ->fields(array('tid', 'parent')); + + foreach ($term->parent as $parent) { + $query->values(array( + 'tid' => $term->id(), + 'parent' => (int) $parent->value, + )); + } + $query->execute(); + } + } diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageControllerInterface.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageControllerInterface.php new file mode 100644 index 00000000000..732a58faacb --- /dev/null +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageControllerInterface.php @@ -0,0 +1,34 @@ +id()); - } - elseif ($entity->getOriginalID() != $entity->id()) { - // Reflect machine name changes in the definitions of existing 'taxonomy' - // fields. - $fields = field_read_fields(); - foreach ($fields as $field_name => $field) { - $update_field = FALSE; - if ($field['type'] == 'taxonomy_term_reference') { - foreach ($field['settings']['allowed_values'] as $key => &$value) { - if ($value['vocabulary'] == $entity->getOriginalID()) { - $value['vocabulary'] = $entity->id(); - $update_field = TRUE; - } - } - if ($update_field) { - field_update_field($field); - } - } - } - // Update bundles. - entity_invoke_bundle_hook('rename', 'taxonomy_term', $entity->getOriginalID(), $entity->id()); - } - parent::postSave($entity, $update); - $this->resetCache($update ? array($entity->getOriginalID()) : array()); - } - - /** - * Overrides Drupal\Core\Config\Entity\ConfigStorageController::preDelete(). - */ - protected function preDelete($entities) { - parent::preDelete($entities); - // Only load terms without a parent, child terms will get deleted too. - $tids = db_query('SELECT t.tid FROM {taxonomy_term_data} t INNER JOIN {taxonomy_term_hierarchy} th ON th.tid = t.tid WHERE t.vid IN (:vids) AND th.parent = 0', array(':vids' => array_keys($entities)))->fetchCol(); - entity_delete_multiple('taxonomy_term', $tids); - } - - /** - * Overrides Drupal\Core\Config\Entity\ConfigStorageController::postDelete(). - */ - protected function postDelete($entities) { - parent::postDelete($entities); - - $vocabularies = array(); - foreach ($entities as $vocabulary) { - $vocabularies[$vocabulary->id()] = $vocabulary->id(); - } - // Load all Taxonomy module fields and delete those which use only this - // vocabulary. - $taxonomy_fields = field_read_fields(array('module' => 'taxonomy')); - foreach ($taxonomy_fields as $field_name => $taxonomy_field) { - $modified_field = FALSE; - // Term reference fields may reference terms from more than one - // vocabulary. - foreach ($taxonomy_field['settings']['allowed_values'] as $key => $allowed_value) { - if (isset($vocabularies[$allowed_value['vocabulary']])) { - unset($taxonomy_field['settings']['allowed_values'][$key]); - $modified_field = TRUE; - } - } - if ($modified_field) { - if (empty($taxonomy_field['settings']['allowed_values'])) { - field_delete_field($field_name); - } - else { - // Update the field definition with the new allowed values. - field_update_field($taxonomy_field); - } - } - } - // Reset caches. - $this->resetCache(array_keys($vocabularies)); - } +class VocabularyStorageController extends ConfigStorageController implements VocabularyStorageControllerInterface { /** * Overrides Drupal\Core\Config\Entity\ConfigStorageController::resetCache(). @@ -104,4 +24,11 @@ class VocabularyStorageController extends ConfigStorageController { entity_info_cache_clear(); } + /** + * {@inheritdoc} + */ + public function getToplevelTids($vids) { + return db_query('SELECT t.tid FROM {taxonomy_term_data} t INNER JOIN {taxonomy_term_hierarchy} th ON th.tid = t.tid WHERE t.vid IN (:vids) AND th.parent = 0', array(':vids' => $vids))->fetchCol(); + } + } diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageControllerInterface.php b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageControllerInterface.php new file mode 100644 index 00000000000..b9e82effc8e --- /dev/null +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageControllerInterface.php @@ -0,0 +1,28 @@ +weight) && ($roles = $storage_controller->load())) { + // Set a role weight to make this new role last. + $max = array_reduce($roles, function($max, $role) { + return $max > $role->weight ? $max : $role->weight; + }); + $this->weight = $max + 1; + } + } + + /** + * {@inheritdoc} + */ + public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + $storage_controller->deleteRoleReferences(array_keys($entities)); + } } diff --git a/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php b/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php index 1f55b49b2da..0f2e28a5b6a 100644 --- a/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php +++ b/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php @@ -7,6 +7,8 @@ namespace Drupal\user\Plugin\Core\Entity; +use Drupal\Core\Entity\EntityStorageControllerInterface; +use Drupal\Core\Entity\EntityMalformedException; use Drupal\Core\Entity\EntityNG; use Drupal\Core\Entity\Annotation\EntityType; use Drupal\Core\Annotation\Translation; @@ -228,6 +230,96 @@ class User extends EntityNG implements UserInterface { unset($this->uuid); } + /** + * {@inheritdoc} + */ + static function preCreate(EntityStorageControllerInterface $storage_controller, array &$values) { + if (!isset($values['created'])) { + $values['created'] = REQUEST_TIME; + } + // Users always have the authenticated user role. + $values['roles'][] = DRUPAL_AUTHENTICATED_RID; + } + + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageControllerInterface $storage_controller) { + // Update the user password if it has changed. + if ($this->isNew() || ($this->pass->value && $this->pass->value != $this->original->pass->value)) { + // Allow alternate password hashing schemes. + $this->pass->value = \Drupal::service('password')->hash(trim($this->pass->value)); + // Abort if the hashing failed and returned FALSE. + if (!$this->pass->value) { + throw new EntityMalformedException('The entity does not have a password.'); + } + } + + if (!$this->isNew()) { + // If the password is empty, that means it was not changed, so use the + // original password. + if (empty($this->pass->value)) { + $this->pass->value = $this->original->pass->value; + } + } + + // Store account cancellation information. + foreach (array('user_cancel_method', 'user_cancel_notify') as $key) { + if (isset($this->{$key})) { + \Drupal::service('user.data')->set('user', $this->id(), substr($key, 5), $this->{$key}); + } + } + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { + if ($update) { + // If the password has been changed, delete all open sessions for the + // user and recreate the current one. + if ($this->pass->value != $this->original->pass->value) { + drupal_session_destroy_uid($this->id()); + if ($this->id() == $GLOBALS['user']->uid) { + drupal_session_regenerate(); + } + } + + // Update user roles if changed. + if ($this->roles->getValue() != $this->original->roles->getValue()) { + $storage_controller->deleteUserRoles(array($this->id())); + $storage_controller->saveRoles($this); + } + + // If the user was blocked, delete the user's sessions to force a logout. + if ($this->original->status->value != $this->status->value && $this->status->value == 0) { + drupal_session_destroy_uid($this->id()); + } + + // Send emails after we have the new user object. + if ($this->status->value != $this->original->status->value) { + // The user's status is changing; conditionally send notification email. + $op = $this->status->value == 1 ? 'status_activated' : 'status_blocked'; + _user_mail_notify($op, $this->getBCEntity()); + } + } + else { + // Save user roles. + if (count($this->roles) > 1) { + $storage_controller->saveRoles($this); + } + } + } + + /** + * {@inheritdoc} + */ + public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + $uids = array_keys($entities); + \Drupal::service('user.data')->delete(NULL, $uids); + $storage_controller->deleteUserRoles($uids); + } + /** * {@inheritdoc} */ diff --git a/core/modules/user/lib/Drupal/user/RoleStorageController.php b/core/modules/user/lib/Drupal/user/RoleStorageController.php index 680b412c549..4b6ca87d2ec 100644 --- a/core/modules/user/lib/Drupal/user/RoleStorageController.php +++ b/core/modules/user/lib/Drupal/user/RoleStorageController.php @@ -8,26 +8,11 @@ namespace Drupal\user; use Drupal\Core\Config\Entity\ConfigStorageController; -use Drupal\Core\Entity\EntityInterface; /** * Controller class for user roles. */ -class RoleStorageController extends ConfigStorageController { - - /** - * {@inheritdoc} - */ - public function preSave(EntityInterface $entity) { - if (!isset($entity->weight) && $roles = entity_load_multiple('user_role')) { - // Set a role weight to make this new role last. - $max = array_reduce($roles, function($max, $entity) { - return $max > $entity->weight ? $max : $entity->weight; - }); - $entity->weight = $max + 1; - } - parent::preSave($entity); - } +class RoleStorageController extends ConfigStorageController implements RoleStorageControllerInterface { /** * {@inheritdoc} @@ -43,9 +28,7 @@ class RoleStorageController extends ConfigStorageController { /** * {@inheritdoc} */ - protected function postDelete($entities) { - $rids = array_keys($entities); - + public function deleteRoleReferences(array $rids) { // Delete permission assignments. db_delete('role_permission') ->condition('rid', $rids) diff --git a/core/modules/user/lib/Drupal/user/RoleStorageControllerInterface.php b/core/modules/user/lib/Drupal/user/RoleStorageControllerInterface.php new file mode 100644 index 00000000000..0e18e2b40df --- /dev/null +++ b/core/modules/user/lib/Drupal/user/RoleStorageControllerInterface.php @@ -0,0 +1,25 @@ + array_keys($queried_users))); - foreach ($result as $record) { - $queried_users[$record->uid]->roles[] = $record->rid; - } + $this->addRoles($queried_users); // Call the default attachLoad() method. This will add fields and call // hook_user_load(). @@ -97,15 +94,9 @@ class UserStorageController extends DatabaseStorageControllerNG { } /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::create(). + * {@inheritdoc} */ public function create(array $values) { - if (!isset($values['created'])) { - $values['created'] = REQUEST_TIME; - } - // Users always have the authenticated user role. - $values['roles'][] = DRUPAL_AUTHENTICATED_RID; - return parent::create($values)->getBCEntity(); } @@ -126,106 +117,38 @@ class UserStorageController extends DatabaseStorageControllerNG { } /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::preSave(). + * {@inheritdoc} */ - protected function preSave(EntityInterface $entity) { - // Update the user password if it has changed. - if ($entity->isNew() || ($entity->pass->value && $entity->pass->value != $entity->original->pass->value)) { - // Allow alternate password hashing schemes. - $entity->pass->value = $this->password->hash(trim($entity->pass->value)); - // Abort if the hashing failed and returned FALSE. - if (!$entity->pass->value) { - throw new EntityMalformedException('The entity does not have a password.'); + public function saveRoles(EntityInterface $user) { + $query = $this->database->insert('users_roles')->fields(array('uid', 'rid')); + foreach ($user->roles as $role) { + if (!in_array($role->value, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) { + $query->values(array( + 'uid' => $user->id(), + 'rid' => $role->value, + )); } } + $query->execute(); + } - if (!$entity->isNew()) { - // If the password is empty, that means it was not changed, so use the - // original password. - if (empty($entity->pass->value)) { - $entity->pass->value = $entity->original->pass->value; - } - } - - // Store account cancellation information. - foreach (array('user_cancel_method', 'user_cancel_notify') as $key) { - if (isset($entity->{$key})) { - $this->userData->set('user', $entity->id(), substr($key, 5), $entity->{$key}); - } + /** + * {@inheritdoc} + */ + public function addRoles(array $users) { + $result = db_query('SELECT rid, uid FROM {users_roles} WHERE uid IN (:uids)', array(':uids' => array_keys($users))); + foreach ($result as $record) { + $users[$record->uid]->roles[] = $record->rid; } } /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::postSave(). + * {@inheritdoc} */ - protected function postSave(EntityInterface $entity, $update) { - - if ($update) { - // If the password has been changed, delete all open sessions for the - // user and recreate the current one. - if ($entity->pass->value != $entity->original->pass->value) { - drupal_session_destroy_uid($entity->id()); - if ($entity->id() == $GLOBALS['user']->uid) { - drupal_session_regenerate(); - } - } - - // Update user roles if changed. - if ($entity->roles->getValue() != $entity->original->roles->getValue()) { - db_delete('users_roles') - ->condition('uid', $entity->id()) - ->execute(); - - $query = $this->database->insert('users_roles')->fields(array('uid', 'rid')); - foreach ($entity->roles as $role) { - if (!in_array($role->value, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) { - - $query->values(array( - 'uid' => $entity->id(), - 'rid' => $role->value, - )); - } - } - $query->execute(); - } - - // If the user was blocked, delete the user's sessions to force a logout. - if ($entity->original->status->value != $entity->status->value && $entity->status->value == 0) { - drupal_session_destroy_uid($entity->id()); - } - - // Send emails after we have the new user object. - if ($entity->status->value != $entity->original->status->value) { - // The user's status is changing; conditionally send notification email. - $op = $entity->status->value == 1 ? 'status_activated' : 'status_blocked'; - _user_mail_notify($op, $entity->getBCEntity()); - } - } - else { - // Save user roles. - if (count($entity->roles) > 1) { - $query = $this->database->insert('users_roles')->fields(array('uid', 'rid')); - foreach ($entity->roles as $role) { - if (!in_array($role->value, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) { - $query->values(array( - 'uid' => $entity->id(), - 'rid' => $role->value, - )); - } - } - $query->execute(); - } - } - } - - /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::postDelete(). - */ - protected function postDelete($entities) { + public function deleteUserRoles(array $uids) { $this->database->delete('users_roles') - ->condition('uid', array_keys($entities), 'IN') + ->condition('uid', $uids) ->execute(); - $this->userData->delete(NULL, array_keys($entities)); } /** diff --git a/core/modules/user/lib/Drupal/user/UserStorageControllerInterface.php b/core/modules/user/lib/Drupal/user/UserStorageControllerInterface.php new file mode 100644 index 00000000000..87688696cab --- /dev/null +++ b/core/modules/user/lib/Drupal/user/UserStorageControllerInterface.php @@ -0,0 +1,39 @@ + array( + 'default' => array( + 'display_plugin' => 'default', + 'id' => 'default', + 'display_title' => 'Master', + 'position' => 0, + 'display_options' => array(), + ), + ) + ); + } + + /** + * {@inheritdoc} + */ + public function postCreate(EntityStorageControllerInterface $storage_controller) { + $this->mergeDefaultDisplaysOptions(); + } + + /** + * {@inheritdoc} + */ + public function mergeDefaultDisplaysOptions() { + $displays = array(); + foreach ($this->get('display') as $key => $options) { + $options += array( + 'display_options' => array(), + 'display_plugin' => NULL, + 'id' => NULL, + 'display_title' => '', + 'position' => NULL, + ); + // Add the defaults for the display. + $displays[$key] = $options; + } + // Sort the displays. + uasort($displays, function ($display1, $display2) { + if ($display1['position'] != $display2['position']) { + return $display1['position'] < $display2['position'] ? -1 : 1; + } + return 0; + }); + $this->set('display', $displays); + } + } diff --git a/core/modules/views/lib/Drupal/views/ViewStorageController.php b/core/modules/views/lib/Drupal/views/ViewStorageController.php index 3ac6731a7c9..a7e131908b5 100644 --- a/core/modules/views/lib/Drupal/views/ViewStorageController.php +++ b/core/modules/views/lib/Drupal/views/ViewStorageController.php @@ -35,72 +35,11 @@ class ViewStorageController extends ConfigStorageController { */ protected function attachLoad(&$queried_entities, $revision_id = FALSE) { foreach ($queried_entities as $id => $entity) { - $this->mergeDefaultDisplaysOptions($entity); + $entity->mergeDefaultDisplaysOptions(); } parent::attachLoad($queried_entities, $revision_id); } - /** - * Overrides Drupal\config\ConfigStorageController::postSave(). - */ - protected function postSave(EntityInterface $entity, $update) { - parent::postSave($entity, $update); - // Clear caches. - views_invalidate_cache(); - } - - /** - * Overrides Drupal\config\ConfigStorageController::create(). - */ - public function create(array $values) { - // If there is no information about displays available add at least the - // default display. - $values += array( - 'display' => array( - 'default' => array( - 'display_plugin' => 'default', - 'id' => 'default', - 'display_title' => 'Master', - 'position' => 0, - 'display_options' => array(), - ), - ) - ); - - $entity = parent::create($values); - - $this->mergeDefaultDisplaysOptions($entity); - return $entity; - } - - /** - * Add defaults to the display options. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The view entity to attach default displays options. - */ - protected function mergeDefaultDisplaysOptions(EntityInterface $entity) { - $displays = array(); - foreach ($entity->get('display') as $key => $options) { - $options += array( - 'display_options' => array(), - 'display_plugin' => NULL, - 'id' => NULL, - 'display_title' => '', - 'position' => NULL, - ); - // Add the defaults for the display. - $displays[$key] = $options; - } - // Sort the displays. - uasort($displays, function ($display1, $display2) { - if ($display1['position'] != $display2['position']) { - return $display1['position'] < $display2['position'] ? -1 : 1; - } - return 0; - }); - $entity->set('display', $displays); - } } diff --git a/core/modules/views/lib/Drupal/views/ViewStorageInterface.php b/core/modules/views/lib/Drupal/views/ViewStorageInterface.php index b09acb263fa..7009bae34dc 100644 --- a/core/modules/views/lib/Drupal/views/ViewStorageInterface.php +++ b/core/modules/views/lib/Drupal/views/ViewStorageInterface.php @@ -25,4 +25,8 @@ interface ViewStorageInterface extends \IteratorAggregate, ConfigEntityInterface */ public function &getDisplay($display_id); + /** + * Add defaults to the display options. + */ + public function mergeDefaultDisplaysOptions(); } diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php index f83e84fb886..beaee0a1f73 100644 --- a/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php +++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php @@ -8,6 +8,7 @@ namespace Drupal\views_ui; use Drupal\views\Views; +use Drupal\Core\Entity\EntityStorageControllerInterface; use Drupal\views\ViewExecutable; use Drupal\Core\Database\Database; use Drupal\Core\TypedData\TypedDataInterface; @@ -1136,12 +1137,69 @@ class ViewUI implements ViewStorageInterface { $this->storage->onChange($property_name); } + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageControllerInterface $storage_controller) { + $this->storage->presave($storage_controller); + } + + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) { + $this->storage->postSave($storage_controller, $update); + } + + /** + * {@inheritdoc} + */ + public static function preCreate(EntityStorageControllerInterface $storage_controller, array &$values) { + } + + /** + * {@inheritdoc} + */ + public function postCreate(EntityStorageControllerInterface $storage_controller) { + $this->storage->postCreate($storage_controller); + } + + /** + * {@inheritdoc} + */ + public static function preDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + } + + /** + * {@inheritdoc} + */ + public static function postDelete(EntityStorageControllerInterface $storage_controller, array $entities) { + } + + /** + * {@inheritdoc} + */ + public static function postLoad(EntityStorageControllerInterface $storage_controller, array $entities) { + } + + /** + * {@inheritdoc} + */ + public function preSaveRevision(EntityStorageControllerInterface $storage_controller, \stdClass $record) { + $this->storage->preSaveRevision($storage_controller, $record); + } + + /** + * {@inheritdoc} + */ + public function mergeDefaultDisplaysOptions() { + $this->storage->mergeDefaultDisplaysOptions(); + } + /** * {@inheritdoc} */ public function uriRelationships() { return $this->storage->uriRelationships(); } - - }