diff --git a/core/modules/config_translation/lib/Drupal/config_translation/ConfigMapperInterface.php b/core/modules/config_translation/lib/Drupal/config_translation/ConfigMapperInterface.php index b572708..4def427 100644 --- a/core/modules/config_translation/lib/Drupal/config_translation/ConfigMapperInterface.php +++ b/core/modules/config_translation/lib/Drupal/config_translation/ConfigMapperInterface.php @@ -9,6 +9,7 @@ use Drupal\Core\Language\Language; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Route; /** * Defines an interface for configuration mapper. @@ -47,6 +48,16 @@ public function getBaseRouteParameters(); public function getBaseRoute(); /** + * Sets the base route object. + * + * @param \Symfony\Component\Routing\Route $route + * The route object used as base route. + * + * @see \Drupal\config_translation\Routing\RouteSubscriber::alterRoutes() + */ + public function setBaseRoute(Route $route); + + /** * Returns a processed path for the base route the mapper is attached to. * * @return string diff --git a/core/modules/config_translation/lib/Drupal/config_translation/ConfigNamesMapper.php b/core/modules/config_translation/lib/Drupal/config_translation/ConfigNamesMapper.php index 2df7387..1351e72 100644 --- a/core/modules/config_translation/lib/Drupal/config_translation/ConfigNamesMapper.php +++ b/core/modules/config_translation/lib/Drupal/config_translation/ConfigNamesMapper.php @@ -91,14 +91,13 @@ class ConfigNamesMapper extends PluginBase implements ConfigMapperInterface, Con public function __construct($plugin_id, array $plugin_definition, ConfigFactory $config_factory, LocaleConfigManager $locale_config_manager, ConfigMapperManagerInterface $config_mapper_manager, RouteProviderInterface $route_provider, TranslationInterface $translation_manager) { $this->pluginId = $plugin_id; $this->pluginDefinition = $plugin_definition; + $this->routeProvider = $route_provider; $this->configFactory = $config_factory; $this->localeConfigManager = $locale_config_manager; $this->configMapperManager = $config_mapper_manager; $this->setTranslationManager($translation_manager); - - $this->baseRoute = $route_provider->getRouteByName($this->getBaseRouteName()); } /** @@ -145,12 +144,25 @@ public function getBaseRouteParameters() { * {@inheritdoc} */ public function getBaseRoute() { + // The base route cannot be set directly in the constructor because during + // route rebuilding we need to instantiate the mappers before the respective + // routes are available. + if (!isset($this->baseRoute)) { + $this->baseRoute = $this->routeProvider->getRouteByName($this->getBaseRouteName()); + } return $this->baseRoute; } /** * {@inheritdoc} */ + public function setBaseRoute(Route $route) { + $this->baseRoute = $route; + } + + /** + * {@inheritdoc} + */ public function getBasePath() { return $this->getPathFromRoute($this->getBaseRoute(), $this->getBaseRouteParameters()); } diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Routing/RouteSubscriber.php b/core/modules/config_translation/lib/Drupal/config_translation/Routing/RouteSubscriber.php index 335faa5..077fe1f 100644 --- a/core/modules/config_translation/lib/Drupal/config_translation/Routing/RouteSubscriber.php +++ b/core/modules/config_translation/lib/Drupal/config_translation/Routing/RouteSubscriber.php @@ -9,6 +9,7 @@ use Drupal\Core\Routing\RouteSubscriberBase; use Drupal\config_translation\ConfigMapperManagerInterface; +use Drupal\Core\Routing\RoutingEvents; use Symfony\Component\Routing\RouteCollection; /** @@ -17,11 +18,18 @@ class RouteSubscriber extends RouteSubscriberBase { /** - * The mapper plugin discovery service. + * An array of all configuration mappers, keyed by ID. * - * @var \Drupal\config_translation\ConfigMapperManagerInterface + * @var \Drupal\config_translation\ConfigMapperInterface[] */ - protected $mapperManager; + protected $mappers; + + /** + * A mapping of configuration mappers to their base route name. + * + * @var array + */ + protected $baseRouteNames = array(); /** * Constructs a new RouteSubscriber. @@ -30,27 +38,52 @@ class RouteSubscriber extends RouteSubscriberBase { * The mapper plugin discovery service. */ public function __construct(ConfigMapperManagerInterface $mapper_manager) { - $this->mapperManager = $mapper_manager; + $this->mappers = $mapper_manager->getMappers(); + + // The entire list of base route names is needed so we can check each route + // collection for matching routes in RouteSubscriber::alterRoutes(). In + // order to pass the route information to the configuration mapper we then + // need the respective mapper ID for the base route name. + foreach ($this->mappers as $mapper_id => $mapper) { + $this->baseRouteNames[$mapper_id] = $mapper->getBaseRouteName(); + } } /** * {@inheritdoc} */ protected function alterRoutes(RouteCollection $collection, $provider) { - // @todo \Drupal\config_translation\ConfigNamesMapper uses the route - // provider directly, which is unsafe during rebuild. This currently only - // works by coincidence; fix in https://drupal.org/node/2158571. - if ($provider != 'dynamic_routes') { - return; - } + foreach ($this->baseRouteNames as $mapper_id => $route_name) { + if ($route = $collection->get($route_name)) { + $mapper = $this->mappers[$mapper_id]; + $mapper->setBaseRoute($route); + $collection->add($mapper->getOverviewRouteName(), $mapper->getOverviewRoute()); + $collection->add($mapper->getAddRouteName(), $mapper->getAddRoute()); + $collection->add($mapper->getEditRouteName(), $mapper->getEditRoute()); + $collection->add($mapper->getDeleteRouteName(), $mapper->getDeleteRoute()); - $mappers = $this->mapperManager->getMappers(); - foreach ($mappers as $mapper) { - $collection->add($mapper->getOverviewRouteName(), $mapper->getOverviewRoute()); - $collection->add($mapper->getAddRouteName(), $mapper->getAddRoute()); - $collection->add($mapper->getEditRouteName(), $mapper->getEditRoute()); - $collection->add($mapper->getDeleteRouteName(), $mapper->getDeleteRoute()); + // We do not need to check for the same base route name on the next + // invocation. + unset($this->baseRouteNames[$mapper_id]); + } } + if ($provider == 'dynamic_routes' && !empty($this->baseRouteNames)) { + throw new \RuntimeException(sprintf('The following routes are needed by configuration mappers but were not found: %s', implode(', ', $this->baseRouteNames))); + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events = parent::getSubscribedEvents(); + // This route subscriber adds routes based on the existence of other routes, + // so all route subscribers that add routes need to run before this one. + // Field UI's route subscriber, for example, has priority -100, but provides + // routes for which we need to generate translation routes so this priority + // needs to be lower than that. + $events[RoutingEvents::ALTER] = array('onAlterRoutes', -1000); + return $events; } } diff --git a/core/modules/config_translation/tests/Drupal/config_translation/Tests/ConfigEntityMapperTest.php b/core/modules/config_translation/tests/Drupal/config_translation/Tests/ConfigEntityMapperTest.php index df9aa0c..15bb9cb 100644 --- a/core/modules/config_translation/tests/Drupal/config_translation/Tests/ConfigEntityMapperTest.php +++ b/core/modules/config_translation/tests/Drupal/config_translation/Tests/ConfigEntityMapperTest.php @@ -67,12 +67,6 @@ public function setUp() { $this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface'); - $this->routeProvider - ->expects($this->once()) - ->method('getRouteByName') - ->with('language.edit') - ->will($this->returnValue(new Route('/admin/config/regional/language/edit/{language_entity}'))); - $definition = array( 'class' => '\Drupal\config_translation\ConfigEntityMapper', 'base_route_name' => 'language.edit', diff --git a/core/modules/config_translation/tests/Drupal/config_translation/Tests/ConfigNamesMapperTest.php b/core/modules/config_translation/tests/Drupal/config_translation/Tests/ConfigNamesMapperTest.php index 3df6ac0..522d0fc 100644 --- a/core/modules/config_translation/tests/Drupal/config_translation/Tests/ConfigNamesMapperTest.php +++ b/core/modules/config_translation/tests/Drupal/config_translation/Tests/ConfigNamesMapperTest.php @@ -96,12 +96,6 @@ public function setUp() { $this->baseRoute = new Route('/admin/config/system/site-information'); - $this->routeProvider - ->expects($this->once()) - ->method('getRouteByName') - ->with('system.site_information_settings') - ->will($this->returnValue($this->baseRoute)); - $this->configNamesMapper = new TestConfigNamesMapper( 'system.site_information_settings', $this->pluginDefinition, @@ -139,8 +133,26 @@ public function testGetBaseRouteParameters() { /** * Tests ConfigNamesMapper::getBaseRoute(). + * + * Also tests ConfigNamesMapper::setBaseRoute(). */ - public function testGetBaseRoute() { + public function testBaseRoute() { + // Test that the route can be set manually. + $this->configNamesMapper->setBaseRoute($this->baseRoute); + $result = $this->configNamesMapper->getBaseRoute(); + $this->assertSame($this->baseRoute, $result); + + // Test that the route is fetched from the route provider if it is not set. + $this->configNamesMapper->unsetBaseRoute(); + $this->routeProvider + ->expects($this->once()) + ->method('getRouteByName') + ->with('system.site_information_settings') + ->will($this->returnValue($this->baseRoute)); + $result = $this->configNamesMapper->getBaseRoute(); + $this->assertSame($this->baseRoute, $result); + + // Test that the base route is stored on the mapper. $result = $this->configNamesMapper->getBaseRoute(); $this->assertSame($this->baseRoute, $result); } @@ -149,6 +161,7 @@ public function testGetBaseRoute() { * Tests ConfigNamesMapper::getBasePath(). */ public function testGetBasePath() { + $this->configNamesMapper->setBaseRoute($this->baseRoute); $result = $this->configNamesMapper->getBasePath(); $this->assertSame('/admin/config/system/site-information', $result); } @@ -174,6 +187,7 @@ public function testGetOverviewRouteParameters() { * Tests ConfigNamesMapper::getOverviewRoute(). */ public function testGetOverviewRoute() { + $this->configNamesMapper->setBaseRoute($this->baseRoute); $expected = new Route('/admin/config/system/site-information/translate', array( '_controller' => '\Drupal\config_translation\Controller\ConfigTranslationController::itemPage', @@ -191,6 +205,7 @@ public function testGetOverviewRoute() { * Tests ConfigNamesMapper::getOverviewPath(). */ public function testGetOverviewPath() { + $this->configNamesMapper->setBaseRoute($this->baseRoute); $result = $this->configNamesMapper->getOverviewPath(); $this->assertSame('/admin/config/system/site-information/translate', $result); } @@ -221,6 +236,7 @@ public function testGetAddRouteParameters() { * Tests ConfigNamesMapper::getAddRoute(). */ public function testGetAddRoute() { + $this->configNamesMapper->setBaseRoute($this->baseRoute); $expected = new Route('/admin/config/system/site-information/translate/{langcode}/add', array( '_form' => '\Drupal\config_translation\Form\ConfigTranslationAddForm', @@ -260,6 +276,7 @@ public function testGetEditRouteParameters() { * Tests ConfigNamesMapper::getEditRoute(). */ public function testGetEditRoute() { + $this->configNamesMapper->setBaseRoute($this->baseRoute); $expected = new Route('/admin/config/system/site-information/translate/{langcode}/edit', array( '_form' => '\Drupal\config_translation\Form\ConfigTranslationEditForm', @@ -298,6 +315,7 @@ public function testGetDeleteRouteParameters() { * Tests ConfigNamesMapper::getRoute(). */ public function testGetDeleteRoute() { + $this->configNamesMapper->setBaseRoute($this->baseRoute); $expected = new Route('/admin/config/system/site-information/translate/{langcode}/delete', array( '_form' => '\Drupal\config_translation\Form\ConfigTranslationDeleteForm', @@ -604,6 +622,7 @@ public function testGetTypeName() { * Tests ConfigNamesMapper::hasTranslation(). */ public function testGetOperations() { + $this->configNamesMapper->setBaseRoute($this->baseRoute); $expected = array( 'translate' => array( 'title' => 'Translate', @@ -653,4 +672,11 @@ public function setConfigFactory(ConfigFactory $config_factory) { $this->configFactory = $config_factory; } + /** + * Unsets the base route that is stored in the mapper. + */ + public function unsetBaseRoute() { + unset($this->baseRoute); + } + }