You are here

FormModesSubscriber.php in Form mode manager 8.2

Same filename and directory in other branches
  1. 8 src/Routing/EventSubscriber/FormModesSubscriber.php

File

src/Routing/EventSubscriber/FormModesSubscriber.php
View source
<?php

namespace Drupal\form_mode_manager\Routing\EventSubscriber;

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\RouteSubscriberBase;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\form_mode_manager\FormModeManagerInterface;
use Drupal\form_mode_manager\EntityRoutingMapManager;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

/**
 * Listens to the dynamic route event and add routes using form modes.
 */
class FormModesSubscriber extends RouteSubscriberBase {
  use StringTranslationTrait;

  /**
   * Namespace of transverse entity controller.
   *
   * @var string
   */
  const FORM_MODE_DEFAULT_CONTROLLER = '\\Drupal\\form_mode_manager\\Controller\\FormModeManagerEntityController';

  /**
   * The Regex pattern to contextualize process by route path.
   *
   * @var string
   */
  const ROUTE_PATH_CONTEXT_REGEX = '/(^.*?\\/edit)|(^.*?\\/{block_content})/';

  /**
   * The entity type plugin definition.
   *
   * @var \Drupal\Core\Entity\EntityTypeInterface
   */
  protected $entityDefinition;

  /**
   * The entity display repository.
   *
   * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
   */
  protected $entityDisplayRepository;

  /**
   * The current instance of Routing Map plugin for a specific entity type.
   *
   * @var \Drupal\form_mode_manager\EntityRoutingMapBase
   */
  protected $entityRoutingDefinition;

  /**
   * The Routing Map Plugin service.
   *
   * @var \Drupal\form_mode_manager\EntityRoutingMapManager
   */
  protected $entityRoutingMap;

  /**
   * The entity type manager service.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The entity Form Mode manager service.
   *
   * @var \Drupal\form_mode_manager\FormModeManagerInterface
   */
  protected $formModeManager;

  /**
   * The route collection for adding routes.
   *
   * @var \Symfony\Component\Routing\RouteCollection
   */
  protected $routeCollection;

  /**
   * Constructs a new RouteSubscriber object.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
   *   The entity display repository.
   * @param \Drupal\form_mode_manager\FormModeManagerInterface $form_mode_manager
   *   The form mode manager.
   * @param \Drupal\form_mode_manager\EntityRoutingMapManager $plugin_routes_manager
   *   Plugin EntityRoutingMap to retrieve entity form operation routes.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityDisplayRepositoryInterface $entity_display_repository, FormModeManagerInterface $form_mode_manager, EntityRoutingMapManager $plugin_routes_manager) {
    $this->entityTypeManager = $entity_type_manager;
    $this->entityDisplayRepository = $entity_display_repository;
    $this->formModeManager = $form_mode_manager;
    $this->entityRoutingMap = $plugin_routes_manager;
  }

  /**
   * Add one route collection per entity using form modes.
   *
   * {@inheritdoc}
   */
  protected function alterRoutes(RouteCollection $collection) {
    $this->routeCollection = $collection;
    foreach ($this->formModeManager
      ->getAllFormModesDefinitions() as $entity_type_id => $form_modes) {
      $this->entityDefinition = $this->entityTypeManager
        ->getDefinition($entity_type_id);
      $this->entityRoutingDefinition = $this->entityRoutingMap
        ->createInstance($entity_type_id, [
        'entityTypeId' => $entity_type_id,
      ]);
      $this
        ->addFormModesRoutes($form_modes);
    }
  }

  /**
   * Add a collection of route per form mode for current entity.
   *
   * Each entity need to define a collection of routes to be used by Drupal.
   * Each `entity operation` is a form handler present for all,
   * ContentEntityType plugins. Each operation represent a Form to be,
   * displayed by formBuilder service in routes controllers.
   * To respect core logic implemented by Drupal we need to add,
   * a route for each operations (add/edit/add_page/admin_add) dynamically and,
   * a transverse controller compatible to use form modes.
   *
   * Each routes generated by `setFormModeCollection` and,
   * `setAddPageCollection` respect the following naming,
   * `ENTITY_ROUTES_BASIS.FORM_MODE_NAME` eg for Node entity :
   *
   * @code
   *  $routes = [
   *    'add_form' => 'node.add.FORM_MODE_NAME',
   *    'edit_form' => 'entity.node.edit_form.FORM_MODE_NAME',
   *    'add_page' => 'form_mode_manager.node.add_page.FORM_MODE_NAME',
   *    'admin_add' => 'node.add.FORM_MODE_NAME',
   *  ];
   * @endcode
   *
   * @param array $form_modes
   *   All form-modes available for specified entity_type_id.
   */
  protected function addFormModesRoutes(array $form_modes) {
    foreach ($form_modes as $form_mode_infos) {
      $this
        ->setFormModeCollection($form_mode_infos, 'add_form');
      $this
        ->setFormModeCollection($form_mode_infos, 'edit_form');
      $this
        ->setFormModeCollection($form_mode_infos, 'admin_add');
      if (!empty($this->entityDefinition
        ->getKey('bundle'))) {
        $this
          ->setAddPageCollection($form_mode_infos);
      }
    }
  }

