Browse Source

All together, I think...

Both the child-uploading, and media-uploading forms.
pull/896/head
Adam Vessey 3 years ago
parent
commit
ad1b6ba013
No known key found for this signature in database
GPG Key ID: 89B39535BF6D0D39
  1. 13
      islandora.routing.yml
  2. 8
      islandora.services.yml
  3. 2
      src/Form/AddChildrenForm.php
  4. 254
      src/Form/AddChildrenWizard/AbstractBatchProcessor.php
  5. 58
      src/Form/AddChildrenWizard/AbstractFileSelectionForm.php
  6. 28
      src/Form/AddChildrenWizard/AbstractForm.php
  7. 22
      src/Form/AddChildrenWizard/Access.php
  8. 190
      src/Form/AddChildrenWizard/ChildBatchProcessor.php
  9. 32
      src/Form/AddChildrenWizard/ChildFileSelectionForm.php
  10. 24
      src/Form/AddChildrenWizard/ChildForm.php
  11. 157
      src/Form/AddChildrenWizard/ChildTypeSelectionForm.php
  12. 2
      src/Form/AddChildrenWizard/FieldTrait.php
  13. 34
      src/Form/AddChildrenWizard/MediaBatchProcessor.php
  14. 32
      src/Form/AddChildrenWizard/MediaFileSelectionForm.php
  15. 24
      src/Form/AddChildrenWizard/MediaForm.php
  16. 130
      src/Form/AddChildrenWizard/MediaTypeSelectionForm.php
  17. 2
      src/Form/AddChildrenWizard/MediaTypeTrait.php
  18. 2
      src/Form/AddMediaForm.php

13
islandora.routing.yml

@ -39,13 +39,13 @@ islandora.add_member_to_node_page:
islandora.upload_children:
path: '/node/{node}/members/upload/{step}'
defaults:
_wizard: '\Drupal\islandora\Form\AddChildrenWizard\Form'
_wizard: '\Drupal\islandora\Form\AddChildrenWizard\ChildForm'
_title: 'Upload Children'
step: 'child_type'
step: 'type_selection'
options:
_admin_route: 'TRUE'
requirements:
_custom_access: '\Drupal\islandora\Form\AddChildrenWizard\Access::checkAccess'
_custom_access: '\Drupal\islandora\Form\AddChildrenWizard\Access::childAccess'
islandora.add_media_to_node_page:
path: '/node/{node}/media/add'
@ -59,14 +59,15 @@ islandora.add_media_to_node_page:
_entity_create_any_access: 'media'
islandora.upload_media:
path: '/node/{node}/media/upload'
path: '/node/{node}/media/upload/{step}'
defaults:
_form: '\Drupal\islandora\Form\AddMediaForm'
_wizard: '\Drupal\islandora\Form\AddChildrenWizard\MediaForm'
_title: 'Add media'
step: 'type_selection'
options:
_admin_route: 'TRUE'
requirements:
_custom_access: '\Drupal\islandora\Form\AddMediaForm::access'
_custom_access: '\Drupal\islandora\Form\AddChildrenWizard\Access::mediaAccess'
islandora.media_source_update:
path: '/media/{media}/source'

8
islandora.services.yml

@ -65,3 +65,11 @@ services:
- '@entity_type.manager'
- '@database'
- '@current_user'
- '@messenger'
islandora.upload_media.batch_processor:
class: Drupal\islandora\Form\AddChildrenWizard\MediaBatchProcessor
arguments:
- '@entity_type.manager'
- '@database'
- '@current_user'
- '@messenger'

2
src/Form/AddChildrenForm.php

