Issue #3007669 by amateescu, Berdir, catch: Add publishing status to path aliases
parent
791a988503
commit
e0c5f41ff4
|
@ -171,17 +171,30 @@ class AliasStorage implements AliasStorageInterface {
|
||||||
$storage->delete($storage->loadMultiple($result));
|
$storage->delete($storage->loadMultiple($result));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a SELECT query for the path_alias base table.
|
||||||
|
*
|
||||||
|
* @return \Drupal\Core\Database\Query\SelectInterface
|
||||||
|
* A Select query object.
|
||||||
|
*/
|
||||||
|
protected function getBaseQuery() {
|
||||||
|
$query = $this->connection->select(static::TABLE, 'base_table');
|
||||||
|
$query->condition('base_table.status', 1);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function preloadPathAlias($preloaded, $langcode) {
|
public function preloadPathAlias($preloaded, $langcode) {
|
||||||
$select = $this->connection->select(static::TABLE)
|
$select = $this->getBaseQuery()
|
||||||
->fields(static::TABLE, ['path', 'alias']);
|
->fields('base_table', ['path', 'alias']);
|
||||||
|
|
||||||
if (!empty($preloaded)) {
|
if (!empty($preloaded)) {
|
||||||
$conditions = new Condition('OR');
|
$conditions = new Condition('OR');
|
||||||
foreach ($preloaded as $preloaded_item) {
|
foreach ($preloaded as $preloaded_item) {
|
||||||
$conditions->condition('path', $this->connection->escapeLike($preloaded_item), 'LIKE');
|
$conditions->condition('base_table.path', $this->connection->escapeLike($preloaded_item), 'LIKE');
|
||||||
}
|
}
|
||||||
$select->condition($conditions);
|
$select->condition($conditions);
|
||||||
}
|
}
|
||||||
|
@ -191,7 +204,7 @@ class AliasStorage implements AliasStorageInterface {
|
||||||
// We order by ID ASC so that fetchAllKeyed() returns the most recently
|
// We order by ID ASC so that fetchAllKeyed() returns the most recently
|
||||||
// created alias for each source. Subsequent queries using fetchField() must
|
// created alias for each source. Subsequent queries using fetchField() must
|
||||||
// use ID DESC to have the same effect.
|
// use ID DESC to have the same effect.
|
||||||
$select->orderBy('id', 'ASC');
|
$select->orderBy('base_table.id', 'ASC');
|
||||||
|
|
||||||
return $select->execute()->fetchAllKeyed();
|
return $select->execute()->fetchAllKeyed();
|
||||||
}
|
}
|
||||||
|
@ -201,13 +214,13 @@ class AliasStorage implements AliasStorageInterface {
|
||||||
*/
|
*/
|
||||||
public function lookupPathAlias($path, $langcode) {
|
public function lookupPathAlias($path, $langcode) {
|
||||||
// See the queries above. Use LIKE for case-insensitive matching.
|
// See the queries above. Use LIKE for case-insensitive matching.
|
||||||
$select = $this->connection->select(static::TABLE)
|
$select = $this->getBaseQuery()
|
||||||
->fields(static::TABLE, ['alias'])
|
->fields('base_table', ['alias'])
|
||||||
->condition('path', $this->connection->escapeLike($path), 'LIKE');
|
->condition('base_table.path', $this->connection->escapeLike($path), 'LIKE');
|
||||||
|
|
||||||
$this->addLanguageFallback($select, $langcode);
|
$this->addLanguageFallback($select, $langcode);
|
||||||
|
|
||||||
$select->orderBy('id', 'DESC');
|
$select->orderBy('base_table.id', 'DESC');
|
||||||
|
|
||||||
return $select->execute()->fetchField();
|
return $select->execute()->fetchField();
|
||||||
}
|
}
|
||||||
|
@ -217,13 +230,13 @@ class AliasStorage implements AliasStorageInterface {
|
||||||
*/
|
*/
|
||||||
public function lookupPathSource($alias, $langcode) {
|
public function lookupPathSource($alias, $langcode) {
|
||||||
// See the queries above. Use LIKE for case-insensitive matching.
|
// See the queries above. Use LIKE for case-insensitive matching.
|
||||||
$select = $this->connection->select(static::TABLE)
|
$select = $this->getBaseQuery()
|
||||||
->fields(static::TABLE, ['path'])
|
->fields('base_table', ['path'])
|
||||||
->condition('alias', $this->connection->escapeLike($alias), 'LIKE');
|
->condition('base_table.alias', $this->connection->escapeLike($alias), 'LIKE');
|
||||||
|
|
||||||
$this->addLanguageFallback($select, $langcode);
|
$this->addLanguageFallback($select, $langcode);
|
||||||
|
|
||||||
$select->orderBy('id', 'DESC');
|
$select->orderBy('base_table.id', 'DESC');
|
||||||
|
|
||||||
return $select->execute()->fetchField();
|
return $select->execute()->fetchField();
|
||||||
}
|
}
|
||||||
|
@ -246,12 +259,12 @@ class AliasStorage implements AliasStorageInterface {
|
||||||
array_pop($langcode_list);
|
array_pop($langcode_list);
|
||||||
}
|
}
|
||||||
elseif ($langcode > LanguageInterface::LANGCODE_NOT_SPECIFIED) {
|
elseif ($langcode > LanguageInterface::LANGCODE_NOT_SPECIFIED) {
|
||||||
$query->orderBy('langcode', 'DESC');
|
$query->orderBy('base_table.langcode', 'DESC');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$query->orderBy('langcode', 'ASC');
|
$query->orderBy('base_table.langcode', 'ASC');
|
||||||
}
|
}
|
||||||
$query->condition('langcode', $langcode_list, 'IN');
|
$query->condition('base_table.langcode', $langcode_list, 'IN');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -304,11 +317,11 @@ class AliasStorage implements AliasStorageInterface {
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function pathHasMatchingAlias($initial_substring) {
|
public function pathHasMatchingAlias($initial_substring) {
|
||||||
$query = $this->connection->select(static::TABLE);
|
$query = $this->getBaseQuery();
|
||||||
$query->addExpression(1);
|
$query->addExpression(1);
|
||||||
|
|
||||||
return (bool) $query
|
return (bool) $query
|
||||||
->condition('path', $this->connection->escapeLike($initial_substring) . '%', 'LIKE')
|
->condition('base_table.path', $this->connection->escapeLike($initial_substring) . '%', 'LIKE')
|
||||||
->range(0, 1)
|
->range(0, 1)
|
||||||
->execute()
|
->execute()
|
||||||
->fetchField();
|
->fetchField();
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace Drupal\Core\Path\Entity;
|
namespace Drupal\Core\Path\Entity;
|
||||||
|
|
||||||
use Drupal\Core\Entity\ContentEntityBase;
|
use Drupal\Core\Entity\ContentEntityBase;
|
||||||
|
use Drupal\Core\Entity\EntityPublishedTrait;
|
||||||
use Drupal\Core\Entity\EntityStorageInterface;
|
use Drupal\Core\Entity\EntityStorageInterface;
|
||||||
use Drupal\Core\Entity\EntityTypeInterface;
|
use Drupal\Core\Entity\EntityTypeInterface;
|
||||||
use Drupal\Core\Field\BaseFieldDefinition;
|
use Drupal\Core\Field\BaseFieldDefinition;
|
||||||
|
@ -34,6 +35,7 @@ use Drupal\Core\Path\PathAliasInterface;
|
||||||
* "revision" = "revision_id",
|
* "revision" = "revision_id",
|
||||||
* "langcode" = "langcode",
|
* "langcode" = "langcode",
|
||||||
* "uuid" = "uuid",
|
* "uuid" = "uuid",
|
||||||
|
* "published" = "status",
|
||||||
* },
|
* },
|
||||||
* admin_permission = "administer url aliases",
|
* admin_permission = "administer url aliases",
|
||||||
* list_cache_tags = { "route_match" },
|
* list_cache_tags = { "route_match" },
|
||||||
|
@ -41,6 +43,8 @@ use Drupal\Core\Path\PathAliasInterface;
|
||||||
*/
|
*/
|
||||||
class PathAlias extends ContentEntityBase implements PathAliasInterface {
|
class PathAlias extends ContentEntityBase implements PathAliasInterface {
|
||||||
|
|
||||||
|
use EntityPublishedTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
@ -61,6 +65,10 @@ class PathAlias extends ContentEntityBase implements PathAliasInterface {
|
||||||
|
|
||||||
$fields['langcode']->setDefaultValue(LanguageInterface::LANGCODE_NOT_SPECIFIED);
|
$fields['langcode']->setDefaultValue(LanguageInterface::LANGCODE_NOT_SPECIFIED);
|
||||||
|
|
||||||
|
// Add the published field.
|
||||||
|
$fields += static::publishedBaseFieldDefinitions($entity_type);
|
||||||
|
$fields['status']->setTranslatable(FALSE);
|
||||||
|
|
||||||
return $fields;
|
return $fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,12 @@
|
||||||
namespace Drupal\Core\Path;
|
namespace Drupal\Core\Path;
|
||||||
|
|
||||||
use Drupal\Core\Entity\ContentEntityInterface;
|
use Drupal\Core\Entity\ContentEntityInterface;
|
||||||
|
use Drupal\Core\Entity\EntityPublishedInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides an interface defining a path_alias entity.
|
* Provides an interface defining a path_alias entity.
|
||||||
*/
|
*/
|
||||||
interface PathAliasInterface extends ContentEntityInterface {
|
interface PathAliasInterface extends ContentEntityInterface, EntityPublishedInterface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the source path of the alias.
|
* Gets the source path of the alias.
|
||||||
|
|
|
@ -17,8 +17,8 @@ class PathAliasStorageSchema extends SqlContentEntityStorageSchema {
|
||||||
$schema = parent::getEntitySchema($entity_type, $reset);
|
$schema = parent::getEntitySchema($entity_type, $reset);
|
||||||
|
|
||||||
$schema[$this->storage->getBaseTable()]['indexes'] += [
|
$schema[$this->storage->getBaseTable()]['indexes'] += [
|
||||||
'path_alias__alias_langcode_id' => ['alias', 'langcode', 'id'],
|
'path_alias__alias_langcode_id_status' => ['alias', 'langcode', 'id', 'status'],
|
||||||
'path_alias__path_langcode_id' => ['path', 'langcode', 'id'],
|
'path_alias__path_langcode_id_status' => ['path', 'langcode', 'id', 'status'],
|
||||||
];
|
];
|
||||||
|
|
||||||
return $schema;
|
return $schema;
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\Core\Routing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extends the router provider interface to provide caching support.
|
||||||
|
*/
|
||||||
|
interface CacheableRouteProviderInterface extends RouteProviderInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a cache key part to be used in the cache ID of the route collection.
|
||||||
|
*
|
||||||
|
* @param string $cache_key_provider
|
||||||
|
* The provider of the cache key part.
|
||||||
|
* @param string $cache_key_part
|
||||||
|
* A string to be used as a cache key part.
|
||||||
|
*/
|
||||||
|
public function addExtraCacheKeyPart($cache_key_provider, $cache_key_part);
|
||||||
|
|
||||||
|
}
|
|
@ -21,7 +21,7 @@ use Drupal\Core\Database\Connection;
|
||||||
/**
|
/**
|
||||||
* A Route Provider front-end for all Drupal-stored routes.
|
* A Route Provider front-end for all Drupal-stored routes.
|
||||||
*/
|
*/
|
||||||
class RouteProvider implements PreloadableRouteProviderInterface, PagedRouteProviderInterface, EventSubscriberInterface {
|
class RouteProvider implements CacheableRouteProviderInterface, PreloadableRouteProviderInterface, PagedRouteProviderInterface, EventSubscriberInterface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The database connection from which to read route information.
|
* The database connection from which to read route information.
|
||||||
|
@ -98,6 +98,13 @@ class RouteProvider implements PreloadableRouteProviderInterface, PagedRouteProv
|
||||||
*/
|
*/
|
||||||
const ROUTE_LOAD_CID_PREFIX = 'route_provider.route_load:';
|
const ROUTE_LOAD_CID_PREFIX = 'route_provider.route_load:';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of cache key parts to be used for the route match cache.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $extraCacheKeyParts = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new PathMatcher.
|
* Constructs a new PathMatcher.
|
||||||
*
|
*
|
||||||
|
@ -442,6 +449,13 @@ class RouteProvider implements PreloadableRouteProviderInterface, PagedRouteProv
|
||||||
return $this->connection->query("SELECT COUNT(*) FROM {" . $this->connection->escapeTable($this->tableName) . "}")->fetchField();
|
return $this->connection->query("SELECT COUNT(*) FROM {" . $this->connection->escapeTable($this->tableName) . "}")->fetchField();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function addExtraCacheKeyPart($cache_key_provider, $cache_key_part) {
|
||||||
|
$this->extraCacheKeyParts[$cache_key_provider] = $cache_key_part;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the cache ID for the route collection cache.
|
* Returns the cache ID for the route collection cache.
|
||||||
*
|
*
|
||||||
|
@ -455,8 +469,17 @@ class RouteProvider implements PreloadableRouteProviderInterface, PagedRouteProv
|
||||||
// Include the current language code in the cache identifier as
|
// Include the current language code in the cache identifier as
|
||||||
// the language information can be elsewhere than in the path, for example
|
// the language information can be elsewhere than in the path, for example
|
||||||
// based on the domain.
|
// based on the domain.
|
||||||
$language_part = $this->getCurrentLanguageCacheIdPart();
|
$this->addExtraCacheKeyPart('language', $this->getCurrentLanguageCacheIdPart());
|
||||||
return 'route:' . $language_part . ':' . $request->getPathInfo() . ':' . $request->getQueryString();
|
|
||||||
|
// Sort the cache key parts by their provider in order to have predictable
|
||||||
|
// cache keys.
|
||||||
|
ksort($this->extraCacheKeyParts);
|
||||||
|
$key_parts = [];
|
||||||
|
foreach ($this->extraCacheKeyParts as $provider => $key_part) {
|
||||||
|
$key_parts[] = '[' . $provider . ']=' . $key_part;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'route:' . implode(':', $key_parts) . ':' . $request->getPathInfo() . ':' . $request->getQueryString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -9,6 +9,7 @@ use Drupal\Core\Url;
|
||||||
* JSON:API integration test for the "PathAlias" content entity type.
|
* JSON:API integration test for the "PathAlias" content entity type.
|
||||||
*
|
*
|
||||||
* @group jsonapi
|
* @group jsonapi
|
||||||
|
* @group path
|
||||||
*/
|
*/
|
||||||
class PathAliasTest extends ResourceTestBase {
|
class PathAliasTest extends ResourceTestBase {
|
||||||
|
|
||||||
|
@ -86,6 +87,7 @@ class PathAliasTest extends ResourceTestBase {
|
||||||
'alias' => '/frontpage1',
|
'alias' => '/frontpage1',
|
||||||
'path' => '/<front>',
|
'path' => '/<front>',
|
||||||
'langcode' => 'en',
|
'langcode' => 'en',
|
||||||
|
'status' => TRUE,
|
||||||
'drupal_internal__id' => 1,
|
'drupal_internal__id' => 1,
|
||||||
'drupal_internal__revision_id' => 1,
|
'drupal_internal__revision_id' => 1,
|
||||||
],
|
],
|
||||||
|
|
|
@ -63,28 +63,42 @@ class PathItem extends FieldItemBase {
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function postSave($update) {
|
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
|
// 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
|
// existing language of an alias can be kept. That could for example be
|
||||||
// unspecified even if the field/entity has a specific langcode.
|
// unspecified even if the field/entity has a specific langcode.
|
||||||
$alias_langcode = ($this->langcode && $this->pid) ? $this->langcode : $this->getLangcode();
|
$alias_langcode = ($this->langcode && $this->pid) ? $this->langcode : $this->getLangcode();
|
||||||
|
|
||||||
if (!$update) {
|
// If we have an alias, we need to create or update a path alias entity.
|
||||||
if ($this->alias) {
|
if ($this->alias) {
|
||||||
$entity = $this->getEntity();
|
if (!$update || !$this->pid) {
|
||||||
if ($path = \Drupal::service('path.alias_storage')->save('/' . $entity->toUrl()->getInternalPath(), $this->alias, $alias_langcode)) {
|
$path_alias = $path_alias_storage->create([
|
||||||
$this->pid = $path['pid'];
|
'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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
elseif ($this->pid && !$this->alias) {
|
||||||
// Delete old alias if user erased it.
|
// Otherwise, delete the old alias if the user erased it.
|
||||||
if ($this->pid && !$this->alias) {
|
$path_alias = $path_alias_storage->load($this->pid);
|
||||||
\Drupal::service('path.alias_storage')->delete(['pid' => $this->pid]);
|
if ($entity->isDefaultRevision()) {
|
||||||
|
$path_alias_storage->delete([$path_alias]);
|
||||||
}
|
}
|
||||||
// Only save a non-empty alias.
|
else {
|
||||||
elseif ($this->alias) {
|
$path_alias_storage->deleteRevision($path_alias->getRevisionID());
|
||||||
$entity = $this->getEntity();
|
|
||||||
\Drupal::service('path.alias_storage')->save('/' . $entity->toUrl()->getInternalPath(), $this->alias, $alias_langcode, $this->pid);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2395,6 +2395,7 @@ function system_update_8803() {
|
||||||
'revision' => 'revision_id',
|
'revision' => 'revision_id',
|
||||||
'langcode' => 'langcode',
|
'langcode' => 'langcode',
|
||||||
'uuid' => 'uuid',
|
'uuid' => 'uuid',
|
||||||
|
'published' => 'status',
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -2423,6 +2424,11 @@ function system_update_8803() {
|
||||||
->setInternal(TRUE)
|
->setInternal(TRUE)
|
||||||
->setRevisionable(TRUE);
|
->setRevisionable(TRUE);
|
||||||
|
|
||||||
|
$field_storage_definitions['status'] = BaseFieldDefinition::create('boolean')
|
||||||
|
->setLabel(new TranslatableMarkup('Published'))
|
||||||
|
->setRevisionable(TRUE)
|
||||||
|
->setDefaultValue(TRUE);
|
||||||
|
|
||||||
$field_storage_definitions['path'] = BaseFieldDefinition::create('string')
|
$field_storage_definitions['path'] = BaseFieldDefinition::create('string')
|
||||||
->setLabel(new TranslatableMarkup('System path'))
|
->setLabel(new TranslatableMarkup('System path'))
|
||||||
->setDescription(new TranslatableMarkup('The path that this alias belongs to.'))
|
->setDescription(new TranslatableMarkup('The path that this alias belongs to.'))
|
||||||
|
@ -2472,9 +2478,9 @@ function system_update_8804(&$sandbox = NULL) {
|
||||||
$uuid = \Drupal::service('uuid');
|
$uuid = \Drupal::service('uuid');
|
||||||
|
|
||||||
$base_table_insert = $database->insert('path_alias');
|
$base_table_insert = $database->insert('path_alias');
|
||||||
$base_table_insert->fields(['id', 'revision_id', 'uuid', 'path', 'alias', 'langcode']);
|
$base_table_insert->fields(['id', 'revision_id', 'uuid', 'path', 'alias', 'langcode', 'status']);
|
||||||
$revision_table_insert = $database->insert('path_alias_revision');
|
$revision_table_insert = $database->insert('path_alias_revision');
|
||||||
$revision_table_insert->fields(['id', 'revision_id', 'path', 'alias', 'langcode', 'revision_default']);
|
$revision_table_insert->fields(['id', 'revision_id', 'path', 'alias', 'langcode', 'status', 'revision_default']);
|
||||||
foreach ($url_aliases as $url_alias) {
|
foreach ($url_aliases as $url_alias) {
|
||||||
$values = [
|
$values = [
|
||||||
'id' => $url_alias->pid,
|
'id' => $url_alias->pid,
|
||||||
|
@ -2483,6 +2489,7 @@ function system_update_8804(&$sandbox = NULL) {
|
||||||
'path' => $url_alias->source,
|
'path' => $url_alias->source,
|
||||||
'alias' => $url_alias->alias,
|
'alias' => $url_alias->alias,
|
||||||
'langcode' => $url_alias->langcode,
|
'langcode' => $url_alias->langcode,
|
||||||
|
'status' => 1,
|
||||||
];
|
];
|
||||||
$base_table_insert->values($values);
|
$base_table_insert->values($values);
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,11 @@ class PathAliasToEntityUpdateTest extends UpdatePathTestBase {
|
||||||
$query->addField('url_alias', 'source', 'path');
|
$query->addField('url_alias', 'source', 'path');
|
||||||
$query->addField('url_alias', 'alias');
|
$query->addField('url_alias', 'alias');
|
||||||
$query->addField('url_alias', 'langcode');
|
$query->addField('url_alias', 'langcode');
|
||||||
|
|
||||||
|
// Path aliases did not have a 'status' value before the conversion to
|
||||||
|
// entities, but we're adding it here to ensure that the field was installed
|
||||||
|
// and populated correctly.
|
||||||
|
$query->addExpression('1', 'status');
|
||||||
$original_records = $query->execute()->fetchAllAssoc('id');
|
$original_records = $query->execute()->fetchAllAssoc('id');
|
||||||
|
|
||||||
// drupal-8.filled.standard.php.gz contains one URL alias and
|
// drupal-8.filled.standard.php.gz contains one URL alias and
|
||||||
|
@ -90,12 +95,12 @@ class PathAliasToEntityUpdateTest extends UpdatePathTestBase {
|
||||||
// Check that correct data was written in both the base and the revision
|
// Check that correct data was written in both the base and the revision
|
||||||
// tables.
|
// tables.
|
||||||
$base_table_records = $database->select('path_alias')
|
$base_table_records = $database->select('path_alias')
|
||||||
->fields('path_alias', ['id', 'path', 'alias', 'langcode'])
|
->fields('path_alias', ['id', 'path', 'alias', 'langcode', 'status'])
|
||||||
->execute()->fetchAllAssoc('id');
|
->execute()->fetchAllAssoc('id');
|
||||||
$this->assertEquals($original_records, $base_table_records);
|
$this->assertEquals($original_records, $base_table_records);
|
||||||
|
|
||||||
$revision_table_records = $database->select('path_alias_revision')
|
$revision_table_records = $database->select('path_alias_revision')
|
||||||
->fields('path_alias_revision', ['id', 'path', 'alias', 'langcode'])
|
->fields('path_alias_revision', ['id', 'path', 'alias', 'langcode', 'status'])
|
||||||
->execute()->fetchAllAssoc('id');
|
->execute()->fetchAllAssoc('id');
|
||||||
$this->assertEquals($original_records, $revision_table_records);
|
$this->assertEquals($original_records, $revision_table_records);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\workspaces;
|
||||||
|
|
||||||
|
use Drupal\Core\Database\Connection;
|
||||||
|
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||||
|
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||||
|
use Drupal\Core\Path\AliasStorage as CoreAliasStorage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides workspace-specific path alias lookup queries.
|
||||||
|
*/
|
||||||
|
class AliasStorage extends CoreAliasStorage {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The workspace manager.
|
||||||
|
*
|
||||||
|
* @var \Drupal\workspaces\WorkspaceManagerInterface
|
||||||
|
*/
|
||||||
|
protected $workspaceManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AliasStorage constructor.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Database\Connection $connection
|
||||||
|
* A database connection for reading and writing path aliases.
|
||||||
|
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||||
|
* The module handler.
|
||||||
|
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||||
|
* The entity type manager.
|
||||||
|
* @param \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager
|
||||||
|
* The workspace manager service.
|
||||||
|
*/
|
||||||
|
public function __construct(Connection $connection, ModuleHandlerInterface $module_handler, EntityTypeManagerInterface $entity_type_manager, WorkspaceManagerInterface $workspace_manager) {
|
||||||
|
parent::__construct($connection, $module_handler, $entity_type_manager);
|
||||||
|
$this->workspaceManager = $workspace_manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function getBaseQuery() {
|
||||||
|
// Don't alter any queries if we're not in a workspace context.
|
||||||
|
if (!$this->workspaceManager->hasActiveWorkspace()) {
|
||||||
|
return parent::getBaseQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
$active_workspace = $this->workspaceManager->getActiveWorkspace();
|
||||||
|
|
||||||
|
$query = $this->connection->select('path_alias', 'base_table_2');
|
||||||
|
$wa_join = $query->leftJoin('workspace_association', NULL, "%alias.target_entity_type_id = 'path_alias' AND %alias.target_entity_id = base_table_2.id AND %alias.workspace = :active_workspace_id", [
|
||||||
|
':active_workspace_id' => $active_workspace->id(),
|
||||||
|
]);
|
||||||
|
$query->innerJoin('path_alias_revision', 'base_table', "%alias.revision_id = COALESCE($wa_join.target_entity_revision_id, base_table_2.revision_id)");
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -107,6 +107,11 @@ class EntityTypeInfo implements ContainerInjectionInterface {
|
||||||
if (isset($definitions['entity_reference'])) {
|
if (isset($definitions['entity_reference'])) {
|
||||||
$definitions['entity_reference']['constraints']['EntityReferenceSupportedNewEntities'] = [];
|
$definitions['entity_reference']['constraints']['EntityReferenceSupportedNewEntities'] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allow path aliases to be changed in workspace-specific pending revisions.
|
||||||
|
if (isset($definitions['path'])) {
|
||||||
|
unset($definitions['path']['constraints']['PathAlias']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\workspaces\EventSubscriber;
|
||||||
|
|
||||||
|
use Drupal\Core\Path\AliasManagerInterface;
|
||||||
|
use Drupal\Core\Path\CurrentPathStack;
|
||||||
|
use Drupal\Core\Routing\CacheableRouteProviderInterface;
|
||||||
|
use Drupal\Core\Routing\RouteProviderInterface;
|
||||||
|
use Drupal\workspaces\WorkspaceManagerInterface;
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
|
||||||
|
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||||
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a event subscriber for setting workspace-specific cache keys.
|
||||||
|
*/
|
||||||
|
class WorkspaceRequestSubscriber implements EventSubscriberInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The alias manager that caches alias lookups based on the request.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Path\AliasManagerInterface
|
||||||
|
*/
|
||||||
|
protected $aliasManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current path.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Path\CurrentPathStack
|
||||||
|
*/
|
||||||
|
protected $currentPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The route provider to load routes by name.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Routing\RouteProviderInterface
|
||||||
|
*/
|
||||||
|
protected $routeProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The workspace manager.
|
||||||
|
*
|
||||||
|
* @var \Drupal\workspaces\WorkspaceManagerInterface
|
||||||
|
*/
|
||||||
|
protected $workspaceManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new WorkspaceRequestSubscriber instance.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Path\AliasManagerInterface $alias_manager
|
||||||
|
* The alias manager.
|
||||||
|
* @param \Drupal\Core\Path\CurrentPathStack $current_path
|
||||||
|
* The current path.
|
||||||
|
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
|
||||||
|
* The route provider.
|
||||||
|
* @param \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager
|
||||||
|
* The workspace manager.
|
||||||
|
*/
|
||||||
|
public function __construct(AliasManagerInterface $alias_manager, CurrentPathStack $current_path, RouteProviderInterface $route_provider, WorkspaceManagerInterface $workspace_manager) {
|
||||||
|
$this->aliasManager = $alias_manager;
|
||||||
|
$this->currentPath = $current_path;
|
||||||
|
$this->routeProvider = $route_provider;
|
||||||
|
$this->workspaceManager = $workspace_manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the cache key on the alias manager cache decorator.
|
||||||
|
*
|
||||||
|
* KernelEvents::CONTROLLER is used in order to be executed after routing.
|
||||||
|
*
|
||||||
|
* @param \Symfony\Component\HttpKernel\Event\FilterControllerEvent $event
|
||||||
|
* The Event to process.
|
||||||
|
*/
|
||||||
|
public function onKernelController(FilterControllerEvent $event) {
|
||||||
|
// Set the cache key on the alias manager cache decorator.
|
||||||
|
if ($event->isMasterRequest() && $this->workspaceManager->hasActiveWorkspace()) {
|
||||||
|
$cache_key = $this->workspaceManager->getActiveWorkspace()->id() . ':' . rtrim($this->currentPath->getPath($event->getRequest()), '/');
|
||||||
|
$this->aliasManager->setCacheKey($cache_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the active workspace as a cache key part to the route provider.
|
||||||
|
*
|
||||||
|
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
|
||||||
|
* An event object.
|
||||||
|
*/
|
||||||
|
public function onKernelRequest(GetResponseEvent $event) {
|
||||||
|
if ($this->workspaceManager->hasActiveWorkspace() && $this->routeProvider instanceof CacheableRouteProviderInterface) {
|
||||||
|
$this->routeProvider->addExtraCacheKeyPart('workspace', $this->workspaceManager->getActiveWorkspace()->id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public static function getSubscribedEvents() {
|
||||||
|
// Use a priority of 190 in order to run after the generic core subscriber.
|
||||||
|
// @see \Drupal\Core\EventSubscriber\PathSubscriber::getSubscribedEvents()
|
||||||
|
$events[KernelEvents::CONTROLLER][] = ['onKernelController', 190];
|
||||||
|
|
||||||
|
// Use a priority of 33 in order to run before Symfony's router listener.
|
||||||
|
// @see \Symfony\Component\HttpKernel\EventListener\RouterListener::getSubscribedEvents()
|
||||||
|
$events[KernelEvents::REQUEST][] = ['onKernelRequest', 33];
|
||||||
|
|
||||||
|
return $events;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -262,6 +262,10 @@ class WorkspaceManager implements WorkspaceManagerInterface {
|
||||||
return 'entity.memory_cache:' . $entity_type_id;
|
return 'entity.memory_cache:' . $entity_type_id;
|
||||||
}, array_keys($this->getSupportedEntityTypes()));
|
}, array_keys($this->getSupportedEntityTypes()));
|
||||||
$this->entityMemoryCache->invalidateTags($cache_tags_to_invalidate);
|
$this->entityMemoryCache->invalidateTags($cache_tags_to_invalidate);
|
||||||
|
|
||||||
|
// Clear the static cache for path aliases. We can't inject the path alias
|
||||||
|
// manager service because it would create a circular dependency.
|
||||||
|
\Drupal::service('path.alias_manager')->cacheClear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -4,6 +4,7 @@ namespace Drupal\workspaces;
|
||||||
|
|
||||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||||
use Drupal\Core\DependencyInjection\ServiceProviderBase;
|
use Drupal\Core\DependencyInjection\ServiceProviderBase;
|
||||||
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines a service provider for the Workspaces module.
|
* Defines a service provider for the Workspaces module.
|
||||||
|
@ -18,6 +19,11 @@ class WorkspacesServiceProvider extends ServiceProviderBase {
|
||||||
$renderer_config = $container->getParameter('renderer.config');
|
$renderer_config = $container->getParameter('renderer.config');
|
||||||
$renderer_config['required_cache_contexts'][] = 'workspace';
|
$renderer_config['required_cache_contexts'][] = 'workspace';
|
||||||
$container->setParameter('renderer.config', $renderer_config);
|
$container->setParameter('renderer.config', $renderer_config);
|
||||||
|
|
||||||
|
// Replace the class of the 'path.alias_storage' service.
|
||||||
|
$container->getDefinition('path.alias_storage')
|
||||||
|
->setClass(AliasStorage::class)
|
||||||
|
->addArgument(new Reference('workspaces.manager'));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,309 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\Tests\workspaces\Functional;
|
||||||
|
|
||||||
|
use Drupal\language\Entity\ConfigurableLanguage;
|
||||||
|
use Drupal\Tests\BrowserTestBase;
|
||||||
|
use Drupal\workspaces\Entity\Workspace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests path aliases with workspaces.
|
||||||
|
*
|
||||||
|
* @group path
|
||||||
|
* @group workspaces
|
||||||
|
*/
|
||||||
|
class PathWorkspacesTest extends BrowserTestBase {
|
||||||
|
|
||||||
|
use WorkspaceTestUtilities;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected static $modules = ['block', 'content_translation', 'node', 'path', 'workspaces'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function setUp() {
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
ConfigurableLanguage::createFromLangcode('ro')->save();
|
||||||
|
$this->rebuildContainer();
|
||||||
|
|
||||||
|
// Create a content type.
|
||||||
|
$this->drupalCreateContentType([
|
||||||
|
'name' => 'article',
|
||||||
|
'type' => 'article',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->drupalLogin($this->rootUser);
|
||||||
|
|
||||||
|
// Enable URL language detection and selection.
|
||||||
|
$edit = ['language_interface[enabled][language-url]' => 1];
|
||||||
|
$this->drupalPostForm('admin/config/regional/language/detection', $edit, 'Save settings');
|
||||||
|
|
||||||
|
// Enable translation for article node.
|
||||||
|
$edit = [
|
||||||
|
'entity_types[node]' => 1,
|
||||||
|
'settings[node][article][translatable]' => 1,
|
||||||
|
'settings[node][article][fields][path]' => 1,
|
||||||
|
'settings[node][article][fields][body]' => 1,
|
||||||
|
'settings[node][article][settings][language][language_alterable]' => 1,
|
||||||
|
];
|
||||||
|
$this->drupalPostForm('admin/config/regional/content-language', $edit, 'Save configuration');
|
||||||
|
\Drupal::entityTypeManager()->clearCachedDefinitions();
|
||||||
|
|
||||||
|
$this->setupWorkspaceSwitcherBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests path aliases with workspaces.
|
||||||
|
*/
|
||||||
|
public function testPathAliases() {
|
||||||
|
// Create a published node in Live, without an alias.
|
||||||
|
$node = $this->drupalCreateNode([
|
||||||
|
'type' => 'article',
|
||||||
|
'status' => TRUE,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Switch to Stage and create an alias for the node.
|
||||||
|
$stage = Workspace::load('stage');
|
||||||
|
$this->switchToWorkspace($stage);
|
||||||
|
|
||||||
|
$edit = [
|
||||||
|
'path[0][alias]' => '/' . $this->randomMachineName(),
|
||||||
|
];
|
||||||
|
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
|
||||||
|
|
||||||
|
// Check that the node can be accessed in Stage with the given alias.
|
||||||
|
$path = $edit['path[0][alias]'];
|
||||||
|
$this->assertAccessiblePaths([$path]);
|
||||||
|
|
||||||
|
// Check that the 'preload-paths' cache includes the active workspace ID in
|
||||||
|
// the cache key.
|
||||||
|
$this->assertNotEmpty(\Drupal::cache('data')->get('preload-paths:stage:/node/1'));
|
||||||
|
$this->assertFalse(\Drupal::cache('data')->get('preload-paths:/node/1'));
|
||||||
|
|
||||||
|
// Check that the alias can not be accessed in Live.
|
||||||
|
$this->switchToLive();
|
||||||
|
$this->assertNotAccessiblePaths([$path]);
|
||||||
|
$this->assertFalse(\Drupal::cache('data')->get('preload-paths:/node/1'));
|
||||||
|
|
||||||
|
// Publish the workspace and check that the alias can be accessed in Live.
|
||||||
|
$stage->publish();
|
||||||
|
$this->assertAccessiblePaths([$path]);
|
||||||
|
$this->assertNotEmpty(\Drupal::cache('data')->get('preload-paths:/node/1'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests path aliases with workspaces and user switching.
|
||||||
|
*/
|
||||||
|
public function testPathAliasesUserSwitch() {
|
||||||
|
// Create a published node in Live, without an alias.
|
||||||
|
$node = $this->drupalCreateNode([
|
||||||
|
'type' => 'article',
|
||||||
|
'status' => TRUE,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Switch to Stage and create an alias for the node.
|
||||||
|
$stage = Workspace::load('stage');
|
||||||
|
$this->switchToWorkspace($stage);
|
||||||
|
|
||||||
|
$edit = [
|
||||||
|
'path[0][alias]' => '/' . $this->randomMachineName(),
|
||||||
|
];
|
||||||
|
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
|
||||||
|
|
||||||
|
// Check that the node can be accessed in Stage with the given alias.
|
||||||
|
$path = $edit['path[0][alias]'];
|
||||||
|
$this->assertAccessiblePaths([$path]);
|
||||||
|
|
||||||
|
// Check that the 'preload-paths' cache includes the active workspace ID in
|
||||||
|
// the cache key.
|
||||||
|
$this->assertNotEmpty(\Drupal::cache('data')->get('preload-paths:stage:/node/1'));
|
||||||
|
$this->assertFalse(\Drupal::cache('data')->get('preload-paths:/node/1'));
|
||||||
|
|
||||||
|
// Check that the alias can not be accessed in Live, by logging out without
|
||||||
|
// an explicit switch.
|
||||||
|
$this->drupalLogout();
|
||||||
|
$this->assertNotAccessiblePaths([$path]);
|
||||||
|
$this->assertFalse(\Drupal::cache('data')->get('preload-paths:/node/1'));
|
||||||
|
|
||||||
|
// Publish the workspace and check that the alias can be accessed in Live.
|
||||||
|
$this->drupalLogin($this->rootUser);
|
||||||
|
$stage->publish();
|
||||||
|
$this->drupalLogout();
|
||||||
|
$this->assertAccessiblePaths([$path]);
|
||||||
|
$this->assertNotEmpty(\Drupal::cache('data')->get('preload-paths:/node/1'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests path aliases with workspaces for translatable nodes.
|
||||||
|
*/
|
||||||
|
public function testPathAliasesWithTranslation() {
|
||||||
|
$stage = Workspace::load('stage');
|
||||||
|
|
||||||
|
// Create one node with a random alias.
|
||||||
|
$default_node = $this->drupalCreateNode([
|
||||||
|
'type' => 'article',
|
||||||
|
'langcode' => 'en',
|
||||||
|
'status' => TRUE,
|
||||||
|
'path' => '/' . $this->randomMachineName(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Add published translation with another alias.
|
||||||
|
$this->drupalGet('node/' . $default_node->id());
|
||||||
|
$this->drupalGet('node/' . $default_node->id() . '/translations');
|
||||||
|
$this->clickLink('Add');
|
||||||
|
$edit_translation = [
|
||||||
|
'body[0][value]' => $this->randomMachineName(),
|
||||||
|
'status[value]' => TRUE,
|
||||||
|
'path[0][alias]' => '/' . $this->randomMachineName(),
|
||||||
|
];
|
||||||
|
$this->drupalPostForm(NULL, $edit_translation, 'Save (this translation)');
|
||||||
|
// Confirm that the alias works.
|
||||||
|
$this->drupalGet('ro' . $edit_translation['path[0][alias]']);
|
||||||
|
$this->assertSession()->pageTextContains($edit_translation['body[0][value]']);
|
||||||
|
|
||||||
|
$default_path = $default_node->path->alias;
|
||||||
|
$translation_path = 'ro' . $edit_translation['path[0][alias]'];
|
||||||
|
|
||||||
|
$this->assertAccessiblePaths([$default_path, $translation_path]);
|
||||||
|
|
||||||
|
$this->switchToWorkspace($stage);
|
||||||
|
|
||||||
|
$this->assertAccessiblePaths([$default_path, $translation_path]);
|
||||||
|
|
||||||
|
// Create a workspace-specific revision for the translation with a new path
|
||||||
|
// alias.
|
||||||
|
$edit_new_translation_draft_with_alias = [
|
||||||
|
'path[0][alias]' => '/' . $this->randomMachineName(),
|
||||||
|
];
|
||||||
|
$this->drupalPostForm('ro/node/' . $default_node->id() . '/edit', $edit_new_translation_draft_with_alias, 'Save (this translation)');
|
||||||
|
$stage_translation_path = 'ro' . $edit_new_translation_draft_with_alias['path[0][alias]'];
|
||||||
|
|
||||||
|
// The new alias of the translation should be available in Stage, but not
|
||||||
|
// available in Live.
|
||||||
|
$this->assertAccessiblePaths([$default_path, $stage_translation_path]);
|
||||||
|
|
||||||
|
// Check that the previous (Live) path alias no longer works.
|
||||||
|
$this->assertNotAccessiblePaths([$translation_path]);
|
||||||
|
|
||||||
|
// Switch out of Stage and check that the initial path aliases still work.
|
||||||
|
$this->switchToLive();
|
||||||
|
$this->assertAccessiblePaths([$default_path, $translation_path]);
|
||||||
|
$this->assertNotAccessiblePaths([$stage_translation_path]);
|
||||||
|
|
||||||
|
// Switch back to Stage.
|
||||||
|
$this->switchToWorkspace($stage);
|
||||||
|
|
||||||
|
// Create new workspace-specific revision for translation without changing
|
||||||
|
// the path alias.
|
||||||
|
$edit_new_translation_draft = [
|
||||||
|
'body[0][value]' => $this->randomMachineName(),
|
||||||
|
];
|
||||||
|
$this->drupalPostForm('ro/node/' . $default_node->id() . '/edit', $edit_new_translation_draft, t('Save (this translation)'));
|
||||||
|
// Confirm that the new draft revision was created.
|
||||||
|
$this->assertSession()->pageTextContains($edit_new_translation_draft['body[0][value]']);
|
||||||
|
|
||||||
|
// Switch out of Stage and check that the initial path aliases still work.
|
||||||
|
$this->switchToLive();
|
||||||
|
$this->assertAccessiblePaths([$default_path, $translation_path]);
|
||||||
|
$this->assertNotAccessiblePaths([$stage_translation_path]);
|
||||||
|
|
||||||
|
// Switch back to Stage.
|
||||||
|
$this->switchToWorkspace($stage);
|
||||||
|
$this->assertAccessiblePaths([$default_path, $stage_translation_path]);
|
||||||
|
$this->assertNotAccessiblePaths([$translation_path]);
|
||||||
|
|
||||||
|
// Create a new workspace-specific revision for translation with path alias
|
||||||
|
// from the original language's default revision.
|
||||||
|
$edit_new_translation_draft_with_defaults_alias = [
|
||||||
|
'path[0][alias]' => $default_node->path->alias,
|
||||||
|
];
|
||||||
|
$this->drupalPostForm('ro/node/' . $default_node->id() . '/edit', $edit_new_translation_draft_with_defaults_alias, 'Save (this translation)');
|
||||||
|
|
||||||
|
// Switch out of Stage and check that the initial path aliases still work.
|
||||||
|
$this->switchToLive();
|
||||||
|
$this->assertAccessiblePaths([$default_path, $translation_path]);
|
||||||
|
$this->assertNotAccessiblePaths([$stage_translation_path]);
|
||||||
|
|
||||||
|
// Check that only one path alias (the original one) is available in Stage.
|
||||||
|
$this->switchToWorkspace($stage);
|
||||||
|
$this->assertAccessiblePaths([$default_path]);
|
||||||
|
$this->assertNotAccessiblePaths([$translation_path, $stage_translation_path]);
|
||||||
|
|
||||||
|
// Create new workspace-specific revision for translation with a deleted
|
||||||
|
// (empty) path alias.
|
||||||
|
$edit_new_translation_draft_empty_alias = [
|
||||||
|
'body[0][value]' => $this->randomMachineName(),
|
||||||
|
'path[0][alias]' => '',
|
||||||
|
];
|
||||||
|
$this->drupalPostForm('ro/node/' . $default_node->id() . '/edit', $edit_new_translation_draft_empty_alias, 'Save (this translation)');
|
||||||
|
|
||||||
|
// Check that only one path alias (the original one) is available now.
|
||||||
|
$this->switchToLive();
|
||||||
|
$this->assertAccessiblePaths([$default_path, $translation_path]);
|
||||||
|
$this->assertNotAccessiblePaths([$stage_translation_path]);
|
||||||
|
|
||||||
|
$this->switchToWorkspace($stage);
|
||||||
|
$this->assertAccessiblePaths([$default_path]);
|
||||||
|
$this->assertNotAccessiblePaths([$translation_path, $stage_translation_path]);
|
||||||
|
|
||||||
|
// Create a new workspace-specific revision for the translation with a new
|
||||||
|
// path alias.
|
||||||
|
$edit_new_translation = [
|
||||||
|
'body[0][value]' => $this->randomMachineName(),
|
||||||
|
'path[0][alias]' => '/' . $this->randomMachineName(),
|
||||||
|
];
|
||||||
|
$this->drupalPostForm('ro/node/' . $default_node->id() . '/edit', $edit_new_translation, 'Save (this translation)');
|
||||||
|
|
||||||
|
// Confirm that the new revision was created.
|
||||||
|
$this->assertSession()->pageTextContains($edit_new_translation['body[0][value]']);
|
||||||
|
$this->assertSession()->addressEquals('ro' . $edit_new_translation['path[0][alias]']);
|
||||||
|
|
||||||
|
// Check that only the new path alias of the translation can be accessed.
|
||||||
|
$new_stage_translation_path = 'ro' . $edit_new_translation['path[0][alias]'];
|
||||||
|
$this->assertAccessiblePaths([$default_path, $new_stage_translation_path]);
|
||||||
|
$this->assertNotAccessiblePaths([$stage_translation_path]);
|
||||||
|
|
||||||
|
// Switch out of Stage and check that none of the workspace-specific path
|
||||||
|
// aliases can be accessed.
|
||||||
|
$this->switchToLive();
|
||||||
|
$this->assertAccessiblePaths([$default_path, $translation_path]);
|
||||||
|
$this->assertNotAccessiblePaths([$stage_translation_path, $new_stage_translation_path]);
|
||||||
|
|
||||||
|
// Publish Stage and check that its path alias for the translation can be
|
||||||
|
// accessed.
|
||||||
|
$stage->publish();
|
||||||
|
$this->assertAccessiblePaths([$default_path, $new_stage_translation_path]);
|
||||||
|
$this->assertNotAccessiblePaths([$stage_translation_path]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper callback to verify paths are responding with status 200.
|
||||||
|
*
|
||||||
|
* @param string[] $paths
|
||||||
|
* An array of paths to check for.
|
||||||
|
*/
|
||||||
|
protected function assertAccessiblePaths(array $paths) {
|
||||||
|
foreach ($paths as $path) {
|
||||||
|
$this->drupalGet($path);
|
||||||
|
$this->assertSession()->statusCodeEquals(200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper callback to verify paths are responding with status 404.
|
||||||
|
*
|
||||||
|
* @param string[] $paths
|
||||||
|
* An array of paths to check for.
|
||||||
|
*/
|
||||||
|
protected function assertNotAccessiblePaths(array $paths) {
|
||||||
|
foreach ($paths as $path) {
|
||||||
|
$this->drupalGet($path);
|
||||||
|
$this->assertSession()->statusCodeEquals(404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\Tests\workspaces\Unit;
|
||||||
|
|
||||||
|
use Drupal\Core\Path\AliasManagerInterface;
|
||||||
|
use Drupal\Core\Path\CurrentPathStack;
|
||||||
|
use Drupal\Core\Routing\CacheableRouteProviderInterface;
|
||||||
|
use Drupal\Core\Routing\RouteProviderInterface;
|
||||||
|
use Drupal\Tests\UnitTestCase;
|
||||||
|
use Drupal\workspaces\EventSubscriber\WorkspaceRequestSubscriber;
|
||||||
|
use Drupal\workspaces\WorkspaceInterface;
|
||||||
|
use Drupal\workspaces\WorkspaceManagerInterface;
|
||||||
|
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @coversDefaultClass \Drupal\workspaces\EventSubscriber\WorkspaceRequestSubscriber
|
||||||
|
*
|
||||||
|
* @group workspace
|
||||||
|
*/
|
||||||
|
class WorkspaceRequestSubscriberTest extends UnitTestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Drupal\Core\Path\AliasManagerInterface
|
||||||
|
*/
|
||||||
|
protected $aliasManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Drupal\Core\Path\CurrentPathStack
|
||||||
|
*/
|
||||||
|
protected $currentPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Drupal\workspaces\WorkspaceManagerInterface
|
||||||
|
*/
|
||||||
|
protected $workspaceManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function setUp() {
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->aliasManager = $this->prophesize(AliasManagerInterface::class)->reveal();
|
||||||
|
$this->currentPath = $this->prophesize(CurrentPathStack::class)->reveal();
|
||||||
|
$this->workspaceManager = $this->prophesize(WorkspaceManagerInterface::class);
|
||||||
|
|
||||||
|
$active_workspace = $this->prophesize(WorkspaceInterface::class);
|
||||||
|
$active_workspace->id()->willReturn('test');
|
||||||
|
$this->workspaceManager->getActiveWorkspace()->willReturn($active_workspace->reveal());
|
||||||
|
$this->workspaceManager->hasActiveWorkspace()->willReturn(TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::onKernelRequest
|
||||||
|
*/
|
||||||
|
public function testOnKernelRequestWithCacheableRouteProvider() {
|
||||||
|
$route_provider = $this->prophesize(CacheableRouteProviderInterface::class);
|
||||||
|
$route_provider->addExtraCacheKeyPart('workspace', 'test')->shouldBeCalled();
|
||||||
|
|
||||||
|
// Check that WorkspaceRequestSubscriber::onKernelRequest() calls
|
||||||
|
// addExtraCacheKeyPart() on a route provider that implements
|
||||||
|
// CacheableRouteProviderInterface.
|
||||||
|
$workspace_request_subscriber = new WorkspaceRequestSubscriber($this->aliasManager, $this->currentPath, $route_provider->reveal(), $this->workspaceManager->reveal());
|
||||||
|
$event = $this->prophesize(GetResponseEvent::class)->reveal();
|
||||||
|
$this->assertNull($workspace_request_subscriber->onKernelRequest($event));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::onKernelRequest
|
||||||
|
*/
|
||||||
|
public function testOnKernelRequestWithoutCacheableRouteProvider() {
|
||||||
|
$route_provider = $this->prophesize(RouteProviderInterface::class);
|
||||||
|
|
||||||
|
// Check that WorkspaceRequestSubscriber::onKernelRequest() doesn't call
|
||||||
|
// addExtraCacheKeyPart() on a route provider that does not implement
|
||||||
|
// CacheableRouteProviderInterface.
|
||||||
|
$workspace_request_subscriber = new WorkspaceRequestSubscriber($this->aliasManager, $this->currentPath, $route_provider->reveal(), $this->workspaceManager->reveal());
|
||||||
|
$event = $this->prophesize(GetResponseEvent::class)->reveal();
|
||||||
|
$this->assertNull($workspace_request_subscriber->onKernelRequest($event));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -35,6 +35,11 @@ services:
|
||||||
arguments: ['@entity.definition_update_manager', '@entity.last_installed_schema.repository', '@workspaces.manager']
|
arguments: ['@entity.definition_update_manager', '@entity.last_installed_schema.repository', '@workspaces.manager']
|
||||||
tags:
|
tags:
|
||||||
- { name: 'event_subscriber' }
|
- { name: 'event_subscriber' }
|
||||||
|
workspaces.workspace_subscriber:
|
||||||
|
class: Drupal\workspaces\EventSubscriber\WorkspaceRequestSubscriber
|
||||||
|
arguments: ['@path.alias_manager', '@path.current', '@router.route_provider', '@workspaces.manager']
|
||||||
|
tags:
|
||||||
|
- { name: event_subscriber }
|
||||||
|
|
||||||
cache_context.workspace:
|
cache_context.workspace:
|
||||||
class: Drupal\workspaces\WorkspaceCacheContext
|
class: Drupal\workspaces\WorkspaceCacheContext
|
||||||
|
|
|
@ -83,6 +83,11 @@ abstract class PathAliasResourceTestBase extends EntityResourceTestBase {
|
||||||
'value' => '/frontpage1',
|
'value' => '/frontpage1',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
'status' => [
|
||||||
|
[
|
||||||
|
'value' => TRUE,
|
||||||
|
],
|
||||||
|
],
|
||||||
'uuid' => [
|
'uuid' => [
|
||||||
[
|
[
|
||||||
'value' => $this->entity->uuid(),
|
'value' => $this->entity->uuid(),
|
||||||
|
|
|
@ -546,7 +546,7 @@ class RouteProviderTest extends KernelTestBase {
|
||||||
$request = Request::create($path, 'GET');
|
$request = Request::create($path, 'GET');
|
||||||
$provider->getRouteCollectionForRequest($request);
|
$provider->getRouteCollectionForRequest($request);
|
||||||
|
|
||||||
$cache = $this->cache->get('route:en:/path/add/one:');
|
$cache = $this->cache->get('route:[language]=en:/path/add/one:');
|
||||||
$this->assertEqual('/path/add/one', $cache->data['path']);
|
$this->assertEqual('/path/add/one', $cache->data['path']);
|
||||||
$this->assertEqual([], $cache->data['query']);
|
$this->assertEqual([], $cache->data['query']);
|
||||||
$this->assertEqual(3, count($cache->data['routes']));
|
$this->assertEqual(3, count($cache->data['routes']));
|
||||||
|
@ -556,7 +556,7 @@ class RouteProviderTest extends KernelTestBase {
|
||||||
$request = Request::create($path, 'GET');
|
$request = Request::create($path, 'GET');
|
||||||
$provider->getRouteCollectionForRequest($request);
|
$provider->getRouteCollectionForRequest($request);
|
||||||
|
|
||||||
$cache = $this->cache->get('route:en:/path/add/one:foo=bar');
|
$cache = $this->cache->get('route:[language]=en:/path/add/one:foo=bar');
|
||||||
$this->assertEqual('/path/add/one', $cache->data['path']);
|
$this->assertEqual('/path/add/one', $cache->data['path']);
|
||||||
$this->assertEqual(['foo' => 'bar'], $cache->data['query']);
|
$this->assertEqual(['foo' => 'bar'], $cache->data['query']);
|
||||||
$this->assertEqual(3, count($cache->data['routes']));
|
$this->assertEqual(3, count($cache->data['routes']));
|
||||||
|
@ -566,7 +566,7 @@ class RouteProviderTest extends KernelTestBase {
|
||||||
$request = Request::create($path, 'GET');
|
$request = Request::create($path, 'GET');
|
||||||
$provider->getRouteCollectionForRequest($request);
|
$provider->getRouteCollectionForRequest($request);
|
||||||
|
|
||||||
$cache = $this->cache->get('route:en:/path/1/one:');
|
$cache = $this->cache->get('route:[language]=en:/path/1/one:');
|
||||||
$this->assertEqual('/path/1/one', $cache->data['path']);
|
$this->assertEqual('/path/1/one', $cache->data['path']);
|
||||||
$this->assertEqual([], $cache->data['query']);
|
$this->assertEqual([], $cache->data['query']);
|
||||||
$this->assertEqual(2, count($cache->data['routes']));
|
$this->assertEqual(2, count($cache->data['routes']));
|
||||||
|
@ -583,7 +583,7 @@ class RouteProviderTest extends KernelTestBase {
|
||||||
$request = Request::create($path, 'GET');
|
$request = Request::create($path, 'GET');
|
||||||
$provider->getRouteCollectionForRequest($request);
|
$provider->getRouteCollectionForRequest($request);
|
||||||
|
|
||||||
$cache = $this->cache->get('route:en:/path/add-one:');
|
$cache = $this->cache->get('route:[language]=en:/path/add-one:');
|
||||||
$this->assertEqual('/path/add/one', $cache->data['path']);
|
$this->assertEqual('/path/add/one', $cache->data['path']);
|
||||||
$this->assertEqual([], $cache->data['query']);
|
$this->assertEqual([], $cache->data['query']);
|
||||||
$this->assertEqual(3, count($cache->data['routes']));
|
$this->assertEqual(3, count($cache->data['routes']));
|
||||||
|
@ -598,7 +598,7 @@ class RouteProviderTest extends KernelTestBase {
|
||||||
$request = Request::create($path, 'GET');
|
$request = Request::create($path, 'GET');
|
||||||
$provider->getRouteCollectionForRequest($request);
|
$provider->getRouteCollectionForRequest($request);
|
||||||
|
|
||||||
$cache = $this->cache->get('route:gsw-berne:/path/add-one:');
|
$cache = $this->cache->get('route:[language]=gsw-berne:/path/add-one:');
|
||||||
$this->assertEquals('/path/add/one', $cache->data['path']);
|
$this->assertEquals('/path/add/one', $cache->data['path']);
|
||||||
$this->assertEquals([], $cache->data['query']);
|
$this->assertEquals([], $cache->data['query']);
|
||||||
$this->assertEquals(3, count($cache->data['routes']));
|
$this->assertEquals(3, count($cache->data['routes']));
|
||||||
|
|
Loading…
Reference in New Issue