  /**
   * Create a route for given form mode and operation form handler.
   *
   * This method add a route for given form mode and respect the standard,
   * of parent entity routing naming. All routes added in collection are,
   * based on parrent route parameters and only add only,
   * the minimum to use form modes. The route add only if parent entity,
   * declare to use this operation in `entityRoutingMap` plugin.
   *
   * @param array $form_mode_infos
   *   A form-mode for specified entity_type_id.
   * @param string $operation_name
   *   The entity operation name.
   */
  public function setFormModeCollection(array $form_mode_infos, $operation_name) {
    if ($this->entityRoutingDefinition
      ->getOperation($operation_name) && ($route = $this
      ->getFormModeRoute($form_mode_infos, $operation_name))) {
      $form_mode_name = $this->formModeManager
        ->getFormModeMachineName($form_mode_infos['id']);
      $form_mode_route_name = "{$this->entityRoutingDefinition->getOperation($operation_name)}.{$form_mode_name}";
      $this->routeCollection
        ->add($form_mode_route_name, $route);
    }
  }

  /**
   * Get the Form Mode Manager route for given operation.
   *
   * @param array $form_mode_infos
   *   The form mode info.
   * @param string $operation_name
   *   The entity operation name.
   *
   * @return \Symfony\Component\Routing\Route|null
   *   The generated route, if available.
   */
  public function getFormModeRoute(array $form_mode_infos, $operation_name) {
    $route_name = $this->entityRoutingDefinition
      ->getOperation($operation_name);
    $form_mode_machine_name = $this->formModeManager
      ->getFormModeMachineName($form_mode_infos['id']);
    $entity_type_id = $this->entityDefinition
      ->id();
    if ($this->formModeManager
      ->hasActiveFormMode($entity_type_id, $form_mode_machine_name) && ($entity_edit_route = $this->routeCollection
      ->get($route_name))) {
      return $this
        ->setRoutes($entity_edit_route, $form_mode_infos);
    }
    return NULL;
  }

  /**
   * Generate a routes based on top of given parent routes.
   *
   * @param \Symfony\Component\Routing\Route $parent_route
   *   The route object of entity.
   * @param array $form_mode_infos
   *   The form mode info.
   *
   * @return \Symfony\Component\Routing\Route
   *   Form Mode Manager route to be added on entity collection.
   */
  protected function setRoutes(Route $parent_route, array $form_mode_infos) {
    $route_path = implode('/', [
      $parent_route
        ->getPath(),
      $this->formModeManager
        ->getFormModeMachineName($form_mode_infos['id']),
    ]);
    $route_defaults = NestedArray::mergeDeep($parent_route
      ->getDefaults(), $this
      ->getFormModeRouteDefaults($parent_route, $form_mode_infos));
    $route_options = NestedArray::mergeDeep($parent_route
      ->getOptions(), $this
      ->getFormModeRouteOptions($form_mode_infos));
    $route_requirements = NestedArray::mergeDeep($parent_route
      ->getRequirements(), $this
      ->getFormModeRouteRequirements($form_mode_infos));
    return new Route($route_path, $route_defaults, $route_requirements, $route_options);
  }

  /**
   * Get defaults parameters needed to build Form Mode Manager routes.
   *
   * @param \Symfony\Component\Routing\Route $route
   *   The route object of entity.
   * @param array $form_mode_infos
   *   The form mode info.
   *
   * @return array
   *   Array contain defaults routes parameters.
   */
  protected function getFormModeRouteDefaults(Route $route, array $form_mode_infos) {
    $route_parameters = [
      '_entity_form' => $form_mode_infos['id'],
      '_controller' => static::FORM_MODE_DEFAULT_CONTROLLER . '::entityAdd',
      '_title_callback' => static::FORM_MODE_DEFAULT_CONTROLLER . '::addPageTitle',
    ];
    if (static::isEditRoute($route)) {
      $route_parameters['_title_callback'] = static::FORM_MODE_DEFAULT_CONTROLLER . '::editPageTitle';
      $route_parameters['_controller'] = static::FORM_MODE_DEFAULT_CONTROLLER . '::entityEdit';
    }
    return $route_parameters;
  }

  /**
   * Evaluate if current context is edit.
   *
   * @param \Symfony\Component\Routing\Route $route
   *   The route object of entity.
   *
   * @return bool
   *   True if current route context is edit or False if not.
   */
  public static function isEditRoute(Route $route) {
    return (bool) preg_match_all(self::ROUTE_PATH_CONTEXT_REGEX, $route
      ->getPath(), $matches, PREG_SET_ORDER, 0);
  }

