0

We are currently working on a Drupal 8 project in conjunction with a PIM (MaPS). We need to use the MaPS Suite module, which allows to import MaPS data into Drupal. Unfortunately, the MaPS Suite module is not available in Drupal 8 (we contacted the developer, but the Drupal 8 port was stopped) and we are therefore trying to bring it to this version.

It has been a week since we started and we are confronted with a problem that seems simple, but that we cannot solve.

In the MaPS Suite module there is a profile system that allows us to create multiple connection profiles to MaPS servers. We have converted this profile management to a configuration entity.

And for each profile, it's possible to create converters, each converter allows (basically) to do the mapping between a MaPS field and a Drupal field. For those converters that are specific to a profile, we created a content entity that bundled the configuration entity profile.

For that, we went through the automatic generation in Drupal console, unfortunately this generation does not do exactly what we needs, it creates a page of configuration on a side and a page of content on the other. We must have the content in the configuration (for ergonomics).

What we want is this:

  • admin/maps-suite/profiles (profile collection)
  • admin/maps-suite/profile/add
  • admin/maps-suite/profile/{maps_import_profile}/edit
  • ...
  • admin/maps-suite/profile/{maps_import_profile}/converters (converter collection)
  • admin/maps-suite/profile/{maps_import_profile}/converter/add
  • admin/maps-suite/profile/{maps_import_profile}/converter/{maps_import_converter}/edit
  • ...

When we do this, we are able to create our content entity, but once done, it is impossible to run the pages collection, edit and other where converters entities are present. The {maps_import_profile} parameters are not retrieved from the collection page and others, so it is impossible to create the edit links and the rest.

The error we get is the following:

First eror resolved with adding parameters in MapsImportConverterHtmlRouteProvider:

Symfony\Component\Routing\Exception\MissingMandatoryParametersException: Some mandatory parameters are missing ("maps_import_profile") to generate a URL for route "entity.maps_import_converter.collection". in Drupal\Core\Routing\UrlGenerator->doGenerate() (line 180 of core\lib\Drupal\Core\Routing\UrlGenerator.php).

New error:

Symfony\Component\Routing\Exception\MissingMandatoryParametersException: Some mandatory parameters are missing ("maps_import_profile") to generate a URL for route "entity.maps_import_converter.edit_form". in Drupal\Core\Routing\UrlGenerator->doGenerate() (line 180 of core\lib\Drupal\Core\Routing\UrlGenerator.php).

This is part of the code we are using.

Profile

/**
 * Defines the Maps import profile entity.
 *
 * @ConfigEntityType(
 *   id = "maps_import_profile",
 *   label = @Translation("Maps import profile"),
 *   label_collection = @Translation("Profiles"),
 *   handlers = {
 *     "form" = {
 *       "add" = "Drupal\maps_import\Form\MapsImportProfileForm",
 *       "edit" = "Drupal\maps_import\Form\MapsImportProfileForm",
 *       "delete" = "Drupal\maps_import\Form\MapsImportProfileDeleteForm",
 *       "export" = "Drupal\maps_import\Form\MapsImportProfileExportForm",
 *       "importer" = "Drupal\maps_import\Form\MapsImportProfileImporterForm"
 *     },
 *     "route_provider" = {
 *       "html" = "Drupal\maps_import\MapsImportProfileHtmlRouteProvider",
 *     },
 *     "list_builder" = "Drupal\maps_import\MapsImportProfileListBuilder",
 *   },
 *   admin_permission = "administer maps suite",
 *   config_prefix = "maps_import_profile",
 *   bundle_of = "maps_import_converter",
 *   entity_keys = {
 *     "id" = "profile",
 *     "label" = "label",
 *     "fetchMethod" = "fetchMethod",
 *     "configurationFile" = "configurationFile",
 *     "objectsFile" = "objectsFile",
 *     "token" = "token",
 *     "publicationId" = "publicationId",
 *     "url" = "url",
 *     "webTemplate" = "webTemplate",
 *     "options" = "options",
 *     "rootObjectId" = "rootObjectId",
 *     "presetGroupId" = "presetGroupId",
 *     "mediaDirectory" = "mediaDirectory",
 *     "mediaAccessibility" = "mediaAccessibility",
 *     "maxObjectsPerRequest" = "maxObjectsPerRequest",
 *     "maxObjectsPerOp" = "maxObjectsPerOp",
 *     "format" = "format",
 *     "enabled" = "enabled"
 *   },
 *   links = {
 *     "add-form" = "/admin/maps-suite/profile/add",
 *     "edit-form" = "/admin/maps-suite/profile/{maps_import_profile}/edit",
 *     "export-form" = "/admin/maps-suite/profile/{maps_import_profile}/export",
 *     "delete-form" = "/admin/maps-suite/profile/{maps_import_profile}/delete",
 *     "importer-form" = "/admin/maps-suite/profile/{maps_import_profile}/importer",
 *     "collection" = "/admin/maps-suite/profiles"
 *   }
 * )
 */

