first commit
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Provides the path admin overview filter form.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PathFilterForm extends FormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'path_admin_filter_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $keys = NULL) {
|
||||
$form['#attributes'] = ['class' => ['search-form']];
|
||||
$form['basic'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Filter aliases'),
|
||||
'#open' => TRUE,
|
||||
'#attributes' => ['class' => ['container-inline']],
|
||||
];
|
||||
$form['basic']['filter'] = [
|
||||
'#type' => 'search',
|
||||
'#title' => $this->t('Path alias'),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => $keys,
|
||||
'#maxlength' => 128,
|
||||
'#size' => 25,
|
||||
];
|
||||
$form['basic']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Filter'),
|
||||
];
|
||||
if ($keys) {
|
||||
$form['basic']['reset'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Reset'),
|
||||
'#submit' => ['::resetForm'],
|
||||
];
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$form_state->setRedirect('entity.path_alias.collection', [], [
|
||||
'query' => ['search' => trim($form_state->getValue('filter'))],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the filter selections.
|
||||
*/
|
||||
public function resetForm(array &$form, FormStateInterface $form_state) {
|
||||
$form_state->setRedirect('entity.path_alias.collection');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityForm;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Form handler for the path alias edit forms.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PathAliasForm extends ContentEntityForm {
|
||||
|
||||
/**
|
||||
* The path_alias entity.
|
||||
*
|
||||
* @var \Drupal\path_alias\PathAliasInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
parent::save($form, $form_state);
|
||||
|
||||
$this->messenger()->addStatus($this->t('The alias has been saved.'));
|
||||
$form_state->setRedirect('entity.path_alias.collection');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityListBuilder;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Form\FormBuilderInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\path_alias\AliasManagerInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\path\Form\PathFilterForm;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Defines a class to build a listing of path_alias entities.
|
||||
*
|
||||
* @see \Drupal\path_alias\Entity\PathAlias
|
||||
*/
|
||||
class PathAliasListBuilder extends EntityListBuilder {
|
||||
|
||||
/**
|
||||
* The current request.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\Request
|
||||
*/
|
||||
protected $currentRequest;
|
||||
|
||||
/**
|
||||
* The form builder.
|
||||
*
|
||||
* @var \Drupal\Core\Form\FormBuilderInterface
|
||||
*/
|
||||
protected $formBuilder;
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* The path alias manager.
|
||||
*
|
||||
* @var \Drupal\path_alias\AliasManagerInterface
|
||||
*/
|
||||
protected $aliasManager;
|
||||
|
||||
/**
|
||||
* Constructs a new PathAliasListBuilder object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type definition.
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
|
||||
* The entity storage class.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $current_request
|
||||
* The current request.
|
||||
* @param \Drupal\Core\Form\FormBuilderInterface $form_builder
|
||||
* The form builder.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\path_alias\AliasManagerInterface $alias_manager
|
||||
* The path alias manager.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, Request $current_request, FormBuilderInterface $form_builder, LanguageManagerInterface $language_manager, AliasManagerInterface $alias_manager) {
|
||||
parent::__construct($entity_type, $storage);
|
||||
|
||||
$this->currentRequest = $current_request;
|
||||
$this->formBuilder = $form_builder;
|
||||
$this->languageManager = $language_manager;
|
||||
$this->aliasManager = $alias_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type,
|
||||
$container->get('entity_type.manager')->getStorage($entity_type->id()),
|
||||
$container->get('request_stack')->getCurrentRequest(),
|
||||
$container->get('form_builder'),
|
||||
$container->get('language_manager'),
|
||||
$container->get('path_alias.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEntityIds() {
|
||||
$query = $this->getStorage()->getQuery();
|
||||
|
||||
$search = $this->currentRequest->query->get('search');
|
||||
if ($search) {
|
||||
$query->condition('alias', $search, 'CONTAINS');
|
||||
}
|
||||
|
||||
// Only add the pager if a limit is specified.
|
||||
if ($this->limit) {
|
||||
$query->pager($this->limit);
|
||||
}
|
||||
|
||||
// Allow the entity query to sort using the table header.
|
||||
$header = $this->buildHeader();
|
||||
$query->tableSort($header);
|
||||
|
||||
return $query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
$keys = $this->currentRequest->query->get('search');
|
||||
$build['path_admin_filter_form'] = $this->formBuilder->getForm(PathFilterForm::class, $keys);
|
||||
$build += parent::render();
|
||||
|
||||
$build['table']['#empty'] = $this->t('No path aliases available. <a href=":link">Add URL alias</a>.', [':link' => Url::fromRoute('entity.path_alias.add_form')->toString()]);
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header = [
|
||||
'alias' => [
|
||||
'data' => $this->t('Alias'),
|
||||
'field' => 'alias',
|
||||
'specifier' => 'alias',
|
||||
'sort' => 'asc',
|
||||
],
|
||||
'path' => [
|
||||
'data' => $this->t('System path'),
|
||||
'field' => 'path',
|
||||
'specifier' => 'path',
|
||||
],
|
||||
];
|
||||
|
||||
// Enable language column and filter if multiple languages are added.
|
||||
if ($this->languageManager->isMultilingual()) {
|
||||
$header['language_name'] = [
|
||||
'data' => $this->t('Language'),
|
||||
'field' => 'langcode',
|
||||
'specifier' => 'langcode',
|
||||
'class' => [RESPONSIVE_PRIORITY_MEDIUM],
|
||||
];
|
||||
}
|
||||
|
||||
return $header + parent::buildHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
/** @var \Drupal\Core\Path\Entity\PathAlias $entity */
|
||||
$langcode = $entity->language()->getId();
|
||||
$alias = $entity->getAlias();
|
||||
$path = $entity->getPath();
|
||||
$url = Url::fromUserInput($path);
|
||||
|
||||
$row['data']['alias']['data'] = [
|
||||
'#type' => 'link',
|
||||
'#title' => Unicode::truncate($alias, 50, FALSE, TRUE),
|
||||
'#url' => $url->setOption('attributes', ['title' => $alias]),
|
||||
];
|
||||
$row['data']['path']['data'] = [
|
||||
'#type' => 'link',
|
||||
'#title' => Unicode::truncate($path, 50, FALSE, TRUE),
|
||||
'#url' => $url->setOption('attributes', ['title' => $path]),
|
||||
];
|
||||
|
||||
if ($this->languageManager->isMultilingual()) {
|
||||
$row['data']['language_name'] = $this->languageManager->getLanguageName($langcode);
|
||||
}
|
||||
|
||||
$row['data']['operations']['data'] = $this->buildOperations($entity);
|
||||
|
||||
// If the system path maps to a different URL alias, highlight this table
|
||||
// row to let the user know of old aliases.
|
||||
if ($alias != $this->aliasManager->getAliasByPath($path, $langcode)) {
|
||||
$row['class'] = ['warning'];
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path\Plugin\Field\FieldType;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Field\FieldItemList;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\TypedData\ComputedItemListTrait;
|
||||
|
||||
/**
|
||||
* Represents a configurable entity path field.
|
||||
*/
|
||||
class PathFieldItemList extends FieldItemList {
|
||||
|
||||
use ComputedItemListTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function computeValue() {
|
||||
// Default the langcode to the current language if this is a new entity or
|
||||
// there is no alias for an existent entity.
|
||||
// @todo Set the langcode to not specified for untranslatable fields
|
||||
// in https://www.drupal.org/node/2689459.
|
||||
$value = ['langcode' => $this->getLangcode()];
|
||||
|
||||
$entity = $this->getEntity();
|
||||
if (!$entity->isNew()) {
|
||||
/** @var \Drupal\path_alias\AliasRepositoryInterface $path_alias_repository */
|
||||
$path_alias_repository = \Drupal::service('path_alias.repository');
|
||||
|
||||
if ($path_alias = $path_alias_repository->lookupBySystemPath('/' . $entity->toUrl()->getInternalPath(), $this->getLangcode())) {
|
||||
$value = [
|
||||
'alias' => $path_alias['alias'],
|
||||
'pid' => $path_alias['id'],
|
||||
'langcode' => $path_alias['langcode'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->list[0] = $this->createItem(0, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultAccess($operation = 'view', AccountInterface $account = NULL) {
|
||||
if ($operation == 'view') {
|
||||
return AccessResult::allowed();
|
||||
}
|
||||
return AccessResult::allowedIfHasPermissions($account, ['create url aliases', 'administer url aliases'], 'OR')->cachePerPermissions();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete() {
|
||||
// Delete all aliases associated with this entity in the current language.
|
||||
$entity = $this->getEntity();
|
||||
$path_alias_storage = \Drupal::entityTypeManager()->getStorage('path_alias');
|
||||
$entities = $path_alias_storage->loadByProperties([
|
||||
'path' => '/' . $entity->toUrl()->getInternalPath(),
|
||||
'langcode' => $entity->language()->getId(),
|
||||
]);
|
||||
$path_alias_storage->delete($entities);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path\Plugin\Field\FieldType;
|
||||
|
||||
use Drupal\Component\Utility\Random;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldItemBase;
|
||||
use Drupal\Core\TypedData\DataDefinition;
|
||||
|
||||
/**
|
||||
* Defines the 'path' entity field type.
|
||||
*
|
||||
* @FieldType(
|
||||
* id = "path",
|
||||
* label = @Translation("Path"),
|
||||
* description = @Translation("An entity field containing a path alias and related data."),
|
||||
* no_ui = TRUE,
|
||||
* default_widget = "path",
|
||||
* list_class = "\Drupal\path\Plugin\Field\FieldType\PathFieldItemList",
|
||||
* constraints = {"PathAlias" = {}},
|
||||
* )
|
||||
*/
|
||||
class PathItem extends FieldItemBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
|
||||
$properties['alias'] = DataDefinition::create('string')
|
||||
->setLabel(t('Path alias'));
|
||||
$properties['pid'] = DataDefinition::create('integer')
|
||||
->setLabel(t('Path id'));
|
||||
$properties['langcode'] = DataDefinition::create('string')
|
||||
->setLabel(t('Language Code'));
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function schema(FieldStorageDefinitionInterface $field_definition) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isEmpty() {
|
||||
return ($this->alias === NULL || $this->alias === '') && ($this->pid === NULL || $this->pid === '') && ($this->langcode === NULL || $this->langcode === '');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preSave() {
|
||||
if ($this->alias !== NULL) {
|
||||
$this->alias = trim($this->alias);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function postSave($update) {
|
||||
$path_alias_storage = \Drupal::entityTypeManager()->getStorage('path_alias');
|
||||
$entity = $this->getEntity();
|
||||
|
||||
// If specified, rely on the langcode property for the language, so that the
|
||||
// existing language of an alias can be kept. That could for example be
|
||||
// unspecified even if the field/entity has a specific langcode.
|
||||
$alias_langcode = ($this->langcode && $this->pid) ? $this->langcode : $this->getLangcode();
|
||||
|
||||
// If we have an alias, we need to create or update a path alias entity.
|
||||
if ($this->alias) {
|
||||
if (!$update || !$this->pid) {
|
||||
$path_alias = $path_alias_storage->create([
|
||||
'path' => '/' . $entity->toUrl()->getInternalPath(),
|
||||
'alias' => $this->alias,
|
||||
'langcode' => $alias_langcode,
|
||||
]);
|
||||
$path_alias->save();
|
||||
$this->pid = $path_alias->id();
|
||||
}
|
||||
elseif ($this->pid) {
|
||||
$path_alias = $path_alias_storage->load($this->pid);
|
||||
|
||||
if ($this->alias != $path_alias->getAlias()) {
|
||||
$path_alias->setAlias($this->alias);
|
||||
$path_alias->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($this->pid && !$this->alias) {
|
||||
// Otherwise, delete the old alias if the user erased it.
|
||||
$path_alias = $path_alias_storage->load($this->pid);
|
||||
if ($entity->isDefaultRevision()) {
|
||||
$path_alias_storage->delete([$path_alias]);
|
||||
}
|
||||
else {
|
||||
$path_alias_storage->deleteRevision($path_alias->getRevisionID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
|
||||
$random = new Random();
|
||||
$values['alias'] = '/' . str_replace(' ', '-', strtolower($random->sentences(3)));
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function mainPropertyName() {
|
||||
return 'alias';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path\Plugin\Field\FieldWidget;
|
||||
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\WidgetBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Symfony\Component\Validator\ConstraintViolationInterface;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'path' widget.
|
||||
*
|
||||
* @FieldWidget(
|
||||
* id = "path",
|
||||
* label = @Translation("URL alias"),
|
||||
* field_types = {
|
||||
* "path"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class PathWidget extends WidgetBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
|
||||
$entity = $items->getEntity();
|
||||
|
||||
$element += [
|
||||
'#element_validate' => [[get_class($this), 'validateFormElement']],
|
||||
];
|
||||
$element['alias'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $element['#title'],
|
||||
'#default_value' => $items[$delta]->alias,
|
||||
'#required' => $element['#required'],
|
||||
'#maxlength' => 255,
|
||||
'#description' => $this->t('Specify an alternative path by which this data can be accessed. For example, type "/about" when writing an about page.'),
|
||||
];
|
||||
$element['pid'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $items[$delta]->pid,
|
||||
];
|
||||
$element['source'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => !$entity->isNew() ? '/' . $entity->toUrl()->getInternalPath() : NULL,
|
||||
];
|
||||
$element['langcode'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $items[$delta]->langcode,
|
||||
];
|
||||
|
||||
// If the advanced settings tabs-set is available (normally rendered in the
|
||||
// second column on wide-resolutions), place the field as a details element
|
||||
// in this tab-set.
|
||||
if (isset($form['advanced'])) {
|
||||
$element += [
|
||||
'#type' => 'details',
|
||||
'#title' => t('URL path settings'),
|
||||
'#open' => !empty($items[$delta]->alias),
|
||||
'#group' => 'advanced',
|
||||
'#access' => $entity->get('path')->access('edit'),
|
||||
'#attributes' => [
|
||||
'class' => ['path-form'],
|
||||
],
|
||||
'#attached' => [
|
||||
'library' => ['path/drupal.path'],
|
||||
],
|
||||
];
|
||||
$element['#weight'] = 30;
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form element validation handler for URL alias form element.
|
||||
*
|
||||
* @param array $element
|
||||
* The form element.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The form state.
|
||||
*/
|
||||
public static function validateFormElement(array &$element, FormStateInterface $form_state) {
|
||||
// Trim the submitted value of whitespace and slashes.
|
||||
$alias = rtrim(trim($element['alias']['#value']), " \\/");
|
||||
if (!empty($alias)) {
|
||||
$form_state->setValueForElement($element['alias'], $alias);
|
||||
|
||||
/** @var \Drupal\path_alias\PathAliasInterface $path_alias */
|
||||
$path_alias = \Drupal::entityTypeManager()->getStorage('path_alias')->create([
|
||||
'path' => $element['source']['#value'],
|
||||
'alias' => $alias,
|
||||
'langcode' => $element['langcode']['#value'],
|
||||
]);
|
||||
$violations = $path_alias->validate();
|
||||
|
||||
foreach ($violations as $violation) {
|
||||
// Newly created entities do not have a system path yet, so we need to
|
||||
// disregard some violations.
|
||||
if (!$path_alias->getPath() && $violation->getPropertyPath() === 'path') {
|
||||
continue;
|
||||
}
|
||||
$form_state->setError($element['alias'], $violation->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, FormStateInterface $form_state) {
|
||||
return $element['alias'];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path\Plugin\Validation\Constraint;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* Validation constraint for changing path aliases in pending revisions.
|
||||
*
|
||||
* @Constraint(
|
||||
* id = "PathAlias",
|
||||
* label = @Translation("Path alias.", context = "Validation"),
|
||||
* )
|
||||
*/
|
||||
class PathAliasConstraint extends Constraint {
|
||||
|
||||
public $message = 'You can only change the URL alias for the <em>published</em> version of this content.';
|
||||
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
|
||||
/**
|
||||
* Constraint validator for changing path aliases in pending revisions.
|
||||
*/
|
||||
class PathAliasConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
private $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Creates a new PathAliasConstraintValidator instance.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($value, Constraint $constraint) {
|
||||
$entity = !empty($value->getParent()) ? $value->getEntity() : NULL;
|
||||
|
||||
if ($entity && !$entity->isNew() && !$entity->isDefaultRevision()) {
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $original */
|
||||
$original = $this->entityTypeManager->getStorage($entity->getEntityTypeId())->loadUnchanged($entity->id());
|
||||
$entity_langcode = $entity->language()->getId();
|
||||
|
||||
// Only add the violation if the current translation does not have the
|
||||
// same path alias.
|
||||
if ($original->hasTranslation($entity_langcode)) {
|
||||
if ($value->alias != $original->getTranslation($entity_langcode)->path->alias) {
|
||||
$this->context->addViolation($constraint->message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\migrate\Plugin\migrate\destination\EntityContentBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
@trigger_error('UrlAlias is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use the entity:path_alias destination instead. See https://www.drupal.org/node/3013865', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* Legacy destination class for non-entity path aliases.
|
||||
*
|
||||
* @MigrateDestination(
|
||||
* id = "url_alias"
|
||||
* )
|
||||
*
|
||||
* @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
|
||||
* the entity:path_alias destination instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3013865
|
||||
*/
|
||||
class UrlAlias extends EntityContentBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = []) {
|
||||
if ($row->getDestinationProperty('source')) {
|
||||
$row->setDestinationProperty('path', $row->getDestinationProperty('source'));
|
||||
}
|
||||
$path = $row->getDestinationProperty('path');
|
||||
|
||||
// Check if this alias is for a node and if that node is a translation.
|
||||
if (preg_match('/^\/node\/\d+$/', $path) && $row->hasDestinationProperty('node_translation')) {
|
||||
|
||||
// Replace the alias source with the translation source path.
|
||||
$node_translation = $row->getDestinationProperty('node_translation');
|
||||
$row->setDestinationProperty('path', '/node/' . $node_translation[0]);
|
||||
$row->setDestinationProperty('langcode', $node_translation[1]);
|
||||
}
|
||||
|
||||
return parent::import($row, $old_destination_id_values);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function getEntityTypeId($plugin_id) {
|
||||
return 'path_alias';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* A process plugin to update the path of a translated node.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - source: An array of two values, the first being the original path, and the
|
||||
* second being an array of the format [nid, langcode] if a translated node
|
||||
* exists (likely from a migration lookup). Paths not of the format
|
||||
* '/node/<nid>' will pass through unchanged, as will any inputs with invalid
|
||||
* or missing translated nodes.
|
||||
*
|
||||
* This plugin will return the correct path for the translated node if the above
|
||||
* conditions are met, and will return the original path otherwise.
|
||||
*
|
||||
* Example:
|
||||
* node_translation:
|
||||
* -
|
||||
* plugin: explode
|
||||
* source: source
|
||||
* delimiter: /
|
||||
* -
|
||||
* # If the source path has no slashes return a dummy default value.
|
||||
* plugin: extract
|
||||
* default: 'INVALID_NID'
|
||||
* index:
|
||||
* - 1
|
||||
* -
|
||||
* plugin: migration_lookup
|
||||
* migration: d7_node_translation
|
||||
* _path:
|
||||
* plugin: concat
|
||||
* source:
|
||||
* - constants/slash
|
||||
* - source
|
||||
* path:
|
||||
* plugin: path_set_translated
|
||||
* source:
|
||||
* - '@_path'
|
||||
* - '@node_translation'
|
||||
*
|
||||
* In the example above, if the node_translation lookup succeeds and the
|
||||
* original path is of the format '/node/<original node nid>', then the new path
|
||||
* will be set to '/node/<translated node nid>'
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "path_set_translated"
|
||||
* )
|
||||
*/
|
||||
class PathSetTranslated extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (!is_array($value)) {
|
||||
throw new MigrateException("The input value should be an array.");
|
||||
}
|
||||
|
||||
$path = isset($value[0]) ? $value[0] : '';
|
||||
$nid = (is_array($value[1]) && isset($value[1][0])) ? $value[1][0] : FALSE;
|
||||
if (preg_match('/^\/node\/\d+$/', $path) && $nid) {
|
||||
return '/node/' . $nid;
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path\Plugin\migrate\process\d6;
|
||||
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
|
||||
/**
|
||||
* Url alias language code process.
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "d6_url_alias_language"
|
||||
* )
|
||||
*/
|
||||
class UrlAliasLanguage extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$langcode = ($value === '') ? LanguageInterface::LANGCODE_NOT_SPECIFIED : $value;
|
||||
return $langcode;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path\Plugin\migrate\source;
|
||||
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
|
||||
/**
|
||||
* Base class for the url_alias source plugins.
|
||||
*/
|
||||
abstract class UrlAliasBase extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
// The order of the migration is significant since
|
||||
// \Drupal\path_alias\AliasRepository::lookupPathAlias() orders by pid
|
||||
// before returning a result. Postgres does not automatically order by
|
||||
// primary key therefore we need to add a specific order by.
|
||||
return $this->select('url_alias', 'ua')->fields('ua')->orderBy('pid');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'pid' => $this->t('The numeric identifier of the path alias.'),
|
||||
'language' => $this->t('The language code of the URL alias.'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['pid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path\Plugin\migrate\source\d6;
|
||||
|
||||
use Drupal\path\Plugin\migrate\source\UrlAliasBase;
|
||||
|
||||
/**
|
||||
* URL aliases source from database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_url_alias",
|
||||
* source_module = "path"
|
||||
* )
|
||||
*/
|
||||
class UrlAlias extends UrlAliasBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = parent::fields();
|
||||
$fields['src'] = $this->t('The internal system path.');
|
||||
$fields['dst'] = $this->t('The path alias.');
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path\Plugin\migrate\source\d7;
|
||||
|
||||
use Drupal\path\Plugin\migrate\source\UrlAliasBase;
|
||||
|
||||
/**
|
||||
* URL aliases source from database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_url_alias",
|
||||
* source_module = "path"
|
||||
* )
|
||||
*/
|
||||
class UrlAlias extends UrlAliasBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = parent::fields();
|
||||
$fields['source'] = $this->t('The internal system path.');
|
||||
$fields['alias'] = $this->t('The path alias.');
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path\Routing;
|
||||
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface;
|
||||
use Drupal\Core\Routing\RouteProviderInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Processes the backwards-compatibility layer for path alias routes.
|
||||
*/
|
||||
class RouteProcessor implements OutboundRouteProcessorInterface {
|
||||
|
||||
/**
|
||||
* The route provider.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteProviderInterface
|
||||
*/
|
||||
protected $routeProvider;
|
||||
|
||||
/**
|
||||
* Constructs a RouteProcessor object.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
|
||||
* The route provider.
|
||||
*/
|
||||
public function __construct(RouteProviderInterface $route_provider) {
|
||||
$this->routeProvider = $route_provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processOutbound($route_name, Route $route, array &$parameters, BubbleableMetadata $bubbleable_metadata = NULL) {
|
||||
$redirected_route_names = [
|
||||
'path.admin_add' => 'entity.path_alias.add_form',
|
||||
'path.admin_edit' => 'entity.path_alias.edit_form',
|
||||
'path.delete' => 'entity.path_alias.delete_form',
|
||||
'path.admin_overview' => 'entity.path_alias.collection',
|
||||
'path.admin_overview_filter' => 'entity.path_alias.collection',
|
||||
];
|
||||
|
||||
if (in_array($route_name, array_keys($redirected_route_names), TRUE)) {
|
||||
@trigger_error("The '{$route_name}' route is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use the '{$redirected_route_names[$route_name]}' route instead. See https://www.drupal.org/node/3013865", E_USER_DEPRECATED);
|
||||
static::overwriteRoute($route, $this->routeProvider->getRouteByName($redirected_route_names[$route_name]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites one route's metadata with the other's.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $target_route
|
||||
* The route whose metadata to overwrite.
|
||||
* @param \Symfony\Component\Routing\Route $source_route
|
||||
* The route whose metadata to read from.
|
||||
*
|
||||
* @see \Symfony\Component\Routing\Route
|
||||
*/
|
||||
protected static function overwriteRoute(Route $target_route, Route $source_route) {
|
||||
$target_route->setPath($source_route->getPath());
|
||||
$target_route->setDefaults($source_route->getDefaults());
|
||||
$target_route->setRequirements($source_route->getRequirements());
|
||||
$target_route->setOptions($source_route->getOptions());
|
||||
$target_route->setHost($source_route->getHost());
|
||||
$target_route->setSchemes($source_route->getSchemes());
|
||||
$target_route->setMethods($source_route->getMethods());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path\Routing;
|
||||
|
||||
use Drupal\Core\Routing\BcRoute;
|
||||
use Drupal\Core\Routing\RouteBuildEvent;
|
||||
use Drupal\Core\Routing\RoutingEvents;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Provides backwards-compatible routes for the path module.
|
||||
*/
|
||||
class RouteSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* Provides routes on route rebuild time.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteBuildEvent $event
|
||||
* The route build event.
|
||||
*/
|
||||
public function onDynamicRouteEvent(RouteBuildEvent $event) {
|
||||
$route_collection = $event->getRouteCollection();
|
||||
|
||||
$route_collection->add('path.admin_add', new BcRoute());
|
||||
$route_collection->add('path.admin_edit', new BcRoute());
|
||||
$route_collection->add('path.delete', new BcRoute());
|
||||
$route_collection->add('path.admin_overview', new BcRoute());
|
||||
$route_collection->add('path.admin_overview_filter', new BcRoute());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
$events[RoutingEvents::DYNAMIC][] = ['onDynamicRouteEvent', 0];
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\path\Tests;
|
||||
|
||||
@trigger_error(__NAMESPACE__ . '\PathTestBase is deprecated for removal before Drupal 9.0.0. Use Drupal\Tests\path\Functional\PathTestBase instead. See https://www.drupal.org/node/2999939', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Provides a base class for testing the Path module.
|
||||
*
|
||||
* @deprecated in drupal:8.?.? and is removed from drupal:9.0.0.
|
||||
* Use \Drupal\Tests\path\Functional\PathTestBase instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2999939
|
||||
*/
|
||||
abstract class PathTestBase extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['node', 'path'];
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Create Basic page and Article node types.
|
||||
if ($this->profile != 'standard') {
|
||||
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
|
||||
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user