AliasUniquifier.php 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. <?php
  2. namespace Drupal\pathauto;
  3. use Drupal\Component\Utility\Unicode;
  4. use Drupal\Core\Config\ConfigFactoryInterface;
  5. use Drupal\Core\Extension\ModuleHandlerInterface;
  6. use Drupal\Core\Language\LanguageInterface;
  7. use Drupal\Core\Path\AliasManagerInterface;
  8. use Drupal\Core\Routing\RouteProviderInterface;
  9. /**
  10. * Provides a utility for creating a unique path alias.
  11. */
  12. class AliasUniquifier implements AliasUniquifierInterface {
  13. /**
  14. * Config factory.
  15. *
  16. * @var \Drupal\Core\Config\ConfigFactoryInterface
  17. */
  18. protected $configFactory;
  19. /**
  20. * The alias storage helper.
  21. *
  22. * @var \Drupal\pathauto\AliasStorageHelperInterface
  23. */
  24. protected $aliasStorageHelper;
  25. /**
  26. * The module handler.
  27. *
  28. * @var \Drupal\Core\Extension\ModuleHandlerInterface
  29. */
  30. protected $moduleHandler;
  31. /**
  32. * The route provider service.
  33. *
  34. * @var \Drupal\Core\Routing\RouteProviderInterface.
  35. */
  36. protected $routeProvider;
  37. /**
  38. * The alias manager.
  39. *
  40. * @var \Drupal\Core\Path\AliasManagerInterface
  41. */
  42. protected $aliasManager;
  43. /**
  44. * Creates a new AliasUniquifier.
  45. *
  46. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
  47. * The config factory.
  48. * @param \Drupal\pathauto\AliasStorageHelperInterface $alias_storage_helper
  49. * The alias storage helper.
  50. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  51. * The module handler.
  52. * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
  53. * The route provider service.
  54. */
  55. public function __construct(ConfigFactoryInterface $config_factory, AliasStorageHelperInterface $alias_storage_helper, ModuleHandlerInterface $module_handler, RouteProviderInterface $route_provider, AliasManagerInterface $alias_manager) {
  56. $this->configFactory = $config_factory;
  57. $this->aliasStorageHelper = $alias_storage_helper;
  58. $this->moduleHandler = $module_handler;
  59. $this->routeProvider = $route_provider;
  60. $this->aliasManager = $alias_manager;
  61. }
  62. /**
  63. * {@inheritdoc}
  64. */
  65. public function uniquify(&$alias, $source, $langcode) {
  66. $config = $this->configFactory->get('pathauto.settings');
  67. if (!$this->isReserved($alias, $source, $langcode)) {
  68. return;
  69. }
  70. // If the alias already exists, generate a new, hopefully unique, variant.
  71. $maxlength = min($config->get('max_length'), $this->aliasStorageHelper->getAliasSchemaMaxlength());
  72. $separator = $config->get('separator');
  73. $original_alias = $alias;
  74. $i = 0;
  75. do {
  76. // Append an incrementing numeric suffix until we find a unique alias.
  77. $unique_suffix = $separator . $i;
  78. $alias = Unicode::truncate($original_alias, $maxlength - Unicode::strlen($unique_suffix), TRUE) . $unique_suffix;
  79. $i++;
  80. } while ($this->isReserved($alias, $source, $langcode));
  81. }
  82. /**
  83. * {@inheritdoc}
  84. */
  85. public function isReserved($alias, $source, $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED) {
  86. // Check if this alias already exists.
  87. if ($existing_source = $this->aliasManager->getPathByAlias($alias, $langcode)) {
  88. if ($existing_source != $alias) {
  89. // If it is an alias for the provided source, it is allowed to keep using
  90. // it. If not, then it is reserved.
  91. return $existing_source != $source;
  92. }
  93. }
  94. // Then check if there is a route with the same path.
  95. if ($this->isRoute($alias)) {
  96. return TRUE;
  97. }
  98. // Finally check if any other modules have reserved the alias.
  99. $args = array(
  100. $alias,
  101. $source,
  102. $langcode,
  103. );
  104. $implementations = $this->moduleHandler->getImplementations('pathauto_is_alias_reserved');
  105. foreach ($implementations as $module) {
  106. $result = $this->moduleHandler->invoke($module, 'pathauto_is_alias_reserved', $args);
  107. if (!empty($result)) {
  108. // As soon as the first module says that an alias is in fact reserved,
  109. // then there is no point in checking the rest of the modules.
  110. return TRUE;
  111. }
  112. }
  113. return FALSE;
  114. }
  115. /**
  116. * Verify if the given path is a valid route.
  117. *
  118. * @param string $path
  119. * A string containing a relative path.
  120. *
  121. * @return bool
  122. * TRUE if the path already exists.
  123. *
  124. * @throws \InvalidArgumentException
  125. */
  126. public function isRoute($path) {
  127. if (is_file(DRUPAL_ROOT . '/' . $path) || is_dir(DRUPAL_ROOT . '/' . $path)) {
  128. // Do not allow existing files or directories to get assigned an automatic
  129. // alias. Note that we do not need to use is_link() to check for symbolic
  130. // links since this returns TRUE for either is_file() or is_dir() already.
  131. return TRUE;
  132. }
  133. $routes = $this->routeProvider->getRoutesByPattern($path);
  134. // Only return true for an exact match, ignore placeholders.
  135. foreach ($routes as $route) {
  136. if ($route->getPath() == $path) {
  137. return TRUE;
  138. }
  139. }
  140. return FALSE;
  141. }
  142. }