Converter

/**
 * Defines the Maps import converter entity.
 *
 * @ingroup maps_import
 *
 * @ContentEntityType(
 *   id = "maps_import_converter",
 *   label = @Translation("Maps import converter"),
 *   bundle_label = @Translation("Maps import profile"),
 *   handlers = {
 *     "list_builder" = "Drupal\maps_import\MapsImportConverterListBuilder",
 *     "form" = {
 *       "default" = "Drupal\maps_import\Form\MapsImportConverterForm",
 *       "add" = "Drupal\maps_import\Form\MapsImportConverterForm",
 *       "edit" = "Drupal\maps_import\Form\MapsImportConverterForm",
 *       "delete" = "Drupal\maps_import\Form\MapsImportConverterDeleteForm",
 *     },
 *     "route_provider" = {
 *       "html" = "Drupal\maps_import\MapsImportConverterHtmlRouteProvider",
 *     },
 *   },
 *   base_table = "maps_import_converter",
 *   admin_permission = "administer maps suite",
 *   entity_keys = {
 *     "id" = "id",
 *     "bundle" = "type",
 *     "label" = "name",
 *     "uuid" = "uuid",
 *   },
 *    links = {
 *     "add-form" = "/admin/maps-suite/profile/{maps_import_profile}/converter/add",
 *     "edit-form" = "/admin/maps-suite/profile/{maps_import_profile}/converter/{maps_import_converter}/edit",
 *     "delete-form" = "/admin/maps-suite/profile/{maps_import_profile}/converter/{maps_import_converter}/delete",
 *     "collection" = "/admin/maps-suite/profile/{maps_import_profile}/converters",
 *   },
 *   bundle_entity_type = "maps_import_profile",
 * )
 */

The bundle is correct on the converter entities but we fail to make sure, once the entity is created, to use its bundle for route generation.

Edit 1 (fix the first error): As we use a config and content entity, there is default routing with custom MapsImportConverterHtmlRouteProvider that extends AdminHtmlRouteProvider. We were able to add the parameters in all the routes of the entity to be converted, this pass the first error for collection but a new one appaer. I put the error above. I put the entire ConfigEntity and ContentEntity above, and here is the MapsImportConverterRouteProvider :

<?php

namespace Drupal\maps_import;

use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
use Drupal\Core\Entity\Controller\EntityController;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider;
use Symfony\Component\Routing\Route;

/**
 * Provides routes for Maps import converter entities.
 *
 * @see \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider
 * @see \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider
 */
class MapsImportConverterHtmlRouteProvider extends AdminHtmlRouteProvider {

