Updated all the contrib modules to their latest versions.
[yaffs-website] / web / modules / contrib / pathauto / src / PathautoGenerator.php
1 <?php
2
3 namespace Drupal\pathauto;
4
5 use Drupal\Core\Config\ConfigFactoryInterface;
6 use Drupal\Core\Entity\ContentEntityInterface;
7 use Drupal\Core\Entity\EntityInterface;
8 use Drupal\Core\Entity\RevisionableInterface;
9 use Drupal\Core\Extension\ModuleHandlerInterface;
10 use Drupal\Core\Language\LanguageInterface;
11 use Drupal\Core\Render\BubbleableMetadata;
12 use Drupal\Core\StringTranslation\StringTranslationTrait;
13 use Drupal\Core\StringTranslation\TranslationInterface;
14 use Drupal\Core\Utility\Token;
15 use Drupal\token\TokenEntityMapperInterface;
16 use Drupal\Core\Entity\EntityTypeManagerInterface;
17
18 /**
19  * Provides methods for generating path aliases.
20  */
21 class PathautoGenerator implements PathautoGeneratorInterface {
22
23   use StringTranslationTrait;
24
25   /**
26    * Config factory.
27    *
28    * @var \Drupal\Core\Config\ConfigFactoryInterface
29    */
30   protected $configFactory;
31
32   /**
33    * Module handler.
34    *
35    * @var \Drupal\Core\Extension\ModuleHandlerInterface
36    */
37   protected $moduleHandler;
38
39   /**
40    * Token service.
41    *
42    * @var \Drupal\Core\Utility\Token
43    */
44   protected $token;
45
46   /**
47    * Calculated pattern for a specific entity.
48    *
49    * @var array
50    */
51   protected $patterns = array();
52
53   /**
54    * Available patterns per entity type ID.
55    *
56    * @var array
57    */
58   protected $patternsByEntityType = array();
59
60   /**
61    * The alias cleaner.
62    *
63    * @var \Drupal\pathauto\AliasCleanerInterface
64    */
65   protected $aliasCleaner;
66
67   /**
68    * The alias storage helper.
69    *
70    * @var \Drupal\pathauto\AliasStorageHelperInterface
71    */
72   protected $aliasStorageHelper;
73
74   /**
75    * The alias uniquifier.
76    *
77    * @var \Drupal\pathauto\AliasUniquifierInterface
78    */
79   protected $aliasUniquifier;
80
81   /**
82    * The messenger service.
83    *
84    * @var \Drupal\pathauto\MessengerInterface
85    */
86   protected $messenger;
87
88   /**
89    * The token entity mapper.
90    *
91    * @var \Drupal\token\TokenEntityMapperInterface
92    */
93   protected $tokenEntityMapper;
94
95   /**
96    * The entity type manager.
97    *
98    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
99    */
100   protected $entityTypeManager;
101
102   /**
103    * Creates a new Pathauto manager.
104    *
105    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
106    *   The config factory.
107    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
108    *   The module handler.
109    * @param \Drupal\Core\Utility\Token $token
110    *   The token utility.
111    * @param \Drupal\pathauto\AliasCleanerInterface $alias_cleaner
112    *   The alias cleaner.
113    * @param \Drupal\pathauto\AliasStorageHelperInterface $alias_storage_helper
114    *   The alias storage helper.
115    * @param AliasUniquifierInterface $alias_uniquifier
116    *   The alias uniquifier.
117    * @param MessengerInterface $messenger
118    *   The messenger service.
119    * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
120    *   The string translation service.
121    * @param \Drupal\token\TokenEntityMapperInterface $token_entity_mapper
122    *   The token entity mapper.
123    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
124    *   The entity type manager.
125    */
126   public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, Token $token, AliasCleanerInterface $alias_cleaner, AliasStorageHelperInterface $alias_storage_helper, AliasUniquifierInterface $alias_uniquifier, MessengerInterface $messenger, TranslationInterface $string_translation, TokenEntityMapperInterface $token_entity_mapper, EntityTypeManagerInterface $entity_type_manager) {
127     $this->configFactory = $config_factory;
128     $this->moduleHandler = $module_handler;
129     $this->token = $token;
130     $this->aliasCleaner = $alias_cleaner;
131     $this->aliasStorageHelper = $alias_storage_helper;
132     $this->aliasUniquifier = $alias_uniquifier;
133     $this->messenger = $messenger;
134     $this->stringTranslation = $string_translation;
135     $this->tokenEntityMapper = $token_entity_mapper;
136     $this->entityTypeManager = $entity_type_manager;
137   }
138
139   /**
140    * {@inheritdoc}
141    */
142   public function createEntityAlias(EntityInterface $entity, $op) {
143     // Retrieve and apply the pattern for this content type.
144     $pattern = $this->getPatternByEntity($entity);
145     if (empty($pattern)) {
146       // No pattern? Do nothing (otherwise we may blow away existing aliases...)
147       return NULL;
148     }
149
150     $source = '/' . $entity->toUrl()->getInternalPath();
151     $config = $this->configFactory->get('pathauto.settings');
152     $langcode = $entity->language()->getId();
153
154     // Core does not handle aliases with language Not Applicable.
155     if ($langcode == LanguageInterface::LANGCODE_NOT_APPLICABLE) {
156       $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED;
157     }
158
159     // Build token data.
160     $data = [
161       $this->tokenEntityMapper->getTokenTypeForEntityType($entity->getEntityTypeId()) => $entity,
162     ];
163
164     // Allow other modules to alter the pattern.
165     $context = array(
166       'module' => $entity->getEntityType()->getProvider(),
167       'op' => $op,
168       'source' => $source,
169       'data' => $data,
170       'bundle' => $entity->bundle(),
171       'language' => &$langcode,
172     );
173     $pattern_original = $pattern->getPattern();
174     $this->moduleHandler->alter('pathauto_pattern', $pattern, $context);
175     $pattern_altered = $pattern->getPattern();
176
177     // Special handling when updating an item which is already aliased.
178     $existing_alias = NULL;
179     if ($op == 'update' || $op == 'bulkupdate') {
180       if ($existing_alias = $this->aliasStorageHelper->loadBySource($source, $langcode)) {
181         switch ($config->get('update_action')) {
182           case PathautoGeneratorInterface::UPDATE_ACTION_NO_NEW:
183             // If an alias already exists,
184             // and the update action is set to do nothing,
185             // then gosh-darn it, do nothing.
186             return NULL;
187         }
188       }
189     }
190
191     // Replace any tokens in the pattern.
192     // Uses callback option to clean replacements. No sanitization.
193     // Pass empty BubbleableMetadata object to explicitly ignore cacheablity,
194     // as the result is never rendered.
195     $alias = $this->token->replace($pattern->getPattern(), $data, array(
196       'clear' => TRUE,
197       'callback' => array($this->aliasCleaner, 'cleanTokenValues'),
198       'langcode' => $langcode,
199       'pathauto' => TRUE,
200     ), new BubbleableMetadata());
201
202     // Check if the token replacement has not actually replaced any values. If
203     // that is the case, then stop because we should not generate an alias.
204     // @see token_scan()
205     $pattern_tokens_removed = preg_replace('/\[[^\s\]:]*:[^\s\]]*\]/', '', $pattern->getPattern());
206     if ($alias === $pattern_tokens_removed) {
207       return NULL;
208     }
209
210     $alias = $this->aliasCleaner->cleanAlias($alias);
211
212     // Allow other modules to alter the alias.
213     $context['source'] = &$source;
214     $context['pattern'] = $pattern;
215     $this->moduleHandler->alter('pathauto_alias', $alias, $context);
216
217     // If we have arrived at an empty string, discontinue.
218     if (!mb_strlen($alias)) {
219       return NULL;
220     }
221
222     // If the alias already exists, generate a new, hopefully unique, variant.
223     $original_alias = $alias;
224     $this->aliasUniquifier->uniquify($alias, $source, $langcode);
225     if ($original_alias != $alias) {
226       // Alert the user why this happened.
227       $this->messenger->addMessage($this->t('The automatically generated alias %original_alias conflicted with an existing alias. Alias changed to %alias.', array(
228         '%original_alias' => $original_alias,
229         '%alias' => $alias,
230       )), $op);
231     }
232
233     // Return the generated alias if requested.
234     if ($op == 'return') {
235       return $alias;
236     }
237
238     // Build the new path alias array and send it off to be created.
239     $path = array(
240       'source' => $source,
241       'alias' => $alias,
242       'language' => $langcode,
243     );
244
245     $return  = $this->aliasStorageHelper->save($path, $existing_alias, $op);
246
247     // Because there is no way to set an altered pattern to not be cached,
248     // change it back to the original value.
249     if ($pattern_altered !== $pattern_original) {
250       $pattern->setPattern($pattern_original);
251     }
252
253     return $return;
254   }
255
256   /**
257    * Loads pathauto patterns for a given entity type ID.
258    *
259    * @param string $entity_type_id
260    *   An entity type ID.
261    *
262    * @return \Drupal\pathauto\PathautoPatternInterface[]
263    *   A list of patterns, sorted by weight.
264    */
265   protected function getPatternByEntityType($entity_type_id) {
266     if (!isset($this->patternsByEntityType[$entity_type_id])) {
267       $ids = \Drupal::entityQuery('pathauto_pattern')
268         ->condition('type', array_keys(\Drupal::service('plugin.manager.alias_type')
269           ->getPluginDefinitionByType($this->tokenEntityMapper->getTokenTypeForEntityType($entity_type_id))))
270         ->condition('status', 1)
271         ->sort('weight')
272         ->execute();
273
274       $this->patternsByEntityType[$entity_type_id] = \Drupal::entityTypeManager()
275         ->getStorage('pathauto_pattern')
276         ->loadMultiple($ids);
277     }
278
279     return $this->patternsByEntityType[$entity_type_id];
280   }
281
282   /**
283    * {@inheritdoc}
284    */
285   public function getPatternByEntity(EntityInterface $entity) {
286     $langcode = $entity->language()->getId();
287     if (!isset($this->patterns[$entity->getEntityTypeId()][$entity->id()][$langcode])) {
288       foreach ($this->getPatternByEntityType($entity->getEntityTypeId()) as $pattern) {
289         if ($pattern->applies($entity)) {
290           $this->patterns[$entity->getEntityTypeId()][$entity->id()][$langcode] = $pattern;
291           break;
292         }
293       }
294       // If still not set.
295       if (!isset($this->patterns[$entity->getEntityTypeId()][$entity->id()][$langcode])) {
296         $this->patterns[$entity->getEntityTypeId()][$entity->id()][$langcode] = NULL;
297       }
298     }
299     return $this->patterns[$entity->getEntityTypeId()][$entity->id()][$langcode];
300   }
301
302   /**
303    * {@inheritdoc}
304    */
305   public function resetCaches() {
306     $this->patterns = [];
307     $this->patternsByEntityType = [];
308     $this->aliasCleaner->resetCaches();
309   }
310
311   /**
312    * {@inheritdoc}
313    */
314   public function updateEntityAlias(EntityInterface $entity, $op, array $options = array()) {
315     // Skip if the entity does not have the path field.
316     if (!($entity instanceof ContentEntityInterface) || !$entity->hasField('path')) {
317       return NULL;
318     }
319
320     // Skip if pathauto processing is disabled.
321     if ($entity->path->pathauto != PathautoState::CREATE && empty($options['force'])) {
322       return NULL;
323     }
324
325     // Only act if this is the default revision.
326     if ($entity instanceof RevisionableInterface && !$entity->isDefaultRevision()) {
327       return NULL;
328     }
329
330     $options += array('language' => $entity->language()->getId());
331     $type = $entity->getEntityTypeId();
332
333     // Skip processing if the entity has no pattern.
334     if (!$this->getPatternByEntity($entity)) {
335       return NULL;
336     }
337
338     // Deal with taxonomy specific logic.
339     // @todo Update and test forum related code.
340     if ($type == 'taxonomy_term') {
341
342       $config_forum = $this->configFactory->get('forum.settings');
343       if ($entity->getVocabularyId() == $config_forum->get('vocabulary')) {
344         $type = 'forum';
345       }
346     }
347
348     try {
349       $result = $this->createEntityAlias($entity, $op);
350     }
351     catch (\InvalidArgumentException $e) {
352       $this->messenger->addError($e->getMessage());
353       return NULL;
354     }
355
356     // @todo Move this to a method on the pattern plugin.
357     if ($type == 'taxonomy_term') {
358       foreach ($this->loadTermChildren($entity->id()) as $subterm) {
359         $this->updateEntityAlias($subterm, $op, $options);
360       }
361     }
362
363     return $result;
364   }
365
366   /**
367    * Finds all children of a term ID.
368    *
369    * @param int $tid
370    *   Term ID to retrieve parents for.
371    *
372    * @return \Drupal\taxonomy\TermInterface[]
373    *   An array of term objects that are the children of the term $tid.
374    */
375   protected function loadTermChildren($tid) {
376     return $this->entityTypeManager->getStorage('taxonomy_term')->loadChildren($tid);
377   }
378
379 }