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