  /**
   * {@inheritdoc}
   */
  public function getRoutes(EntityTypeInterface $entity_type) {
    $collection = parent::getRoutes($entity_type);

    $entity_type_id = $entity_type->id();

    $routes = $collection->all();
    foreach ($routes as $route_id => $route) {
      // Use the add form handler, if available, otherwise default.
      $operation = 'default';
      if ($entity_type->getFormClass('add')) {
        $operation = 'add';
      }
      $route->addDefaults(['entity_type_id' => $entity_type_id]);

      // If the entity has bundles, we can provide a bundle-specific title
      // and access requirements.
      $expected_parameter = $entity_type->getBundleEntityType() ?: $entity_type->getKey('bundle');
      // @todo: We have to check if a route contains a bundle in its path as
      //   test entities have inconsistent usage of "add-form" link templates.
      //   Fix it in https://www.drupal.org/node/2699959.
      if (($bundle_key = $entity_type->getKey('bundle')) && strpos($route->getPath(), '{' . $expected_parameter . '}') !== FALSE) {
        $route->addDefaults(['_title_callback' => EntityController::class . '::addBundleTitle']);
        // If the bundles are entities themselves, we can add parameter
        // information to the route options.
        if ($bundle_entity_type_id = $entity_type->getBundleEntityType()) {
          $bundle_entity_type = $this->entityTypeManager->getDefinition($bundle_entity_type_id);

          $route
            // The title callback uses the value of the bundle parameter to
            // fetch the respective bundle at runtime.
            ->addDefaults(['bundle_parameter' => $bundle_entity_type_id])
            ->addRequirements(['_entity_create_access' => $entity_type_id . ':{' . $bundle_entity_type_id . '}']);

          // Entity types with serial IDs can specify this in their route
          // requirements, improving the matching process.
          if ($this->getEntityTypeIdKeyType($bundle_entity_type) === 'integer') {
            $route->addRequirements([$entity_type_id => '\d+']);
          }

          $bundle_entity_parameter = ['type' => 'entity:' . $bundle_entity_type_id];
          if ($bundle_entity_type instanceof ConfigEntityTypeInterface) {
            // The add page might be displayed on an admin path. Even then, we
            // need to load configuration overrides so that, for example, the
            // bundle label gets translated correctly.
            // @see \Drupal\Core\ParamConverter\AdminPathConfigEntityConverter
            $bundle_entity_parameter['with_config_overrides'] = TRUE;
          }
          $parameters = $route->getOption('parameters');
          $parameters[$bundle_entity_type_id] = $bundle_entity_parameter;
          $route->setOption('parameters', $parameters);
        }
      }
    }

    return $collection;
  }

}

Edit 2 (fix the second error): After many tests, we managed to run the collection page of the converters. To do this, in the MapsImportConverterListBuilder, we had to add the following:

public function getDefaultOperations(EntityInterface $entity) {
  $operations = parent::getDefaultOperations($entity);

  foreach($operations as &$operation) {
    $operation['url']->setRouteParameter('maps_import_profile', $entity->getType());
  }

  return $operations;
}

The last problem that persists now is to be able to filter the converters correctly on the collection page. As seen above, the collection page can be found here: - admin/maps-suite/profile/{maps_import_profile}/converters This page works but show all the converters, not only those with the {maps_import_profile} provided. I already know that we need to use the getEntityIds function to achieve this but i can't figure out how to retrieve the parameters from the route in this function.

0

After a while, we finally managed to get what we wanted. For this, here is the list of errors and their correction:

Error:

Symfony\Component\Routing\Exception\MissingMandatoryParametersException: Some mandatory parameters are missing ("maps_import_profile") to generate a URL for route "entity.maps_import_converter.collection". in Drupal\Core\Routing\UrlGenerator->doGenerate() (line 180 of core\lib\Drupal\Core\Routing\UrlGenerator.php).

Correction:

As we use a config and content entity, there is default routing with custom MapsImportConverterHtmlRouteProvider that extends AdminHtmlRouteProvider. We were able to add the parameters in all the routes of the entity to be converted, this pass the first error for collection but a new one appaer. I put the error above. Here is the MapsImportConverterRouteProvider :

<?php

namespace Drupal\maps_import;

use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
use Drupal\Core\Entity\Controller\EntityController;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider;
use Symfony\Component\Routing\Route;

