From f8ba5567ecaae7e1f71304fa81931623a481f622 Mon Sep 17 00:00:00 2001 From: M Parker Date: Fri, 1 Sep 2017 10:38:30 -0400 Subject: [PATCH] 1886616-46 --- core/lib/Drupal/Core/CoreServiceProvider.php | 3 + .../Drupal/Core/Wizard/WizardFormController.php | 235 +++++++++++++++++++++ 2 files changed, 238 insertions(+) create mode 100644 core/lib/Drupal/Core/Wizard/WizardFormController.php diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php index 8a22bc50d4..6c56de5337 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -56,6 +56,9 @@ public function register(ContainerBuilder $container) { ->addTag('stream_wrapper', ['scheme' => 'private']); } + // Register the Entity WizardFormController. + $container->register('entity.wizard', 'Drupal\Core\Wizard\WizardFormController'); + // Add the compiler pass that lets service providers modify existing // service definitions. This pass must come first so that later // list-building passes are operating on the post-alter services list. diff --git a/core/lib/Drupal/Core/Wizard/WizardFormController.php b/core/lib/Drupal/Core/Wizard/WizardFormController.php new file mode 100644 index 0000000000..465df44f86 --- /dev/null +++ b/core/lib/Drupal/Core/Wizard/WizardFormController.php @@ -0,0 +1,235 @@ +operation = $operation; + } + } + + /** + * Provides a create utility for multi page entity wizard forms. + * + * @param string $entity_type + * The type of entity we are working with. This will be used for + * entity_create() calls as well as unique storage within tempstore. + * @param string $operation + * The current step in the form wizard process. + * @param string $id + * The unique identifier of the entity the form wizard is building. + * @param string $steps_key + * The key that indicates which group of steps to pull back from the entity + * definition. + * + * @return array + * The renderable array from the current step of the entity's available form + * controllers. + */ + public function create($entity_type, $operation, $id = NULL, $steps_key = 'steps') { + $this->operation = $operation; + if (empty($id)) { + $entity = entity_create($entity_type, array()); + } + else { + $entity = drupal_container()->get('user.tempstore')->get($entity_type)->get($id); + if (!$entity instanceof EntityInterface) { + $entity = entity_load($entity_type, $id); + } + } + $definition = $entity->entityInfo(); + // This could be any entity form controller and is not necessarily documented + // in the steps. If it is not in the steps, it will not have a title. + if (isset($definition[$steps_key][$operation])) { + drupal_set_title($definition[$steps_key][$operation]); + } + return entity_get_form($entity, $operation); + } + + /** + * Overrides Drupal\Core\Entity\EntityFormController::init(). + */ + protected function init(array &$form_state, EntityInterface $entity) { + parent::init($form_state, $entity); + $this->entityInfo = $entity->entityInfo(); + } + + /** + * Overrides Drupal\Core\Entity\EntityFormController::actions(). + * + * Digs through the entity definition's steps to determine if previous or + * next steps exist in order to display the appropriate buttons and text on + * those buttons. + */ + protected function actions(array $form, array &$form_state) { + $actions = parent::actions($form, $form_state); + // Delete should not generally be accessible from a wizard. Set access + // to false so that others can easily re-enable if they want it. + if (isset($actions['delete'])) { + $actions['delete']['#access'] = FALSE; + } + $entity = $this->buildEntity($form, $form_state += array('values' => array())); + // Get the steps of the wizard from the entity definition. + $steps = array_keys($this->entityInfo[$this->stepKey]); + // Slice to find the operations that occur before the current operation. + $before = array_slice($this->entityInfo[$this->stepKey], 0, array_search($this->getOperation(), $steps)); + // Slice to find the operations that occur after the current operation. + $after = array_slice($this->entityInfo[$this->stepKey], array_search($this->getOperation(), $steps) + 1); + + // If there are steps after this one, label the button "Next" otherwise + // label it "Finish". + if ($after) { + $actions['submit']['#value'] = t('Next'); + } + else { + $actions['submit']['#value'] = t('Finish'); + $actions['submit']['#submit'][] = array($this, 'finish'); + } + + // If there are steps before this one, label the button "previous" + // otherwise do not display a button. + if ($before) { + $actions['previous'] = array( + '#value' => t('Previous'), + '#submit' => array( + array($this, 'previous'), + ), + '#limit_validation_errors' => array(), + '#weight' => -10, + ); + } + + return $actions; + } + + /** + * Overrides Drupal\Core\Entity\EntityFormController::submit(). + * + * Instead of saving the entity at each step, we populate a tempstore with + * the entity for the next step. + */ + public function submit(array $form, array &$form_state) { + if ($form_state['values']['op'] == 'Next') { + $entity = $this->buildEntity($form, $form_state); + drupal_container()->get('user.tempstore')->get($entity->entityType())->set($entity->id(), $entity); + // Get the steps by key. + $steps = array_keys($this->entityInfo[$this->stepKey]); + // Get the steps after the current step. + $after = array_slice($this->entityInfo[$this->stepKey], array_search($this->getOperation(), $steps) + 1); + // Get the steps after the current step by key. + $after = array_keys($after); + + $form_state['redirect'] = $this->redirect($entity, $after[0]); + } + } + + /** + * Form submission handler for the 'previous' action. + * + * This method finds the previous step and redirects us to it. + * + * @param array $form + * An associative array containing the structure of the form. + * @param array $form_state + * A reference to a keyed array containing the current state of the form. + */ + public function previous(array $form, array &$form_state) { + $entity = $this->buildEntity($form, $form_state); + // Get the steps by key. + $steps = array_keys($this->entityInfo[$this->stepKey]); + // Get the steps before the current step. + $before = array_slice($this->entityInfo[$this->stepKey], 0, array_search($this->getOperation(), $steps)); + // Get the steps before the current step by key. + $before = array_keys($before); + // Reverse the steps for easy access to the next step. + $before = array_reverse($before); + + $form_state['redirect'] = $this->redirect($entity, $before[0]); + } + + /** + * Form submission handler for the 'previous' action. + * + * Retrieves the entity, saves it and deletes the values in the tempstore. + * This is handled as a separate method from submit() in order to keep the + * concerns of next() separate from finish(). There is no next() method + * because submit() will be fired in all forward moving steps. Submit just + * checks the operator to make sure that it is not firing on finish(). + * + * @param array $form + * An associative array containing the structure of the form. + * @param array $form_state + * A reference to a keyed array containing the current state of the form. + */ + public function finish(array $form, array &$form_state) { + $entity = parent::submit($form, $form_state); + $entity->save(); + drupal_container()->get('user.tempstore')->get($entity->entityType())->delete($entity->id()); + $form_state['redirect'] = $this->redirect($entity); + } + + /** + * A simple method for managing the redirect url of each step. + * + * @param EntityInterface $entity + * The entity being saved or updated. + * @param string $operation + * A simple string representation of the current step and corresponding + * form controller. + * + * @return string + * A uri for to be placed in $form_state['redirect']. + */ + public function redirect(EntityInterface $entity, $operation = NULL) { + if (!empty($operation)) { + return $this->entityInfo[$this->destinationKey] . '/' . $entity->id() . '/' . $operation; + } + return $this->entityInfo[$this->destinationKey]; + } +} -- 2.14.1