operation = $operation; } return $this; } /** * {@inheritdoc} */ public function getBaseFormID() { // Assign ENTITYTYPE_form as base form ID to invoke corresponding // hook_form_alter(), #validate, #submit, and #theme callbacks, but only if // it is different from the actual form ID, since callbacks would be invoked // twice otherwise. $base_form_id = $this->entity->getEntityTypeId() . '_form'; if ($base_form_id == $this->getFormId()) { $base_form_id = NULL; } return $base_form_id; } /** * {@inheritdoc} */ public function getFormId() { $form_id = $this->entity->getEntityTypeId(); if ($this->entity->getEntityType()->hasKey('bundle')) { $form_id = $this->entity->bundle() . '_' . $form_id; } if ($this->operation != 'default') { $form_id = $form_id . '_' . $this->operation; } return $form_id . '_form'; } /** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { // During the initial form build, add this form object to the form state and // allow for initial preparation before form building and processing. if (!isset($form_state['controller'])) { $this->init($form_state); } // Retrieve the form array using the possibly updated entity in form state. $form = $this->form($form, $form_state); // Retrieve and add the form actions array. $actions = $this->actionsElement($form, $form_state); if (!empty($actions)) { $form['actions'] = $actions; } return $form; } /** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { } /** * Initialize the form state and the entity before the first form build. */ protected function init(FormStateInterface $form_state) { // Add the form object to the form state so it can be easily accessed by // module-provided form handlers there. $form_state['controller'] = $this; // Prepare the entity to be presented in the entity form. $this->prepareEntity(); // Invoke the prepare form hooks. $this->prepareInvokeAll('entity_prepare_form', $form_state); $this->prepareInvokeAll($this->entity->getEntityTypeId() . '_prepare_form', $form_state); } /** * Returns the actual form array to be built. * * @see \Drupal\Core\Entity\EntityForm::build() */ public function form(array $form, FormStateInterface $form_state) { $entity = $this->entity; // Add a process callback. $form['#process'][] = '::processForm'; if (!isset($form['langcode'])) { // If the form did not specify otherwise, default to keeping the existing // language of the entity or defaulting to the site default language for // new entities. $form['langcode'] = array( '#type' => 'value', '#value' => !$entity->isNew() ? $entity->language()->id : language_default()->id, ); } return $form; } /** * Process callback: assigns weights and hides extra fields. * * @see \Drupal\Core\Entity\EntityForm::form() */ public function processForm($element, FormStateInterface $form_state, $form) { // If the form is cached, process callbacks may not have a valid reference // to the entity object, hence we must restore it. $this->entity = $form_state['controller']->getEntity(); return $element; } /** * Returns the action form element for the current entity form. */ protected function actionsElement(array $form, FormStateInterface $form_state) { $element = $this->actions($form, $form_state); if (isset($element['delete'])) { // Move the delete action as last one, unless weights are explicitly // provided. $delete = $element['delete']; unset($element['delete']); $element['delete'] = $delete; $element['delete']['#button_type'] = 'danger'; } if (isset($element['submit'])) { // Give the primary submit button a #button_type of primary. $element['submit']['#button_type'] = 'primary'; } $count = 0; foreach (Element::children($element) as $action) { $element[$action] += array( '#weight' => ++$count * 5, ); } if (!empty($element)) { $element['#type'] = 'actions'; } return $element; } /** * Returns an array of supported actions for the current entity form. * * @todo Consider introducing a 'preview' action here, since it is used by * many entity types. */ protected function actions(array $form, FormStateInterface $form_state) { // @todo Rename the action key from submit to save. $actions['submit'] = array( '#type' => 'submit', '#value' => $this->t('Save'), '#validate' => array('::validate'), '#submit' => array('::submit', '::save'), ); if (!$this->entity->isNew() && $this->entity->hasLinkTemplate('delete-form')) { $route_info = $this->entity->urlInfo('delete-form'); if ($this->getRequest()->query->has('destination')) { $query = $route_info->getOption('query'); $query['destination'] = $this->getRequest()->query->get('destination'); $route_info->setOption('query', $query); } $actions['delete'] = array( '#type' => 'link', '#title' => $this->t('Delete'), '#access' => $this->entity->access('delete'), '#attributes' => array( 'class' => array('button', 'button--danger'), ), ); $actions['delete'] += $route_info->toRenderArray(); } return $actions; } /** * {@inheritdoc} */ public function validate(array $form, FormStateInterface $form_state) { $this->updateFormLangcode($form_state); // @todo Remove this. // Execute legacy global validation handlers. unset($form_state['validate_handlers']); form_execute_handlers('validate', $form, $form_state); } /** * {@inheritdoc} * * This is the default entity object builder function. It is called before any * other submit handler to build the new entity object to be passed to the * following submit handlers. At this point of the form workflow the entity is * validated and the form state can be updated, this way the subsequently * invoked handlers can retrieve a regular entity object to act on. * * @param array $form * An associative array containing the structure of the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. */ public function submit(array $form, FormStateInterface $form_state) { // Remove button and internal Form API values from submitted values. form_state_values_clean($form_state); $this->entity = $this->buildEntity($form, $form_state); return $this->entity; } /** * Form submission handler for the 'save' action. * * @param array $form * An associative array containing the structure of the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. */ public function save(array $form, FormStateInterface $form_state) { // @todo Perform common save operations. } /** * {@inheritdoc} */ public function getFormLangcode(FormStateInterface $form_state) { return $this->entity->language()->id; } /** * {@inheritdoc} */ public function isDefaultFormLangcode(FormStateInterface $form_state) { // The entity is not translatable, this is always the default language. return TRUE; } /** * Updates the form language to reflect any change to the entity language. * * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. */ protected function updateFormLangcode(FormStateInterface $form_state) { // Update the form language as it might have changed. if ($form_state->hasValue('langcode') && $this->isDefaultFormLangcode($form_state)) { $form_state['langcode'] = $form_state->getValue('langcode'); } } /** * {@inheritdoc} */ public function buildEntity(array $form, FormStateInterface $form_state) { $entity = clone $this->entity; $this->copyFormValuesToEntity($entity, $form, $form_state); // Invoke all specified builders for copying form values to entity // properties. if (isset($form['#entity_builders'])) { foreach ($form['#entity_builders'] as $function) { call_user_func_array($function, array($entity->getEntityTypeId(), $entity, &$form, &$form_state)); } } return $entity; } /** * Copies top-level form values to entity properties * * This should not change existing entity properties that are not being edited * by this form. * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity the current form should operate upon. * @param array $form * A nested array of form elements comprising the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. */ protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) { // @todo: This relies on a method that only exists for config and content // entities, in a different way. Consider moving this logic to a config // entity specific implementation. foreach ($form_state->getValues() as $key => $value) { $entity->set($key, $value); } } /** * {@inheritdoc} */ public function getEntity() { return $this->entity; } /** * {@inheritdoc} */ public function setEntity(EntityInterface $entity) { $this->entity = $entity; return $this; } /** * Prepares the entity object before the form is built first. */ protected function prepareEntity() {} /** * Invokes the specified prepare hook variant. * * @param string $hook * The hook variant name. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. */ protected function prepareInvokeAll($hook, FormStateInterface $form_state) { $implementations = $this->moduleHandler->getImplementations($hook); foreach ($implementations as $module) { $function = $module . '_' . $hook; if (function_exists($function)) { // Ensure we pass an updated translation object and form display at // each invocation, since they depend on form state which is alterable. $args = array($this->entity, $this->operation, &$form_state); call_user_func_array($function, $args); } } } /** * {@inheritdoc} */ public function getOperation() { return $this->operation; } /** * {@inheritdoc} */ public function setModuleHandler(ModuleHandlerInterface $module_handler) { $this->moduleHandler = $module_handler; return $this; } }