diff --git a/islandora.routing.yml b/islandora.routing.yml index 5387e9a4..9215a360 100644 --- a/islandora.routing.yml +++ b/islandora.routing.yml @@ -37,7 +37,7 @@ islandora.add_member_to_node_page: _entity_create_any_access: 'node' islandora.upload_children: - path: '/node/{node}/members/upload' + path: '/node/{node}/members/upload_old' defaults: _form: '\Drupal\islandora\Form\AddChildrenForm' _title: 'Upload Children' @@ -46,6 +46,18 @@ islandora.upload_children: requirements: _custom_access: '\Drupal\islandora\Form\AddChildrenForm::access' +islandora.upload_children_wizard: + path: '/node/{node}/members/upload/{step}' + defaults: + _wizard: '\Drupal\islandora\Form\AddChildrenWizard\Form' + _title: 'Upload Children' + step: 'child_type' + options: + _admin_route: 'TRUE' + requirements: + #_custom_access: '\Drupal\islandora\Form\AddChildrenWizard\Form::access' + _custom_access: '\Drupal\islandora\Form\AddChildrenForm::access' + islandora.add_media_to_node_page: path: '/node/{node}/media/add' defaults: diff --git a/src/Form/AddChildrenForm.php b/src/Form/AddChildrenForm.php index 0ff72496..528b4283 100644 --- a/src/Form/AddChildrenForm.php +++ b/src/Form/AddChildrenForm.php @@ -229,7 +229,7 @@ class AddChildrenForm extends AddMediaForm { * @param \Drupal\Core\Routing\RouteMatch $route_match * The current routing match. * - * @return \Drupal\Core\Access\AccessResultAllowed|\Drupal\Core\Access\AccessResultForbidden + * @return \Drupal\Core\Access\AccessResultInterface * Whether we can or can't show the "thing". */ public function access(RouteMatch $route_match) { diff --git a/src/Form/AddChildrenWizard/FileSelectionForm.php b/src/Form/AddChildrenWizard/FileSelectionForm.php index 5ea6581d..f056d7d6 100644 --- a/src/Form/AddChildrenWizard/FileSelectionForm.php +++ b/src/Form/AddChildrenWizard/FileSelectionForm.php @@ -2,24 +2,198 @@ namespace Drupal\islandora\Form\AddChildrenWizard; +use Drupal\Component\Plugin\PluginManagerInterface; +use Drupal\Core\Batch\BatchBuilder; +use Drupal\Core\Database\Connection; +use Drupal\Core\Entity\EntityFieldManagerInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Field\Entity\BaseFieldOverride; +use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\FieldItemList; +use Drupal\Core\Field\FieldStorageDefinitionInterface; +use Drupal\Core\Field\WidgetInterface; +use Drupal\Core\Field\WidgetPluginManager; use Drupal\Core\Form\FormBase; +use Drupal\Core\Form\FormState; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Queue\QueueFactory; +use Drupal\Core\Queue\QueueInterface; +use Drupal\Core\Session\AccountProxyInterface; +use Drupal\file\FileInterface; +use Drupal\islandora\IslandoraUtils; +use Drupal\media\MediaSourceInterface; +use Drupal\media\MediaTypeInterface; +use Drupal\node\NodeInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; class FileSelectionForm extends FormBase { + protected ?EntityTypeManagerInterface $entityTypeManager; + + /** + * The widget plugin manager service. + * + * @var WidgetPluginManager + */ + protected ?PluginManagerInterface $widgetPluginManager; + + protected ?EntityFieldManagerInterface $entityFieldManager; + + protected ?Connection $database; + + protected ?AccountProxyInterface $currentUser; + + public static function create(ContainerInterface $container) { + $instance = parent::create($container); + + $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'); + + return $instance; + } + public function getFormId() { return 'islandora_add_children_wizard_file_selection'; } - public function buildForm(array $form, FormStateInterface $form_state) { + protected function getMediaType(FormStateInterface $form_state) : MediaTypeInterface { + return $this->doGetMediaType($form_state->getTemporaryValue('wizard')); + } + + protected function doGetMediaType(array $values) : MediaTypeInterface { + /** @var MediaTypeInterface $media_type */ + return $this->entityTypeManager->getStorage('media_type')->load($values['media_type']); + } + + protected function getField(FormStateInterface $form_state) : FieldDefinitionInterface { + $cached_values = $form_state->getTemporaryValue('wizard'); + + $field = $this->doGetField($cached_values); + $field->getFieldStorageDefinition()->set('cardinality', FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED); + + return $field; + } + + protected function doGetField(array $values) : FieldDefinitionInterface { + $media_type = $this->doGetMediaType($values); + $media_source = $media_type->getSource(); + $source_field = $media_source->getSourceFieldDefinition($media_type); + + $fields = $this->entityFieldManager->getFieldDefinitions('media', $media_type->id()); + + return isset($fields[$source_field->getFieldStorageDefinition()->getName()]) ? + $fields[$source_field->getFieldStorageDefinition()->getName()] : + $media_source->createSourceField(); + } + + protected function getWidget(FormStateInterface $form_state) : WidgetInterface { + return $this->widgetPluginManager->getInstance([ + 'field_definition' => $this->getField($form_state), + 'form_mode' => 'default', + 'prepare' => TRUE, + ]); + } + + public function buildForm(array $form, FormStateInterface $form_state) : array { // TODO: 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()); + + $form['#tree'] = TRUE; + $form['#parents'] = []; + $widget = $this->getWidget($form_state); + $form['files'] = $widget->form( + $items, + $form, + $form_state + ); + return $form; } + /** + * {@inheritdoc} + */ public function submitForm(array &$form, FormStateInterface $form_state) { - // TODO: Implement submitForm() method. - $form_state->setError($form, 'Oh no!'); + $cached_values = $form_state->getTemporaryValue('wizard'); + + dsm($form_state); + + $builder = (new BatchBuilder()) + ->setTitle($this->t('Creating children...')) + ->setInitMessage($this->t('Initializing...')) + ->setFinishCallback([$this, 'batchProcessFinished']); + foreach ($form_state->getValue($this->doGetField($cached_values)->getName()) as $file) { + $builder->addOperation([$this, 'batchProcess'], [$file, $cached_values]); + } + batch_set($builder->toArray()); } + + public function batchProcess($fid, array $values, &$context) { + $transaction = \Drupal::database()->startTransaction(); + + try { + $taxonomy_term_storage = $this->entityTypeManager->getStorage('taxonomy_term'); + + /** @var FileInterface $file */ + $file = $this->entityTypeManager->getStorage('file')->load($fid); + $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). + $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. + $media = $this->entityTypeManager->getStorage('media')->create([ + '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, + $this->doGetField($values)->getName() => $file->id(), + ]); + if ($media->save() !== SAVED_NEW) { + throw new \Exception("Failed to create media for file '{$file->id()}."); + } + } + catch (HttpExceptionInterface $e) { + $transaction->rollBack(); + throw $e; + } + catch (\Exception $e) { + $transaction->rollBack(); + throw new HttpException(500, $e->getMessage(), $e); + } + } + + public function batchProcessFinished() { + // TODO: Dump out status message of some sort? + } + } diff --git a/src/Form/AddChildrenWizard/Form.php b/src/Form/AddChildrenWizard/Form.php index 2be973be..d689fd37 100644 --- a/src/Form/AddChildrenWizard/Form.php +++ b/src/Form/AddChildrenWizard/Form.php @@ -2,15 +2,83 @@ namespace Drupal\islandora\Form\AddChildrenWizard; +use Drupal\Core\Access\AccessResult; +use Drupal\Core\DependencyInjection\ClassResolverInterface; +use Drupal\Core\Form\FormBuilderInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\RendererInterface; +use Drupal\Core\Routing\RouteMatch; +use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\Session\AccountProxyInterface; +use Drupal\Core\TempStore\SharedTempStoreFactory; use Drupal\ctools\Wizard\FormWizardBase; +use Drupal\islandora\IslandoraUtils; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; class Form extends FormWizardBase { + protected IslandoraUtils $utils; + protected string $nodeId; + protected RouteMatchInterface $currentRoute; + protected AccountProxyInterface $currentUser; + + /** + * Constructor. + */ + public function __construct( + SharedTempStoreFactory $tempstore, + FormBuilderInterface $builder, + ClassResolverInterface $class_resolver, + EventDispatcherInterface $event_dispatcher, + RouteMatchInterface $route_match, + $tempstore_id, + IslandoraUtils $utils, + RouteMatchInterface $current_route_match, + AccountProxyInterface $current_user, + $machine_name = NULL, + $step = NULL + ) { + parent::__construct($tempstore, $builder, $class_resolver, $event_dispatcher, $route_match, $tempstore_id, + $machine_name, $step); + + $this->utils = $utils; + $this->currentRoute = $current_route_match; + $this->nodeId = $this->currentRoute->getParameter('node'); + $this->currentUser = $current_user; + } + + /** + * {@inheritdoc} + */ + public static function getParameters() : array { + return array_merge( + parent::getParameters(), + [ + 'utils' => \Drupal::service('islandora.utils'), + 'tempstore_id' => 'islandora.upload_children', + //'machine_name' => 'islandora_add_children_wizard', + 'current_route_match' => \Drupal::service('current_route_match'), + 'current_user' => \Drupal::service('current_user'), + ] + ); + } + + /** + * {@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) { - // TODO: Implement getOperations() method. return [ 'child_type' => [ 'title' => $this->t('Type of children'), @@ -22,4 +90,18 @@ class Form extends FormWizardBase { ] ]; } + + public function getNextParameters($cached_values) { + return parent::getNextParameters($cached_values) + ['node' => $this->nodeId]; + } + + public function getPreviousParameters($cached_values) { + return parent::getPreviousParameters($cached_values) + ['node' => $this->nodeId]; + } + + public function finish(array &$form, FormStateInterface $form_state) { + parent::finish($form, $form_state); // TODO: Change the autogenerated stub + dsm($form_state->getTemporaryValue('wizard')); + } + } diff --git a/src/Form/AddChildrenWizard/TypeSelectionForm.php b/src/Form/AddChildrenWizard/TypeSelectionForm.php index 41882e8e..fffb2ae9 100644 --- a/src/Form/AddChildrenWizard/TypeSelectionForm.php +++ b/src/Form/AddChildrenWizard/TypeSelectionForm.php @@ -133,12 +133,19 @@ class TypeSelectionForm extends FormBase { * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { - $this->cacheableMetadata = CacheableMetadata::createFromRenderArray($form); + $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, ]; @@ -149,6 +156,8 @@ class TypeSelectionForm extends FormBase { '#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, @@ -162,6 +171,8 @@ class TypeSelectionForm extends FormBase { '#type' => 'select', '#title' => $this->t('Media Type'), '#description' => $this->t('Each media created will have this type.'), + '#empty_value' => '', + '#default_value' => $cached_values['media_type'] ?? '', '#options' => $this->getMediaBundleOptions(), '#required' => TRUE, ]; @@ -173,6 +184,7 @@ class TypeSelectionForm extends FormBase { ':url' => 'https://pcdm.org/2015/05/12/use', ]), '#options' => iterator_to_array($this->getMediaUseOptions()), + '#default_value' => $cached_values['use'] ?? [], '#states' => [ 'visible' => [ ':input[name="media_type"]' => $use_states, @@ -191,6 +203,17 @@ class TypeSelectionForm extends FormBase { * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { - // TODO: Implement submitForm() method. + $keys = [ + 'bundle', + 'model', + 'media_type', + 'use', + ]; + $cached_values = $form_state->getTemporaryValue('wizard'); + foreach ($keys as $key) { + $cached_values[$key] = $form_state->getValue($key); + } + $form_state->setTemporaryValue('wizard', $cached_values); } + }