123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433 |
- <?php
- namespace Drupal\Core\Entity;
- use Drupal\Core\Form\FormBase;
- use Drupal\Core\Extension\ModuleHandlerInterface;
- use Drupal\Core\Form\FormStateInterface;
- use Drupal\Core\Render\Element;
- use Drupal\Core\Routing\RouteMatchInterface;
- /**
- * Base class for entity forms.
- *
- * @ingroup entity_api
- */
- class EntityForm extends FormBase implements EntityFormInterface {
- /**
- * The name of the current operation.
- *
- * Subclasses may use this to implement different behaviors depending on its
- * value.
- *
- * @var string
- */
- protected $operation;
- /**
- * The module handler service.
- *
- * @var \Drupal\Core\Extension\ModuleHandlerInterface
- */
- protected $moduleHandler;
- /**
- * The entity manager.
- *
- * @var \Drupal\Core\Entity\EntityManagerInterface
- *
- * @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
- *
- * @see https://www.drupal.org/node/2549139
- */
- protected $entityManager;
- /**
- * The entity type manager.
- *
- * @var \Drupal\Core\Entity\EntityTypeManagerInterface
- */
- protected $entityTypeManager;
- /**
- * The entity being used by this form.
- *
- * @var \Drupal\Core\Entity\EntityInterface
- */
- protected $entity;
- /**
- * {@inheritdoc}
- */
- public function setOperation($operation) {
- // If NULL is passed, do not overwrite the operation.
- if ($operation) {
- $this->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();
- }
- 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 (!$form_state->has('entity_form_initialized')) {
- $this->init($form_state);
- }
- // Ensure that edit forms have the correct cacheability metadata so they can
- // be cached.
- if (!$this->entity->isNew()) {
- \Drupal::service('renderer')->addCacheableDependency($form, $this->entity);
- }
- // 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;
- }
- /**
- * Initialize the form state and the entity before the first form build.
- */
- protected function init(FormStateInterface $form_state) {
- // Flag that this form has been initialized.
- $form_state->set('entity_form_initialized', TRUE);
- // 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);
- }
- /**
- * Gets the actual form array to be built.
- *
- * @see \Drupal\Core\Entity\EntityForm::processForm()
- * @see \Drupal\Core\Entity\EntityForm::afterBuild()
- */
- public function form(array $form, FormStateInterface $form_state) {
- // Add #process and #after_build callbacks.
- $form['#process'][] = '::processForm';
- $form['#after_build'][] = '::afterBuild';
- 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->getFormObject()->getEntity();
- return $element;
- }
- /**
- * Form element #after_build callback: Updates the entity with submitted data.
- *
- * Updates the internal $this->entity object with submitted values when the
- * form is being rebuilt (e.g. submitted via AJAX), so that subsequent
- * processing (e.g. AJAX callbacks) can rely on it.
- */
- public function afterBuild(array $element, FormStateInterface $form_state) {
- // Rebuild the entity if #after_build is being called as part of a form
- // rebuild, i.e. if we are processing input.
- if ($form_state->isProcessingInput()) {
- $this->entity = $this->buildEntity($element, $form_state);
- }
- 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] += [
- '#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 Consider renaming the action key from submit to save. The impacts
- // are hard to predict. For example, see
- // \Drupal\language\Element\LanguageConfiguration::processLanguageConfiguration().
- $actions['submit'] = [
- '#type' => 'submit',
- '#value' => $this->t('Save'),
- '#submit' => ['::submitForm', '::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'] = [
- '#type' => 'link',
- '#title' => $this->t('Delete'),
- '#access' => $this->entity->access('delete'),
- '#attributes' => [
- 'class' => ['button', 'button--danger'],
- ],
- ];
- $actions['delete']['#url'] = $route_info;
- }
- return $actions;
- }
- /**
- * {@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 used by 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. Generally
- * this method should not be overridden unless the entity requires the same
- * preparation for two actions, see \Drupal\comment\CommentForm for an example
- * with the save and preview actions.
- *
- * @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 submitForm(array &$form, FormStateInterface $form_state) {
- // Remove button and internal Form API values from submitted values.
- $form_state->cleanValues();
- $this->entity = $this->buildEntity($form, $form_state);
- }
- /**
- * {@inheritdoc}
- */
- public function save(array $form, FormStateInterface $form_state) {
- return $this->entity->save();
- }
- /**
- * {@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($form_state->prepareCallback($function), [$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) {
- $values = $form_state->getValues();
- if ($this->entity instanceof EntityWithPluginCollectionInterface) {
- // Do not manually update values represented by plugin collections.
- $values = array_diff_key($values, $this->entity->getPluginCollections());
- }
- // @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 ($values 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;
- }
- /**
- * {@inheritdoc}
- */
- public function getEntityFromRouteMatch(RouteMatchInterface $route_match, $entity_type_id) {
- if ($route_match->getRawParameter($entity_type_id) !== NULL) {
- $entity = $route_match->getParameter($entity_type_id);
- }
- else {
- $values = [];
- // If the entity has bundles, fetch it from the route match.
- $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
- if ($bundle_key = $entity_type->getKey('bundle')) {
- if (($bundle_entity_type_id = $entity_type->getBundleEntityType()) && $route_match->getRawParameter($bundle_entity_type_id)) {
- $values[$bundle_key] = $route_match->getParameter($bundle_entity_type_id)->id();
- }
- elseif ($route_match->getRawParameter($bundle_key)) {
- $values[$bundle_key] = $route_match->getParameter($bundle_key);
- }
- }
- $entity = $this->entityTypeManager->getStorage($entity_type_id)->create($values);
- }
- return $entity;
- }
- /**
- * 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 = [$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;
- }
- /**
- * {@inheritdoc}
- */
- public function setEntityManager(EntityManagerInterface $entity_manager) {
- $this->entityManager = $entity_manager;
- return $this;
- }
- /**
- * {@inheritdoc}
- */
- public function setEntityTypeManager(EntityTypeManagerInterface $entity_type_manager) {
- $this->entityTypeManager = $entity_type_manager;
- return $this;
- }
- }
|