@ -13,7 +13,7 @@ use Symfony\Component\HttpKernel\Exception\HttpException;
/**
* Form that lets users upload one or more files as children to a resource node.
*
* @deprecated Replaced with the "wizard" appraach.
* @deprecated Use the \Drupal\islandora\Form\AddChildrenWizard\ChildForm instead.
*/
class AddChildrenForm extends AddMediaForm {

254
src/Form/AddChildrenWizard/AbstractBatchProcessor.php

@ -0,0 +1,254 @@
<?php
namespace Drupal\islandora\Form\AddChildrenWizard;
use Drupal\Core\Database\Connection;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\file\FileInterface;
use Drupal\islandora\IslandoraUtils;
use Drupal\media\MediaInterface;
use Drupal\node\NodeInterface;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
/**
* Children addition batch processor.
*/
abstract class AbstractBatchProcessor {
use FieldTrait;
use DependencySerializationTrait;
use StringTranslationTrait;
/**
* The entity type manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface|null
*/
protected ?EntityTypeManagerInterface $entityTypeManager = NULL;
/**
* The database connection serivce.
*
* @var \Drupal\Core\Database\Connection|null
*/
protected ?Connection $database;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountProxyInterface|null
*/
protected ?AccountProxyInterface $currentUser;
/**
* The messenger service.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected MessengerInterface $messenger;
/**
* Constructor.
*/
public function __construct(
EntityTypeManagerInterface $entity_type_manager,
Connection $database,
AccountProxyInterface $current_user,
MessengerInterface $messenger
) {
$this->entityTypeManager = $entity_type_manager;
$this->database = $database;
$this->currentUser = $current_user;
$this->messenger = $messenger;
}
/**
* Implements callback_batch_operation() for our child addition batch.
*/
public function batchOperation($delta, $info, array $values, &$context) {
$transaction = $this->database->startTransaction();
try {
$entities[] = $this->persistFile($info, $values);
$entities[] = $node = $this->getNode($info, $values);
$entities[] = $this->createMedia($node, $info, $values);
$context['results'] = array_merge_recursive($context['results'], [
'validation_violations' => $this->validationClassification($entities),
]);
$context['results']['count'] += 1;
}
catch (HttpExceptionInterface $e) {
$transaction->rollBack();
throw $e;
}
catch (\Exception $e) {
$transaction->rollBack();
throw new HttpException(500, $e->getMessage(), $e);
}
}
/**
* Loads the file indicated.
*
* @param array $info
* An associative array containing at least:
* - target_id: The id of the file to load.
*
* @return \Drupal\file\FileInterface
* The loaded file.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function getFile(array $info) : FileInterface {
return $this->entityTypeManager->getStorage('file')->load($info['target_id']);
}
/**
* Loads and marks the target file as permanent.
*
* @param array $info
* An associative array containing at least:
* - target_id: The id of the file to load.
*
* @return \Drupal\file\FileInterface
* The loaded file, after it has been marked as permanent.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Core\Entity\EntityStorageException
*/
protected function persistFile(array $info) : FileInterface {
$file = $this->getFile($info);
$file->setPermanent();
if ($file->save() !== SAVED_UPDATED) {
throw new \Exception("Failed to update file '{$file->id()}' to be permanent.");
}
return $file;
}
/**
* Get the node to which to attach our media.
*
* @param array $info
* Info from the widget used to create the request.
* @param array $values
* Additional form inputs.
*
* @return \Drupal\node\NodeInterface
* The node to which to attach the created media.
*/
abstract protected function getNode(array $info, array $values) : NodeInterface;
/**
* Create a media referencing the given file, associated with the given node.
*
* @param \Drupal\node\NodeInterface $node
* The node to which the media should be associated.
* @param array $info
* The widget info, which should have a 'target_id' identifying the target
* file.
* @param array $values
* Values from the wizard, which should contain at least:
* - media_type: The machine name/ID of the media type as which to create
* the media
* - use: An array of the selected "media use" terms.
*
* @return \Drupal\media\MediaInterface
* The created media entity.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Core\Entity\EntityStorageException
*/
protected function createMedia(NodeInterface $node, array $info, array $values) : MediaInterface {
$taxonomy_term_storage = $this->entityTypeManager->getStorage('taxonomy_term');
$file = $this->getFile($info);
// Create a media with the file attached and also pointing at the node.
$field = $this->getField($values);
$media_values = array_merge(
[
'bundle' => $values['media_type'],
'name' => $file->getFilename(),
IslandoraUtils::MEDIA_OF_FIELD => $node,
IslandoraUtils::MEDIA_USAGE_FIELD => ($values['use'] ?
$taxonomy_term_storage->loadMultiple($values['use']) :
NULL),
'uid' => $this->currentUser->id(),
// XXX: Published... no constant?
'status' => 1,
],
[
$field->getName() => [
$info,
],
]
);
$media = $this->entityTypeManager->getStorage('media')->create($media_values);
if ($media->save() !== SAVED_NEW) {
throw new \Exception("Failed to create media for file '{$file->id()}.");
}
return $media;
}
/**
* Helper to bulk process validatable entities.
*
* @param array $entities
* An array of entities to scan for validation violations.
*
* @return array
* An associative array mapping entity type IDs to entity IDs to a count
* of validation violations found on then given entity.
*/
protected function validationClassification(array $entities) {
$violations = [];
foreach ($entities as $entity) {
$entity_violations = $entity->validate();
if ($entity_violations->count() > 0) {
$violations[$entity->getEntityTypeId()][$entity->id()] = $entity_violations->count();
}
}
return $violations;
}
/**
* Implements callback_batch_finished() for our child addition batch.
*/
public function batchProcessFinished($success, $results, $operations): void {
if ($success) {
foreach ($results['validation_violations'] ?? [] as $entity_type => $info) {
foreach ($info as $id => $count) {
$this->messenger->addWarning($this->formatPlural(
$count,
'1 validation error present in <a target="_blank" href=":uri">bulk created entity of type %type, with ID %id</a>.',
'@count validation errors present in <a target="_blank" href=":uri">bulk created entity of type %type, with ID %id</a>.',
[
'%type' => $entity_type,
':uri' => Url::fromRoute("entity.{$entity_type}.canonical", [$entity_type => $id])->toString(),
'%id' => $id,
]
));
}
}
}
else {
$this->messenger->addError($this->t('Encountered an error when processing.'));
}
}
}

58
src/Form/AddChildrenWizard/FileSelectionForm.php → src/Form/AddChildrenWizard/AbstractFileSelectionForm.php

@ -3,7 +3,6 @@
namespace Drupal\islandora\Form\AddChildrenWizard;
use Drupal\Core\Batch\BatchBuilder;
use Drupal\Core\Database\Connection;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemList;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
@ -11,27 +10,17 @@ use Drupal\Core\Field\WidgetInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Url;
use Drupal\media\MediaTypeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Children addition wizard's second step.
*/
class FileSelectionForm extends FormBase {
abstract class AbstractFileSelectionForm extends FormBase {
use WizardTrait {
WizardTrait::getField as doGetField;
WizardTrait::getMediaType as doGetMediaType;
WizardTrait::getWidget as doGetWidget;
}
use WizardTrait;
/**
* The database connection serivce.
*
* @var \Drupal\Core\Database\Connection|null
*/
protected ?Connection $database;
const BATCH_PROCESSOR = 'abstract.abstract';
/**
* The current user.
@ -43,9 +32,9 @@ class FileSelectionForm extends FormBase {
/**
* The batch processor service.
*
* @var \Drupal\islandora\Form\AddChildrenWizard\ChildBatchProcessor|null
* @var \Drupal\islandora\Form\AddChildrenWizard\AbstractBatchProcessor|null
*/
protected ?ChildBatchProcessor $batchProcessor;
protected ?AbstractBatchProcessor $batchProcessor;
/**
* {@inheritdoc}
@ -56,21 +45,13 @@ class FileSelectionForm extends FormBase {
$instance->entityTypeManager = $container->get('entity_type.manager');
$instance->widgetPluginManager = $container->get('plugin.manager.field.widget');
$instance->entityFieldManager = $container->get('entity_field.manager');
$instance->database = $container->get('database');
$instance->currentUser = $container->get('current_user');
$instance->batchProcessor = $container->get('islandora.upload_children.batch_processor');
$instance->batchProcessor = $container->get(static::BATCH_PROCESSOR);
return $instance;
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'islandora_add_children_wizard_file_selection';
}
/**
* Helper; get the media type, based off discovering from form state.
*
@ -83,8 +64,8 @@ class FileSelectionForm extends FormBase {
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function getMediaType(FormStateInterface $form_state): MediaTypeInterface {
return $this->doGetMediaType($form_state->getTemporaryValue('wizard'));
protected function getMediaTypeFromFormState(FormStateInterface $form_state): MediaTypeInterface {
return $this->getMediaType($form_state->getTemporaryValue('wizard'));
}
/**
@ -96,10 +77,10 @@ class FileSelectionForm extends FormBase {
* @return \Drupal\Core\Field\FieldDefinitionInterface
* The field definition.
*/
protected function getField(FormStateInterface $form_state): FieldDefinitionInterface {
protected function getFieldFromFormState(FormStateInterface $form_state): FieldDefinitionInterface {
$cached_values = $form_state->getTemporaryValue('wizard');
$field = $this->doGetField($cached_values);
$field = $this->getField($cached_values);
$field->getFieldStorageDefinition()->set('cardinality', FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
return $field;
@ -114,8 +95,8 @@ class FileSelectionForm extends FormBase {
* @return \Drupal\Core\Field\WidgetInterface
* The widget.
*/
protected function getWidget(FormStateInterface $form_state): WidgetInterface {
return $this->doGetWidget($this->getField($form_state));
protected function getWidgetFromFormState(FormStateInterface $form_state): WidgetInterface {
return $this->getWidget($this->getFieldFromFormState($form_state));
}
/**
@ -125,12 +106,12 @@ class FileSelectionForm extends FormBase {
// Using the media type selected in the previous step, grab the
// media bundle's "source" field, and create a multi-file upload widget
// for it, with the same kind of constraints.
$field = $this->getField($form_state);
$items = FieldItemList::createInstance($field, $field->getName(), $this->getMediaType($form_state)->getTypedData());
$field = $this->getFieldFromFormState($form_state);
$items = FieldItemList::createInstance($field, $field->getName(), $this->getMediaTypeFromFormState($form_state)->getTypedData());
$form['#tree'] = TRUE;
$form['#parents'] = [];
$widget = $this->getWidget($form_state);
$widget = $this->getWidgetFromFormState($form_state);
$form['files'] = $widget->form(
$items,
$form,
@ -146,21 +127,20 @@ class FileSelectionForm extends FormBase {
public function submitForm(array &$form, FormStateInterface $form_state) {
$cached_values = $form_state->getTemporaryValue('wizard');
$widget = $this->getWidget($form_state);
$widget = $this->getWidgetFromFormState($form_state);
$builder = (new BatchBuilder())
->setTitle($this->t('Creating children...'))
->setInitMessage($this->t('Initializing...'))
->setFinishCallback([$this->batchProcessor, 'batchProcessFinished']);
$values = $form_state->getValue($this->doGetField($cached_values)->getName());
$values = $form_state->getValue($this->getField($cached_values)->getName());
$massaged_values = $widget->massageFormValues($values, $form, $form_state);
foreach ($massaged_values as $delta => $file) {
foreach ($massaged_values as $delta => $info) {
$builder->addOperation(
[$this->batchProcessor, 'batchOperation'],
[$delta, $file, $cached_values]
[$delta, $info, $cached_values]
);
}
batch_set($builder->toArray());
$form_state->setRedirectUrl(Url::fromUri("internal:/node/{$cached_values['node']}/members"));
}
}

28
src/Form/AddChildrenWizard/Form.php → src/Form/AddChildrenWizard/AbstractForm.php

@ -14,7 +14,12 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Bulk children addition wizard base form.
*/
class Form extends FormWizardBase {
abstract class AbstractForm extends FormWizardBase {
const TEMPSTORE_ID = 'abstract.abstract';
const TYPE_SELECTION_FORM = MediaTypeSelectionForm::class;
const FILE_SELECTION_FORM = AbstractFileSelectionForm::class;
const BATCH_PROCESSOR_SERVICE_NAME = 'abstract.abstract';
/**
* The Islandora Utils service.
@ -72,39 +77,28 @@ class Form extends FormWizardBase {
return array_merge(
parent::getParameters(),
[
'tempstore_id' => 'islandora.upload_children',
'tempstore_id' => static::TEMPSTORE_ID,
'current_user' => \Drupal::service('current_user'),
'batch_processor' => \Drupal::service('islandora.upload_children.batch_processor'),
]
);
}
/**
* {@inheritdoc}
*/
public function getMachineName() {
return strtr("islandora_add_children_wizard__{userid}__{nodeid}", [
'{userid}' => $this->currentUser->id(),
'{nodeid}' => $this->nodeId,
]);
}
/**
* {@inheritdoc}
*/
public function getOperations($cached_values) {
$ops = [];
$ops['child_type'] = [
$ops['type_selection'] = [
'title' => $this->t('Type of children'),
'form' => TypeSelectionForm::class,
'form' => static::TYPE_SELECTION_FORM,
'values' => [
'node' => $this->nodeId,
],
];
$ops['child_files'] = [
$ops['file_selection'] = [
'title' => $this->t('Files for children'),
'form' => FileSelectionForm::class,
'form' => static::FILE_SELECTION_FORM,
'values' => [
'node' => $this->nodeId,
],

22
src/Form/AddChildrenWizard/Access.php

@ -49,15 +49,23 @@ class Access implements ContainerInjectionInterface {
* @return \Drupal\Core\Access\AccessResultInterface
* Whether we can or cannot show the "thing".
*/
public function checkAccess(RouteMatch $route_match) : AccessResultInterface {
$can_create_media = $this->utils->canCreateIslandoraEntity('media', 'media_type');
$can_create_node = $this->utils->canCreateIslandoraEntity('node', 'node_type');
public function childAccess(RouteMatch $route_match) : AccessResultInterface {
return AccessResult::allowedIf($this->utils->canCreateIslandoraEntity('node', 'node_type'))
->andIf($this->mediaAccess($route_match));
if ($can_create_media && $can_create_node) {
return AccessResult::allowed();
}
}
return AccessResult::forbidden();
/**
* Check if the user can create any "Islandora" media.
*
* @param \Drupal\Core\Routing\RouteMatch $route_match
* The current routing match.
*
* @return \Drupal\Core\Access\AccessResultInterface
* Whether we can or cannot show the "thing".
*/
public function mediaAccess(RouteMatch $route_match) : AccessResultInterface {
return AccessResult::allowedIf($this->utils->canCreateIslandoraEntity('media', 'media_type'));
}
}

190
src/Form/AddChildrenWizard/ChildBatchProcessor.php

@ -2,189 +2,57 @@
namespace Drupal\islandora\Form\AddChildrenWizard;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Url;
use Drupal\islandora\IslandoraUtils;
use Drupal\node\NodeInterface;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
/**
* Children addition batch processor.
*/
class ChildBatchProcessor {
use FieldTrait;
/**
* The entity type manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface|null
*/
protected ?EntityTypeManagerInterface $entityTypeManager;
/**
* The database connection serivce.
*
* @var \Drupal\Core\Database\Connection|null
*/
protected ?Connection $database;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountProxyInterface|null
*/
protected ?AccountProxyInterface $currentUser;
/**
* Constructor.
*/
public function __construct(
EntityTypeManagerInterface $entity_type_manager,
Connection $database,
AccountProxyInterface $current_user
) {
$this->entityTypeManager = $entity_type_manager;
$this->database = $database;
$this->currentUser = $current_user;
}
/**
* Implements callback_batch_operation() for our child addition batch.
*/
public function batchOperation($delta, $info, array $values, &$context) {
$transaction = $this->database->startTransaction();
try {
$taxonomy_term_storage = $this->entityTypeManager->getStorage('taxonomy_term');
/** @var \Drupal\file\FileInterface $file */
$file = $this->entityTypeManager->getStorage('file')->load($info['target_id']);
$file->setPermanent();
if ($file->save() !== SAVED_UPDATED) {
throw new \Exception("Failed to update file '{$file->id()}' to be permanent.");
}
$node_storage = $this->entityTypeManager->getStorage('node');
$parent = $node_storage->load($values['node']);
// Create a node (with the filename?) (and also belonging to the target
// node).
/** @var \Drupal\node\NodeInterface $node */
$node = $node_storage->create([
'type' => $values['bundle'],
'title' => $file->getFilename(),
IslandoraUtils::MEMBER_OF_FIELD => $parent,
'uid' => $this->currentUser->id(),
'status' => NodeInterface::PUBLISHED,
IslandoraUtils::MODEL_FIELD => ($values['model'] ?
$taxonomy_term_storage->load($values['model']) :
NULL),
]);
if ($node->save() !== SAVED_NEW) {
throw new \Exception("Failed to create node for file '{$file->id()}'.");
}
// Create a media with the file attached and also pointing at the node.
$field = $this->getField($values);
$media_values = array_merge(
[
'bundle' => $values['media_type'],
'name' => $file->getFilename(),
IslandoraUtils::MEDIA_OF_FIELD => $node,
IslandoraUtils::MEDIA_USAGE_FIELD => ($values['use'] ?
$taxonomy_term_storage->loadMultiple($values['use']) :
NULL),
'uid' => $this->currentUser->id(),
// XXX: Published... no constant?
'status' => 1,
],
[
$field->getName() => [
$info,
],
]
);
$media = $this->entityTypeManager->getStorage('media')->create($media_values);
if ($media->save() !== SAVED_NEW) {
throw new \Exception("Failed to create media for file '{$file->id()}.");
}
$context['results'] = array_merge_recursive($context['results'], [
'validation_violations' => $this->validationClassification([
$file,
$media,
$node,
]),
]);
$context['results']['count'] += 1;
}
catch (HttpExceptionInterface $e) {
$transaction->rollBack();
throw $e;
}
catch (\Exception $e) {
$transaction->rollBack();
throw new HttpException(500, $e->getMessage(), $e);
}
}
class ChildBatchProcessor extends AbstractBatchProcessor {
/**
* Helper to bulk process validatable entities.
*
* @param array $entities
* An array of entities to scan for validation violations.
*
* @return array
* An associative array mapping entity type IDs to entity IDs to a count
* of validation violations found on then given entity.
* {@inheritdoc}
*/
protected function validationClassification(array $entities) {
$violations = [];
foreach ($entities as $entity) {
$entity_violations = $entity->validate();
if ($entity_violations->count() > 0) {
$violations[$entity->getEntityTypeId()][$entity->id()] = $entity_violations->count();
}
protected function getNode(array $info, array $values) : NodeInterface {
$taxonomy_term_storage = $this->entityTypeManager->getStorage('taxonomy_term');
$node_storage = $this->entityTypeManager->getStorage('node');
$parent = $node_storage->load($values['node']);
$file = $this->getFile($info);
// Create a node (with the filename?) (and also belonging to the target
// node).
/** @var \Drupal\node\NodeInterface $node */
$node = $node_storage->create([
'type' => $values['bundle'],
'title' => $file->getFilename(),
IslandoraUtils::MEMBER_OF_FIELD => $parent,
'uid' => $this->currentUser->id(),
'status' => NodeInterface::PUBLISHED,
IslandoraUtils::MODEL_FIELD => ($values['model'] ?
$taxonomy_term_storage->load($values['model']) :
NULL),
]);
if ($node->save() !== SAVED_NEW) {
throw new \Exception("Failed to create node for file '{$file->id()}'.");
}
return $violations;
return $node;
}
/**
* Implements callback_batch_finished() for our child addition batch.
* {@inheritdoc}
*/
public function batchProcessFinished($success, $results, $operations): void {
if ($success) {
$this->messenger()->addMessage($this->formatPlural(
$this->messenger->addMessage($this->formatPlural(
$results['count'],
'Added 1 child node.',
'Added @count child nodes.'
));
foreach ($results['validation_violations'] ?? [] as $entity_type => $info) {
foreach ($info as $id => $count) {
$this->messenger()->addWarning($this->formatPlural(
$count,
'1 validation error present in <a target="_blank" href=":uri">bulk created entity of type %type, with ID %id</a>.',
'@count validation errors present in <a target="_blank" href=":uri">bulk created entity of type %type, with ID %id</a>.',
[
'%type' => $entity_type,
':uri' => Url::fromRoute("entity.{$entity_type}.canonical", [$entity_type => $id])->toString(),
'%id' => $id,
]
));
}
}
}
else {
$this->messenger()->addError($this->t('Encountered an error when adding children.'));
}
parent::batchProcessFinished($success, $results, $operations);
}
}

32
src/Form/AddChildrenWizard/ChildFileSelectionForm.php

@ -0,0 +1,32 @@
<?php
namespace Drupal\islandora\Form\AddChildrenWizard;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
/**
* Children addition wizard's second step.
*/
class ChildFileSelectionForm extends AbstractFileSelectionForm {
public const BATCH_PROCESSOR = 'islandora.upload_children.batch_processor';
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'islandora_add_children_wizard_file_selection';
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
$cached_values = $form_state->getTemporaryValue('wizard');
$form_state->setRedirectUrl(Url::fromUri("internal:/node/{$cached_values['node']}/members"));
}
}

24
src/Form/AddChildrenWizard/ChildForm.php

@ -0,0 +1,24 @@
<?php
namespace Drupal\islandora\Form\AddChildrenWizard;
/**
* Bulk children addition wizard base form.
*/
class ChildForm extends AbstractForm {
const TEMPSTORE_ID = 'islandora.upload_children';
const TYPE_SELECTION_FORM = ChildTypeSelectionForm::class;
const FILE_SELECTION_FORM = ChildFileSelectionForm::class;
/**
* {@inheritdoc}
*/
public function getMachineName() {
return strtr("islandora_add_children_wizard__{userid}__{nodeid}", [
'{userid}' => $this->currentUser->id(),
'{nodeid}' => $this->nodeId,
]);
}
}

157
src/Form/AddChildrenWizard/ChildTypeSelectionForm.php

@ -0,0 +1,157 @@
<?php
namespace Drupal\islandora\Form\AddChildrenWizard;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Form\FormStateInterface;
use Drupal\islandora\IslandoraUtils;
/**
* Children addition wizard's first step.
*/
class ChildTypeSelectionForm extends MediaTypeSelectionForm {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'islandora_add_children_type_selection';
}
/**
* Memoization for ::getNodeBundleOptions().
*
* @var array|null
*/
protected ?array $nodeBundleOptions = NULL;
/**
* Indicate presence of model field on node bundles.
*
* Populated as a side effect of ::getNodeBundleOptions().
*
* @var array|null
*/
protected ?array $nodeBundleHasModelField = NULL;
/**
* Helper; get the node bundle options available to the current user.
*
* @return array
* An associative array mapping node bundle machine names to their human-
* readable labels.
*/
protected function getNodeBundleOptions() : array {
if ($this->nodeBundleOptions === NULL) {
$this->nodeBundleOptions = [];
$this->nodeBundleHasModelField = [];
$access_handler = $this->entityTypeManager->getAccessControlHandler('node');
foreach ($this->entityTypeBundleInfo->getBundleInfo('node') as $bundle => $info) {
$access = $access_handler->createAccess(
$bundle,
NULL,
[],
TRUE
);
$this->cacheableMetadata->addCacheableDependency($access);
if (!$access->isAllowed()) {
continue;
}
$this->nodeBundleOptions[$bundle] = $info['label'];
$fields = $this->entityFieldManager->getFieldDefinitions('node', $bundle);
$this->nodeBundleHasModelField[$bundle] = array_key_exists(IslandoraUtils::MODEL_FIELD, $fields);
}
}
return $this->nodeBundleOptions;
}
/**
* Generates a mapping of taxonomy term IDs to their names.
*
* @return \Generator
* The mapping of taxonomy term IDs to their names.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function getModelOptions() : \Generator {
$terms = $this->entityTypeManager->getStorage('taxonomy_term')
->loadTree('islandora_models', 0, NULL, TRUE);
foreach ($terms as $term) {
yield $term->id() => $term->getName();
}
}
/**
* Helper; map node bundles supporting the "has model" field, for #states.
*
* @return \Generator
* Yields associative array mapping the string 'value' to the bundles which
* have the given field.
*/
protected function mapModelStates() : \Generator {
$this->getNodeBundleOptions();
foreach (array_keys(array_filter($this->nodeBundleHasModelField)) as $bundle) {
yield ['value' => $bundle];
}
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$this->cacheableMetadata = CacheableMetadata::createFromRenderArray($form)
->addCacheContexts([
'url',
'url.query_args',
]);
$cached_values = $form_state->getTemporaryValue('wizard');
$form['bundle'] = [
'#type' => 'select',
'#title' => $this->t('Content Type'),
'#description' => $this->t('Each child created will have this content type.'),
'#empty_value' => '',
'#default_value' => $cached_values['bundle'] ?? '',
'#options' => $this->getNodeBundleOptions(),
'#required' => TRUE,
];
$model_states = iterator_to_array($this->mapModelStates());
$form['model'] = [
'#type' => 'select',
'#title' => $this->t('Model'),
'#description' => $this->t('Each child will be tagged with this model.'),
'#options' => iterator_to_array($this->getModelOptions()),
'#empty_value' => '',
'#default_value' => $cached_values['model'] ?? '',
'#states' => [
'visible' => [
':input[name="bundle"]' => $model_states,
],
'required' => [
':input[name="bundle"]' => $model_states,
],
],
];
$this->cacheableMetadata->applyTo($form);
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
protected static function keysToSave() {
return array_merge(
parent::keysToSave(),
[
'bundle',
'model',
]
);
}
}

2
src/Form/AddChildrenWizard/FieldTrait.php

@ -17,7 +17,7 @@ trait FieldTrait {
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface|null
*/
protected ?EntityFieldManagerInterface $entityFieldManager;
protected ?EntityFieldManagerInterface $entityFieldManager = NULL;
/**
* Helper; get field instance, given our required values.

34
src/Form/AddChildrenWizard/MediaBatchProcessor.php

@ -0,0 +1,34 @@
<?php
namespace Drupal\islandora\Form\AddChildrenWizard;
use Drupal\node\NodeInterface;
/**
* Media addition batch processor.
*/
class MediaBatchProcessor extends AbstractBatchProcessor {
/**
* {@inheritdoc}
*/
protected function getNode(array $info, array $values) : NodeInterface {
return $this->entityTypeManager->getStorage('node')->load($values['node']);
}
/**
* {@inheritdoc}
*/
public function batchProcessFinished($success, $results, $operations): void {
if ($success) {
$this->messenger->addMessage($this->formatPlural(
$results['count'],
'Added 1 media.',
'Added @count media.'
));
}
parent::batchProcessFinished($success, $results, $operations);
}
}

32
src/Form/AddChildrenWizard/MediaFileSelectionForm.php

@ -0,0 +1,32 @@
<?php
namespace Drupal\islandora\Form\AddChildrenWizard;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
/**
* Media addition wizard's second step.
*/
class MediaFileSelectionForm extends AbstractFileSelectionForm {
public const BATCH_PROCESSOR = 'islandora.upload_media.batch_processor';
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'islandora_add_media_wizard_file_selection';
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
$cached_values = $form_state->getTemporaryValue('wizard');
$form_state->setRedirectUrl(Url::fromUri("internal:/node/{$cached_values['node']}/media"));
}
}

24
src/Form/AddChildrenWizard/MediaForm.php

@ -0,0 +1,24 @@
<?php
namespace Drupal\islandora\Form\AddChildrenWizard;
/**
* Bulk children addition wizard base form.
*/
class MediaForm extends AbstractForm {
const TEMPSTORE_ID = 'islandora.upload_media';
const TYPE_SELECTION_FORM = MediaTypeSelectionForm::class;
const FILE_SELECTION_FORM = MediaFileSelectionForm::class;
/**
* {@inheritdoc}
*/
public function getMachineName() {
return strtr("islandora_add_media_wizard__{userid}__{nodeid}", [
'{userid}' => $this->currentUser->id(),
'{nodeid}' => $this->nodeId,
]);
}
}

130
src/Form/AddChildrenWizard/TypeSelectionForm.php → src/Form/AddChildrenWizard/MediaTypeSelectionForm.php

@ -14,7 +14,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Children addition wizard's first step.
*/
class TypeSelectionForm extends FormBase {
class MediaTypeSelectionForm extends FormBase {
/**
* Cacheable metadata that is instantiated and used internally.
@ -61,87 +61,7 @@ class TypeSelectionForm extends FormBase {
* {@inheritdoc}
*/
public function getFormId() {
return 'islandora_add_children_type_selection';
}
/**
* Memoization for ::getNodeBundleOptions().
*
* @var array|null
*/
protected ?array $nodeBundleOptions = NULL;
/**
* Indicate presence of model field on node bundles.
*
* Populated as a side effect of ::getNodeBundleOptions().
*
* @var array|null
*/
protected ?array $nodeBundleHasModelField = NULL;
/**
* Helper; get the node bundle options available to the current user.
*
* @return array
* An associative array mapping node bundle machine names to their human-
* readable labels.
*/
protected function getNodeBundleOptions() : array {
if ($this->nodeBundleOptions === NULL) {
$this->nodeBundleOptions = [];
$this->nodeBundleHasModelField = [];
$access_handler = $this->entityTypeManager->getAccessControlHandler('node');
foreach ($this->entityTypeBundleInfo->getBundleInfo('node') as $bundle => $info) {
$access = $access_handler->createAccess(
$bundle,
NULL,
[],
TRUE
);
$this->cacheableMetadata->addCacheableDependency($access);
if (!$access->isAllowed()) {
continue;
}
$this->nodeBundleOptions[$bundle] = $info['label'];
$fields = $this->entityFieldManager->getFieldDefinitions('node', $bundle);
$this->nodeBundleHasModelField[$bundle] = array_key_exists(IslandoraUtils::MODEL_FIELD, $fields);
}
}
return $this->nodeBundleOptions;
}
/**
* Generates a mapping of taxonomy term IDs to their names.
*
* @return \Generator
* The mapping of taxonomy term IDs to their names.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function getModelOptions() : \Generator {
$terms = $this->entityTypeManager->getStorage('taxonomy_term')
->loadTree('islandora_models', 0, NULL, TRUE);
foreach ($terms as $term) {
yield $term->id() => $term->getName();
}
}
/**
* Helper; map node bundles supporting the "has model" field, for #states.
*
* @return \Generator
* Yields associative array mapping the string 'value' to the bundles which
* have the given field.
*/
protected function mapModelStates() : \Generator {
$this->getNodeBundleOptions();
foreach (array_keys(array_filter($this->nodeBundleHasModelField)) as $bundle) {
yield ['value' => $bundle];
}
return 'islandora_add_media_type_selection';
}
/**
@ -237,33 +157,6 @@ class TypeSelectionForm extends FormBase {
]);
$cached_values = $form_state->getTemporaryValue('wizard');
$form['bundle'] = [
'#type' => 'select',
'#title' => $this->t('Content Type'),
'#description' => $this->t('Each child created will have this content type.'),
'#empty_value' => '',
'#default_value' => $cached_values['bundle'] ?? '',
'#options' => $this->getNodeBundleOptions(),
'#required' => TRUE,
];
$model_states = iterator_to_array($this->mapModelStates());
$form['model'] = [
'#type' => 'select',
'#title' => $this->t('Model'),
'#description' => $this->t('Each child will be tagged with this model.'),
'#options' => iterator_to_array($this->getModelOptions()),
'#empty_value' => '',
'#default_value' => $cached_values['model'] ?? '',
'#states' => [
'visible' => [
':input[name="bundle"]' => $model_states,
],
'required' => [
':input[name="bundle"]' => $model_states,
],
],
];
$form['media_type'] = [
'#type' => 'select',
'#title' => $this->t('Media Type'),
@ -297,17 +190,24 @@ class TypeSelectionForm extends FormBase {
}
/**
* {@inheritdoc}
* Helper; enumerate keys to persist in form state.
*
* @return string[]
* The keys to be persisted in our temp value in form state.
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$keys = [
'bundle',
'model',
protected static function keysToSave() {
return [
'media_type',
'use',
];
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$cached_values = $form_state->getTemporaryValue('wizard');
foreach ($keys as $key) {
foreach (static::keysToSave() as $key) {
$cached_values[$key] = $form_state->getValue($key);
}
$form_state->setTemporaryValue('wizard', $cached_values);

2
src/Form/AddChildrenWizard/MediaTypeTrait.php

@ -15,7 +15,7 @@ trait MediaTypeTrait {
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface|null
*/
protected ?EntityTypeManagerInterface $entityTypeManager;
protected ?EntityTypeManagerInterface $entityTypeManager = NULL;
/**
* Helper; get media type, given our required values.

2
src/Form/AddMediaForm.php

@ -23,6 +23,8 @@ use Symfony\Component\HttpKernel\Exception\HttpException;
/**
* Form that lets users upload one or more files as children to a resource node.
*
* @deprecated Use the \Drupal\islandora\Form\AddChildrenWizard\MediaForm instead.
*/
class AddMediaForm extends FormBase {

Loading…
Cancel
Save