  /**
   * Get options parameters nedeed to build Form Mode Manager routes.
   *
   * @param array $form_mode_infos
   *   The form mode info.
   *
   * @return array
   *   Array contain options routes parameters.
   */
  protected function getFormModeRouteOptions(array $form_mode_infos) {
    $entity_definition = $this->entityDefinition;
    $entity_type_id = $entity_definition
      ->id();
    $route_definition = [
      '_form_mode_manager_entity_type_id' => $entity_type_id,
      'form_mode_theme' => NULL,
      'parameters' => [
        $entity_type_id => [
          'type' => "entity:{$entity_type_id}",
        ],
        'form_mode' => $form_mode_infos + [
          'type' => NULL,
        ],
      ],
    ];
    if (!empty($entity_definition
      ->getKey('bundle'))) {
      $route_definition['_form_mode_manager_bundle_entity_type_id'] = $entity_definition
        ->getBundleEntityType();
    }
    return $route_definition;
  }

  /**
   * Get options requirements nedeed to build Form Mode Manager routes.
   *
   * @param array $form_mode_infos
   *   The form mode info.
   *
   * @return array
   *   Array contain requirements routes parameters.
   */
  protected function getFormModeRouteRequirements(array $form_mode_infos) {
    return [
      '_permission' => "use {$form_mode_infos['id']} form mode",
      '_custom_access' => static::FORM_MODE_DEFAULT_CONTROLLER . '::checkAccess',
    ];
  }

  /**
   * Add one route for `add_page` entity operation per form_mode.
   *
   * This page concern only bundled entities using a route for listing all,
   * bundles using by this entity. Form mode manager add more granularity ,
   * permit to choose what bundle is compatible with a specific form mode.
   * This method add one listing route by form mode to provide,
   * these granularity. Not all entities declare a `add_page` operation,
   * because this isn't needed for all usecases but Form mode manager need,
   * to add this possibility as a standard.
   * All routes key are named with to follow these standard,
   * `form_mode_manager.ENTITY_TYPE_ID.add_page.FORM_MODE_NAME`
   *
   * @param array $form_mode_infos
   *   A form-mode for specified entity_type_id.
   */
  public function setAddPageCollection(array $form_mode_infos) {
    $form_mode_name = $this->formModeManager
      ->getFormModeMachineName($form_mode_infos['id']);
    if ($route = $this
      ->getFormModeListPageRoute($form_mode_infos)) {
      $form_mode_route_name = "form_mode_manager.{$this->entityDefinition->id()}.add_page.{$form_mode_name}";
      $this->routeCollection
        ->add($form_mode_route_name, $route);
    }
  }

  /**
   * Generate routes to use `add-list` entity operation per form modes.
   *
   * @param array $form_mode_infos
   *   An associative array represent a DisplayForm entity.
   *
   * @return \Symfony\Component\Routing\Route|null
   *   The generated route, if available.
   */
  public function getFormModeListPageRoute(array $form_mode_infos) {
    $form_mode_machine_name = $this->formModeManager
      ->getFormModeMachineName($form_mode_infos['id']);
    $entity_type_id = $this->entityDefinition
      ->id();
    $route = NULL;
    if ($this->formModeManager
      ->hasActiveFormMode($entity_type_id, $form_mode_machine_name)) {
      $route_path = implode('/', [
        $entity_type_id,
        'add-list',
        $this->formModeManager
          ->getFormModeMachineName($form_mode_infos['id']),
      ]);
      $route_defaults = [
        '_controller' => static::FORM_MODE_DEFAULT_CONTROLLER . '::addPage',
        '_title' => $this
          ->getListRouteTitle($form_mode_infos['label']),
        'form_mode_name' => $this->formModeManager
          ->getFormModeMachineName($form_mode_infos['id']),
      ];
      $route_requirements = [
        '_permission' => "use {$form_mode_infos['id']} form mode",
      ];
      $route_options = [
        '_form_mode_manager_entity_type_id' => $entity_type_id,
        '_form_mode_manager_bundle_entity_type_id' => $this->entityDefinition
          ->getBundleEntityType(),
        '_admin_route' => TRUE,
      ];
      $route = new Route("/{$route_path}", $route_defaults, $route_requirements, $route_options);
    }
    return $route;
  }

  /**
   * Format the title for `add_list` routes.
   *
   * @param string $form_mode_label
   *   The label of current DisplayForm entity.
   *
   * @return string
   *   The translated title string of `add_list` operation routes.
   */
  public function getListRouteTitle($form_mode_label) {
    $translatable_markup = $this
      ->t('Add @entity_type as @form_mode_label', [
      '@entity_type' => $this->entityDefinition
        ->getLabel(),
      '@form_mode_label' => $form_mode_label,
    ]);
    return $translatable_markup
      ->render();
  }

}

Classes

Namesort descending Description
FormModesSubscriber Listens to the dynamic route event and add routes using form modes.