diff --git a/islandora.module b/islandora.module index d715db52d..8fa478a7f 100644 --- a/islandora.module +++ b/islandora.module @@ -16,19 +16,18 @@ use Drupal\Component\Plugin\Exception\PluginNotFoundException; use Drupal\Core\Entity\Display\EntityViewDisplayInterface; +use Drupal\Core\Entity\EntityForm; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Site\Settings; use Drupal\Core\Url; +use Drupal\file\FileInterface; use Drupal\islandora\Form\IslandoraSettingsForm; -use Drupal\node\NodeInterface; use Drupal\media\MediaInterface; -use Drupal\file\FileInterface; -use Drupal\taxonomy\TermInterface; -use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\node\NodeInterface; use Drupal\serialization\Normalizer\CacheableNormalizerInterface; -use Drupal\Core\Entity\EntityForm; -use Drupal\file\Entity\File; +use Drupal\taxonomy\TermInterface; /** * Implements hook_help(). @@ -408,132 +407,25 @@ function islandora_media_custom_form_submit(&$form, FormStateInterface $form_sta * Implements a submit handler for the delete form. */ function islandora_object_delete_form_submit($form, FormStateInterface $form_state) { - $result = $form_state->getValues('delete_associated_content'); - $utils = \Drupal::service('islandora.utils'); - if ($result['delete_associated_content'] == 1) { - + $utils = \Drupal::service('islandora.utils'); $node = $form_state->getFormObject()->getEntity(); $medias = $utils->getMedia($node); - $media_list = []; - - $entity_field_manager = \Drupal::service('entity_field.manager'); - $current_user = \Drupal::currentUser(); + $results = $utils->deleteMediaAndFiles($medias); $logger = \Drupal::logger('logger.channel.islandora'); $messenger = \Drupal::messenger(); - - $delete_media = []; - $media_translations = []; - $media_files = []; - $entity_protected_medias = []; - $inaccessible_entities = []; - - foreach ($medias as $id => $media) { - $lang = $media->language()->getId(); - $selected_langcodes[$lang] = $lang; - - if (!$media->access('delete', $current_user)) { - $inaccessible_entities[] = $media; - continue; - } - // Check for files. - $fields = $entity_field_manager->getFieldDefinitions('media', $media->bundle()); - foreach ($fields as $field) { - $type = $field->getType(); - if ($type == 'file' || $type == 'image') { - $target_id = $media->get($field->getName())->target_id; - $file = File::load($target_id); - if ($file) { - if (!$file->access('delete', $current_user)) { - $inaccessible_entities[] = $file; - continue; - } - $media_files[$id][$file->id()] = $file; - } - } - } - - foreach ($selected_langcodes as $langcode) { - // We're only working with media, which are translatable. - $entity = $media->getTranslation($langcode); - if ($entity->isDefaultTranslation()) { - $delete_media[$id] = $entity; - unset($media_translations[$id]); - } - elseif (!isset($delete_media[$id])) { - $media_translations[$id][] = $entity; - } - } + if (isset($results['inaccessible'])) { + $messenger->addWarning($results['inaccessible']); } - - if ($delete_media) { - foreach ($delete_media as $id => $media) { - try { - $media->delete(); - $media_list[] = $id; - $logger->notice('The media %label has been deleted.', [ - '%label' => $media->label(), - ]); - } - catch (Exception $e) { - $entity_protected_medias[] = $id; - } - } - } - - $delete_files = array_filter($media_files, function ($media) use ($entity_protected_medias) { - return !in_array($media, $entity_protected_medias); - }, ARRAY_FILTER_USE_KEY); - - if ($delete_files) { - foreach ($delete_files as $files_array) { - foreach ($files_array as $file) { - $file->delete(); - $logger->notice('The file %label has been deleted.', [ - '%label' => $file->label(), - ]); - } - } - } - - $delete_media_translations = array_filter($media_translations, function ($media) use ($entity_protected_medias) { - return !in_array($media, $entity_protected_medias); - }, ARRAY_FILTER_USE_KEY); - - if ($delete_media_translations) { - foreach ($delete_media_translations as $id => $translations) { - $media = $medias[$id]; - foreach ($translations as $translation) { - $media->removeTranslation($translation->language()->getId()); - } - $media->save(); - foreach ($translations as $translation) { - $logger->notice('The media %label @language translation has been deleted', [ - '%label' => $media->label(), - '@language' => $translation->language()->getName(), - ]); - } - } - } - - if ($inaccessible_entities) { - $messenger->addWarning("@count items have not been deleted because you do not have the necessary permissions.", [ - '@count' => count($inaccessible_entities), - ]); - } - + $logger->notice($results['deleted']); $build = [ 'heading' => [ '#type' => 'html_tag', '#tag' => 'div', '#value' => t("The repository item @node and @media", [ '@node' => $node->getTitle(), - '@media' => \Drupal::translation()->formatPlural( - count($media_list), 'the media with the id @media has been deleted.', - 'the medias with the ids @media have been deleted.', - ['@media' => implode(", ", $media_list)], - ), + '@media' => $results['deleted'], ]), ], ]; diff --git a/islandora.services.yml b/islandora.services.yml index 74725d803..36ef7dac3 100644 --- a/islandora.services.yml +++ b/islandora.services.yml @@ -54,7 +54,7 @@ services: arguments: ['@entity_type.manager', '@current_user', '@language_manager', '@file_system', '@islandora.utils'] islandora.utils: class: Drupal\islandora\IslandoraUtils - arguments: ['@entity_type.manager', '@entity_field.manager', '@context.manager', '@flysystem_factory', '@language_manager'] + arguments: ['@entity_type.manager', '@entity_field.manager', '@context.manager', '@flysystem_factory', '@language_manager', '@current_user'] islandora.entity_mapper: class: Islandora\EntityMapper\EntityMapper islandora.stomp.auth_header_listener: diff --git a/src/Form/ConfirmDeleteMediaAndFile.php b/src/Form/ConfirmDeleteMediaAndFile.php index a9613871f..90e61878a 100644 --- a/src/Form/ConfirmDeleteMediaAndFile.php +++ b/src/Form/ConfirmDeleteMediaAndFile.php @@ -2,7 +2,6 @@ namespace Drupal\islandora\Form; -use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\Form\DeleteMultipleForm; use Drupal\Core\Form\FormStateInterface; @@ -10,8 +9,7 @@ use Drupal\Core\Session\AccountInterface; use Drupal\Core\TempStore\PrivateTempStoreFactory; use Drupal\Core\Url; -use Drupal\file\Entity\File; -use Drupal\islandora\MediaSource\MediaSourceService; +use Drupal\islandora\IslandoraUtils; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -21,11 +19,11 @@ class ConfirmDeleteMediaAndFile extends DeleteMultipleForm { /** - * Media source service. + * The current user. * - * @var \Drupal\islandora\MediaSource\MediaSourceService + * @var \Drupal\Core\Session\AccountInterface */ - protected $mediaSourceService; + protected $currentUser; /** * Logger. @@ -42,23 +40,22 @@ class ConfirmDeleteMediaAndFile extends DeleteMultipleForm { protected $selection = []; /** - * Entity field manager. + * The Islandora Utils service. * - * @var \Drupal\Core\Entity\EntityFieldManagerInterface + * @var \Drupal\islandora\IslandoraUtils */ - protected $entityFieldManager; + protected IslandoraUtils $utils; /** * {@inheritdoc} */ - public function __construct(AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, PrivateTempStoreFactory $temp_store_factory, MessengerInterface $messenger, MediaSourceService $media_source_service, LoggerInterface $logger) { + public function __construct(AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory, MessengerInterface $messenger, LoggerInterface $logger, IslandoraUtils $utils) { $this->currentUser = $current_user; $this->entityTypeManager = $entity_type_manager; - $this->entityFieldManager = $entity_field_manager; $this->tempStore = $temp_store_factory->get('media_and_file_delete_confirm'); $this->messenger = $messenger; - $this->mediaSourceService = $media_source_service; $this->logger = $logger; + $this->utils = $utils; } /** @@ -68,11 +65,11 @@ public static function create(ContainerInterface $container) { return new static( $container->get('current_user'), $container->get('entity_type.manager'), - $container->get('entity_field.manager'), $container->get('tempstore.private'), $container->get('messenger'), - $container->get('islandora.media_source_service'), - $container->get('logger.channel.islandora')); + $container->get('logger.channel.islandora'), + $container->get('islandora.utils') + ); } /** @@ -111,94 +108,13 @@ public function buildForm(array $form, FormStateInterface $form_state, $entity_t public function submitForm(array &$form, FormStateInterface $form_state) { // Similar to parent::submitForm(), but let's blend in the related files and // optimize based on the fact that we know we're working with media. - $total_count = 0; - $delete_media = []; - $delete_media_translations = []; - $delete_files = []; - $inaccessible_entities = []; $media_storage = $this->entityTypeManager->getStorage('media'); - $file_storage = $this->entityTypeManager->getStorage('file'); $media = $media_storage->loadMultiple(array_keys($this->selection)); - foreach ($this->selection as $id => $selected_langcodes) { - $entity = $media[$id]; - if (!$entity->access('delete', $this->currentUser)) { - $inaccessible_entities[] = $entity; - continue; - } - // Check for files. - $fields = $this->entityFieldManager->getFieldDefinitions('media', $entity->bundle()); - foreach ($fields as $field) { - if ($field->getName() == 'thumbnail') { - continue; - } - $type = $field->getType(); - if ($type == 'file' || $type == 'image') { - $target_id = $entity->get($field->getName())->target_id; - $file = File::load($target_id); - if ($file) { - if (!$file->access('delete', $this->currentUser)) { - $inaccessible_entities[] = $file; - continue; - } - if (!array_key_exists($file->id(), $delete_files)) { - $delete_files[$file->id()] = $file; - $total_count++; - } - - } - } - } - - foreach ($selected_langcodes as $langcode) { - // We're only working with media, which are translatable. - $entity = $entity->getTranslation($langcode); - if ($entity->isDefaultTranslation()) { - $delete_media[$id] = $entity; - unset($delete_media_translations[$id]); - $total_count += count($entity->getTranslationLanguages()); - } - elseif (!isset($delete_media[$id])) { - $delete_media_translations[$id][] = $entity; - } - } - } - if ($delete_media) { - $media_storage->delete($delete_media); - foreach ($delete_media as $entity) { - $this->logger->notice('The media %label has been deleted.', [ - '%label' => $entity->label(), - ]); - } - } - if ($delete_files) { - $file_storage->delete($delete_files); - foreach ($delete_files as $entity) { - $this->logger->notice('The file %label has been deleted.', [ - '%label' => $entity->label(), - ]); - } - } - if ($delete_media_translations) { - foreach ($delete_media_translations as $id => $translations) { - $entity = $media[$id]; - foreach ($translations as $translation) { - $entity->removeTranslation($translation->language()->getId()); - } - $entity->save(); - foreach ($translations as $translation) { - $this->logger->notice('The media %label @language translation has been deleted', [ - '%label' => $entity->label(), - '@language' => $translation->language()->getName(), - ]); - } - $total_count += count($translations); - } - } - if ($total_count) { - $this->messenger->addStatus($this->getDeletedMessage($total_count)); - } - if ($inaccessible_entities) { - $this->messenger->addWarning($this->getInaccessibleMessage(count($inaccessible_entities))); + $results = $this->utils->deleteMediaAndFiles($media); + $this->logger->notice($results['deleted']); + $this->messenger->addStatus($results['deleted']); + if (isset($results['inaccessible'])) { + $this->messenger->addWarning($results['inaccessible']); } $this->tempStore->delete($this->currentUser->id()); $form_state->setRedirectUrl($this->getCancelUrl()); diff --git a/src/IslandoraUtils.php b/src/IslandoraUtils.php index a2df75896..5d667fc42 100644 --- a/src/IslandoraUtils.php +++ b/src/IslandoraUtils.php @@ -10,7 +10,9 @@ use Drupal\Core\Entity\Query\QueryException; use Drupal\Core\Entity\Query\QueryInterface; use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\Session\AccountInterface; use Drupal\Core\Site\Settings; +use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\Url; use Drupal\file\FileInterface; use Drupal\flysystem\FlysystemFactory; @@ -26,13 +28,15 @@ * Utility functions for figuring out when to fire derivative reactions. */ class IslandoraUtils { - + use StringTranslationTrait; const EXTERNAL_URI_FIELD = 'field_external_uri'; const MEDIA_OF_FIELD = 'field_media_of'; const MEDIA_USAGE_FIELD = 'field_media_use'; + const MEMBER_OF_FIELD = 'field_member_of'; + const MODEL_FIELD = 'field_model'; /** @@ -68,7 +72,14 @@ class IslandoraUtils { * * @var \Drupal\Core\Language\LanguageManagerInterface */ - protected $languageManager; + protected LanguageManagerInterface $languageManager; + + /** + * The current user. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected AccountInterface $currentUser; /** * Constructor. @@ -83,19 +94,23 @@ class IslandoraUtils { * Flysystem factory. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager * Language manager. + * @param \Drupal\Core\Session\AccountInterface $current_user + * The current user. */ public function __construct( EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, ContextManager $context_manager, FlysystemFactory $flysystem_factory, - LanguageManagerInterface $language_manager + LanguageManagerInterface $language_manager, + AccountInterface $current_user ) { $this->entityTypeManager = $entity_type_manager; $this->entityFieldManager = $entity_field_manager; $this->contextManager = $context_manager; $this->flysystemFactory = $flysystem_factory; $this->languageManager = $language_manager; + $this->currentUser = $current_user; } /** @@ -423,7 +438,6 @@ public function executeDerivativeReactions($reaction_type, NodeInterface $node, * TRUE if the fields have changed. */ public function haveFieldsChanged(ContentEntityInterface $entity, ContentEntityInterface $original) { - $field_definitions = $this->entityFieldManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle()); $ignore_list = ['vid' => 1, 'changed' => 1, 'path' => 1]; @@ -528,7 +542,8 @@ public function getMediaReferencingNodeAndTerm(NodeInterface $node, TermInterfac * Array of fields. */ public function getReferencingFields($entity_type, $target_type) { - $fields = $this->entityTypeManager->getStorage('field_storage_config')->getQuery() + $fields = $this->entityTypeManager->getStorage('field_storage_config') + ->getQuery() ->condition('entity_type', $entity_type) ->condition('settings.target_type', $target_type) ->execute(); @@ -657,8 +672,6 @@ public function isIslandoraType($entity_type, $bundle) { public function canCreateIslandoraEntity($entity_type, $bundle_type) { $bundles = $this->entityTypeManager->getStorage($bundle_type)->loadMultiple(); $access_control_handler = $this->entityTypeManager->getAccessControlHandler($entity_type); - - $allowed = []; foreach (array_keys($bundles) as $bundle) { // Skip bundles that aren't 'Islandora' types. if (!$this->isIslandoraType($entity_type, $bundle)) { @@ -755,4 +768,71 @@ protected function getParentsByEntityReference(ContentEntityInterface $entity, a return $parents; } + /** + * Deletes Media and all associated files. + * + * @param \Drupal\media\MediaInterface[] $media + * Array of media objects to be deleted along with their files. + * + * @return array + * Associative array keyed 'deleted' and 'inaccessible'. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + * @throws \Drupal\Core\Entity\EntityStorageException + */ + public function deleteMediaAndFiles(array $media) { + $results = []; + $delete_media = []; + $delete_files = []; + $inaccessible_entities = []; + $media_storage = $this->entityTypeManager->getStorage('media'); + $file_storage = $this->entityTypeManager->getStorage('file'); + foreach ($media as $entity) { + if (!$entity->access('delete', $this->currentUser)) { + $inaccessible_entities[] = $entity; + continue; + } + else { + $delete_media[$entity->id()] = $entity; + } + // Check for source and additional files. + $fields = $this->entityFieldManager->getFieldDefinitions('media', $entity->bundle()); + foreach ($fields as $field) { + if ($field->getName() == 'thumbnail') { + continue; + } + $type = $field->getType(); + if ($type == 'file' || $type == 'image') { + $target_id = $entity->get($field->getName())->target_id; + $file = $file_storage->load($target_id); + if ($file) { + if (!$file->access('delete', $this->currentUser)) { + $inaccessible_entities[] = $file; + continue; + } + if (!array_key_exists($file->id(), $delete_files)) { + $delete_files[$file->id()] = $file; + } + } + } + } + } + if ($delete_media) { + $media_storage->delete($delete_media); + } + if ($delete_files) { + $file_storage->delete($delete_files); + } + $results['deleted'] = $this->formatPlural( + count($delete_media), 'The media with the id @media has been deleted.', + 'The medias with the ids @media have been deleted.', + ['@media' => implode(", ", array_keys($delete_media))], + ); + if ($inaccessible_entities) { + $results['inaccessible'] = $this->formatPlural($inaccessible_entities, "@count item has not been deleted because you do not have the necessary permissions.", "@count items have not been deleted because you do not have the necessary permissions."); + } + return $results; + } + } diff --git a/tests/src/Functional/DeleteMediaTest.php b/tests/src/Functional/DeleteMediaTest.php index c52eca31f..957146fdc 100644 --- a/tests/src/Functional/DeleteMediaTest.php +++ b/tests/src/Functional/DeleteMediaTest.php @@ -93,7 +93,7 @@ public function testDeleteMediaAndFile() { $this->assertSession()->pageTextContains('Are you sure you want to delete this media and associated files?'); $page->pressButton('Delete'); // Should assert that a media and file were deleted. - $this->assertSession()->pageTextContains('Deleted 2 items.'); + $this->assertSession()->pageTextContains("The media with the id $mid has been deleted"); // Attempt to reload the entities. // Both media and file should be gone.