/**
 * Provides routes for Maps import converter entities.
 *
 * @see \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider
 * @see \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider
 */
class MapsImportConverterHtmlRouteProvider extends AdminHtmlRouteProvider {

  /**
   * {@inheritdoc}
   */
  public function getRoutes(EntityTypeInterface $entity_type) {
    $collection = parent::getRoutes($entity_type);

    $entity_type_id = $entity_type->id();

    $routes = $collection->all();
    foreach ($routes as $route_id => $route) {
      // Use the add form handler, if available, otherwise default.
      $operation = 'default';
      if ($entity_type->getFormClass('add')) {
        $operation = 'add';
      }
      $route->addDefaults(['entity_type_id' => $entity_type_id]);

      // If the entity has bundles, we can provide a bundle-specific title
      // and access requirements.
      $expected_parameter = $entity_type->getBundleEntityType() ?: $entity_type->getKey('bundle');
      // @todo: We have to check if a route contains a bundle in its path as
      //   test entities have inconsistent usage of "add-form" link templates.
      //   Fix it in https://www.drupal.org/node/2699959.
      if (($bundle_key = $entity_type->getKey('bundle')) && strpos($route->getPath(), '{' . $expected_parameter . '}') !== FALSE) {
        $route->addDefaults(['_title_callback' => EntityController::class . '::addBundleTitle']);
        // If the bundles are entities themselves, we can add parameter
        // information to the route options.
        if ($bundle_entity_type_id = $entity_type->getBundleEntityType()) {
          $bundle_entity_type = $this->entityTypeManager->getDefinition($bundle_entity_type_id);

          $route
            // The title callback uses the value of the bundle parameter to
            // fetch the respective bundle at runtime.
            ->addDefaults(['bundle_parameter' => $bundle_entity_type_id])
            ->addRequirements(['_entity_create_access' => $entity_type_id . ':{' . $bundle_entity_type_id . '}']);

          // Entity types with serial IDs can specify this in their route
          // requirements, improving the matching process.
          if ($this->getEntityTypeIdKeyType($bundle_entity_type) === 'integer') {
            $route->addRequirements([$entity_type_id => '\d+']);
          }

          $bundle_entity_parameter = ['type' => 'entity:' . $bundle_entity_type_id];
          if ($bundle_entity_type instanceof ConfigEntityTypeInterface) {
            // The add page might be displayed on an admin path. Even then, we
            // need to load configuration overrides so that, for example, the
            // bundle label gets translated correctly.
            // @see \Drupal\Core\ParamConverter\AdminPathConfigEntityConverter
            $bundle_entity_parameter['with_config_overrides'] = TRUE;
          }
          $parameters = $route->getOption('parameters');
          $parameters[$bundle_entity_type_id] = $bundle_entity_parameter;
          $route->setOption('parameters', $parameters);
        }
      }
    }

    return $collection;
  }

}

Error:

Symfony\Component\Routing\Exception\MissingMandatoryParametersException: Some mandatory parameters are missing ("maps_import_profile") to generate a URL for route "entity.maps_import_converter.edit_form". in Drupal\Core\Routing\UrlGenerator->doGenerate() (line 180 of core\lib\Drupal\Core\Routing\UrlGenerator.php).

Correction:

After many tests, we managed to run the collection page of the converters. To do this, in the MapsImportConverterListBuilder, we had to add the following:

public function getDefaultOperations(EntityInterface $entity) {
  $operations = parent::getDefaultOperations($entity);

  foreach($operations as &$operation) {
    $operation['url']->setRouteParameter('maps_import_profile', $entity->getType());
  }

  return $operations;
}

We had to make similar changes in editing, deleting, and so on.

Last problem:

In the Collection page, all converters were displayed, not just those of the current bundle.

Correction:

To solve this problem we had to remove the management of the collection of the entity to create ourselves a controller and be able to add the parameters. For this we used the Block module with BlockListingController.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.