DomainSourcePathProcessor.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. <?php
  2. namespace Drupal\domain_source\HttpKernel;
  3. use Drupal\domain\DomainNegotiatorInterface;
  4. use Drupal\Core\Config\ConfigFactoryInterface;
  5. use Drupal\Core\Entity\EntityTypeManagerInterface;
  6. use Drupal\Core\Path\AliasManagerInterface;
  7. use Drupal\Core\Extension\ModuleHandlerInterface;
  8. use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
  9. use Drupal\Core\Render\BubbleableMetadata;
  10. use Drupal\Core\Url;
  11. use Symfony\Component\HttpFoundation\Request;
  12. /**
  13. * Processes the outbound path using path alias lookups.
  14. */
  15. class DomainSourcePathProcessor implements OutboundPathProcessorInterface {
  16. /**
  17. * The Domain negotiator.
  18. *
  19. * @var \Drupal\domain\DomainNegotiatorInterface
  20. */
  21. protected $negotiator;
  22. /**
  23. * The module handler.
  24. *
  25. * @var \Drupal\Core\Extension\ModuleHandlerInterface
  26. */
  27. protected $moduleHandler;
  28. /**
  29. * The entity type manager.
  30. *
  31. * @var \Drupal\Core\Entity\EntityTypeManagerInterface
  32. */
  33. protected $entityTypeManager;
  34. /**
  35. * The path alias manager.
  36. *
  37. * @var \Drupal\Core\Path\AliasManagerInterface
  38. */
  39. protected $aliasManager;
  40. /**
  41. * The config factory.
  42. *
  43. * @var \Drupal\Core\Config\ConfigFactoryInterface
  44. */
  45. protected $configFactory;
  46. /**
  47. * An array of content entity types.
  48. *
  49. * @var array
  50. */
  51. protected $entityTypes;
  52. /**
  53. * An array of routes exclusion settings, keyed by route.
  54. *
  55. * @var array
  56. */
  57. protected $excludedRoutes;
  58. /**
  59. * The active domain request.
  60. *
  61. * @var \Drupal\domain\DomainInterface
  62. */
  63. protected $activeDomain;
  64. /**
  65. * The domain storage.
  66. *
  67. * @var \Drupal\domain\DomainStorageInterface|null
  68. */
  69. protected $domainStorage;
  70. /**
  71. * Constructs a DomainSourcePathProcessor object.
  72. *
  73. * @param \Drupal\domain\DomainNegotiatorInterface $negotiator
  74. * The domain negotiator.
  75. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  76. * The module handler service.
  77. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
  78. * The entity type manager.
  79. * @param \Drupal\Core\Path\AliasManagerInterface $alias_manager
  80. * The path alias manager.
  81. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
  82. * The config factory.
  83. */
  84. public function __construct(DomainNegotiatorInterface $negotiator, ModuleHandlerInterface $module_handler, EntityTypeManagerInterface $entity_type_manager, AliasManagerInterface $alias_manager, ConfigFactoryInterface $config_factory) {
  85. $this->negotiator = $negotiator;
  86. $this->moduleHandler = $module_handler;
  87. $this->entityTypeManager = $entity_type_manager;
  88. $this->aliasManager = $alias_manager;
  89. $this->configFactory = $config_factory;
  90. }
  91. /**
  92. * {@inheritdoc}
  93. */
  94. public function processOutbound($path, &$options = [], Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {
  95. // Load the active domain if not set.
  96. if (empty($options['active_domain'])) {
  97. $active_domain = $this->getActiveDomain();
  98. }
  99. // Only act on valid internal paths and when a domain loads.
  100. if (empty($active_domain) || empty($path) || !empty($options['external'])) {
  101. return $path;
  102. }
  103. // Set the default source information.
  104. $source = NULL;
  105. $options['active_domain'] = $active_domain;
  106. // Get the current language.
  107. $langcode = NULL;
  108. if (!empty($options['language'])) {
  109. $langcode = $options['language']->getId();
  110. }
  111. // Get the URL object for this request.
  112. $alias = $this->aliasManager->getPathByAlias($path, $langcode);
  113. $url = Url::fromUserInput($alias, $options);
  114. // Check the route, if available. Entities can be configured to
  115. // only rewrite specific routes.
  116. if ($url->isRouted() && $this->allowedRoute($url->getRouteName())) {
  117. // Load the entity to check.
  118. if (!empty($options['entity'])) {
  119. $entity = $options['entity'];
  120. }
  121. else {
  122. $parameters = $url->getRouteParameters();
  123. if (!empty($parameters)) {
  124. $entity = $this->getEntity($parameters);
  125. }
  126. }
  127. }
  128. // One hook for entities.
  129. if (!empty($entity)) {
  130. // Enmsure we send the right translation.
  131. if (!empty($langcode) && method_exists($entity, 'hasTranslation') && $entity->hasTranslation($langcode) && $translation = $entity->getTranslation($langcode)) {
  132. $entity = $translation;
  133. }
  134. if (isset($options['domain_target_id'])) {
  135. $target_id = $options['domain_target_id'];
  136. }
  137. else {
  138. $target_id = domain_source_get($entity);
  139. }
  140. if (!empty($target_id)) {
  141. $source = $this->domainStorage()->load($target_id);
  142. }
  143. $options['entity'] = $entity;
  144. $options['entity_type'] = $entity->getEntityTypeId();
  145. $this->moduleHandler->alter('domain_source', $source, $path, $options);
  146. }
  147. // One for other, because the latter is resource-intensive.
  148. else {
  149. if (isset($options['domain_target_id'])) {
  150. $target_id = $options['domain_target_id'];
  151. $source = $this->domainStorage()->load($target_id);
  152. }
  153. $this->moduleHandler->alter('domain_source_path', $source, $path, $options);
  154. }
  155. // If a source domain is specified, rewrite the link.
  156. if (!empty($source)) {
  157. // Note that url rewrites add a leading /, which getPath() also adds.
  158. $options['base_url'] = trim($source->getPath(), '/');
  159. $options['absolute'] = TRUE;
  160. }
  161. return $path;
  162. }
  163. /**
  164. * Derive entity data from a given route's parameters.
  165. *
  166. * @param array $parameters
  167. * An array of route parameters.
  168. *
  169. * @return \Drupal\Core\Entity\EntityInterface|null
  170. * Returns the entity when available, otherwise NULL.
  171. */
  172. public function getEntity(array $parameters) {
  173. $entity = NULL;
  174. $entity_type = key($parameters);
  175. $entity_types = $this->getEntityTypes();
  176. foreach ($parameters as $entity_type => $value) {
  177. if (!empty($entity_type) && isset($entity_types[$entity_type])) {
  178. $entity = $this->entityTypeManager->getStorage($entity_type)->load($value);
  179. }
  180. }
  181. return $entity;
  182. }
  183. /**
  184. * Checks that a route's common name is not disallowed.
  185. *
  186. * Looks at the name (e.g. canonical) of the route without regard for
  187. * the entity type.
  188. *
  189. * @parameter $name
  190. * The route name being checked.
  191. *
  192. * @return bool
  193. * Returns TRUE when allowed, otherwise FALSE.
  194. */
  195. public function allowedRoute($name) {
  196. $excluded = $this->getExcludedRoutes();
  197. $parts = explode('.', $name);
  198. $route_name = end($parts);
  199. // Config is stored as an array. Empty items are not excluded.
  200. return !isset($excluded[$route_name]);
  201. }
  202. /**
  203. * Gets an array of content entity types, keyed by type.
  204. *
  205. * @return \Drupal\Core\Entity\EntityTypeInterface[]
  206. * An array of content entity types, keyed by type.
  207. */
  208. public function getEntityTypes() {
  209. if (!isset($this->entityTypes)) {
  210. foreach ($this->entityTypeManager->getDefinitions() as $type => $definition) {
  211. if ($definition->getGroup() == 'content') {
  212. $this->entityTypes[$type] = $type;
  213. }
  214. }
  215. }
  216. return $this->entityTypes;
  217. }
  218. /**
  219. * Gets the settings for domain source path rewrites.
  220. *
  221. * @return array
  222. * The settings for domain source path rewrites.
  223. */
  224. public function getExcludedRoutes() {
  225. if (!isset($this->excludedRoutes)) {
  226. $config = $this->configFactory->get('domain_source.settings');
  227. $routes = $config->get('exclude_routes');
  228. if (is_array($routes)) {
  229. $this->excludedRoutes = array_flip($routes);
  230. }
  231. else {
  232. $this->excludedRoutes = [];
  233. }
  234. }
  235. return $this->excludedRoutes;
  236. }
  237. /**
  238. * Gets the active domain.
  239. *
  240. * @return \Drupal\domain\DomainInterface
  241. * The active domain.
  242. */
  243. public function getActiveDomain() {
  244. if (!isset($this->activeDomain)) {
  245. // Ensure that the loader has run.
  246. // In some tests, the kernel event has not.
  247. $active = $this->negotiator->getActiveDomain();
  248. if (empty($active)) {
  249. $active = $this->negotiator->getActiveDomain(TRUE);
  250. }
  251. $this->activeDomain = $active;
  252. }
  253. return $this->activeDomain;
  254. }
  255. /**
  256. * Retrieves the domain storage handler.
  257. *
  258. * @return \Drupal\domain\DomainStorageInterface
  259. * The domain storage handler.
  260. */
  261. protected function domainStorage() {
  262. if (!$this->domainStorage) {
  263. $this->domainStorage = $this->entityTypeManager->getStorage('domain');
  264. }
  265. return $this->domainStorage;
  266. }
  267. }