View source  
  <?php
namespace Drupal\pathauto;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityMalformedException;
use Drupal\Core\Entity\Exception\UndefinedLinkTemplateException;
use Drupal\Core\Entity\RevisionableInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Messenger\MessengerTrait;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\Utility\Token;
use Drupal\token\TokenEntityMapperInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
class PathautoGenerator implements PathautoGeneratorInterface {
  use MessengerTrait;
  use StringTranslationTrait;
  
  protected $configFactory;
  
  protected $moduleHandler;
  
  protected $token;
  
  protected $patterns = [];
  
  protected $patternsByEntityType = [];
  
  protected $aliasCleaner;
  
  protected $aliasStorageHelper;
  
  protected $aliasUniquifier;
  
  protected $pathautoMessenger;
  
  protected $tokenEntityMapper;
  
  protected $entityTypeManager;
  
  protected $aliasTypeManager;
  
  public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, Token $token, AliasCleanerInterface $alias_cleaner, AliasStorageHelperInterface $alias_storage_helper, AliasUniquifierInterface $alias_uniquifier, MessengerInterface $pathauto_messenger, TranslationInterface $string_translation, TokenEntityMapperInterface $token_entity_mapper, EntityTypeManagerInterface $entity_type_manager, AliasTypeManager $alias_type_manager = NULL) {
    $this->configFactory = $config_factory;
    $this->moduleHandler = $module_handler;
    $this->token = $token;
    $this->aliasCleaner = $alias_cleaner;
    $this->aliasStorageHelper = $alias_storage_helper;
    $this->aliasUniquifier = $alias_uniquifier;
    $this->pathautoMessenger = $pathauto_messenger;
    $this->stringTranslation = $string_translation;
    $this->tokenEntityMapper = $token_entity_mapper;
    $this->entityTypeManager = $entity_type_manager;
    $this->aliasTypeManager = $alias_type_manager ?: \Drupal::service('plugin.manager.alias_type');
  }
  
  public function createEntityAlias(EntityInterface $entity, $op) {
    
    $pattern = $this
      ->getPatternByEntity($entity);
    if (empty($pattern)) {
      
      return NULL;
    }
    try {
      $internalPath = $entity
        ->toUrl()
        ->getInternalPath();
    } catch (EntityMalformedException $exception) {
      return NULL;
    } catch (UndefinedLinkTemplateException $exception) {
      return NULL;
    } catch (\UnexpectedValueException $exception) {
      return NULL;
    }
    $source = '/' . $internalPath;
    $config = $this->configFactory
      ->get('pathauto.settings');
    $langcode = $entity
      ->language()
      ->getId();
    
    if ($langcode == LanguageInterface::LANGCODE_NOT_APPLICABLE) {
      $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED;
    }
    
    $data = [
      $this->tokenEntityMapper
        ->getTokenTypeForEntityType($entity
        ->getEntityTypeId()) => $entity,
    ];
    
    $context = [
      'module' => $entity
        ->getEntityType()
        ->getProvider(),
      'op' => $op,
      'source' => $source,
      'data' => $data,
      'bundle' => $entity
        ->bundle(),
      'language' => &$langcode,
    ];
    $pattern_original = $pattern
      ->getPattern();
    $this->moduleHandler
      ->alter('pathauto_pattern', $pattern, $context);
    $pattern_altered = $pattern
      ->getPattern();
    
    $existing_alias = NULL;
    if ($op == 'update' || $op == 'bulkupdate') {
      if ($existing_alias = $this->aliasStorageHelper
        ->loadBySource($source, $langcode)) {
        switch ($config
          ->get('update_action')) {
          case PathautoGeneratorInterface::UPDATE_ACTION_NO_NEW:
            
            return NULL;
        }
      }
    }
    
    $alias = $this->token
      ->replace($pattern
      ->getPattern(), $data, [
      'clear' => TRUE,
      'callback' => [
        $this->aliasCleaner,
        'cleanTokenValues',
      ],
      'langcode' => $langcode,
      'pathauto' => TRUE,
    ], new BubbleableMetadata());
    
    $pattern_tokens_removed = preg_replace('/\\[[^\\s\\]:]*:[^\\s\\]]*\\]/', '', $pattern
      ->getPattern());
    if ($alias === $pattern_tokens_removed) {
      return NULL;
    }
    $alias = $this->aliasCleaner
      ->cleanAlias($alias);
    
    $context['source'] =& $source;
    $context['pattern'] = $pattern;
    $this->moduleHandler
      ->alter('pathauto_alias', $alias, $context);
    
    if (!mb_strlen($alias)) {
      return NULL;
    }
    
    $original_alias = $alias;
    $this->aliasUniquifier
      ->uniquify($alias, $source, $langcode);
    if ($original_alias != $alias) {
      
      $this->pathautoMessenger
        ->addMessage($this
        ->t('The automatically generated alias %original_alias conflicted with an existing alias. Alias changed to %alias.', [
        '%original_alias' => $original_alias,
        '%alias' => $alias,
      ]), $op);
    }
    
    if ($op == 'return') {
      return $alias;
    }
    
    $path = [
      'source' => $source,
      'alias' => $alias,
      'language' => $langcode,
    ];
    $return = $this->aliasStorageHelper
      ->save($path, $existing_alias, $op);
    
    if ($pattern_altered !== $pattern_original) {
      $pattern
        ->setPattern($pattern_original);
    }
    return $return;
  }
  
  protected function getPatternByEntityType($entity_type_id) {
    if (!isset($this->patternsByEntityType[$entity_type_id])) {
      $ids = $this->entityTypeManager
        ->getStorage('pathauto_pattern')
        ->getQuery()
        ->condition('type', array_keys($this->aliasTypeManager
        ->getPluginDefinitionByType($this->tokenEntityMapper
        ->getTokenTypeForEntityType($entity_type_id))))
        ->condition('status', 1)
        ->sort('weight')
        ->execute();
      $this->patternsByEntityType[$entity_type_id] = $this->entityTypeManager
        ->getStorage('pathauto_pattern')
        ->loadMultiple($ids);
    }
    return $this->patternsByEntityType[$entity_type_id];
  }
  
  public function getPatternByEntity(EntityInterface $entity) {
    $langcode = $entity
      ->language()
      ->getId();
    if (!isset($this->patterns[$entity
      ->getEntityTypeId()][$entity
      ->id()][$langcode])) {
      foreach ($this
        ->getPatternByEntityType($entity
        ->getEntityTypeId()) as $pattern) {
        if ($pattern
          ->applies($entity)) {
          $this->patterns[$entity
            ->getEntityTypeId()][$entity
            ->id()][$langcode] = $pattern;
          break;
        }
      }
      
      if (!isset($this->patterns[$entity
        ->getEntityTypeId()][$entity
        ->id()][$langcode])) {
        $this->patterns[$entity
          ->getEntityTypeId()][$entity
          ->id()][$langcode] = NULL;
      }
    }
    return $this->patterns[$entity
      ->getEntityTypeId()][$entity
      ->id()][$langcode];
  }
  
  public function resetCaches() {
    $this->patterns = [];
    $this->patternsByEntityType = [];
    $this->aliasCleaner
      ->resetCaches();
  }
  
  public function updateEntityAlias(EntityInterface $entity, $op, array $options = []) {
    
    if (!$entity instanceof ContentEntityInterface || !$entity
      ->hasField('path')) {
      return NULL;
    }
    
    if ($entity->path->pathauto != PathautoState::CREATE && empty($options['force'])) {
      return NULL;
    }
    
    if ($entity instanceof RevisionableInterface && !$entity
      ->isDefaultRevision()) {
      return NULL;
    }
    $options += [
      'language' => $entity
        ->language()
        ->getId(),
    ];
    $type = $entity
      ->getEntityTypeId();
    
    if (!$this
      ->getPatternByEntity($entity)) {
      return NULL;
    }
    
    if ($type == 'taxonomy_term') {
      $config_forum = $this->configFactory
        ->get('forum.settings');
      if ($entity
        ->bundle() == $config_forum
        ->get('vocabulary')) {
        $type = 'forum';
      }
    }
    try {
      $result = $this
        ->createEntityAlias($entity, $op);
    } catch (\InvalidArgumentException $e) {
      $this
        ->messenger()
        ->addError($e
        ->getMessage());
      return NULL;
    }
    
    if ($type == 'taxonomy_term') {
      foreach ($this
        ->loadTermChildren($entity
        ->id()) as $subterm) {
        $this
          ->updateEntityAlias($subterm, $op, $options);
      }
    }
    return $result;
  }
  
  protected function loadTermChildren($tid) {
    return $this->entityTypeManager
      ->getStorage('taxonomy_term')
      ->loadChildren($tid);
  }
}