Update Composer, update everything
This commit is contained in:
parent
ea3e94409f
commit
dda5c284b6
19527 changed files with 1135420 additions and 351004 deletions
|
@ -38,7 +38,7 @@ interface AccessManagerInterface {
|
|||
/**
|
||||
* Execute access checks against the incoming request.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The incoming request.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* (optional) Run access checks for this account. Defaults to the current
|
||||
|
|
|
@ -39,7 +39,7 @@ abstract class AccessResult implements AccessResultInterface, RefinableCacheable
|
|||
* isNeutral() will be TRUE.
|
||||
*/
|
||||
public static function neutral($reason = NULL) {
|
||||
assert('is_string($reason) || is_null($reason)');
|
||||
assert(is_string($reason) || is_null($reason));
|
||||
return new AccessResultNeutral($reason);
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ abstract class AccessResult implements AccessResultInterface, RefinableCacheable
|
|||
* isForbidden() will be TRUE.
|
||||
*/
|
||||
public static function forbidden($reason = NULL) {
|
||||
assert('is_string($reason) || is_null($reason)');
|
||||
assert(is_string($reason) || is_null($reason));
|
||||
return new AccessResultForbidden($reason);
|
||||
}
|
||||
|
||||
|
@ -87,13 +87,16 @@ abstract class AccessResult implements AccessResultInterface, RefinableCacheable
|
|||
*
|
||||
* @param bool $condition
|
||||
* The condition to evaluate.
|
||||
* @param string|null $reason
|
||||
* (optional) The reason why access is forbidden. Intended for developers,
|
||||
* hence not translatable
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResult
|
||||
* If $condition is TRUE, isForbidden() will be TRUE, otherwise isNeutral()
|
||||
* will be TRUE.
|
||||
*/
|
||||
public static function forbiddenIf($condition) {
|
||||
return $condition ? static::forbidden() : static::neutral();
|
||||
public static function forbiddenIf($condition, $reason = NULL) {
|
||||
return $condition ? static::forbidden($reason) : static::neutral();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -333,10 +336,10 @@ abstract class AccessResult implements AccessResultInterface, RefinableCacheable
|
|||
$merge_other = TRUE;
|
||||
}
|
||||
|
||||
if ($this->isForbidden() && $this instanceof AccessResultReasonInterface) {
|
||||
if ($this->isForbidden() && $this instanceof AccessResultReasonInterface && !is_null($this->getReason())) {
|
||||
$result->setReason($this->getReason());
|
||||
}
|
||||
elseif ($other->isForbidden() && $other instanceof AccessResultReasonInterface) {
|
||||
elseif ($other->isForbidden() && $other instanceof AccessResultReasonInterface && !is_null($other->getReason())) {
|
||||
$result->setReason($other->getReason());
|
||||
}
|
||||
}
|
||||
|
@ -350,14 +353,13 @@ abstract class AccessResult implements AccessResultInterface, RefinableCacheable
|
|||
$result = static::neutral();
|
||||
if (!$this->isNeutral() || ($this->getCacheMaxAge() === 0 && $other->isNeutral()) || ($this->getCacheMaxAge() !== 0 && $other instanceof CacheableDependencyInterface && $other->getCacheMaxAge() !== 0)) {
|
||||
$merge_other = TRUE;
|
||||
if ($other instanceof AccessResultReasonInterface) {
|
||||
$result->setReason($other->getReason());
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($this instanceof AccessResultReasonInterface) {
|
||||
$result->setReason($this->getReason());
|
||||
}
|
||||
|
||||
if ($this instanceof AccessResultReasonInterface && !is_null($this->getReason())) {
|
||||
$result->setReason($this->getReason());
|
||||
}
|
||||
elseif ($other instanceof AccessResultReasonInterface && !is_null($other->getReason())) {
|
||||
$result->setReason($other->getReason());
|
||||
}
|
||||
}
|
||||
$result->inheritCacheability($this);
|
||||
|
@ -424,9 +426,9 @@ abstract class AccessResult implements AccessResultInterface, RefinableCacheable
|
|||
/**
|
||||
* Inherits the cacheability of the other access result, if any.
|
||||
*
|
||||
* inheritCacheability() differs from addCacheableDependency() in how it
|
||||
* handles max-age, because it is designed to inherit the cacheability of the
|
||||
* second operand in the andIf() and orIf() operations. There, the situation
|
||||
* This method differs from addCacheableDependency() in how it handles
|
||||
* max-age, because it is designed to inherit the cacheability of the second
|
||||
* operand in the andIf() and orIf() operations. There, the situation
|
||||
* "allowed, max-age=0 OR allowed, max-age=1000" needs to yield max-age 1000
|
||||
* as the end result.
|
||||
*
|
||||
|
|
|
@ -18,13 +18,12 @@ class AccessResultForbidden extends AccessResult implements AccessResultReasonIn
|
|||
* Constructs a new AccessResultForbidden instance.
|
||||
*
|
||||
* @param null|string $reason
|
||||
* (optional) a message to provide details about this access result
|
||||
* (optional) A message to provide details about this access result.
|
||||
*/
|
||||
public function __construct($reason = NULL) {
|
||||
$this->reason = $reason;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -18,7 +18,7 @@ class AccessResultNeutral extends AccessResult implements AccessResultReasonInte
|
|||
* Constructs a new AccessResultNeutral instance.
|
||||
*
|
||||
* @param null|string $reason
|
||||
* (optional) a message to provide details about this access result
|
||||
* (optional) A message to provide details about this access result
|
||||
*/
|
||||
public function __construct($reason = NULL) {
|
||||
$this->reason = $reason;
|
||||
|
|
|
@ -142,6 +142,7 @@ class CheckProvider implements CheckProviderInterface, ContainerAwareInterface {
|
|||
|
||||
return $checks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles a mapping of requirement keys to access checker service IDs.
|
||||
*/
|
||||
|
|
|
@ -15,7 +15,6 @@ use Symfony\Component\Routing\RouteCollection;
|
|||
*/
|
||||
interface CheckProviderInterface {
|
||||
|
||||
|
||||
/**
|
||||
* For each route, saves a list of applicable access checks to the route.
|
||||
*
|
||||
|
|
|
@ -61,7 +61,14 @@ class CustomAccessCheck implements RoutingAccessInterface {
|
|||
* The access result.
|
||||
*/
|
||||
public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account) {
|
||||
$callable = $this->controllerResolver->getControllerFromDefinition($route->getRequirement('_custom_access'));
|
||||
try {
|
||||
$callable = $this->controllerResolver->getControllerFromDefinition($route->getRequirement('_custom_access'));
|
||||
}
|
||||
catch (\InvalidArgumentException $e) {
|
||||
// The custom access controller method was not found.
|
||||
throw new \BadMethodCallException(sprintf('The "%s" method is not callable as a _custom_access callback in route "%s"', $route->getRequirement('_custom_access'), $route->getPath()));
|
||||
}
|
||||
|
||||
$arguments_resolver = $this->argumentsResolverFactory->getArgumentsResolver($route_match, $account);
|
||||
$arguments = $arguments_resolver->getArguments($callable);
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ abstract class ConfigurableActionBase extends ActionBase implements Configurable
|
|||
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->configuration += $this->defaultConfiguration();
|
||||
$this->setConfiguration($configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -38,7 +38,7 @@ abstract class ConfigurableActionBase extends ActionBase implements Configurable
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function setConfiguration(array $configuration) {
|
||||
$this->configuration = $configuration;
|
||||
$this->configuration = $configuration + $this->defaultConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\TempStore\PrivateTempStoreFactory;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Redirects to an entity deletion form.
|
||||
*
|
||||
* @Action(
|
||||
* id = "entity:delete_action",
|
||||
* action_label = @Translation("Delete"),
|
||||
* deriver = "Drupal\Core\Action\Plugin\Action\Derivative\EntityDeleteActionDeriver",
|
||||
* )
|
||||
*/
|
||||
class DeleteAction extends EntityActionBase {
|
||||
|
||||
/**
|
||||
* The tempstore object.
|
||||
*
|
||||
* @var \Drupal\Core\TempStore\SharedTempStore
|
||||
*/
|
||||
protected $tempStore;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* Constructs a new DeleteAction object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
|
||||
* The tempstore factory.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* Current user.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) {
|
||||
$this->currentUser = $current_user;
|
||||
$this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm');
|
||||
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('tempstore.private'),
|
||||
$container->get('current_user')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function executeMultiple(array $entities) {
|
||||
/** @var \Drupal\Core\Entity\EntityInterface[] $entities */
|
||||
$selection = [];
|
||||
foreach ($entities as $entity) {
|
||||
$langcode = $entity->language()->getId();
|
||||
$selection[$entity->id()][$langcode] = $langcode;
|
||||
}
|
||||
$this->tempStore->set($this->currentUser->id() . ':' . $this->getPluginDefinition()['type'], $selection);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($object = NULL) {
|
||||
$this->executeMultiple([$object]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
return $object->access('delete', $account, $return_as_object);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Plugin\Action\Derivative;
|
||||
|
||||
use Drupal\Component\Plugin\Derivative\DeriverBase;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\StringTranslation\TranslationInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a base action for each entity type with specific interfaces.
|
||||
*/
|
||||
abstract class EntityActionDeriverBase extends DeriverBase implements ContainerDeriverInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a new EntityActionDeriverBase object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
|
||||
* The string translation service.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->stringTranslation = $string_translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('string_translation')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the deriver can be used for the provided entity type.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the entity type can be used, FALSE otherwise.
|
||||
*/
|
||||
abstract protected function isApplicable(EntityTypeInterface $entity_type);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
if (empty($this->derivatives)) {
|
||||
$definitions = [];
|
||||
foreach ($this->getApplicableEntityTypes() as $entity_type_id => $entity_type) {
|
||||
$definition = $base_plugin_definition;
|
||||
$definition['type'] = $entity_type_id;
|
||||
$definition['label'] = sprintf('%s %s', $base_plugin_definition['action_label'], $entity_type->getSingularLabel());
|
||||
$definitions[$entity_type_id] = $definition;
|
||||
}
|
||||
$this->derivatives = $definitions;
|
||||
}
|
||||
|
||||
return parent::getDerivativeDefinitions($base_plugin_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of applicable entity types.
|
||||
*
|
||||
* The list consists of all entity types which match the conditions for the
|
||||
* given deriver.
|
||||
* For example, if the action applies to entities that are publishable,
|
||||
* this method will find all entity types that are publishable.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityTypeInterface[]
|
||||
* The applicable entity types, keyed by entity type ID.
|
||||
*/
|
||||
protected function getApplicableEntityTypes() {
|
||||
$entity_types = $this->entityTypeManager->getDefinitions();
|
||||
$entity_types = array_filter($entity_types, function (EntityTypeInterface $entity_type) {
|
||||
return $this->isApplicable($entity_type);
|
||||
});
|
||||
|
||||
return $entity_types;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Plugin\Action\Derivative;
|
||||
|
||||
use Drupal\Core\Entity\EntityChangedInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
|
||||
/**
|
||||
* Provides an action deriver that finds entity types of EntityChangedInterface.
|
||||
*
|
||||
* @see \Drupal\Core\Action\Plugin\Action\SaveAction
|
||||
*/
|
||||
class EntityChangedActionDeriver extends EntityActionDeriverBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function isApplicable(EntityTypeInterface $entity_type) {
|
||||
return $entity_type->entityClassImplements(EntityChangedInterface::class);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Plugin\Action\Derivative;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
|
||||
/**
|
||||
* Provides an action deriver that finds entity types with delete form.
|
||||
*
|
||||
* @see \Drupal\Core\Action\Plugin\Action\DeleteAction
|
||||
*/
|
||||
class EntityDeleteActionDeriver extends EntityActionDeriverBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
if (empty($this->derivatives)) {
|
||||
$definitions = [];
|
||||
foreach ($this->getApplicableEntityTypes() as $entity_type_id => $entity_type) {
|
||||
$definition = $base_plugin_definition;
|
||||
$definition['type'] = $entity_type_id;
|
||||
$definition['label'] = $this->t('Delete @entity_type', ['@entity_type' => $entity_type->getSingularLabel()]);
|
||||
$definition['confirm_form_route_name'] = 'entity.' . $entity_type->id() . '.delete_multiple_form';
|
||||
$definitions[$entity_type_id] = $definition;
|
||||
}
|
||||
$this->derivatives = $definitions;
|
||||
}
|
||||
|
||||
return $this->derivatives;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function isApplicable(EntityTypeInterface $entity_type) {
|
||||
return $entity_type->hasLinkTemplate('delete-multiple-form');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Plugin\Action\Derivative;
|
||||
|
||||
use Drupal\Core\Entity\EntityPublishedInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
|
||||
/**
|
||||
* Provides an action deriver that finds publishable entity types.
|
||||
*
|
||||
* @see \Drupal\Core\Action\Plugin\Action\PublishAction
|
||||
* @see \Drupal\Core\Action\Plugin\Action\UnpublishAction
|
||||
*/
|
||||
class EntityPublishedActionDeriver extends EntityActionDeriverBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function isApplicable(EntityTypeInterface $entity_type) {
|
||||
return $entity_type->entityClassImplements(EntityPublishedInterface::class);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Plugin\Action;
|
||||
|
||||
use Drupal\Component\Plugin\DependentPluginInterface;
|
||||
use Drupal\Core\Action\ActionBase;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Base class for entity-based actions.
|
||||
*/
|
||||
abstract class EntityActionBase extends ActionBase implements DependentPluginInterface, ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a EntityActionBase object.
|
||||
*
|
||||
* @param mixed[] $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
$module_name = $this->entityTypeManager
|
||||
->getDefinition($this->getPluginDefinition()['type'])
|
||||
->getProvider();
|
||||
return ['module' => [$module_name]];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Publishes an entity.
|
||||
*
|
||||
* @Action(
|
||||
* id = "entity:publish_action",
|
||||
* action_label = @Translation("Publish"),
|
||||
* deriver = "Drupal\Core\Action\Plugin\Action\Derivative\EntityPublishedActionDeriver",
|
||||
* )
|
||||
*/
|
||||
class PublishAction extends EntityActionBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($entity = NULL) {
|
||||
$entity->setPublished()->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$key = $object->getEntityType()->getKey('published');
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityInterface $object */
|
||||
$result = $object->access('update', $account, TRUE)
|
||||
->andIf($object->$key->access('edit', $account, TRUE));
|
||||
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
}
|
79
web/core/lib/Drupal/Core/Action/Plugin/Action/SaveAction.php
Normal file
79
web/core/lib/Drupal/Core/Action/Plugin/Action/SaveAction.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Plugin\Action;
|
||||
|
||||
use Drupal\Component\Datetime\TimeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides an action that can save any entity.
|
||||
*
|
||||
* @Action(
|
||||
* id = "entity:save_action",
|
||||
* action_label = @Translation("Save"),
|
||||
* deriver = "Drupal\Core\Action\Plugin\Action\Derivative\EntityChangedActionDeriver",
|
||||
* )
|
||||
*/
|
||||
class SaveAction extends EntityActionBase {
|
||||
|
||||
/**
|
||||
* The time service.
|
||||
*
|
||||
* @var \Drupal\Component\Datetime\TimeInterface
|
||||
*/
|
||||
protected $time;
|
||||
|
||||
/**
|
||||
* Constructs a SaveAction object.
|
||||
*
|
||||
* @param mixed[] $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Component\Datetime\TimeInterface $time
|
||||
* The time service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, TimeInterface $time) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager);
|
||||
$this->time = $time;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('datetime.time')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($entity = NULL) {
|
||||
$entity->setChangedTime($this->time->getRequestTime())->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
// It's not necessary to check the changed field access here, because
|
||||
// Drupal\Core\Field\ChangedFieldItemList would anyway return 'not allowed'.
|
||||
// Also changing the changed field value is only a workaround to trigger an
|
||||
// entity resave. Without a field change, this would not be possible.
|
||||
/** @var \Drupal\Core\Entity\EntityInterface $object */
|
||||
return $object->access('update', $account, $return_as_object);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Unpublishes an entity.
|
||||
*
|
||||
* @Action(
|
||||
* id = "entity:unpublish_action",
|
||||
* action_label = @Translation("Unpublish"),
|
||||
* deriver = "Drupal\Core\Action\Plugin\Action\Derivative\EntityPublishedActionDeriver",
|
||||
* )
|
||||
*/
|
||||
class UnpublishAction extends EntityActionBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($entity = NULL) {
|
||||
$entity->setUnpublished()->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$key = $object->getEntityType()->getKey('published');
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityInterface $object */
|
||||
$result = $object->access('update', $account, TRUE)
|
||||
->andIf($object->$key->access('edit', $account, TRUE));
|
||||
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
}
|
56
web/core/lib/Drupal/Core/Ajax/AjaxFormHelperTrait.php
Normal file
56
web/core/lib/Drupal/Core/Ajax/AjaxFormHelperTrait.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Provides a helper to for submitting an AJAX form.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait AjaxFormHelperTrait {
|
||||
|
||||
use AjaxHelperTrait;
|
||||
|
||||
/**
|
||||
* Submit form dialog #ajax callback.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*
|
||||
* @return \Drupal\Core\Ajax\AjaxResponse
|
||||
* An AJAX response that display validation error messages or represents a
|
||||
* successful submission.
|
||||
*/
|
||||
public function ajaxSubmit(array &$form, FormStateInterface $form_state) {
|
||||
if ($form_state->hasAnyErrors()) {
|
||||
$form['status_messages'] = [
|
||||
'#type' => 'status_messages',
|
||||
'#weight' => -1000,
|
||||
];
|
||||
$response = new AjaxResponse();
|
||||
$response->addCommand(new ReplaceCommand('[data-drupal-selector="' . $form['#attributes']['data-drupal-selector'] . '"]', $form));
|
||||
}
|
||||
else {
|
||||
$response = $this->successfulAjaxSubmit($form, $form_state);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the form to respond to a successful AJAX submission.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*
|
||||
* @return \Drupal\Core\Ajax\AjaxResponse
|
||||
* An AJAX response.
|
||||
*/
|
||||
abstract protected function successfulAjaxSubmit(array $form, FormStateInterface $form_state);
|
||||
|
||||
}
|
39
web/core/lib/Drupal/Core/Ajax/AjaxHelperTrait.php
Normal file
39
web/core/lib/Drupal/Core/Ajax/AjaxHelperTrait.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
|
||||
|
||||
/**
|
||||
* Provides a helper to determine if the current request is via AJAX.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait AjaxHelperTrait {
|
||||
|
||||
/**
|
||||
* Determines if the current request is via AJAX.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the current request is via AJAX, FALSE otherwise.
|
||||
*/
|
||||
protected function isAjax() {
|
||||
foreach (['drupal_ajax', 'drupal_modal', 'drupal_dialog'] as $wrapper) {
|
||||
if (strpos($this->getRequestWrapperFormat(), $wrapper) !== FALSE) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the wrapper format of the current request.
|
||||
*
|
||||
* @string
|
||||
* The wrapper format.
|
||||
*/
|
||||
protected function getRequestWrapperFormat() {
|
||||
return \Drupal::request()->get(MainContentViewSubscriber::WRAPPER_FORMAT);
|
||||
}
|
||||
|
||||
}
|
|
@ -119,7 +119,7 @@ class OpenDialogCommand implements CommandInterface, CommandWithAttachedAssetsIn
|
|||
* The new title of the dialog.
|
||||
*/
|
||||
public function setDialogTitle($title) {
|
||||
$this->setDialogOptions('title', $title);
|
||||
$this->setDialogOption('title', $title);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace Drupal\Core\Ajax;
|
|||
* @ingroup ajax
|
||||
*/
|
||||
class OpenModalDialogCommand extends OpenDialogCommand {
|
||||
|
||||
/**
|
||||
* Constructs an OpenModalDialog object.
|
||||
*
|
||||
|
|
72
web/core/lib/Drupal/Core/Ajax/OpenOffCanvasDialogCommand.php
Normal file
72
web/core/lib/Drupal/Core/Ajax/OpenOffCanvasDialogCommand.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* Defines an AJAX command to open content in a dialog in a off-canvas dialog.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class OpenOffCanvasDialogCommand extends OpenDialogCommand {
|
||||
|
||||
/**
|
||||
* The dialog width to use if none is provided.
|
||||
*/
|
||||
const DEFAULT_DIALOG_WIDTH = 300;
|
||||
|
||||
/**
|
||||
* Constructs an OpenOffCanvasDialogCommand object.
|
||||
*
|
||||
* The off-canvas dialog differs from the normal modal provided by
|
||||
* OpenDialogCommand in that a off-canvas has built in positioning and
|
||||
* behaviours. Drupal provides a built-in off-canvas dialog for this purpose,
|
||||
* so the selector is hard-coded in the call to the parent constructor.
|
||||
*
|
||||
* @param string $title
|
||||
* The title of the dialog.
|
||||
* @param string|array $content
|
||||
* The content that will be placed in the dialog, either a render array
|
||||
* or an HTML string.
|
||||
* @param array $dialog_options
|
||||
* (optional) Settings to be passed to the dialog implementation. Any
|
||||
* jQuery UI option can be used. See http://api.jqueryui.com/dialog.
|
||||
* @param array|null $settings
|
||||
* (optional) Custom settings that will be passed to the Drupal behaviors
|
||||
* on the content of the dialog. If left empty, the settings will be
|
||||
* populated automatically from the current request.
|
||||
* @param string $position
|
||||
* (optional) The position to render the off-canvas dialog.
|
||||
*/
|
||||
public function __construct($title, $content, array $dialog_options = [], $settings = NULL, $position = 'side') {
|
||||
parent::__construct('#drupal-off-canvas', $title, $content, $dialog_options, $settings);
|
||||
$this->dialogOptions['modal'] = FALSE;
|
||||
$this->dialogOptions['autoResize'] = FALSE;
|
||||
$this->dialogOptions['resizable'] = 'w';
|
||||
$this->dialogOptions['draggable'] = FALSE;
|
||||
$this->dialogOptions['drupalAutoButtons'] = FALSE;
|
||||
$this->dialogOptions['drupalOffCanvasPosition'] = $position;
|
||||
// @todo drupal.ajax.js does not respect drupalAutoButtons properly, pass an
|
||||
// empty set of buttons until https://www.drupal.org/node/2793343 is in.
|
||||
$this->dialogOptions['buttons'] = [];
|
||||
if (empty($dialog_options['dialogClass'])) {
|
||||
$this->dialogOptions['dialogClass'] = "ui-dialog-off-canvas ui-dialog-position-$position";
|
||||
}
|
||||
// If no width option is provided then use the default width to avoid the
|
||||
// dialog staying at the width of the previous instance when opened
|
||||
// more than once, with different widths, on a single page.
|
||||
if (!isset($this->dialogOptions['width'])) {
|
||||
$this->dialogOptions['width'] = static::DEFAULT_DIALOG_WIDTH;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
$build = parent::render();
|
||||
$build['effect'] = 'fade';
|
||||
$build['speed'] = 1000;
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
|
@ -116,10 +116,36 @@ class ContextDefinition extends Plugin {
|
|||
if (isset($values['class']) && !in_array('Drupal\Core\Plugin\Context\ContextDefinitionInterface', class_implements($values['class']))) {
|
||||
throw new \Exception('ContextDefinition class must implement \Drupal\Core\Plugin\Context\ContextDefinitionInterface.');
|
||||
}
|
||||
$class = isset($values['class']) ? $values['class'] : 'Drupal\Core\Plugin\Context\ContextDefinition';
|
||||
|
||||
$class = $this->getDefinitionClass($values);
|
||||
$this->definition = new $class($values['value'], $values['label'], $values['required'], $values['multiple'], $values['description'], $values['default_value']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the context definition class to use.
|
||||
*
|
||||
* If the annotation specifies a specific context definition class, we use
|
||||
* that. Otherwise, we use \Drupal\Core\Plugin\Context\EntityContextDefinition
|
||||
* if the data type starts with 'entity:', since it contains specialized logic
|
||||
* specific to entities. Otherwise, we fall back to the generic
|
||||
* \Drupal\Core\Plugin\Context\ContextDefinition class.
|
||||
*
|
||||
* @param array $values
|
||||
* The annotation values.
|
||||
*
|
||||
* @return string
|
||||
* The fully-qualified name of the context definition class.
|
||||
*/
|
||||
protected function getDefinitionClass(array $values) {
|
||||
if (isset($values['class'])) {
|
||||
return $values['class'];
|
||||
}
|
||||
if (strpos($values['value'], 'entity:') === 0) {
|
||||
return 'Drupal\Core\Plugin\Context\EntityContextDefinition';
|
||||
}
|
||||
return 'Drupal\Core\Plugin\Context\ContextDefinition';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of an annotation.
|
||||
*
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
* The following changes have been done:
|
||||
* Added namespace Drupal\Core\Archiver.
|
||||
* Removed require_once 'PEAR.php'.
|
||||
* Added defintion of OS_WINDOWS taken from PEAR.php.
|
||||
* Added definition of OS_WINDOWS taken from PEAR.php.
|
||||
* Renamed class to ArchiveTar.
|
||||
* Removed extends PEAR from class.
|
||||
* Removed call parent:: __construct().
|
||||
|
@ -181,7 +181,7 @@ class ArchiveTar
|
|||
if ($data == "\37\213") {
|
||||
$this->_compress = true;
|
||||
$this->_compress_type = 'gz';
|
||||
// No sure it's enought for a magic code ....
|
||||
// Not sure it's enough for a magic code ....
|
||||
} elseif ($data == "BZ") {
|
||||
$this->_compress = true;
|
||||
$this->_compress_type = 'bz2';
|
||||
|
@ -577,7 +577,7 @@ class ArchiveTar
|
|||
* indicated by $p_path. When relevant the memorized path of the
|
||||
* files/dir can be modified by removing the $p_remove_path path at the
|
||||
* beginning of the file/dir path.
|
||||
* While extracting a file, if the directory path does not exists it is
|
||||
* While extracting a file, if the directory path does not exist it is
|
||||
* created.
|
||||
* While extracting a file, if the file already exists it is replaced
|
||||
* without looking for last modification date.
|
||||
|
@ -2385,7 +2385,7 @@ class ArchiveTar
|
|||
|
||||
/**
|
||||
* Compress path by changing for example "/dir/foo/../bar" to "/dir/bar",
|
||||
* rand emove double slashes.
|
||||
* and remove double slashes.
|
||||
*
|
||||
* @param string $p_dir path to reduce
|
||||
*
|
||||
|
|
|
@ -45,7 +45,7 @@ class AssetResolver implements AssetResolverInterface {
|
|||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
|
|
|
@ -175,7 +175,7 @@ class CssCollectionOptimizer implements AssetCollectionOptimizerInterface {
|
|||
public function deleteAll() {
|
||||
$this->state->delete('drupal_css_cache_files');
|
||||
|
||||
$delete_stale = function($uri) {
|
||||
$delete_stale = function ($uri) {
|
||||
// Default stale file threshold is 30 days.
|
||||
if (REQUEST_TIME - filemtime($uri) > \Drupal::config('system.performance')->get('stale_file_threshold')) {
|
||||
file_unmanaged_delete($uri);
|
||||
|
|
|
@ -119,7 +119,7 @@ class CssOptimizer implements AssetOptimizerInterface {
|
|||
// If a BOM is found, convert the file to UTF-8, then use substr() to
|
||||
// remove the BOM from the result.
|
||||
if ($encoding = (Unicode::encodingFromBOM($contents))) {
|
||||
$contents = Unicode::substr(Unicode::convertToUtf8($contents, $encoding), 1);
|
||||
$contents = mb_substr(Unicode::convertToUtf8($contents, $encoding), 1);
|
||||
}
|
||||
// If no BOM, check for fallback encoding. Per CSS spec the regex is very strict.
|
||||
elseif (preg_match('/^@charset "([^"]+)";/', $contents, $matches)) {
|
||||
|
@ -189,7 +189,7 @@ class CssOptimizer implements AssetOptimizerInterface {
|
|||
if ($optimize) {
|
||||
// Perform some safe CSS optimizations.
|
||||
// Regexp to match comment blocks.
|
||||
$comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/';
|
||||
$comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/';
|
||||
// Regexp to match double quoted strings.
|
||||
$double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
|
||||
// Regexp to match single quoted strings.
|
||||
|
|
|
@ -4,7 +4,6 @@ namespace Drupal\Core\Asset;
|
|||
|
||||
use Drupal\Core\State\StateInterface;
|
||||
|
||||
|
||||
/**
|
||||
* Optimizes JavaScript assets.
|
||||
*/
|
||||
|
@ -178,7 +177,7 @@ class JsCollectionOptimizer implements AssetCollectionOptimizerInterface {
|
|||
*/
|
||||
public function deleteAll() {
|
||||
$this->state->delete('system.js_cache_files');
|
||||
$delete_stale = function($uri) {
|
||||
$delete_stale = function ($uri) {
|
||||
// Default stale file threshold is 30 days.
|
||||
if (REQUEST_TIME - filemtime($uri) > \Drupal::config('system.performance')->get('stale_file_threshold')) {
|
||||
file_unmanaged_delete($uri);
|
||||
|
|
|
@ -24,7 +24,7 @@ class JsOptimizer implements AssetOptimizerInterface {
|
|||
// remove the BOM from the result.
|
||||
$data = file_get_contents($js_asset['data']);
|
||||
if ($encoding = (Unicode::encodingFromBOM($data))) {
|
||||
$data = Unicode::substr(Unicode::convertToUtf8($data, $encoding), 1);
|
||||
$data = mb_substr(Unicode::convertToUtf8($data, $encoding), 1);
|
||||
}
|
||||
// If no BOM is found, check for the charset attribute.
|
||||
elseif (isset($js_asset['attributes']['charset'])) {
|
||||
|
|
|
@ -65,6 +65,8 @@ class LibraryDependencyResolver implements LibraryDependencyResolverInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMinimalRepresentativeSubset(array $libraries) {
|
||||
assert(count($libraries) === count(array_unique($libraries)), '$libraries can\'t contain duplicate items.');
|
||||
|
||||
$minimal = [];
|
||||
|
||||
// Determine each library's dependencies.
|
||||
|
|
|
@ -19,7 +19,7 @@ class LibraryDiscovery implements LibraryDiscoveryInterface {
|
|||
/**
|
||||
* The final library definitions, statically cached.
|
||||
*
|
||||
* hook_library_info_alter() and hook_js_settings_alter() allows modules
|
||||
* Hooks hook_library_info_alter() and hook_js_settings_alter() allow modules
|
||||
* and themes to dynamically alter a library definition (once per request).
|
||||
*
|
||||
* @var array
|
||||
|
|
|
@ -129,13 +129,17 @@ class LibraryDiscoveryParser {
|
|||
// properly resolve dependencies for all (css) libraries per category,
|
||||
// and only once prior to rendering out an HTML page.
|
||||
if ($type == 'css' && !empty($library[$type])) {
|
||||
assert(static::validateCssLibrary($library[$type]) < 2, 'CSS files should be specified as key/value pairs, where the values are configuration options. See https://www.drupal.org/node/2274843.');
|
||||
assert(static::validateCssLibrary($library[$type]) === 0, 'CSS must be nested under a category. See https://www.drupal.org/node/2274843.');
|
||||
foreach ($library[$type] as $category => $files) {
|
||||
$category_weight = 'CSS_' . strtoupper($category);
|
||||
assert(defined($category_weight), 'Invalid CSS category: ' . $category . '. See https://www.drupal.org/node/2274843.');
|
||||
foreach ($files as $source => $options) {
|
||||
if (!isset($options['weight'])) {
|
||||
$options['weight'] = 0;
|
||||
}
|
||||
// Apply the corresponding weight defined by CSS_* constants.
|
||||
$options['weight'] += constant('CSS_' . strtoupper($category));
|
||||
$options['weight'] += constant($category_weight);
|
||||
$library[$type][$source] = $options;
|
||||
}
|
||||
unset($library[$type][$category]);
|
||||
|
@ -460,4 +464,34 @@ class LibraryDiscoveryParser {
|
|||
return $overriding_asset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates CSS library structure.
|
||||
*
|
||||
* @param array $library
|
||||
* The library definition array.
|
||||
*
|
||||
* @return int
|
||||
* Returns based on validity:
|
||||
* - 0 if the library definition is valid
|
||||
* - 1 if the library definition has improper nesting
|
||||
* - 2 if the library definition specifies files as an array
|
||||
*/
|
||||
public static function validateCssLibrary($library) {
|
||||
$categories = [];
|
||||
// Verify options first and return early if invalid.
|
||||
foreach ($library as $category => $files) {
|
||||
if (!is_array($files)) {
|
||||
return 2;
|
||||
}
|
||||
$categories[] = $category;
|
||||
foreach ($files as $source => $options) {
|
||||
if (!is_array($options)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
340
web/core/lib/Drupal/Core/Batch/BatchBuilder.php
Normal file
340
web/core/lib/Drupal/Core/Batch/BatchBuilder.php
Normal file
|
@ -0,0 +1,340 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Batch;
|
||||
|
||||
use Drupal\Core\Queue\QueueInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Builds an array for a batch process.
|
||||
*
|
||||
* Example code to create a batch:
|
||||
* @code
|
||||
* $batch_builder = (new BatchBuilder())
|
||||
* ->setTitle(t('Batch Title'))
|
||||
* ->setFinishCallback('batch_example_finished_callback')
|
||||
* ->setInitMessage(t('The initialization message (optional)'));
|
||||
* foreach ($ids as $id) {
|
||||
* $batch_builder->addOperation('batch_example_callback', [$id]);
|
||||
* }
|
||||
* batch_set($batch_builder->toArray());
|
||||
* @endcode
|
||||
*/
|
||||
class BatchBuilder {
|
||||
|
||||
/**
|
||||
* The set of operations to be processed.
|
||||
*
|
||||
* Each operation is a tuple of the function / method to use and an array
|
||||
* containing any parameters to be passed.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $operations = [];
|
||||
|
||||
/**
|
||||
* The title for the batch.
|
||||
*
|
||||
* @var string|\Drupal\Core\StringTranslation\TranslatableMarkup
|
||||
*/
|
||||
protected $title;
|
||||
|
||||
/**
|
||||
* The initializing message for the batch.
|
||||
*
|
||||
* @var string|\Drupal\Core\StringTranslation\TranslatableMarkup
|
||||
*/
|
||||
protected $initMessage;
|
||||
|
||||
/**
|
||||
* The message to be shown while the batch is in progress.
|
||||
*
|
||||
* @var string|\Drupal\Core\StringTranslation\TranslatableMarkup
|
||||
*/
|
||||
protected $progressMessage;
|
||||
|
||||
/**
|
||||
* The message to be shown if a problem occurs.
|
||||
*
|
||||
* @var string|\Drupal\Core\StringTranslation\TranslatableMarkup
|
||||
*/
|
||||
protected $errorMessage;
|
||||
|
||||
/**
|
||||
* The name of a function / method to be called when the batch finishes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $finished;
|
||||
|
||||
/**
|
||||
* The file containing the operation and finished callbacks.
|
||||
*
|
||||
* If the callbacks are in the .module file or can be autoloaded, for example,
|
||||
* static methods on a class, then this does not need to be set.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $file;
|
||||
|
||||
/**
|
||||
* An array of libraries to be included when processing the batch.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $libraries = [];
|
||||
|
||||
/**
|
||||
* An array of options to be used with the redirect URL.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $urlOptions = [];
|
||||
|
||||
/**
|
||||
* Specifies if the batch is progressive.
|
||||
*
|
||||
* If true, multiple calls are used. Otherwise an attempt is made to process
|
||||
* the batch in a single run.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $progressive = TRUE;
|
||||
|
||||
/**
|
||||
* The details of the queue to use.
|
||||
*
|
||||
* A tuple containing the name of the queue and the class of the queue to use.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $queue;
|
||||
|
||||
/**
|
||||
* Sets the default values for the batch builder.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->title = new TranslatableMarkup('Processing');
|
||||
$this->initMessage = new TranslatableMarkup('Initializing.');
|
||||
$this->progressMessage = new TranslatableMarkup('Completed @current of @total.');
|
||||
$this->errorMessage = new TranslatableMarkup('An error has occurred.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the title.
|
||||
*
|
||||
* @param string|\Drupal\Core\StringTranslation\TranslatableMarkup $title
|
||||
* The title.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setTitle($title) {
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the finished callback.
|
||||
*
|
||||
* This callback will be executed if the batch process is done.
|
||||
*
|
||||
* @param callable $callback
|
||||
* The callback.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setFinishCallback(callable $callback) {
|
||||
$this->finished = $callback;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the displayed message while processing is initialized.
|
||||
*
|
||||
* Defaults to 'Initializing.'.
|
||||
*
|
||||
* @param string|\Drupal\Core\StringTranslation\TranslatableMarkup $message
|
||||
* The text to display.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setInitMessage($message) {
|
||||
$this->initMessage = $message;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message to display when the batch is being processed.
|
||||
*
|
||||
* Defaults to 'Completed @current of @total.'.
|
||||
*
|
||||
* @param string|\Drupal\Core\StringTranslation\TranslatableMarkup $message
|
||||
* The text to display. Available placeholders are:
|
||||
* - '@current'
|
||||
* - '@remaining'
|
||||
* - '@total'
|
||||
* - '@percentage'
|
||||
* - '@estimate'
|
||||
* - '@elapsed'.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setProgressMessage($message) {
|
||||
$this->progressMessage = $message;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message to display if an error occurs while processing.
|
||||
*
|
||||
* Defaults to 'An error has occurred.'.
|
||||
*
|
||||
* @param string|\Drupal\Core\StringTranslation\TranslatableMarkup $message
|
||||
* The text to display.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setErrorMessage($message) {
|
||||
$this->errorMessage = $message;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the file that contains the callback functions.
|
||||
*
|
||||
* The path should be relative to base_path(), and thus should be built using
|
||||
* drupal_get_path(). Defaults to {module_name}.module.
|
||||
*
|
||||
* @param string $filename
|
||||
* The path to the file.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setFile($filename) {
|
||||
$this->file = $filename;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the libraries to use when processing the batch.
|
||||
*
|
||||
* Adds the libraries for use on the progress page. Any previously added
|
||||
* libraries are removed.
|
||||
*
|
||||
* @param string[] $libraries
|
||||
* The libraries to be used.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setLibraries(array $libraries) {
|
||||
$this->libraries = $libraries;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the options for redirect URLs.
|
||||
*
|
||||
* @param array $options
|
||||
* The options to use.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @see \Drupal\Core\Url
|
||||
*/
|
||||
public function setUrlOptions(array $options) {
|
||||
$this->urlOptions = $options;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the batch to run progressively.
|
||||
*
|
||||
* @param bool $is_progressive
|
||||
* (optional) A Boolean that indicates whether or not the batch needs to run
|
||||
* progressively. TRUE indicates that the batch will run in more than one
|
||||
* run. FALSE indicates that the batch will finish in a single run. Defaults
|
||||
* to TRUE.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setProgressive($is_progressive = TRUE) {
|
||||
$this->progressive = $is_progressive;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an override for the default queue.
|
||||
*
|
||||
* The class will typically either be \Drupal\Core\Queue\Batch or
|
||||
* \Drupal\Core\Queue\BatchMemory. The class defaults to Batch if progressive
|
||||
* is TRUE, or to BatchMemory if progressive is FALSE.
|
||||
*
|
||||
* @param string $name
|
||||
* The unique identifier for the queue.
|
||||
* @param string $class
|
||||
* The fully qualified name of a class that implements
|
||||
* \Drupal\Core\Queue\QueueInterface.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setQueue($name, $class) {
|
||||
if (!class_exists($class)) {
|
||||
throw new \InvalidArgumentException('Class ' . $class . ' does not exist.');
|
||||
}
|
||||
|
||||
if (!in_array(QueueInterface::class, class_implements($class))) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Class ' . $class . ' does not implement \Drupal\Core\Queue\QueueInterface.'
|
||||
);
|
||||
}
|
||||
|
||||
$this->queue = [
|
||||
'name' => $name,
|
||||
'class' => $class,
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a batch operation.
|
||||
*
|
||||
* @param callable $callback
|
||||
* The name of the callback function.
|
||||
* @param array $arguments
|
||||
* An array of arguments to pass to the callback function.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addOperation(callable $callback, array $arguments = []) {
|
||||
$this->operations[] = [$callback, $arguments];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a \Drupal\Core\Batch\Batch object into an array.
|
||||
*
|
||||
* @return array
|
||||
* The array representation of the object.
|
||||
*/
|
||||
public function toArray() {
|
||||
$array = [
|
||||
'operations' => $this->operations ?: [],
|
||||
'title' => $this->title ?: '',
|
||||
'init_message' => $this->initMessage ?: '',
|
||||
'progress_message' => $this->progressMessage ?: '',
|
||||
'error_message' => $this->errorMessage ?: '',
|
||||
'finished' => $this->finished,
|
||||
'file' => $this->file,
|
||||
'library' => $this->libraries ?: [],
|
||||
'url_options' => $this->urlOptions ?: [],
|
||||
'progressive' => $this->progressive,
|
||||
];
|
||||
|
||||
if ($this->queue) {
|
||||
$array['queue'] = $this->queue;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
}
|
|
@ -200,6 +200,8 @@ class BatchStorage implements BatchStorageInterface {
|
|||
|
||||
/**
|
||||
* Defines the schema for the batch table.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function schemaDefinition() {
|
||||
return [
|
||||
|
|
|
@ -4,9 +4,9 @@ namespace Drupal\Core\Block;
|
|||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Messenger\MessengerTrait;
|
||||
use Drupal\Core\Plugin\ContextAwarePluginAssignmentTrait;
|
||||
use Drupal\Core\Plugin\ContextAwarePluginBase;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Plugin\PluginWithFormsInterface;
|
||||
|
@ -26,6 +26,7 @@ use Drupal\Component\Transliteration\TransliterationInterface;
|
|||
abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginInterface, PluginWithFormsInterface {
|
||||
|
||||
use ContextAwarePluginAssignmentTrait;
|
||||
use MessengerTrait;
|
||||
use PluginWithFormsTrait;
|
||||
|
||||
/**
|
||||
|
@ -244,7 +245,7 @@ abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginIn
|
|||
// \Drupal\system\MachineNameController::transliterate(), so it might make
|
||||
// sense to provide a common service for the two.
|
||||
$transliterated = $this->transliteration()->transliterate($admin_label, LanguageInterface::LANGCODE_DEFAULT, '_');
|
||||
$transliterated = Unicode::strtolower($transliterated);
|
||||
$transliterated = mb_strtolower($transliterated);
|
||||
|
||||
$transliterated = preg_replace('@[^a-z0-9_.]+@', '', $transliterated);
|
||||
|
||||
|
|
|
@ -6,8 +6,9 @@ use Drupal\Component\Plugin\FallbackPluginManagerInterface;
|
|||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\CategorizingPluginManagerTrait;
|
||||
use Drupal\Core\Plugin\Context\ContextAwarePluginManagerTrait;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
use Drupal\Core\Plugin\FilteredPluginManagerTrait;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Manages discovery and instantiation of block plugins.
|
||||
|
@ -20,9 +21,15 @@ class BlockManager extends DefaultPluginManager implements BlockManagerInterface
|
|||
|
||||
use CategorizingPluginManagerTrait {
|
||||
getSortedDefinitions as traitGetSortedDefinitions;
|
||||
getGroupedDefinitions as traitGetGroupedDefinitions;
|
||||
}
|
||||
use ContextAwarePluginManagerTrait;
|
||||
use FilteredPluginManagerTrait;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Constructs a new \Drupal\Core\Block\BlockManager object.
|
||||
|
@ -34,12 +41,22 @@ class BlockManager extends DefaultPluginManager implements BlockManagerInterface
|
|||
* Cache backend instance to use.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler to invoke the alter hook with.
|
||||
* @param \Psr\Log\LoggerInterface $logger
|
||||
* The logger.
|
||||
*/
|
||||
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
|
||||
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, LoggerInterface $logger) {
|
||||
parent::__construct('Plugin/Block', $namespaces, $module_handler, 'Drupal\Core\Block\BlockPluginInterface', 'Drupal\Core\Block\Annotation\Block');
|
||||
|
||||
$this->alterInfo('block');
|
||||
$this->alterInfo($this->getType());
|
||||
$this->setCacheBackend($cache_backend, 'block_plugins');
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getType() {
|
||||
return 'block';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -54,23 +71,13 @@ class BlockManager extends DefaultPluginManager implements BlockManagerInterface
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSortedDefinitions(array $definitions = NULL) {
|
||||
// Sort the plugins first by category, then by label.
|
||||
// Sort the plugins first by category, then by admin label.
|
||||
$definitions = $this->traitGetSortedDefinitions($definitions, 'admin_label');
|
||||
// Do not display the 'broken' plugin in the UI.
|
||||
unset($definitions['broken']);
|
||||
return $definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getGroupedDefinitions(array $definitions = NULL) {
|
||||
$definitions = $this->traitGetGroupedDefinitions($definitions, 'admin_label');
|
||||
// Do not display the 'broken' plugin in the UI.
|
||||
unset($definitions[$this->t('Block')]['broken']);
|
||||
return $definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -78,4 +85,12 @@ class BlockManager extends DefaultPluginManager implements BlockManagerInterface
|
|||
return 'broken';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function handlePluginNotFound($plugin_id, array $configuration) {
|
||||
$this->logger->warning('The "%plugin_id" was not found', ['%plugin_id' => $plugin_id]);
|
||||
return parent::handlePluginNotFound($plugin_id, $configuration);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,10 +4,11 @@ namespace Drupal\Core\Block;
|
|||
|
||||
use Drupal\Component\Plugin\CategorizingPluginManagerInterface;
|
||||
use Drupal\Core\Plugin\Context\ContextAwarePluginManagerInterface;
|
||||
use Drupal\Core\Plugin\FilteredPluginManagerInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface for the discovery and instantiation of block plugins.
|
||||
*/
|
||||
interface BlockManagerInterface extends ContextAwarePluginManagerInterface, CategorizingPluginManagerInterface {
|
||||
interface BlockManagerInterface extends ContextAwarePluginManagerInterface, CategorizingPluginManagerInterface, FilteredPluginManagerInterface {
|
||||
|
||||
}
|
||||
|
|
|
@ -5,11 +5,10 @@ namespace Drupal\Core\Block;
|
|||
/**
|
||||
* The interface for "messages" (#type => status_messages) blocks.
|
||||
*
|
||||
* @see drupal_set_message()
|
||||
* @see drupal_get_message()
|
||||
* @see \Drupal\Core\Messenger\MessengerInterface
|
||||
* @see \Drupal\Core\Render\Element\StatusMessages
|
||||
* @see \Drupal\block\Plugin\DisplayVariant\BlockPageVariant
|
||||
*
|
||||
* @ingroup block_api
|
||||
*/
|
||||
interface MessagesBlockPluginInterface extends BlockPluginInterface { }
|
||||
interface MessagesBlockPluginInterface extends BlockPluginInterface {}
|
||||
|
|
|
@ -38,7 +38,7 @@ class Broken extends BlockBase {
|
|||
*/
|
||||
protected function brokenMessage() {
|
||||
$build['message'] = [
|
||||
'#markup' => $this->t('This block is broken or missing. You may be missing content or you might need to enable the original module.')
|
||||
'#markup' => $this->t('This block is broken or missing. You may be missing content or you might need to enable the original module.'),
|
||||
];
|
||||
|
||||
return $build;
|
||||
|
|
|
@ -11,6 +11,9 @@ use Drupal\Core\Block\TitleBlockPluginInterface;
|
|||
* @Block(
|
||||
* id = "page_title_block",
|
||||
* admin_label = @Translation("Page title"),
|
||||
* forms = {
|
||||
* "settings_tray" = FALSE,
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class PageTitleBlock extends BlockBase implements TitleBlockPluginInterface {
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Drupal\Core\Cache;
|
||||
|
||||
use Drupal\Component\Assertion\Inspector;
|
||||
|
||||
/**
|
||||
* Stores cache items in the Alternative PHP Cache User Cache (APCu).
|
||||
*/
|
||||
|
@ -161,7 +163,7 @@ class ApcuBackend implements CacheBackendInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($cid, $data, $expire = CacheBackendInterface::CACHE_PERMANENT, array $tags = []) {
|
||||
assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)', 'Cache tags must be strings.');
|
||||
assert(Inspector::assertAllStrings($tags), 'Cache tags must be strings.');
|
||||
$tags = array_unique($tags);
|
||||
$cache = new \stdClass();
|
||||
$cache->cid = $cid;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Cache;
|
||||
|
||||
/**
|
||||
* Defines a chained cache implementation for combining multiple cache backends.
|
||||
*
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\Core\Cache;
|
||||
|
||||
use Drupal\Component\Assertion\Inspector;
|
||||
use Drupal\Core\Database\Query\SelectInterface;
|
||||
|
||||
/**
|
||||
|
@ -20,16 +21,16 @@ class Cache {
|
|||
* Merges arrays of cache contexts and removes duplicates.
|
||||
*
|
||||
* @param array $a
|
||||
* Cache contexts array to merge.
|
||||
* Cache contexts array to merge.
|
||||
* @param array $b
|
||||
* Cache contexts array to merge.
|
||||
* Cache contexts array to merge.
|
||||
*
|
||||
* @return string[]
|
||||
* The merged array of cache contexts.
|
||||
*/
|
||||
public static function mergeContexts(array $a = [], array $b = []) {
|
||||
$cache_contexts = array_unique(array_merge($a, $b));
|
||||
assert('\Drupal::service(\'cache_contexts_manager\')->assertValidTokens($cache_contexts)');
|
||||
assert(\Drupal::service('cache_contexts_manager')->assertValidTokens($cache_contexts));
|
||||
sort($cache_contexts);
|
||||
return $cache_contexts;
|
||||
}
|
||||
|
@ -46,15 +47,15 @@ class Cache {
|
|||
* they're constituted from.
|
||||
*
|
||||
* @param array $a
|
||||
* Cache tags array to merge.
|
||||
* Cache tags array to merge.
|
||||
* @param array $b
|
||||
* Cache tags array to merge.
|
||||
* Cache tags array to merge.
|
||||
*
|
||||
* @return string[]
|
||||
* The merged array of cache tags.
|
||||
*/
|
||||
public static function mergeTags(array $a = [], array $b = []) {
|
||||
assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($a) && \Drupal\Component\Assertion\Inspector::assertAllStrings($b)', 'Cache tags must be valid strings');
|
||||
assert(Inspector::assertAllStrings($a) && Inspector::assertAllStrings($b), 'Cache tags must be valid strings');
|
||||
|
||||
$cache_tags = array_unique(array_merge($a, $b));
|
||||
sort($cache_tags);
|
||||
|
@ -67,9 +68,9 @@ class Cache {
|
|||
* Ensures infinite max-age (Cache::PERMANENT) is taken into account.
|
||||
*
|
||||
* @param int $a
|
||||
* Max age value to merge.
|
||||
* Max age value to merge.
|
||||
* @param int $b
|
||||
* Max age value to merge.
|
||||
* Max age value to merge.
|
||||
*
|
||||
* @return int
|
||||
* The minimum max-age value.
|
||||
|
@ -96,7 +97,7 @@ class Cache {
|
|||
* An array of cache tags.
|
||||
*
|
||||
* @deprecated
|
||||
* Use assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)');
|
||||
* Use assert(Inspector::assertAllStrings($tags));
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace Drupal\Core\Cache;
|
|||
* Drupal\Core\Cache\DatabaseBackend provides the default implementation, which
|
||||
* can be consulted as an example.
|
||||
*
|
||||
* The cache indentifiers are case sensitive.
|
||||
* The cache identifiers are case sensitive.
|
||||
*
|
||||
* @ingroup cache
|
||||
*/
|
||||
|
@ -91,7 +91,7 @@ interface CacheBackendInterface {
|
|||
* identify objects used to build the cache item, which should trigger
|
||||
* cache invalidation when updated. For example if a cached item represents
|
||||
* a node, both the node ID and the author's user ID might be passed in as
|
||||
* tags. For example array('node' => array(123), 'user' => array(92)).
|
||||
* tags. For example ['node:123', 'node:456', 'user:789'].
|
||||
*
|
||||
* @see \Drupal\Core\Cache\CacheBackendInterface::get()
|
||||
* @see \Drupal\Core\Cache\CacheBackendInterface::getMultiple()
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\Core\Cache;
|
||||
|
||||
use Drupal\Component\Assertion\Inspector;
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Core\DestructableInterface;
|
||||
use Drupal\Core\Lock\LockBackendInterface;
|
||||
|
@ -111,7 +112,7 @@ abstract class CacheCollector implements CacheCollectorInterface, DestructableIn
|
|||
* (optional) The tags to specify for the cache item.
|
||||
*/
|
||||
public function __construct($cid, CacheBackendInterface $cache, LockBackendInterface $lock, array $tags = []) {
|
||||
assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)', 'Cache tags must be strings.');
|
||||
assert(Inspector::assertAllStrings($tags), 'Cache tags must be strings.');
|
||||
$this->cid = $cid;
|
||||
$this->cache = $cache;
|
||||
$this->tags = $tags;
|
||||
|
@ -246,6 +247,18 @@ abstract class CacheCollector implements CacheCollectorInterface, DestructableIn
|
|||
}
|
||||
$data = array_merge($cache->data, $data);
|
||||
}
|
||||
elseif ($this->cacheCreated) {
|
||||
// Getting here indicates that there was a cache entry at the
|
||||
// beginning of the request, but now it's gone (some other process
|
||||
// must have cleared it). We back out to prevent corrupting the cache
|
||||
// with incomplete data, since we won't be able to properly merge
|
||||
// the existing cache data from earlier with the new data.
|
||||
// A future request will properly hydrate the cache from scratch.
|
||||
if ($lock) {
|
||||
$this->lock->release($lock_name);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Remove keys marked for deletion.
|
||||
foreach ($this->keysToRemove as $delete_key) {
|
||||
unset($data[$delete_key]);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\Core\Cache;
|
||||
|
||||
use Drupal\Component\Assertion\Inspector;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
|
||||
|
||||
/**
|
||||
|
@ -22,7 +23,7 @@ class CacheTagsInvalidator implements CacheTagsInvalidatorInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function invalidateTags(array $tags) {
|
||||
assert('Drupal\Component\Assertion\Inspector::assertAllStrings($tags)', 'Cache tags must be strings.');
|
||||
assert(Inspector::assertAllStrings($tags), 'Cache tags must be strings.');
|
||||
|
||||
// Notify all added cache tags invalidators.
|
||||
foreach ($this->invalidators as $invalidator) {
|
||||
|
|
67
web/core/lib/Drupal/Core/Cache/CacheableDependencyTrait.php
Normal file
67
web/core/lib/Drupal/Core/Cache/CacheableDependencyTrait.php
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Cache;
|
||||
|
||||
/**
|
||||
* Trait for \Drupal\Core\Cache\CacheableDependencyInterface.
|
||||
*/
|
||||
trait CacheableDependencyTrait {
|
||||
|
||||
/**
|
||||
* Cache contexts.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $cacheContexts = [];
|
||||
|
||||
/**
|
||||
* Cache tags.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $cacheTags = [];
|
||||
|
||||
/**
|
||||
* Cache max-age.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $cacheMaxAge = Cache::PERMANENT;
|
||||
|
||||
/**
|
||||
* Sets cacheability; useful for value object constructors.
|
||||
*
|
||||
* @param \Drupal\Core\Cache\CacheableDependencyInterface $cacheability
|
||||
* The cacheability to set.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function setCacheability(CacheableDependencyInterface $cacheability) {
|
||||
$this->cacheContexts = $cacheability->getCacheContexts();
|
||||
$this->cacheTags = $cacheability->getCacheTags();
|
||||
$this->cacheMaxAge = $cacheability->getCacheMaxAge();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
return $this->cacheTags;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
return $this->cacheContexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
return $this->cacheMaxAge;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Cache;
|
||||
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
|
||||
|
||||
|
|
|
@ -100,7 +100,7 @@ class CacheContextsManager {
|
|||
* cacheability metadata.
|
||||
*/
|
||||
public function convertTokensToKeys(array $context_tokens) {
|
||||
assert('$this->assertValidTokens($context_tokens)');
|
||||
assert($this->assertValidTokens($context_tokens));
|
||||
$cacheable_metadata = new CacheableMetadata();
|
||||
$optimized_tokens = $this->optimizeTokens($context_tokens);
|
||||
// Iterate over cache contexts that have been optimized away and get their
|
||||
|
|
|
@ -36,7 +36,6 @@ class CacheContextsPass implements CompilerPassInterface {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
$container->setParameter('cache_contexts', $cache_contexts);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,11 +25,28 @@ class HeadersCacheContext extends RequestStackCacheContextBase implements Calcul
|
|||
*/
|
||||
public function getContext($header = NULL) {
|
||||
if ($header === NULL) {
|
||||
return $this->requestStack->getCurrentRequest()->headers->all();
|
||||
$headers = $this->requestStack->getCurrentRequest()->headers->all();
|
||||
// Order headers by name to have less cache variations.
|
||||
ksort($headers);
|
||||
$result = '';
|
||||
foreach ($headers as $name => $value) {
|
||||
if ($result) {
|
||||
$result .= '&';
|
||||
}
|
||||
// Sort values to minimize cache variations.
|
||||
sort($value);
|
||||
$result .= $name . '=' . implode(',', $value);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
else {
|
||||
return $this->requestStack->getCurrentRequest()->headers->get($header);
|
||||
elseif ($this->requestStack->getCurrentRequest()->headers->has($header)) {
|
||||
$value = $this->requestStack->getCurrentRequest()->headers->get($header);
|
||||
if ($value !== '') {
|
||||
return $value;
|
||||
}
|
||||
return '?valueless?';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,7 +13,7 @@ class LanguagesCacheContext implements CalculatedCacheContextInterface {
|
|||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
|
|
|
@ -22,8 +22,11 @@ class SessionCacheContext extends RequestStackCacheContextBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContext() {
|
||||
$sid = $this->requestStack->getCurrentRequest()->getSession()->getId();
|
||||
return Crypt::hashBase64($sid);
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
if ($request->hasSession()) {
|
||||
return Crypt::hashBase64($request->getSession()->getId());
|
||||
}
|
||||
return 'none';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use Drupal\Core\Cache\CacheableMetadata;
|
|||
/**
|
||||
* Defines the SiteCacheContext service, for "per site" caching.
|
||||
*
|
||||
* Cache context ID: 'site'.
|
||||
* Cache context ID: 'url.site'.
|
||||
*
|
||||
* A "site" is defined as the combination of URI scheme, domain name, port and
|
||||
* base path. It allows for varying between the *same* site being accessed via
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\Core\Cache;
|
||||
|
||||
use Drupal\Component\Assertion\Inspector;
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Database\SchemaObjectExistsException;
|
||||
|
@ -16,6 +17,30 @@ use Drupal\Core\Database\SchemaObjectExistsException;
|
|||
*/
|
||||
class DatabaseBackend implements CacheBackendInterface {
|
||||
|
||||
/**
|
||||
* The default maximum number of rows that this cache bin table can store.
|
||||
*
|
||||
* This maximum is introduced to ensure that the database is not filled with
|
||||
* hundred of thousand of cache entries with gigabytes in size.
|
||||
*
|
||||
* Read about how to change it in the @link cache Cache API topic. @endlink
|
||||
*/
|
||||
const DEFAULT_MAX_ROWS = 5000;
|
||||
|
||||
/**
|
||||
* -1 means infinite allows numbers of rows for the cache backend.
|
||||
*/
|
||||
const MAXIMUM_NONE = -1;
|
||||
|
||||
/**
|
||||
* The maximum number of rows that this cache bin table is allowed to store.
|
||||
*
|
||||
* @see ::MAXIMUM_NONE
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $maxRows;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
@ -45,14 +70,18 @@ class DatabaseBackend implements CacheBackendInterface {
|
|||
* The cache tags checksum provider.
|
||||
* @param string $bin
|
||||
* The cache bin for which the object is created.
|
||||
* @param int $max_rows
|
||||
* (optional) The maximum number of rows that are allowed in this cache bin
|
||||
* table.
|
||||
*/
|
||||
public function __construct(Connection $connection, CacheTagsChecksumInterface $checksum_provider, $bin) {
|
||||
public function __construct(Connection $connection, CacheTagsChecksumInterface $checksum_provider, $bin, $max_rows = NULL) {
|
||||
// All cache tables should be prefixed with 'cache_'.
|
||||
$bin = 'cache_' . $bin;
|
||||
|
||||
$this->bin = $bin;
|
||||
$this->connection = $connection;
|
||||
$this->checksumProvider = $checksum_provider;
|
||||
$this->maxRows = $max_rows === NULL ? static::DEFAULT_MAX_ROWS : $max_rows;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -194,7 +223,7 @@ class DatabaseBackend implements CacheBackendInterface {
|
|||
'tags' => [],
|
||||
];
|
||||
|
||||
assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($item[\'tags\'])', 'Cache Tags must be strings.');
|
||||
assert(Inspector::assertAllStrings($item['tags']), 'Cache Tags must be strings.');
|
||||
$item['tags'] = array_unique($item['tags']);
|
||||
// Sort the cache tags so that they are stored consistently in the DB.
|
||||
sort($item['tags']);
|
||||
|
@ -326,6 +355,22 @@ class DatabaseBackend implements CacheBackendInterface {
|
|||
*/
|
||||
public function garbageCollection() {
|
||||
try {
|
||||
// Bounded size cache bin, using FIFO.
|
||||
if ($this->maxRows !== static::MAXIMUM_NONE) {
|
||||
$first_invalid_create_time = $this->connection->select($this->bin)
|
||||
->fields($this->bin, ['created'])
|
||||
->orderBy("{$this->bin}.created", 'DESC')
|
||||
->range($this->maxRows, $this->maxRows + 1)
|
||||
->execute()
|
||||
->fetchField();
|
||||
|
||||
if ($first_invalid_create_time) {
|
||||
$this->connection->delete($this->bin)
|
||||
->condition('created', $first_invalid_create_time, '<=')
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
$this->connection->delete($this->bin)
|
||||
->condition('expire', Cache::PERMANENT, '<>')
|
||||
->condition('expire', REQUEST_TIME, '<')
|
||||
|
@ -417,6 +462,8 @@ class DatabaseBackend implements CacheBackendInterface {
|
|||
|
||||
/**
|
||||
* Defines the schema for the {cache_*} bin tables.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function schemaDefinition() {
|
||||
$schema = [
|
||||
|
@ -472,10 +519,20 @@ class DatabaseBackend implements CacheBackendInterface {
|
|||
],
|
||||
'indexes' => [
|
||||
'expire' => ['expire'],
|
||||
'created' => ['created'],
|
||||
],
|
||||
'primary key' => ['cid'],
|
||||
];
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of rows that this cache bin table is allowed to store.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getMaxRows() {
|
||||
return $this->maxRows;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace Drupal\Core\Cache;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Site\Settings;
|
||||
|
||||
class DatabaseBackendFactory implements CacheFactoryInterface {
|
||||
|
||||
|
@ -20,6 +21,13 @@ class DatabaseBackendFactory implements CacheFactoryInterface {
|
|||
*/
|
||||
protected $checksumProvider;
|
||||
|
||||
/**
|
||||
* The settings array.
|
||||
*
|
||||
* @var \Drupal\Core\Site\Settings
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* Constructs the DatabaseBackendFactory object.
|
||||
*
|
||||
|
@ -27,10 +35,15 @@ class DatabaseBackendFactory implements CacheFactoryInterface {
|
|||
* Database connection
|
||||
* @param \Drupal\Core\Cache\CacheTagsChecksumInterface $checksum_provider
|
||||
* The cache tags checksum provider.
|
||||
* @param \Drupal\Core\Site\Settings $settings
|
||||
* (optional) The settings array.
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function __construct(Connection $connection, CacheTagsChecksumInterface $checksum_provider) {
|
||||
public function __construct(Connection $connection, CacheTagsChecksumInterface $checksum_provider, Settings $settings = NULL) {
|
||||
$this->connection = $connection;
|
||||
$this->checksumProvider = $checksum_provider;
|
||||
$this->settings = $settings ?: Settings::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -43,7 +56,35 @@ class DatabaseBackendFactory implements CacheFactoryInterface {
|
|||
* The cache backend object for the specified cache bin.
|
||||
*/
|
||||
public function get($bin) {
|
||||
return new DatabaseBackend($this->connection, $this->checksumProvider, $bin);
|
||||
$max_rows = $this->getMaxRowsForBin($bin);
|
||||
return new DatabaseBackend($this->connection, $this->checksumProvider, $bin, $max_rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the max rows for the specified cache bin.
|
||||
*
|
||||
* @param string $bin
|
||||
* The cache bin for which the object is created.
|
||||
*
|
||||
* @return int
|
||||
* The maximum number of rows for the given bin. Defaults to
|
||||
* DatabaseBackend::DEFAULT_MAX_ROWS.
|
||||
*/
|
||||
protected function getMaxRowsForBin($bin) {
|
||||
$max_rows_settings = $this->settings->get('database_cache_max_rows');
|
||||
// First, look for a cache bin specific setting.
|
||||
if (isset($max_rows_settings['bins'][$bin])) {
|
||||
$max_rows = $max_rows_settings['bins'][$bin];
|
||||
}
|
||||
// Second, use configured default backend.
|
||||
elseif (isset($max_rows_settings['default'])) {
|
||||
$max_rows = $max_rows_settings['default'];
|
||||
}
|
||||
else {
|
||||
// Fall back to the default max rows if nothing else is configured.
|
||||
$max_rows = DatabaseBackend::DEFAULT_MAX_ROWS;
|
||||
}
|
||||
return $max_rows;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -163,6 +163,8 @@ class DatabaseCacheTagsChecksum implements CacheTagsChecksumInterface, CacheTags
|
|||
|
||||
/**
|
||||
* Defines the schema for the {cachetags} table.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function schemaDefinition() {
|
||||
$schema = [
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Drupal\Core\Cache;
|
||||
|
||||
use Drupal\Component\Assertion\Inspector;
|
||||
|
||||
/**
|
||||
* Defines a memory cache implementation.
|
||||
*
|
||||
|
@ -98,7 +100,7 @@ class MemoryBackend implements CacheBackendInterface, CacheTagsInvalidatorInterf
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = []) {
|
||||
assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)', 'Cache Tags must be strings.');
|
||||
assert(Inspector::assertAllStrings($tags), 'Cache Tags must be strings.');
|
||||
$tags = array_unique($tags);
|
||||
// Sort the cache tags so that they are stored consistently in the database.
|
||||
sort($tags);
|
||||
|
@ -155,9 +157,7 @@ class MemoryBackend implements CacheBackendInterface, CacheTagsInvalidatorInterf
|
|||
*/
|
||||
public function invalidateMultiple(array $cids) {
|
||||
foreach ($cids as $cid) {
|
||||
if (isset($this->cache[$cid])) {
|
||||
$this->cache[$cid]->expire = $this->getRequestTime() - 1;
|
||||
}
|
||||
$this->cache[$cid]->expire = $this->getRequestTime() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
63
web/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php
Normal file
63
web/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Cache\MemoryCache;
|
||||
|
||||
use Drupal\Component\Assertion\Inspector;
|
||||
use Drupal\Core\Cache\MemoryBackend;
|
||||
|
||||
/**
|
||||
* Defines a memory cache implementation.
|
||||
*
|
||||
* Stores cache items in memory using a PHP array.
|
||||
*
|
||||
* @ingroup cache
|
||||
*/
|
||||
class MemoryCache extends MemoryBackend implements MemoryCacheInterface {
|
||||
|
||||
/**
|
||||
* Prepares a cached item.
|
||||
*
|
||||
* Checks that items are either permanent or did not expire, and returns data
|
||||
* as appropriate.
|
||||
*
|
||||
* @param object $cache
|
||||
* An item loaded from cache_get() or cache_get_multiple().
|
||||
* @param bool $allow_invalid
|
||||
* (optional) If TRUE, cache items may be returned even if they have expired
|
||||
* or been invalidated. Defaults to FALSE.
|
||||
*
|
||||
* @return mixed
|
||||
* The item with data as appropriate or FALSE if there is no
|
||||
* valid item to load.
|
||||
*/
|
||||
protected function prepareItem($cache, $allow_invalid = FALSE) {
|
||||
if (!isset($cache->data)) {
|
||||
return FALSE;
|
||||
}
|
||||
// Check expire time.
|
||||
$cache->valid = $cache->expire == static::CACHE_PERMANENT || $cache->expire >= $this->getRequestTime();
|
||||
|
||||
if (!$allow_invalid && !$cache->valid) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($cid, $data, $expire = MemoryCacheInterface::CACHE_PERMANENT, array $tags = []) {
|
||||
assert(Inspector::assertAllStrings($tags), 'Cache tags must be strings.');
|
||||
$tags = array_unique($tags);
|
||||
|
||||
$this->cache[$cid] = (object) [
|
||||
'cid' => $cid,
|
||||
'data' => $data,
|
||||
'created' => $this->getRequestTime(),
|
||||
'expire' => $expire,
|
||||
'tags' => $tags,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Cache\MemoryCache;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for memory cache implementations.
|
||||
*
|
||||
* This has additional requirements over CacheBackendInterface and
|
||||
* CacheTagsInvalidatorInterface. Objects stored must be the same instance when
|
||||
* retrieved from cache, so that this can be used as a replacement for protected
|
||||
* properties and similar.
|
||||
*
|
||||
* @ingroup cache
|
||||
*/
|
||||
interface MemoryCacheInterface extends CacheBackendInterface, CacheTagsInvalidatorInterface {}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\Core\Cache;
|
||||
|
||||
use Drupal\Component\Assertion\Inspector;
|
||||
use Drupal\Core\PhpStorage\PhpStorageFactory;
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
|
||||
|
@ -143,7 +144,8 @@ class PhpBackend implements CacheBackendInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = []) {
|
||||
assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)', 'Cache Tags must be strings.');
|
||||
assert(Inspector::assertAllStrings($tags), 'Cache Tags must be strings.');
|
||||
|
||||
$item = (object) [
|
||||
'cid' => $cid,
|
||||
'data' => $data,
|
||||
|
|
|
@ -7,47 +7,7 @@ namespace Drupal\Core\Cache;
|
|||
*/
|
||||
trait RefinableCacheableDependencyTrait {
|
||||
|
||||
/**
|
||||
* Cache contexts.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $cacheContexts = [];
|
||||
|
||||
/**
|
||||
* Cache tags.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $cacheTags = [];
|
||||
|
||||
/**
|
||||
* Cache max-age.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $cacheMaxAge = Cache::PERMANENT;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
return $this->cacheTags;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
return $this->cacheContexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
return $this->cacheMaxAge;
|
||||
}
|
||||
use CacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
|
|
@ -69,7 +69,7 @@ class DbDumpCommand extends DbCommandBase {
|
|||
* The database connection to use.
|
||||
* @param array $schema_only
|
||||
* Table patterns for which to only dump the schema, no data.
|
||||
* @return string The PHP script.
|
||||
* @return string
|
||||
* The PHP script.
|
||||
*/
|
||||
protected function generateScript(Connection $connection, array $schema_only = []) {
|
||||
|
@ -102,7 +102,7 @@ class DbDumpCommand extends DbCommandBase {
|
|||
*
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* The database connection to use.
|
||||
* @return array An array of table names.
|
||||
* @return array
|
||||
* An array of table names.
|
||||
*/
|
||||
protected function getTables(Connection $connection) {
|
||||
|
@ -145,7 +145,7 @@ class DbDumpCommand extends DbCommandBase {
|
|||
$name = $row['Field'];
|
||||
// Parse out the field type and meta information.
|
||||
preg_match('@([a-z]+)(?:\((\d+)(?:,(\d+))?\))?\s*(unsigned)?@', $row['Type'], $matches);
|
||||
$type = $this->fieldTypeMap($connection, $matches[1]);
|
||||
$type = $this->fieldTypeMap($connection, $matches[1]);
|
||||
if ($row['Extra'] === 'auto_increment') {
|
||||
// If this is an auto increment, then the type is 'serial'.
|
||||
$type = 'serial';
|
||||
|
@ -259,8 +259,12 @@ class DbDumpCommand extends DbCommandBase {
|
|||
$query = $connection->query("SHOW TABLE STATUS LIKE '{" . $table . "}'");
|
||||
$data = $query->fetchAssoc();
|
||||
|
||||
// Map the collation to a character set. For example, 'utf8mb4_general_ci'
|
||||
// (MySQL 5) or 'utf8mb4_0900_ai_ci' (MySQL 8) will be mapped to 'utf8mb4'.
|
||||
list($charset,) = explode('_', $data['Collation'], 2);
|
||||
|
||||
// Set `mysql_character_set`. This will be ignored by other backends.
|
||||
$definition['mysql_character_set'] = str_replace('_general_ci', '', $data['Collation']);
|
||||
$definition['mysql_character_set'] = $charset;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
337
web/core/lib/Drupal/Core/Command/InstallCommand.php
Normal file
337
web/core/lib/Drupal/Core/Command/InstallCommand.php
Normal file
|
@ -0,0 +1,337 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Command;
|
||||
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Core\Database\ConnectionNotDefinedException;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\DrupalKernel;
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
use Drupal\Core\Extension\InfoParserDynamic;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
/**
|
||||
* Installs a Drupal site for local testing/development.
|
||||
*
|
||||
* @internal
|
||||
* This command makes no guarantee of an API for Drupal extensions.
|
||||
*/
|
||||
class InstallCommand extends Command {
|
||||
|
||||
/**
|
||||
* The class loader.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $classLoader;
|
||||
|
||||
/**
|
||||
* Constructs a new InstallCommand command.
|
||||
*
|
||||
* @param object $class_loader
|
||||
* The class loader.
|
||||
*/
|
||||
public function __construct($class_loader) {
|
||||
parent::__construct('install');
|
||||
$this->classLoader = $class_loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure() {
|
||||
$this->setName('install')
|
||||
->setDescription('Installs a Drupal demo site. This is not meant for production and might be too simple for custom development. It is a quick and easy way to get Drupal running.')
|
||||
->addArgument('install-profile', InputArgument::OPTIONAL, 'Install profile to install the site in.')
|
||||
->addOption('langcode', NULL, InputOption::VALUE_OPTIONAL, 'The language to install the site in.', 'en')
|
||||
->addOption('site-name', NULL, InputOption::VALUE_OPTIONAL, 'Set the site name.', 'Drupal')
|
||||
->addUsage('demo_umami --langcode fr')
|
||||
->addUsage('standard --site-name QuickInstall');
|
||||
|
||||
parent::configure();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
if (!extension_loaded('pdo_sqlite')) {
|
||||
$io->getErrorStyle()->error('You must have the pdo_sqlite PHP extension installed. See core/INSTALL.sqlite.txt for instructions.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Change the directory to the Drupal root.
|
||||
chdir(dirname(dirname(dirname(dirname(dirname(__DIR__))))));
|
||||
|
||||
// Check whether there is already an installation.
|
||||
if ($this->isDrupalInstalled()) {
|
||||
// Do not fail if the site is already installed so this command can be
|
||||
// chained with ServerCommand.
|
||||
$output->writeln('<info>Drupal is already installed.</info> If you want to reinstall, remove sites/default/files and sites/default/settings.php.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$install_profile = $input->getArgument('install-profile');
|
||||
if ($install_profile && !$this->validateProfile($install_profile, $io)) {
|
||||
return 1;
|
||||
}
|
||||
if (!$install_profile) {
|
||||
$install_profile = $this->selectProfile($io);
|
||||
}
|
||||
|
||||
return $this->install($this->classLoader, $io, $install_profile, $input->getOption('langcode'), $this->getSitePath(), $input->getOption('site-name'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether there is already an existing Drupal installation.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isDrupalInstalled() {
|
||||
try {
|
||||
$kernel = new DrupalKernel('prod', $this->classLoader, FALSE);
|
||||
$kernel::bootEnvironment();
|
||||
$kernel->setSitePath($this->getSitePath());
|
||||
Settings::initialize($kernel->getAppRoot(), $kernel->getSitePath(), $this->classLoader);
|
||||
$kernel->boot();
|
||||
}
|
||||
catch (ConnectionNotDefinedException $e) {
|
||||
return FALSE;
|
||||
}
|
||||
return !empty(Database::getConnectionInfo());
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs Drupal with specified installation profile.
|
||||
*
|
||||
* @param object $class_loader
|
||||
* The class loader.
|
||||
* @param \Symfony\Component\Console\Style\SymfonyStyle $io
|
||||
* The Symfony output decorator.
|
||||
* @param string $profile
|
||||
* The installation profile to use.
|
||||
* @param string $langcode
|
||||
* The language to install the site in.
|
||||
* @param string $site_path
|
||||
* The path to install the site to, like 'sites/default'.
|
||||
* @param string $site_name
|
||||
* The site name.
|
||||
*
|
||||
* @throws \Exception
|
||||
* Thrown when failing to create the $site_path directory or settings.php.
|
||||
*/
|
||||
protected function install($class_loader, SymfonyStyle $io, $profile, $langcode, $site_path, $site_name) {
|
||||
$password = Crypt::randomBytesBase64(12);
|
||||
$parameters = [
|
||||
'interactive' => FALSE,
|
||||
'site_path' => $site_path,
|
||||
'parameters' => [
|
||||
'profile' => $profile,
|
||||
'langcode' => $langcode,
|
||||
],
|
||||
'forms' => [
|
||||
'install_settings_form' => [
|
||||
'driver' => 'sqlite',
|
||||
'sqlite' => [
|
||||
'database' => $site_path . '/files/.sqlite',
|
||||
],
|
||||
],
|
||||
'install_configure_form' => [
|
||||
'site_name' => $site_name,
|
||||
'site_mail' => 'drupal@localhost',
|
||||
'account' => [
|
||||
'name' => 'admin',
|
||||
'mail' => 'admin@localhost',
|
||||
'pass' => [
|
||||
'pass1' => $password,
|
||||
'pass2' => $password,
|
||||
],
|
||||
],
|
||||
'enable_update_status_module' => TRUE,
|
||||
// form_type_checkboxes_value() requires NULL instead of FALSE values
|
||||
// for programmatic form submissions to disable a checkbox.
|
||||
'enable_update_status_emails' => NULL,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// Create the directory and settings.php if not there so that the installer
|
||||
// works.
|
||||
if (!is_dir($site_path)) {
|
||||
if ($io->isVerbose()) {
|
||||
$io->writeln("Creating directory: $site_path");
|
||||
}
|
||||
if (!mkdir($site_path, 0775)) {
|
||||
throw new \RuntimeException("Failed to create directory $site_path");
|
||||
}
|
||||
}
|
||||
if (!file_exists("{$site_path}/settings.php")) {
|
||||
if ($io->isVerbose()) {
|
||||
$io->writeln("Creating file: {$site_path}/settings.php");
|
||||
}
|
||||
if (!copy('sites/default/default.settings.php', "{$site_path}/settings.php")) {
|
||||
throw new \RuntimeException("Copying sites/default/default.settings.php to {$site_path}/settings.php failed.");
|
||||
}
|
||||
}
|
||||
|
||||
require_once 'core/includes/install.core.inc';
|
||||
|
||||
$progress_bar = $io->createProgressBar();
|
||||
install_drupal($class_loader, $parameters, function ($install_state) use ($progress_bar) {
|
||||
static $started = FALSE;
|
||||
if (!$started) {
|
||||
$started = TRUE;
|
||||
// We've already done 1.
|
||||
$progress_bar->setFormat("%current%/%max% [%bar%]\n%message%\n");
|
||||
$progress_bar->setMessage(t('Installing @drupal', ['@drupal' => drupal_install_profile_distribution_name()]));
|
||||
$tasks = install_tasks($install_state);
|
||||
$progress_bar->start(count($tasks) + 1);
|
||||
}
|
||||
$tasks_to_perform = install_tasks_to_perform($install_state);
|
||||
$task = current($tasks_to_perform);
|
||||
if (isset($task['display_name'])) {
|
||||
$progress_bar->setMessage($task['display_name']);
|
||||
}
|
||||
$progress_bar->advance();
|
||||
});
|
||||
$success_message = t('Congratulations, you installed @drupal!', [
|
||||
'@drupal' => drupal_install_profile_distribution_name(),
|
||||
'@name' => 'admin',
|
||||
'@pass' => $password,
|
||||
], ['langcode' => $langcode]);
|
||||
$progress_bar->setMessage('<info>' . $success_message . '</info>');
|
||||
$progress_bar->display();
|
||||
$progress_bar->finish();
|
||||
$io->writeln('<info>Username:</info> admin');
|
||||
$io->writeln("<info>Password:</info> $password");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the site path.
|
||||
*
|
||||
* Defaults to 'sites/default'. For testing purposes this can be overridden
|
||||
* using the DRUPAL_DEV_SITE_PATH environment variable.
|
||||
*
|
||||
* @return string
|
||||
* The site path to use.
|
||||
*/
|
||||
protected function getSitePath() {
|
||||
return getenv('DRUPAL_DEV_SITE_PATH') ?: 'sites/default';
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the install profile to use.
|
||||
*
|
||||
* @param \Symfony\Component\Console\Style\SymfonyStyle $io
|
||||
* Symfony style output decorator.
|
||||
*
|
||||
* @return string
|
||||
* The selected install profile.
|
||||
*
|
||||
* @see _install_select_profile()
|
||||
* @see \Drupal\Core\Installer\Form\SelectProfileForm
|
||||
*/
|
||||
protected function selectProfile(SymfonyStyle $io) {
|
||||
$profiles = $this->getProfiles();
|
||||
|
||||
// If there is a distribution there will be only one profile.
|
||||
if (count($profiles) == 1) {
|
||||
return key($profiles);
|
||||
}
|
||||
// Display alphabetically by human-readable name, but always put the core
|
||||
// profiles first (if they are present in the filesystem).
|
||||
natcasesort($profiles);
|
||||
if (isset($profiles['minimal'])) {
|
||||
// If the expert ("Minimal") core profile is present, put it in front of
|
||||
// any non-core profiles rather than including it with them
|
||||
// alphabetically, since the other profiles might be intended to group
|
||||
// together in a particular way.
|
||||
$profiles = ['minimal' => $profiles['minimal']] + $profiles;
|
||||
}
|
||||
if (isset($profiles['standard'])) {
|
||||
// If the default ("Standard") core profile is present, put it at the very
|
||||
// top of the list. This profile will have its radio button pre-selected,
|
||||
// so we want it to always appear at the top.
|
||||
$profiles = ['standard' => $profiles['standard']] + $profiles;
|
||||
}
|
||||
reset($profiles);
|
||||
return $io->choice('Select an installation profile', $profiles, current($profiles));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a user provided install profile.
|
||||
*
|
||||
* @param string $install_profile
|
||||
* Install profile to validate.
|
||||
* @param \Symfony\Component\Console\Style\SymfonyStyle $io
|
||||
* Symfony style output decorator.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the profile is valid, FALSE if not.
|
||||
*/
|
||||
protected function validateProfile($install_profile, SymfonyStyle $io) {
|
||||
// Allow people to install hidden and non-distribution profiles if they
|
||||
// supply the argument.
|
||||
$profiles = $this->getProfiles(TRUE, FALSE);
|
||||
if (!isset($profiles[$install_profile])) {
|
||||
$error_msg = sprintf("'%s' is not a valid install profile.", $install_profile);
|
||||
$alternatives = [];
|
||||
foreach (array_keys($profiles) as $profile_name) {
|
||||
$lev = levenshtein($install_profile, $profile_name);
|
||||
if ($lev <= strlen($profile_name) / 4 || FALSE !== strpos($profile_name, $install_profile)) {
|
||||
$alternatives[] = $profile_name;
|
||||
}
|
||||
}
|
||||
if (!empty($alternatives)) {
|
||||
$error_msg .= sprintf(" Did you mean '%s'?", implode("' or '", $alternatives));
|
||||
}
|
||||
$io->getErrorStyle()->error($error_msg);
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of profiles.
|
||||
*
|
||||
* @param bool $include_hidden
|
||||
* (optional) Whether to include hidden profiles. Defaults to FALSE.
|
||||
* @param bool $auto_select_distributions
|
||||
* (optional) Whether to only return the first distribution found.
|
||||
*
|
||||
* @return string[]
|
||||
* An array of profile descriptions keyed by the profile machine name.
|
||||
*/
|
||||
protected function getProfiles($include_hidden = FALSE, $auto_select_distributions = TRUE) {
|
||||
// Build a list of all available profiles.
|
||||
$listing = new ExtensionDiscovery(getcwd(), FALSE);
|
||||
$listing->setProfileDirectories([]);
|
||||
$profiles = [];
|
||||
$info_parser = new InfoParserDynamic();
|
||||
foreach ($listing->scan('profile') as $profile) {
|
||||
$details = $info_parser->parse($profile->getPathname());
|
||||
// Don't show hidden profiles.
|
||||
if (!$include_hidden && !empty($details['hidden'])) {
|
||||
continue;
|
||||
}
|
||||
// Determine the name of the profile; default to the internal name if none
|
||||
// is specified.
|
||||
$name = isset($details['name']) ? $details['name'] : $profile->getName();
|
||||
$description = isset($details['description']) ? $details['description'] : $name;
|
||||
$profiles[$profile->getName()] = $description;
|
||||
|
||||
if ($auto_select_distributions && !empty($details['distribution'])) {
|
||||
return [$profile->getName() => $description];
|
||||
}
|
||||
}
|
||||
return $profiles;
|
||||
}
|
||||
|
||||
}
|
76
web/core/lib/Drupal/Core/Command/QuickStartCommand.php
Normal file
76
web/core/lib/Drupal/Core/Command/QuickStartCommand.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Installs a Drupal site and starts a webserver for local testing/development.
|
||||
*
|
||||
* Wraps 'install' and 'server' commands.
|
||||
*
|
||||
* @internal
|
||||
* This command makes no guarantee of an API for Drupal extensions.
|
||||
*
|
||||
* @see \Drupal\Core\Command\InstallCommand
|
||||
* @see \Drupal\Core\Command\ServerCommand
|
||||
*/
|
||||
class QuickStartCommand extends Command {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure() {
|
||||
$this->setName('quick-start')
|
||||
->setDescription('Installs a Drupal site and runs a web server. This is not meant for production and might be too simple for custom development. It is a quick and easy way to get Drupal running.')
|
||||
->addArgument('install-profile', InputArgument::OPTIONAL, 'Install profile to install the site in.')
|
||||
->addOption('langcode', NULL, InputOption::VALUE_OPTIONAL, 'The language to install the site in. Defaults to en.', 'en')
|
||||
->addOption('site-name', NULL, InputOption::VALUE_OPTIONAL, 'Set the site name. Defaults to Drupal.', 'Drupal')
|
||||
->addOption('host', NULL, InputOption::VALUE_OPTIONAL, 'Provide a host for the server to run on. Defaults to 127.0.0.1.', '127.0.0.1')
|
||||
->addOption('port', NULL, InputOption::VALUE_OPTIONAL, 'Provide a port for the server to run on. Will be determined automatically if none supplied.')
|
||||
->addOption('suppress-login', 's', InputOption::VALUE_NONE, 'Disable opening a login URL in a browser.')
|
||||
->addUsage('demo_umami --langcode fr')
|
||||
->addUsage('standard --site-name QuickInstall --host localhost --port 8080')
|
||||
->addUsage('minimal --host my-site.com --port 80');
|
||||
|
||||
parent::configure();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$command = $this->getApplication()->find('install');
|
||||
|
||||
$arguments = [
|
||||
'command' => 'install',
|
||||
'install-profile' => $input->getArgument('install-profile'),
|
||||
'--langcode' => $input->getOption('langcode'),
|
||||
'--site-name' => $input->getOption('site-name'),
|
||||
];
|
||||
|
||||
$installInput = new ArrayInput($arguments);
|
||||
$returnCode = $command->run($installInput, $output);
|
||||
|
||||
if ($returnCode === 0) {
|
||||
$command = $this->getApplication()->find('server');
|
||||
$arguments = [
|
||||
'command' => 'server',
|
||||
'--host' => $input->getOption('host'),
|
||||
'--port' => $input->getOption('port'),
|
||||
];
|
||||
if ($input->getOption('suppress-login')) {
|
||||
$arguments['--suppress-login'] = TRUE;
|
||||
}
|
||||
$serverInput = new ArrayInput($arguments);
|
||||
$returnCode = $command->run($serverInput, $output);
|
||||
}
|
||||
return $returnCode;
|
||||
}
|
||||
|
||||
}
|
278
web/core/lib/Drupal/Core/Command/ServerCommand.php
Normal file
278
web/core/lib/Drupal/Core/Command/ServerCommand.php
Normal file
|
@ -0,0 +1,278 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Command;
|
||||
|
||||
use Drupal\Core\Database\ConnectionNotDefinedException;
|
||||
use Drupal\Core\DrupalKernel;
|
||||
use Drupal\Core\DrupalKernelInterface;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\user\Entity\User;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
use Symfony\Component\Process\PhpProcess;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
/**
|
||||
* Runs the PHP webserver for a Drupal site for local testing/development.
|
||||
*
|
||||
* @internal
|
||||
* This command makes no guarantee of an API for Drupal extensions.
|
||||
*/
|
||||
class ServerCommand extends Command {
|
||||
|
||||
/**
|
||||
* The class loader.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $classLoader;
|
||||
|
||||
/**
|
||||
* Constructs a new ServerCommand command.
|
||||
*
|
||||
* @param object $class_loader
|
||||
* The class loader.
|
||||
*/
|
||||
public function __construct($class_loader) {
|
||||
parent::__construct('server');
|
||||
$this->classLoader = $class_loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure() {
|
||||
$this->setDescription('Starts up a webserver for a site.')
|
||||
->addOption('host', NULL, InputOption::VALUE_OPTIONAL, 'Provide a host for the server to run on.', '127.0.0.1')
|
||||
->addOption('port', NULL, InputOption::VALUE_OPTIONAL, 'Provide a port for the server to run on. Will be determined automatically if none supplied.')
|
||||
->addOption('suppress-login', 's', InputOption::VALUE_NONE, 'Disable opening a login URL in a browser.')
|
||||
->addUsage('--host localhost --port 8080')
|
||||
->addUsage('--host my-site.com --port 80');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$host = $input->getOption('host');
|
||||
$port = $input->getOption('port');
|
||||
if (!$port) {
|
||||
$port = $this->findAvailablePort($host);
|
||||
}
|
||||
if (!$port) {
|
||||
$io->getErrorStyle()->error('Unable to automatically determine a port. Use the --port to hardcode an available port.');
|
||||
}
|
||||
|
||||
try {
|
||||
$kernel = $this->boot();
|
||||
}
|
||||
catch (ConnectionNotDefinedException $e) {
|
||||
$io->getErrorStyle()->error("No installation found. Use the 'install' command.");
|
||||
return 1;
|
||||
}
|
||||
return $this->start($host, $port, $kernel, $input, $io);
|
||||
}
|
||||
|
||||
/**
|
||||
* Boots up a Drupal environment.
|
||||
*
|
||||
* @return \Drupal\Core\DrupalKernelInterface
|
||||
* The Drupal kernel.
|
||||
*
|
||||
* @throws \Exception
|
||||
* Exception thrown if kernel does not boot.
|
||||
*/
|
||||
protected function boot() {
|
||||
$kernel = new DrupalKernel('prod', $this->classLoader, FALSE);
|
||||
$kernel::bootEnvironment();
|
||||
$kernel->setSitePath($this->getSitePath());
|
||||
Settings::initialize($kernel->getAppRoot(), $kernel->getSitePath(), $this->classLoader);
|
||||
$kernel->boot();
|
||||
// Some services require a request to work. For example, CommentManager.
|
||||
// This is needed as generating the URL fires up entity load hooks.
|
||||
$kernel->getContainer()
|
||||
->get('request_stack')
|
||||
->push(Request::createFromGlobals());
|
||||
|
||||
return $kernel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an available port.
|
||||
*
|
||||
* @param string $host
|
||||
* The host to find a port on.
|
||||
*
|
||||
* @return int|false
|
||||
* The available port or FALSE, if no available port found,
|
||||
*/
|
||||
protected function findAvailablePort($host) {
|
||||
$port = 8888;
|
||||
while ($port >= 8888 && $port <= 9999) {
|
||||
$connection = @fsockopen($host, $port);
|
||||
if (is_resource($connection)) {
|
||||
// Port is being used.
|
||||
fclose($connection);
|
||||
}
|
||||
else {
|
||||
// Port is available.
|
||||
return $port;
|
||||
}
|
||||
$port++;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a URL in your system default browser.
|
||||
*
|
||||
* @param string $url
|
||||
* The URL to browser to.
|
||||
* @param \Symfony\Component\Console\Style\SymfonyStyle $io
|
||||
* The IO.
|
||||
*/
|
||||
protected function openBrowser($url, SymfonyStyle $io) {
|
||||
$is_windows = defined('PHP_WINDOWS_VERSION_BUILD');
|
||||
if ($is_windows) {
|
||||
// Handle escaping ourselves.
|
||||
$cmd = 'start "web" "' . $url . '""';
|
||||
}
|
||||
else {
|
||||
$url = escapeshellarg($url);
|
||||
}
|
||||
|
||||
$is_linux = (new Process('which xdg-open'))->run();
|
||||
$is_osx = (new Process('which open'))->run();
|
||||
if ($is_linux === 0) {
|
||||
$cmd = 'xdg-open ' . $url;
|
||||
}
|
||||
elseif ($is_osx === 0) {
|
||||
$cmd = 'open ' . $url;
|
||||
}
|
||||
|
||||
if (empty($cmd)) {
|
||||
$io->getErrorStyle()
|
||||
->error('No suitable browser opening command found, open yourself: ' . $url);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($io->isVerbose()) {
|
||||
$io->writeln("<info>Browser command:</info> $cmd");
|
||||
}
|
||||
|
||||
// Need to escape double quotes in the command so the PHP will work.
|
||||
$cmd = str_replace('"', '\"', $cmd);
|
||||
// Sleep for 2 seconds before opening the browser. This allows the command
|
||||
// to start up the PHP built-in webserver in the meantime. We use a
|
||||
// PhpProcess so that Windows powershell users also get a browser opened
|
||||
// for them.
|
||||
$php = "<?php sleep(2); passthru(\"$cmd\"); ?>";
|
||||
$process = new PhpProcess($php);
|
||||
$process->start();
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a one time login URL for user 1.
|
||||
*
|
||||
* @return string
|
||||
* The one time login URL for user 1.
|
||||
*/
|
||||
protected function getOneTimeLoginUrl() {
|
||||
$user = User::load(1);
|
||||
\Drupal::moduleHandler()->load('user');
|
||||
return user_pass_reset_url($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts up a webserver with a running Drupal.
|
||||
*
|
||||
* @param string $host
|
||||
* The hostname of the webserver.
|
||||
* @param int $port
|
||||
* The port to start the webserver on.
|
||||
* @param \Drupal\Core\DrupalKernelInterface $kernel
|
||||
* The Drupal kernel.
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input
|
||||
* The input.
|
||||
* @param \Symfony\Component\Console\Style\SymfonyStyle $io
|
||||
* The IO.
|
||||
*
|
||||
* @return int
|
||||
* The exit status of the PHP in-built webserver command.
|
||||
*/
|
||||
protected function start($host, $port, DrupalKernelInterface $kernel, InputInterface $input, SymfonyStyle $io) {
|
||||
$finder = new PhpExecutableFinder();
|
||||
$binary = $finder->find();
|
||||
if ($binary === FALSE) {
|
||||
throw new \RuntimeException('Unable to find the PHP binary.');
|
||||
}
|
||||
|
||||
$io->writeln("<info>Drupal development server started:</info> <http://{$host}:{$port}>");
|
||||
$io->writeln('<info>This server is not meant for production use.</info>');
|
||||
$one_time_login = "http://$host:$port{$this->getOneTimeLoginUrl()}/login";
|
||||
$io->writeln("<info>One time login url:</info> <$one_time_login>");
|
||||
$io->writeln('Press Ctrl-C to quit the Drupal development server.');
|
||||
|
||||
if (!$input->getOption('suppress-login')) {
|
||||
if ($this->openBrowser("$one_time_login?destination=" . urlencode("/"), $io) === 1) {
|
||||
$io->error('Error while opening up a one time login URL');
|
||||
}
|
||||
}
|
||||
|
||||
// Use the Process object to construct an escaped command line.
|
||||
$process = new Process([
|
||||
$binary,
|
||||
'-S',
|
||||
$host . ':' . $port,
|
||||
'.ht.router.php',
|
||||
], $kernel->getAppRoot(), [], NULL, NULL);
|
||||
if ($io->isVerbose()) {
|
||||
$io->writeln("<info>Server command:</info> {$process->getCommandLine()}");
|
||||
}
|
||||
|
||||
// Carefully manage output so we can display output only in verbose mode.
|
||||
$descriptors = [];
|
||||
$descriptors[0] = STDIN;
|
||||
$descriptors[1] = ['pipe', 'w'];
|
||||
$descriptors[2] = ['pipe', 'w'];
|
||||
$server = proc_open($process->getCommandLine(), $descriptors, $pipes, $kernel->getAppRoot());
|
||||
if (is_resource($server)) {
|
||||
if ($io->isVerbose()) {
|
||||
// Write a blank line so that server output and the useful information are
|
||||
// visually separated.
|
||||
$io->writeln('');
|
||||
}
|
||||
$server_status = proc_get_status($server);
|
||||
while ($server_status['running']) {
|
||||
if ($io->isVerbose()) {
|
||||
fpassthru($pipes[2]);
|
||||
}
|
||||
sleep(1);
|
||||
$server_status = proc_get_status($server);
|
||||
}
|
||||
}
|
||||
return proc_close($server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the site path.
|
||||
*
|
||||
* Defaults to 'sites/default'. For testing purposes this can be overridden
|
||||
* using the DRUPAL_DEV_SITE_PATH environment variable.
|
||||
*
|
||||
* @return string
|
||||
* The site path to use.
|
||||
*/
|
||||
protected function getSitePath() {
|
||||
return getenv('DRUPAL_DEV_SITE_PATH') ?: 'sites/default';
|
||||
}
|
||||
|
||||
}
|
|
@ -6,6 +6,7 @@ use Drupal\Component\PhpStorage\FileStorage;
|
|||
use Composer\Script\Event;
|
||||
use Composer\Installer\PackageEvent;
|
||||
use Composer\Semver\Constraint\Constraint;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
|
||||
/**
|
||||
* Provides static functions for composer script events.
|
||||
|
@ -72,35 +73,42 @@ class Composer {
|
|||
* Add vendor classes to Composer's static classmap.
|
||||
*/
|
||||
public static function preAutoloadDump(Event $event) {
|
||||
// Get the configured vendor directory.
|
||||
$vendor_dir = $event->getComposer()->getConfig()->get('vendor-dir');
|
||||
|
||||
// We need the root package so we can add our classmaps to its loader.
|
||||
$package = $event->getComposer()->getPackage();
|
||||
// We need the local repository so that we can query and see if it's likely
|
||||
// that our files are present there.
|
||||
$repository = $event->getComposer()->getRepositoryManager()->getLocalRepository();
|
||||
// This is, essentially, a null constraint. We only care whether the package
|
||||
// is present in vendor/ yet, but findPackage() requires it.
|
||||
// is present in the vendor directory yet, but findPackage() requires it.
|
||||
$constraint = new Constraint('>', '');
|
||||
// It's possible that there is no classmap specified in a custom project
|
||||
// composer.json file. We need one so we can optimize lookup for some of our
|
||||
// dependencies.
|
||||
$autoload = $package->getAutoload();
|
||||
if (!isset($autoload['classmap'])) {
|
||||
$autoload['classmap'] = [];
|
||||
}
|
||||
// Check for our packages, and then optimize them if they're present.
|
||||
if ($repository->findPackage('symfony/http-foundation', $constraint)) {
|
||||
$autoload = $package->getAutoload();
|
||||
$autoload['classmap'] = array_merge($autoload['classmap'], [
|
||||
'vendor/symfony/http-foundation/Request.php',
|
||||
'vendor/symfony/http-foundation/ParameterBag.php',
|
||||
'vendor/symfony/http-foundation/FileBag.php',
|
||||
'vendor/symfony/http-foundation/ServerBag.php',
|
||||
'vendor/symfony/http-foundation/HeaderBag.php',
|
||||
$vendor_dir . '/symfony/http-foundation/Request.php',
|
||||
$vendor_dir . '/symfony/http-foundation/ParameterBag.php',
|
||||
$vendor_dir . '/symfony/http-foundation/FileBag.php',
|
||||
$vendor_dir . '/symfony/http-foundation/ServerBag.php',
|
||||
$vendor_dir . '/symfony/http-foundation/HeaderBag.php',
|
||||
]);
|
||||
$package->setAutoload($autoload);
|
||||
}
|
||||
if ($repository->findPackage('symfony/http-kernel', $constraint)) {
|
||||
$autoload = $package->getAutoload();
|
||||
$autoload['classmap'] = array_merge($autoload['classmap'], [
|
||||
'vendor/symfony/http-kernel/HttpKernel.php',
|
||||
'vendor/symfony/http-kernel/HttpKernelInterface.php',
|
||||
'vendor/symfony/http-kernel/TerminableInterface.php',
|
||||
$vendor_dir . '/symfony/http-kernel/HttpKernel.php',
|
||||
$vendor_dir . '/symfony/http-kernel/HttpKernelInterface.php',
|
||||
$vendor_dir . '/symfony/http-kernel/TerminableInterface.php',
|
||||
]);
|
||||
$package->setAutoload($autoload);
|
||||
}
|
||||
$package->setAutoload($autoload);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -136,6 +144,48 @@ EOT;
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires the drupal-phpunit-upgrade script event if necessary.
|
||||
*
|
||||
* @param \Composer\Script\Event $event
|
||||
*/
|
||||
public static function upgradePHPUnit(Event $event) {
|
||||
$repository = $event->getComposer()->getRepositoryManager()->getLocalRepository();
|
||||
// This is, essentially, a null constraint. We only care whether the package
|
||||
// is present in the vendor directory yet, but findPackage() requires it.
|
||||
$constraint = new Constraint('>', '');
|
||||
$phpunit_package = $repository->findPackage('phpunit/phpunit', $constraint);
|
||||
if (!$phpunit_package) {
|
||||
// There is nothing to do. The user is probably installing using the
|
||||
// --no-dev flag.
|
||||
return;
|
||||
}
|
||||
|
||||
// If the PHP version is 7.0 or above and PHPUnit is less than version 6
|
||||
// call the drupal-phpunit-upgrade script to upgrade PHPUnit.
|
||||
if (!static::upgradePHPUnitCheck($phpunit_package->getVersion())) {
|
||||
$event->getComposer()
|
||||
->getEventDispatcher()
|
||||
->dispatchScript('drupal-phpunit-upgrade');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if PHPUnit needs to be upgraded.
|
||||
*
|
||||
* This method is located in this file because it is possible that it is
|
||||
* called before the autoloader is available.
|
||||
*
|
||||
* @param string $phpunit_version
|
||||
* The PHPUnit version string.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the PHPUnit needs to be upgraded, FALSE if not.
|
||||
*/
|
||||
public static function upgradePHPUnitCheck($phpunit_version) {
|
||||
return !(version_compare(PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION, '7.0') >= 0 && version_compare($phpunit_version, '6.1') < 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove possibly problematic test files from vendored projects.
|
||||
*
|
||||
|
@ -220,6 +270,13 @@ EOT;
|
|||
return $package_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes Composer's timeout so that scripts can run indefinitely.
|
||||
*/
|
||||
public static function removeTimeout() {
|
||||
ProcessExecutor::setTimeout(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to remove directories and the files they contain.
|
||||
*
|
||||
|
|
|
@ -8,8 +8,9 @@ use Drupal\Core\Executable\ExecutableManagerInterface;
|
|||
use Drupal\Core\Executable\ExecutableInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\CategorizingPluginManagerTrait;
|
||||
use Drupal\Core\Plugin\Context\ContextAwarePluginManagerTrait;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
use Drupal\Core\Plugin\FilteredPluginManagerInterface;
|
||||
use Drupal\Core\Plugin\FilteredPluginManagerTrait;
|
||||
|
||||
/**
|
||||
* A plugin manager for condition plugins.
|
||||
|
@ -20,10 +21,10 @@ use Drupal\Core\Plugin\DefaultPluginManager;
|
|||
*
|
||||
* @ingroup plugin_api
|
||||
*/
|
||||
class ConditionManager extends DefaultPluginManager implements ExecutableManagerInterface, CategorizingPluginManagerInterface {
|
||||
class ConditionManager extends DefaultPluginManager implements ExecutableManagerInterface, CategorizingPluginManagerInterface, FilteredPluginManagerInterface {
|
||||
|
||||
use CategorizingPluginManagerTrait;
|
||||
use ContextAwarePluginManagerTrait;
|
||||
use FilteredPluginManagerTrait;
|
||||
|
||||
/**
|
||||
* Constructs a ConditionManager object.
|
||||
|
@ -43,6 +44,13 @@ class ConditionManager extends DefaultPluginManager implements ExecutableManager
|
|||
parent::__construct('Plugin/Condition', $namespaces, $module_handler, 'Drupal\Core\Condition\ConditionInterface', 'Drupal\Core\Condition\Annotation\Condition');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getType() {
|
||||
return 'condition';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -278,7 +278,7 @@ class CachedStorage implements StorageInterface, StorageCacheInterface {
|
|||
*/
|
||||
protected function getCacheKeys(array $names) {
|
||||
$prefix = $this->getCollectionPrefix();
|
||||
$cache_keys = array_map(function($name) use ($prefix) {
|
||||
$cache_keys = array_map(function ($name) use ($prefix) {
|
||||
return $prefix . $name;
|
||||
}, $names);
|
||||
|
||||
|
|
|
@ -219,6 +219,10 @@ class Config extends StorableConfigBase {
|
|||
}
|
||||
}
|
||||
|
||||
// Potentially configuration schema could have changed the underlying data's
|
||||
// types.
|
||||
$this->resetOverriddenData();
|
||||
|
||||
$this->storage->write($this->name, $this->data);
|
||||
if (!$this->isNew) {
|
||||
Cache::invalidateTags($this->getCacheTags());
|
||||
|
@ -226,9 +230,6 @@ class Config extends StorableConfigBase {
|
|||
$this->isNew = FALSE;
|
||||
$this->eventDispatcher->dispatch(ConfigEvents::SAVE, new ConfigCrudEvent($this));
|
||||
$this->originalData = $this->data;
|
||||
// Potentially configuration schema could have changed the underlying data's
|
||||
// types.
|
||||
$this->resetOverriddenData();
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -303,4 +304,42 @@ class Config extends StorableConfigBase {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if overrides are applied to a key for this configuration object.
|
||||
*
|
||||
* @param string $key
|
||||
* (optional) A string that maps to a key within the configuration data.
|
||||
* For instance in the following configuration array:
|
||||
* @code
|
||||
* array(
|
||||
* 'foo' => array(
|
||||
* 'bar' => 'baz',
|
||||
* ),
|
||||
* );
|
||||
* @endcode
|
||||
* A key of 'foo.bar' would map to the string 'baz'. However, a key of 'foo'
|
||||
* would map to the array('bar' => 'baz').
|
||||
* If not supplied TRUE will be returned if there are any overrides at all
|
||||
* for this configuration object.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if there are any overrides for the key, otherwise FALSE.
|
||||
*/
|
||||
public function hasOverrides($key = '') {
|
||||
if (empty($key)) {
|
||||
return !(empty($this->moduleOverrides) && empty($this->settingsOverrides));
|
||||
}
|
||||
else {
|
||||
$parts = explode('.', $key);
|
||||
$override_exists = FALSE;
|
||||
if (isset($this->moduleOverrides) && is_array($this->moduleOverrides)) {
|
||||
$override_exists = NestedArray::keyExists($this->moduleOverrides, $parts);
|
||||
}
|
||||
if (!$override_exists && isset($this->settingsOverrides) && is_array($this->settingsOverrides)) {
|
||||
$override_exists = NestedArray::keyExists($this->settingsOverrides, $parts);
|
||||
}
|
||||
return $override_exists;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use Drupal\Component\Render\MarkupInterface;
|
|||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
|
||||
use \Drupal\Core\DependencyInjection\DependencySerializationTrait;
|
||||
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
|
||||
|
||||
/**
|
||||
* Provides a base class for configuration objects with get/set support.
|
||||
|
|
|
@ -306,7 +306,7 @@ class ConfigFactory implements ConfigFactoryInterface, EventSubscriberInterface
|
|||
* An array of cache keys that match the provided config name.
|
||||
*/
|
||||
protected function getConfigCacheKeys($name) {
|
||||
return array_filter(array_keys($this->cache), function($key) use ($name) {
|
||||
return array_filter(array_keys($this->cache), function ($key) use ($name) {
|
||||
// Return TRUE if the key is the name or starts with the configuration
|
||||
// name plus the delimiter.
|
||||
return $key === $name || strpos($key, $name . ':') === 0;
|
||||
|
|
|
@ -405,6 +405,14 @@ class ConfigImporter {
|
|||
$module_list = array_reverse($module_list);
|
||||
$this->extensionChangelist['module']['install'] = array_intersect(array_keys($module_list), $install);
|
||||
|
||||
// If we're installing the install profile ensure it comes last. This will
|
||||
// occur when installing a site from configuration.
|
||||
$install_profile_key = array_search($new_extensions['profile'], $this->extensionChangelist['module']['install'], TRUE);
|
||||
if ($install_profile_key !== FALSE) {
|
||||
unset($this->extensionChangelist['module']['install'][$install_profile_key]);
|
||||
$this->extensionChangelist['module']['install'][] = $new_extensions['profile'];
|
||||
}
|
||||
|
||||
// Work out what themes to install and to uninstall.
|
||||
$this->extensionChangelist['theme']['install'] = array_keys(array_diff_key($new_extensions['theme'], $current_extensions['theme']));
|
||||
$this->extensionChangelist['theme']['uninstall'] = array_keys(array_diff_key($current_extensions['theme'], $new_extensions['theme']));
|
||||
|
@ -725,7 +733,8 @@ class ConfigImporter {
|
|||
}
|
||||
$this->eventDispatcher->dispatch(ConfigEvents::IMPORT_VALIDATE, new ConfigImporterEvent($this));
|
||||
if (count($this->getErrors())) {
|
||||
throw new ConfigImporterException('There were errors validating the config synchronization.');
|
||||
$errors = array_merge(['There were errors validating the config synchronization.'], $this->getErrors());
|
||||
throw new ConfigImporterException(implode(PHP_EOL, $errors));
|
||||
}
|
||||
else {
|
||||
$this->validated = TRUE;
|
||||
|
@ -788,9 +797,8 @@ class ConfigImporter {
|
|||
// services.
|
||||
$this->reInjectMe();
|
||||
// During a module install or uninstall the container is rebuilt and the
|
||||
// module handler is called from drupal_get_complete_schema(). This causes
|
||||
// the container's instance of the module handler not to have loaded all
|
||||
// the enabled modules.
|
||||
// module handler is called. This causes the container's instance of the
|
||||
// module handler not to have loaded all the enabled modules.
|
||||
$this->moduleHandler->loadAll();
|
||||
}
|
||||
if ($type == 'theme') {
|
||||
|
|
|
@ -3,9 +3,7 @@
|
|||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Config\Entity\ConfigDependencyManager;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityDependency;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
class ConfigInstaller implements ConfigInstallerInterface {
|
||||
|
@ -162,7 +160,10 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
|||
*/
|
||||
public function installOptionalConfig(StorageInterface $storage = NULL, $dependency = []) {
|
||||
$profile = $this->drupalGetProfile();
|
||||
$optional_profile_config = [];
|
||||
$enabled_extensions = $this->getEnabledExtensions();
|
||||
$existing_config = $this->getActiveStorages()->listAll();
|
||||
|
||||
// Create the storages to read configuration from.
|
||||
if (!$storage) {
|
||||
// Search the install profile's optional configuration too.
|
||||
$storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, TRUE, $this->installProfile);
|
||||
|
@ -173,7 +174,6 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
|||
// Creates a profile storage to search for overrides.
|
||||
$profile_install_path = $this->drupalGetPath('module', $profile) . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY;
|
||||
$profile_storage = new FileStorage($profile_install_path, StorageInterface::DEFAULT_COLLECTION);
|
||||
$optional_profile_config = $profile_storage->listAll();
|
||||
}
|
||||
else {
|
||||
// Profile has not been set yet. For example during the first steps of the
|
||||
|
@ -181,11 +181,18 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
|||
$profile_storage = NULL;
|
||||
}
|
||||
|
||||
$enabled_extensions = $this->getEnabledExtensions();
|
||||
$existing_config = $this->getActiveStorages()->listAll();
|
||||
// Build the list of possible configuration to create.
|
||||
$list = $storage->listAll();
|
||||
if ($profile_storage && !empty($dependency)) {
|
||||
// Only add the optional profile configuration into the list if we are
|
||||
// have a dependency to check. This ensures that optional profile
|
||||
// configuration is not unexpectedly re-created after being deleted.
|
||||
$list = array_unique(array_merge($list, $profile_storage->listAll()));
|
||||
}
|
||||
|
||||
$list = array_unique(array_merge($storage->listAll(), $optional_profile_config));
|
||||
$list = array_filter($list, function($config_name) use ($existing_config) {
|
||||
// Filter the list of configuration to only include configuration that
|
||||
// should be created.
|
||||
$list = array_filter($list, function ($config_name) use ($existing_config) {
|
||||
// Only list configuration that:
|
||||
// - does not already exist
|
||||
// - is a configuration entity (this also excludes config that has an
|
||||
|
@ -205,16 +212,19 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
|||
$dependency_manager = new ConfigDependencyManager();
|
||||
$dependency_manager->setData($config_to_create);
|
||||
$config_to_create = array_merge(array_flip($dependency_manager->sortAll()), $config_to_create);
|
||||
if (!empty($dependency)) {
|
||||
// In order to work out dependencies we need the full config graph.
|
||||
$dependency_manager->setData($this->getActiveStorages()->readMultiple($existing_config) + $config_to_create);
|
||||
$dependencies = $dependency_manager->getDependentEntities(key($dependency), reset($dependency));
|
||||
}
|
||||
|
||||
foreach ($config_to_create as $config_name => $data) {
|
||||
// Remove configuration where its dependencies cannot be met.
|
||||
$remove = !$this->validateDependencies($config_name, $data, $enabled_extensions, $all_config);
|
||||
// If $dependency is defined, remove configuration that does not have a
|
||||
// matching dependency.
|
||||
// Remove configuration that is not dependent on $dependency, if it is
|
||||
// defined.
|
||||
if (!$remove && !empty($dependency)) {
|
||||
// Create a light weight dependency object to check dependencies.
|
||||
$config_entity = new ConfigEntityDependency($config_name, $data);
|
||||
$remove = !$config_entity->hasDependency(key($dependency), reset($dependency));
|
||||
$remove = !isset($dependencies[$config_name]);
|
||||
}
|
||||
|
||||
if ($remove) {
|
||||
|
@ -225,6 +235,8 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
|||
unset($all_config[$config_name]);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the optional configuration if there is any left after filtering.
|
||||
if (!empty($config_to_create)) {
|
||||
$this->createConfiguration(StorageInterface::DEFAULT_COLLECTION, $config_to_create, TRUE);
|
||||
}
|
||||
|
@ -316,10 +328,11 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
|||
$entity_storage = $this->configManager
|
||||
->getEntityManager()
|
||||
->getStorage($entity_type);
|
||||
|
||||
$id = $entity_storage->getIDFromConfigName($name, $entity_storage->getEntityType()->getConfigPrefix());
|
||||
// It is possible that secondary writes can occur during configuration
|
||||
// creation. Updates of such configuration are allowed.
|
||||
if ($this->getActiveStorages($collection)->exists($name)) {
|
||||
$id = $entity_storage->getIDFromConfigName($name, $entity_storage->getEntityType()->getConfigPrefix());
|
||||
$entity = $entity_storage->load($id);
|
||||
$entity = $entity_storage->updateFromStorageRecord($entity, $new_config->get());
|
||||
}
|
||||
|
@ -328,6 +341,9 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
|||
}
|
||||
if ($entity->isInstallable()) {
|
||||
$entity->trustData()->save();
|
||||
if ($id !== $entity->id()) {
|
||||
trigger_error(sprintf('The configuration name "%s" does not match the ID "%s"', $name, $entity->id()), E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -344,7 +360,7 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
|||
// Only install configuration for enabled extensions.
|
||||
$enabled_extensions = $this->getEnabledExtensions();
|
||||
$config_to_install = array_filter($storage->listAll(), function ($config_name) use ($enabled_extensions) {
|
||||
$provider = Unicode::substr($config_name, 0, strpos($config_name, '.'));
|
||||
$provider = mb_substr($config_name, 0, strpos($config_name, '.'));
|
||||
return in_array($provider, $enabled_extensions);
|
||||
});
|
||||
if (!empty($config_to_install)) {
|
||||
|
|
|
@ -43,7 +43,8 @@ interface ConfigInstallerInterface {
|
|||
* @param \Drupal\Core\Config\StorageInterface $storage
|
||||
* (optional) The configuration storage to search for optional
|
||||
* configuration. If not provided, all enabled extension's optional
|
||||
* configuration directories will be searched.
|
||||
* configuration directories including the install profile's will be
|
||||
* searched.
|
||||
* @param array $dependency
|
||||
* (optional) If set, ensures that the configuration being installed has
|
||||
* this dependency. The format is dependency type as the key ('module',
|
||||
|
|
|
@ -233,7 +233,7 @@ class ConfigManager implements ConfigManagerInterface {
|
|||
// dependencies on the config entity classes. Assume data with UUID is a
|
||||
// config entity. Only configuration entities can be depended on so we can
|
||||
// ignore everything else.
|
||||
$data = array_map(function($config) {
|
||||
$data = array_map(function ($config) {
|
||||
$data = $config->get();
|
||||
if (isset($data['uuid'])) {
|
||||
return $data;
|
||||
|
@ -292,78 +292,85 @@ class ConfigManager implements ConfigManagerInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfigEntitiesToChangeOnDependencyRemoval($type, array $names, $dry_run = TRUE) {
|
||||
// Determine the current list of dependent configuration entities and set up
|
||||
// initial values.
|
||||
$dependency_manager = $this->getConfigDependencyManager();
|
||||
$dependents = $this->findConfigEntityDependentsAsEntities($type, $names, $dependency_manager);
|
||||
$original_dependencies = $dependents;
|
||||
$delete_uuids = [];
|
||||
|
||||
// Store the list of dependents in three separate variables. This allows us
|
||||
// to determine how the dependency graph changes as entities are fixed by
|
||||
// calling the onDependencyRemoval() method.
|
||||
|
||||
// The list of original dependents on $names. This list never changes.
|
||||
$original_dependents = $this->findConfigEntityDependentsAsEntities($type, $names, $dependency_manager);
|
||||
|
||||
// The current list of dependents on $names. This list is recalculated when
|
||||
// calling an entity's onDependencyRemoval() method results in the entity
|
||||
// changing. This list is passed to each entity's onDependencyRemoval()
|
||||
// method as the list of affected entities.
|
||||
$current_dependents = $original_dependents;
|
||||
|
||||
// The list of dependents to process. This list changes as entities are
|
||||
// processed and are either fixed or deleted.
|
||||
$dependents_to_process = $original_dependents;
|
||||
|
||||
// Initialize other variables.
|
||||
$affected_uuids = [];
|
||||
$return = [
|
||||
'update' => [],
|
||||
'delete' => [],
|
||||
'unchanged' => [],
|
||||
];
|
||||
|
||||
// Create a map of UUIDs to $original_dependencies key so that we can remove
|
||||
// fixed dependencies.
|
||||
$uuid_map = [];
|
||||
foreach ($original_dependencies as $key => $entity) {
|
||||
$uuid_map[$entity->uuid()] = $key;
|
||||
}
|
||||
|
||||
// Try to fix any dependencies and find out what will happen to the
|
||||
// dependency graph. Entities are processed in the order of most dependent
|
||||
// first. For example, this ensures that Menu UI third party dependencies on
|
||||
// node types are fixed before processing the node type's other
|
||||
// dependencies.
|
||||
while ($dependent = array_pop($dependents)) {
|
||||
// Try to fix the dependents and find out what will happen to the dependency
|
||||
// graph. Entities are processed in the order of most dependent first. For
|
||||
// example, this ensures that Menu UI third party dependencies on node types
|
||||
// are fixed before processing the node type's other dependents.
|
||||
while ($dependent = array_pop($dependents_to_process)) {
|
||||
/** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $dependent */
|
||||
if ($dry_run) {
|
||||
// Clone the entity so any changes do not change any static caches.
|
||||
$dependent = clone $dependent;
|
||||
}
|
||||
$fixed = FALSE;
|
||||
if ($this->callOnDependencyRemoval($dependent, $original_dependencies, $type, $names)) {
|
||||
if ($this->callOnDependencyRemoval($dependent, $current_dependents, $type, $names)) {
|
||||
// Recalculate dependencies and update the dependency graph data.
|
||||
$dependent->calculateDependencies();
|
||||
$dependency_manager->updateData($dependent->getConfigDependencyName(), $dependent->getDependencies());
|
||||
// Based on the updated data rebuild the list of dependents. This will
|
||||
// remove entities that are no longer dependent after the recalculation.
|
||||
$dependents = $this->findConfigEntityDependentsAsEntities($type, $names, $dependency_manager);
|
||||
// Remove any entities that we've already marked for deletion.
|
||||
$dependents = array_filter($dependents, function ($dependent) use ($delete_uuids) {
|
||||
return !in_array($dependent->uuid(), $delete_uuids);
|
||||
// Based on the updated data rebuild the list of current dependents.
|
||||
// This will remove entities that are no longer dependent after the
|
||||
// recalculation.
|
||||
$current_dependents = $this->findConfigEntityDependentsAsEntities($type, $names, $dependency_manager);
|
||||
// Rebuild the list of entities that we need to process using the new
|
||||
// list of current dependents and removing any entities that we've
|
||||
// already processed.
|
||||
$dependents_to_process = array_filter($current_dependents, function ($current_dependent) use ($affected_uuids) {
|
||||
return !in_array($current_dependent->uuid(), $affected_uuids);
|
||||
});
|
||||
// Ensure that the dependency has actually been fixed. It is possible
|
||||
// that the dependent has multiple dependencies that cause it to be in
|
||||
// the dependency chain.
|
||||
// Ensure that the dependent has actually been fixed. It is possible
|
||||
// that other dependencies cause it to still be in the list.
|
||||
$fixed = TRUE;
|
||||
foreach ($dependents as $key => $entity) {
|
||||
foreach ($dependents_to_process as $key => $entity) {
|
||||
if ($entity->uuid() == $dependent->uuid()) {
|
||||
$fixed = FALSE;
|
||||
unset($dependents[$key]);
|
||||
unset($dependents_to_process[$key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($fixed) {
|
||||
// Remove the fixed dependency from the list of original dependencies.
|
||||
unset($original_dependencies[$uuid_map[$dependent->uuid()]]);
|
||||
$affected_uuids[] = $dependent->uuid();
|
||||
$return['update'][] = $dependent;
|
||||
}
|
||||
}
|
||||
// If the entity cannot be fixed then it has to be deleted.
|
||||
if (!$fixed) {
|
||||
$delete_uuids[] = $dependent->uuid();
|
||||
$affected_uuids[] = $dependent->uuid();
|
||||
// Deletes should occur in the order of the least dependent first. For
|
||||
// example, this ensures that fields are removed before field storages.
|
||||
array_unshift($return['delete'], $dependent);
|
||||
}
|
||||
}
|
||||
// Use the lists of UUIDs to filter the original list to work out which
|
||||
// configuration entities are unchanged.
|
||||
$return['unchanged'] = array_filter($original_dependencies, function ($dependent) use ($delete_uuids) {
|
||||
return !(in_array($dependent->uuid(), $delete_uuids));
|
||||
// Use the list of affected UUIDs to filter the original list to work out
|
||||
// which configuration entities are unchanged.
|
||||
$return['unchanged'] = array_filter($original_dependents, function ($dependent) use ($affected_uuids) {
|
||||
return !(in_array($dependent->uuid(), $affected_uuids));
|
||||
});
|
||||
|
||||
return $return;
|
||||
|
@ -452,7 +459,9 @@ class ConfigManager implements ConfigManagerInterface {
|
|||
// Key the entity arrays by config dependency name to make searching easy.
|
||||
foreach (['config', 'content'] as $dependency_type) {
|
||||
$affected_dependencies[$dependency_type] = array_combine(
|
||||
array_map(function ($entity) { return $entity->getConfigDependencyName(); }, $affected_dependencies[$dependency_type]),
|
||||
array_map(function ($entity) {
|
||||
return $entity->getConfigDependencyName();
|
||||
}, $affected_dependencies[$dependency_type]),
|
||||
$affected_dependencies[$dependency_type]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -159,7 +159,7 @@ class DatabaseStorage implements StorageInterface {
|
|||
* @throws \Drupal\Core\Config\StorageException
|
||||
* If a database error occurs.
|
||||
*/
|
||||
protected function ensureTableExists() {
|
||||
protected function ensureTableExists() {
|
||||
try {
|
||||
if (!$this->connection->schema()->tableExists($this->table)) {
|
||||
$this->connection->schema()->createTable($this->table, static::schemaDefinition());
|
||||
|
@ -180,6 +180,8 @@ class DatabaseStorage implements StorageInterface {
|
|||
|
||||
/**
|
||||
* Defines the schema for the configuration table.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected static function schemaDefinition() {
|
||||
$schema = [
|
||||
|
@ -226,7 +228,6 @@ class DatabaseStorage implements StorageInterface {
|
|||
->execute();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Config\StorageInterface::rename().
|
||||
*
|
||||
|
@ -318,7 +319,8 @@ class DatabaseStorage implements StorageInterface {
|
|||
public function getAllCollectionNames() {
|
||||
try {
|
||||
return $this->connection->query('SELECT DISTINCT collection FROM {' . $this->connection->escapeTable($this->table) . '} WHERE collection <> :collection ORDER by collection', [
|
||||
':collection' => StorageInterface::DEFAULT_COLLECTION]
|
||||
':collection' => StorageInterface::DEFAULT_COLLECTION,
|
||||
]
|
||||
)->fetchCol();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
namespace Drupal\Core\Config\Development;
|
||||
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Core\Config\ConfigCrudEvent;
|
||||
use Drupal\Core\Config\ConfigEvents;
|
||||
use Drupal\Core\Config\Schema\SchemaCheckTrait;
|
||||
|
@ -90,7 +90,7 @@ class ConfigSchemaChecker implements EventSubscriberInterface {
|
|||
elseif (is_array($errors)) {
|
||||
$text_errors = [];
|
||||
foreach ($errors as $key => $error) {
|
||||
$text_errors[] = SafeMarkup::format('@key @error', ['@key' => $key, '@error' => $error]);
|
||||
$text_errors[] = new FormattableMarkup('@key @error', ['@key' => $key, '@error' => $error]);
|
||||
}
|
||||
throw new SchemaIncompleteException("Schema errors for $name with the following errors: " . implode(', ', $text_errors));
|
||||
}
|
||||
|
|
|
@ -267,18 +267,13 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
|
|||
/** @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type */
|
||||
$entity_type = $this->getEntityType();
|
||||
|
||||
$properties_to_export = $entity_type->getPropertiesToExport();
|
||||
if (empty($properties_to_export)) {
|
||||
$config_name = $entity_type->getConfigPrefix() . '.' . $this->id();
|
||||
$definition = $this->getTypedConfig()->getDefinition($config_name);
|
||||
if (!isset($definition['mapping'])) {
|
||||
throw new SchemaIncompleteException("Incomplete or missing schema for $config_name");
|
||||
}
|
||||
$properties_to_export = array_combine(array_keys($definition['mapping']), array_keys($definition['mapping']));
|
||||
}
|
||||
|
||||
$id_key = $entity_type->getKey('id');
|
||||
foreach ($properties_to_export as $property_name => $export_name) {
|
||||
$property_names = $entity_type->getPropertiesToExport($this->id());
|
||||
if (empty($property_names)) {
|
||||
$config_name = $entity_type->getConfigPrefix() . '.' . $this->id();
|
||||
throw new SchemaIncompleteException("Incomplete or missing schema for $config_name");
|
||||
}
|
||||
foreach ($property_names as $property_name => $export_name) {
|
||||
// Special handling for IDs so that computed compound IDs work.
|
||||
// @see \Drupal\Core\Entity\EntityDisplayBase::id()
|
||||
if ($property_name == $id_key) {
|
||||
|
|
|
@ -189,7 +189,7 @@ interface ConfigEntityInterface extends EntityInterface, ThirdPartySettingsInter
|
|||
* For example, a default view might not be installable if the base table
|
||||
* doesn't exist.
|
||||
*
|
||||
* @retun bool
|
||||
* @return bool
|
||||
* TRUE if the entity is installable, FALSE otherwise.
|
||||
*/
|
||||
public function isInstallable();
|
||||
|
|
|
@ -37,14 +37,14 @@ class ConfigEntityListBuilder extends EntityListBuilder {
|
|||
$operations['enable'] = [
|
||||
'title' => t('Enable'),
|
||||
'weight' => -10,
|
||||
'url' => $entity->urlInfo('enable'),
|
||||
'url' => $this->ensureDestination($entity->toUrl('enable')),
|
||||
];
|
||||
}
|
||||
elseif ($entity->hasLinkTemplate('disable')) {
|
||||
$operations['disable'] = [
|
||||
'title' => t('Disable'),
|
||||
'weight' => 40,
|
||||
'url' => $entity->urlInfo('disable'),
|
||||
'url' => $this->ensureDestination($entity->toUrl('disable')),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace Drupal\Core\Config\Entity;
|
||||
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Config\ConfigImporterException;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
@ -104,9 +105,11 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
|
|||
* The UUID service.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface|null $memory_cache
|
||||
* The memory cache backend.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager) {
|
||||
parent::__construct($entity_type);
|
||||
public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, MemoryCacheInterface $memory_cache = NULL) {
|
||||
parent::__construct($entity_type, $memory_cache);
|
||||
|
||||
$this->configFactory = $config_factory;
|
||||
$this->uuidService = $uuid_service;
|
||||
|
@ -121,7 +124,8 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
|
|||
$entity_type,
|
||||
$container->get('config.factory'),
|
||||
$container->get('uuid'),
|
||||
$container->get('language_manager')
|
||||
$container->get('language_manager'),
|
||||
$container->get('entity.memory_cache')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -317,43 +321,17 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets entities from the static cache.
|
||||
*
|
||||
* @param array $ids
|
||||
* If not empty, return entities that match these IDs.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface[]
|
||||
* Array of entities from the entity cache.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getFromStaticCache(array $ids) {
|
||||
$entities = [];
|
||||
// Load any available entities from the internal cache.
|
||||
if ($this->entityType->isStaticallyCacheable() && !empty($this->entities)) {
|
||||
$config_overrides_key = $this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys());
|
||||
foreach ($ids as $id) {
|
||||
if (!empty($this->entities[$id])) {
|
||||
if (isset($this->entities[$id][$config_overrides_key])) {
|
||||
$entities[$id] = $this->entities[$id][$config_overrides_key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $entities;
|
||||
public function hasData() {
|
||||
return (bool) $this->configFactory->listAll($this->getPrefix());
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores entities in the static entity cache.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface[] $entities
|
||||
* Entities to store in the cache.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setStaticCache(array $entities) {
|
||||
if ($this->entityType->isStaticallyCacheable()) {
|
||||
$config_overrides_key = $this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys());
|
||||
foreach ($entities as $id => $entity) {
|
||||
$this->entities[$id][$config_overrides_key] = $entity;
|
||||
}
|
||||
}
|
||||
protected function buildCacheId($id) {
|
||||
return parent::buildCacheId($id) . ':' . ($this->overrideFree ? '' : implode(':', $this->configFactory->getCacheKeys()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -438,7 +416,7 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
|
|||
* @param bool $is_syncing
|
||||
* Is the configuration entity being created as part of a config sync.
|
||||
*
|
||||
* @return ConfigEntityInterface
|
||||
* @return \Drupal\Core\Config\ConfigEntityInterface
|
||||
* The configuration entity.
|
||||
*
|
||||
* @see \Drupal\Core\Config\Entity\ConfigEntityStorageInterface::createFromStorageRecord()
|
||||
|
@ -472,9 +450,27 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
|
|||
$data = $this->mapFromStorageRecords([$values]);
|
||||
$updated_entity = current($data);
|
||||
|
||||
foreach (array_keys($values) as $property) {
|
||||
$value = $updated_entity->get($property);
|
||||
$entity->set($property, $value);
|
||||
/** @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type */
|
||||
$entity_type = $this->getEntityType();
|
||||
$id_key = $entity_type->getKey('id');
|
||||
$properties = $entity_type->getPropertiesToExport($updated_entity->get($id_key));
|
||||
|
||||
if (empty($properties)) {
|
||||
// Fallback to using the provided values. If the properties cannot be
|
||||
// determined for the config entity type annotation or configuration
|
||||
// schema.
|
||||
$properties = array_keys($values);
|
||||
}
|
||||
foreach ($properties as $property) {
|
||||
if ($property === $this->uuidKey) {
|
||||
// During an update the UUID field should not be copied. Under regular
|
||||
// circumstances the values will be equal. If configuration is written
|
||||
// twice during configuration install the updated entity will not have a
|
||||
// UUID.
|
||||
// @see \Drupal\Core\Config\ConfigInstaller::createConfiguration()
|
||||
continue;
|
||||
}
|
||||
$entity->set($property, $updated_entity->get($property));
|
||||
}
|
||||
|
||||
return $entity;
|
||||
|
|
|
@ -142,30 +142,40 @@ class ConfigEntityType extends EntityType implements ConfigEntityTypeInterface {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPropertiesToExport() {
|
||||
if (!empty($this->config_export)) {
|
||||
if (empty($this->mergedConfigExport)) {
|
||||
// Always add default properties to be exported.
|
||||
$this->mergedConfigExport = [
|
||||
'uuid' => 'uuid',
|
||||
'langcode' => 'langcode',
|
||||
'status' => 'status',
|
||||
'dependencies' => 'dependencies',
|
||||
'third_party_settings' => 'third_party_settings',
|
||||
'_core' => '_core',
|
||||
];
|
||||
foreach ($this->config_export as $property => $name) {
|
||||
if (is_numeric($property)) {
|
||||
$this->mergedConfigExport[$name] = $name;
|
||||
}
|
||||
else {
|
||||
$this->mergedConfigExport[$property] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
public function getPropertiesToExport($id = NULL) {
|
||||
if (!empty($this->mergedConfigExport)) {
|
||||
return $this->mergedConfigExport;
|
||||
}
|
||||
return NULL;
|
||||
if (!empty($this->config_export)) {
|
||||
// Always add default properties to be exported.
|
||||
$this->mergedConfigExport = [
|
||||
'uuid' => 'uuid',
|
||||
'langcode' => 'langcode',
|
||||
'status' => 'status',
|
||||
'dependencies' => 'dependencies',
|
||||
'third_party_settings' => 'third_party_settings',
|
||||
'_core' => '_core',
|
||||
];
|
||||
foreach ($this->config_export as $property => $name) {
|
||||
if (is_numeric($property)) {
|
||||
$this->mergedConfigExport[$name] = $name;
|
||||
}
|
||||
else {
|
||||
$this->mergedConfigExport[$property] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// @todo https://www.drupal.org/project/drupal/issues/2949021 Deprecate
|
||||
// fallback to schema.
|
||||
$config_name = $this->getConfigPrefix() . '.' . $id;
|
||||
$definition = \Drupal::service('config.typed')->getDefinition($config_name);
|
||||
if (!isset($definition['mapping'])) {
|
||||
return NULL;
|
||||
}
|
||||
$this->mergedConfigExport = array_combine(array_keys($definition['mapping']), array_keys($definition['mapping']));
|
||||
}
|
||||
return $this->mergedConfigExport;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -65,11 +65,18 @@ interface ConfigEntityTypeInterface extends EntityTypeInterface {
|
|||
/**
|
||||
* Gets the config entity properties to export if declared on the annotation.
|
||||
*
|
||||
* Falls back to determining the properties using configuration schema, if the
|
||||
* config entity properties are not declared.
|
||||
*
|
||||
* @param string $id
|
||||
* The ID of the configuration entity. Used when checking schema instead of
|
||||
* the annotation.
|
||||
*
|
||||
* @return array|null
|
||||
* The properties to export or NULL if they can not be determine from the
|
||||
* config entity type annotation.
|
||||
* config entity type annotation or the schema.
|
||||
*/
|
||||
public function getPropertiesToExport();
|
||||
public function getPropertiesToExport($id = NULL);
|
||||
|
||||
/**
|
||||
* Gets the keys that are available for fast lookup.
|
||||
|
|
119
web/core/lib/Drupal/Core/Config/Entity/ConfigEntityUpdater.php
Normal file
119
web/core/lib/Drupal/Core/Config/Entity/ConfigEntityUpdater.php
Normal file
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Config\Entity;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* A utility class to make updating configuration entities simple.
|
||||
*
|
||||
* Use this in a post update function like so:
|
||||
* @code
|
||||
* // Update the dependencies of all Vocabulary configuration entities.
|
||||
* \Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'taxonomy_vocabulary');
|
||||
* @endcode
|
||||
*
|
||||
* The number of entities processed in each batch is determined by the
|
||||
* 'entity_update_batch_size' setting.
|
||||
*
|
||||
* @see default.settings.php
|
||||
*/
|
||||
class ConfigEntityUpdater implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The number of entities to process in each batch.
|
||||
* @var int
|
||||
*/
|
||||
protected $batchSize;
|
||||
|
||||
/**
|
||||
* ConfigEntityUpdater constructor.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param int $batch_size
|
||||
* The number of entities to process in each batch.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, $batch_size) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->batchSize = $batch_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('settings')->get('entity_update_batch_size', 50)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates configuration entities as part of a Drupal update.
|
||||
*
|
||||
* @param array $sandbox
|
||||
* Stores information for batch updates.
|
||||
* @param string $entity_type_id
|
||||
* The configuration entity type ID. For example, 'view' or 'vocabulary'.
|
||||
* @param callable $callback
|
||||
* (optional) A callback to determine if a configuration entity should be
|
||||
* saved. The callback will be passed each entity of the provided type that
|
||||
* exists. The callback should not save an entity itself. Return TRUE to
|
||||
* save an entity. The callback can make changes to an entity. Note that all
|
||||
* changes should comply with schema as an entity's data will not be
|
||||
* validated against schema on save to avoid unexpected errors. If a
|
||||
* callback is not provided, the default behaviour is to update the
|
||||
* dependencies if required.
|
||||
*
|
||||
* @see hook_post_update_NAME()
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown when the provided entity type ID is not a configuration entity
|
||||
* type.
|
||||
*/
|
||||
public function update(array &$sandbox, $entity_type_id, callable $callback = NULL) {
|
||||
$storage = $this->entityTypeManager->getStorage($entity_type_id);
|
||||
$sandbox_key = 'config_entity_updater:' . $entity_type_id;
|
||||
if (!isset($sandbox[$sandbox_key])) {
|
||||
$entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
|
||||
if (!($entity_type instanceof ConfigEntityTypeInterface)) {
|
||||
throw new \InvalidArgumentException("The provided entity type ID '$entity_type_id' is not a configuration entity type");
|
||||
}
|
||||
$sandbox[$sandbox_key]['entities'] = $storage->getQuery()->accessCheck(FALSE)->execute();
|
||||
$sandbox[$sandbox_key]['count'] = count($sandbox[$sandbox_key]['entities']);
|
||||
}
|
||||
|
||||
// The default behaviour is to fix dependencies.
|
||||
if ($callback === NULL) {
|
||||
$callback = function ($entity) {
|
||||
/** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */
|
||||
$original_dependencies = $entity->getDependencies();
|
||||
return $original_dependencies !== $entity->calculateDependencies()->getDependencies();
|
||||
};
|
||||
}
|
||||
|
||||
/** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */
|
||||
$entities = $storage->loadMultiple(array_splice($sandbox[$sandbox_key]['entities'], 0, $this->batchSize));
|
||||
foreach ($entities as $entity) {
|
||||
if (call_user_func($callback, $entity)) {
|
||||
$entity->trustData();
|
||||
$entity->save();
|
||||
}
|
||||
}
|
||||
|
||||
$sandbox['#finished'] = empty($sandbox[$sandbox_key]['entities']) ? 1 : ($sandbox[$sandbox_key]['count'] - count($sandbox[$sandbox_key]['entities'])) / $sandbox[$sandbox_key]['count'];
|
||||
}
|
||||
|
||||
}
|
|
@ -107,7 +107,7 @@ abstract class DraggableListBuilder extends ConfigEntityListBuilder implements F
|
|||
$form[$this->entitiesKey] = [
|
||||
'#type' => 'table',
|
||||
'#header' => $this->buildHeader(),
|
||||
'#empty' => t('There is no @label yet.', ['@label' => $this->entityType->getLabel()]),
|
||||
'#empty' => t('There are no @label yet.', ['@label' => $this->entityType->getPluralLabel()]),
|
||||
'#tabledrag' => [
|
||||
[
|
||||
'action' => 'order',
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Drupal\Core\Config\Entity\Query;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Entity\Query\ConditionBase;
|
||||
use Drupal\Core\Entity\Query\ConditionInterface;
|
||||
use Drupal\Core\Entity\Query\QueryException;
|
||||
|
@ -32,10 +31,10 @@ class Condition extends ConditionBase {
|
|||
|
||||
// Lowercase condition value(s) for case-insensitive matches.
|
||||
if (is_array($condition['value'])) {
|
||||
$condition['value'] = array_map('Drupal\Component\Utility\Unicode::strtolower', $condition['value']);
|
||||
$condition['value'] = array_map('mb_strtolower', $condition['value']);
|
||||
}
|
||||
elseif (!is_bool($condition['value'])) {
|
||||
$condition['value'] = Unicode::strtolower($condition['value']);
|
||||
$condition['value'] = mb_strtolower($condition['value']);
|
||||
}
|
||||
|
||||
$single_conditions[] = $condition;
|
||||
|
@ -50,7 +49,7 @@ class Condition extends ConditionBase {
|
|||
// matter and this config object does not match.
|
||||
// If OR and it is matching, then the rest of conditions do not
|
||||
// matter and this config object does match.
|
||||
if ($and != $match ) {
|
||||
if ($and != $match) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -154,10 +153,17 @@ class Condition extends ConditionBase {
|
|||
* TRUE when matches else FALSE.
|
||||
*/
|
||||
protected function match(array $condition, $value) {
|
||||
// "IS NULL" and "IS NOT NULL" conditions can also deal with array values,
|
||||
// so we return early for them to avoid problems.
|
||||
if (in_array($condition['operator'], ['IS NULL', 'IS NOT NULL'], TRUE)) {
|
||||
$should_be_set = $condition['operator'] === 'IS NOT NULL';
|
||||
return $should_be_set === isset($value);
|
||||
}
|
||||
|
||||
if (isset($value)) {
|
||||
// We always want a case-insensitive match.
|
||||
if (!is_bool($value)) {
|
||||
$value = Unicode::strtolower($value);
|
||||
$value = mb_strtolower($value);
|
||||
}
|
||||
|
||||
switch ($condition['operator']) {
|
||||
|
@ -183,15 +189,11 @@ class Condition extends ConditionBase {
|
|||
return strpos($value, $condition['value']) !== FALSE;
|
||||
case 'ENDS_WITH':
|
||||
return substr($value, -strlen($condition['value'])) === (string) $condition['value'];
|
||||
case 'IS NOT NULL':
|
||||
return TRUE;
|
||||
case 'IS NULL':
|
||||
return FALSE;
|
||||
default:
|
||||
throw new QueryException('Invalid condition operator.');
|
||||
}
|
||||
}
|
||||
return $condition['operator'] === 'IS NULL';
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ class Query extends QueryBase implements QueryInterface {
|
|||
foreach ($this->sort as $sort) {
|
||||
$direction = $sort['direction'] == 'ASC' ? -1 : 1;
|
||||
$field = $sort['field'];
|
||||
uasort($result, function($a, $b) use ($field, $direction) {
|
||||
uasort($result, function ($a, $b) use ($field, $direction) {
|
||||
return ($a[$field] <= $b[$field]) ? $direction : -$direction;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ class QueryFactory implements QueryFactoryInterface, EventSubscriberInterface {
|
|||
/**
|
||||
* The config factory used by the config entity query.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface;
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
|
@ -219,7 +219,7 @@ class QueryFactory implements QueryFactoryInterface, EventSubscriberInterface {
|
|||
/**
|
||||
* Updates configuration entity in the key store.
|
||||
*
|
||||
* @param ConfigCrudEvent $event
|
||||
* @param \Drupal\Core\Config\ConfigCrudEvent $event
|
||||
* The configuration event.
|
||||
*/
|
||||
public function onConfigSave(ConfigCrudEvent $event) {
|
||||
|
|
|
@ -41,7 +41,6 @@ interface ThirdPartySettingsInterface {
|
|||
*/
|
||||
public function getThirdPartySetting($module, $key, $default = NULL);
|
||||
|
||||
|
||||
/**
|
||||
* Gets all third-party settings of a given module.
|
||||
*
|
||||
|
|
|
@ -46,7 +46,7 @@ class FileStorage implements StorageInterface {
|
|||
$this->collection = $collection;
|
||||
|
||||
// Use a NULL File Cache backend by default. This will ensure only the
|
||||
// internal statc caching of FileCache is used and thus avoids blowing up
|
||||
// internal static caching of FileCache is used and thus avoids blowing up
|
||||
// the APCu cache.
|
||||
$this->fileCache = FileCacheFactory::get('config', ['cache_backend_class' => NULL]);
|
||||
}
|
||||
|
@ -279,6 +279,9 @@ class FileStorage implements StorageInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAllCollectionNames() {
|
||||
if (!is_dir($this->directory)) {
|
||||
return [];
|
||||
}
|
||||
$collections = $this->getAllCollectionNamesHelper($this->directory);
|
||||
sort($collections);
|
||||
return $collections;
|
||||
|
@ -305,7 +308,8 @@ class FileStorage implements StorageInterface {
|
|||
* @param string $directory
|
||||
* The directory to check for sub directories. This allows this
|
||||
* function to be used recursively to discover all the collections in the
|
||||
* storage.
|
||||
* storage. It is the responsibility of the caller to ensure the directory
|
||||
* exists.
|
||||
*
|
||||
* @return array
|
||||
* A list of collection names contained within the provided directory.
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Config\Importer;
|
||||
|
||||
use Drupal\Core\Config\ConfigImporter;
|
||||
|
||||
/**
|
||||
* Methods for running the ConfigImporter in a batch.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigImporter
|
||||
*/
|
||||
class ConfigImporterBatch {
|
||||
|
||||
/**
|
||||
* Processes the config import batch and persists the importer.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigImporter $config_importer
|
||||
* The batch config importer object to persist.
|
||||
* @param string $sync_step
|
||||
* The synchronization step to do.
|
||||
* @param array $context
|
||||
* The batch context.
|
||||
*/
|
||||
public static function process(ConfigImporter $config_importer, $sync_step, &$context) {
|
||||
if (!isset($context['sandbox']['config_importer'])) {
|
||||
$context['sandbox']['config_importer'] = $config_importer;
|
||||
}
|
||||
|
||||
$config_importer = $context['sandbox']['config_importer'];
|
||||
$config_importer->doSyncStep($sync_step, $context);
|
||||
if ($errors = $config_importer->getErrors()) {
|
||||
if (!isset($context['results']['errors'])) {
|
||||
$context['results']['errors'] = [];
|
||||
}
|
||||
$context['results']['errors'] = array_merge($errors, $context['results']['errors']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish batch.
|
||||
*
|
||||
* This function is a static function to avoid serializing the ConfigSync
|
||||
* object unnecessarily.
|
||||
*
|
||||
* @param bool $success
|
||||
* Indicate that the batch API tasks were all completed successfully.
|
||||
* @param array $results
|
||||
* An array of all the results that were updated in update_do_one().
|
||||
* @param array $operations
|
||||
* A list of the operations that had not been completed by the batch API.
|
||||
*/
|
||||
public static function finish($success, $results, $operations) {
|
||||
$messenger = \Drupal::messenger();
|
||||
if ($success) {
|
||||
if (!empty($results['errors'])) {
|
||||
$logger = \Drupal::logger('config_sync');
|
||||
foreach ($results['errors'] as $error) {
|
||||
$messenger->addError($error);
|
||||
$logger->error($error);
|
||||
}
|
||||
$messenger->addWarning(t('The configuration was imported with errors.'));
|
||||
}
|
||||
elseif (!drupal_installation_attempted()) {
|
||||
// Display a success message when not installing Drupal.
|
||||
$messenger->addStatus(t('The configuration was imported successfully.'));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// An error occurred.
|
||||
// $operations contains the operations that remained unprocessed.
|
||||
$error_operation = reset($operations);
|
||||
$message = t('An error occurred while processing %error_operation with arguments: @arguments', ['%error_operation' => $error_operation[0], '@arguments' => print_r($error_operation[1], TRUE)]);
|
||||
$messenger->addError($message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -133,7 +133,7 @@ class InstallStorage extends FileStorage {
|
|||
else {
|
||||
$return = [];
|
||||
foreach ($names as $index => $name) {
|
||||
if (strpos($name, $prefix) === 0 ) {
|
||||
if (strpos($name, $prefix) === 0) {
|
||||
$return[$index] = $names[$index];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
|
||||
/**
|
||||
* An exception thrown if configuration with the same name already exists.
|
||||
|
@ -56,10 +56,10 @@ class PreExistingConfigException extends ConfigException {
|
|||
* @return \Drupal\Core\Config\PreExistingConfigException
|
||||
*/
|
||||
public static function create($extension, array $config_objects) {
|
||||
$message = SafeMarkup::format('Configuration objects (@config_names) provided by @extension already exist in active configuration',
|
||||
$message = new FormattableMarkup('Configuration objects (@config_names) provided by @extension already exist in active configuration',
|
||||
[
|
||||
'@config_names' => implode(', ', static::flattenConfigObjects($config_objects)),
|
||||
'@extension' => $extension
|
||||
'@extension' => $extension,
|
||||
]
|
||||
);
|
||||
$e = new static($message);
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
use Drupal\Core\TypedData\ComplexDataInterface;
|
||||
|
||||
/**
|
||||
* Defines a generic configuration element that contains multiple properties.
|
||||
*/
|
||||
abstract class ArrayElement extends Element implements \IteratorAggregate, TypedConfigInterface {
|
||||
abstract class ArrayElement extends Element implements \IteratorAggregate, TypedConfigInterface, ComplexDataInterface {
|
||||
|
||||
/**
|
||||
* Parsed elements.
|
||||
|
@ -46,7 +48,7 @@ abstract class ArrayElement extends Element implements \IteratorAggregate, Typed
|
|||
*
|
||||
* @return \Drupal\Core\TypedData\DataDefinitionInterface
|
||||
*/
|
||||
protected abstract function getElementDefinition($key);
|
||||
abstract protected function getElementDefinition($key);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -161,4 +163,25 @@ abstract class ArrayElement extends Element implements \IteratorAggregate, Typed
|
|||
return isset($this->definition['nullable']) && $this->definition['nullable'] == TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($property_name, $value, $notify = TRUE) {
|
||||
$this->value[$property_name] = $value;
|
||||
// Config schema elements do not make use of notifications. Thus, we skip
|
||||
// notifying parents.
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProperties($include_computed = FALSE) {
|
||||
$properties = [];
|
||||
foreach (array_keys($this->value) as $name) {
|
||||
$properties[$name] = $this->get($name);
|
||||
}
|
||||
return $properties;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -55,9 +55,7 @@ trait SchemaCheckTrait {
|
|||
if (!$typed_config->hasConfigSchema($config_name)) {
|
||||
return FALSE;
|
||||
}
|
||||
$definition = $typed_config->getDefinition($config_name);
|
||||
$data_definition = $typed_config->buildDataDefinition($definition, $config_data);
|
||||
$this->schema = $typed_config->create($data_definition, $config_data);
|
||||
$this->schema = $typed_config->createFromNameAndData($config_name, $config_data);
|
||||
$errors = [];
|
||||
foreach ($config_data as $key => $value) {
|
||||
$errors = array_merge($errors, $this->checkValue($key, $value));
|
||||
|
|
|
@ -10,6 +10,12 @@ namespace Drupal\Core\Config\Schema;
|
|||
*
|
||||
* Read https://www.drupal.org/node/1905070 for more details about configuration
|
||||
* schema, types and type resolution.
|
||||
*
|
||||
* Note that sequences implement the typed data ComplexDataInterface (via the
|
||||
* parent ArrayElement) rather than the ListInterface. This is because sequences
|
||||
* may have named keys, which is not supported by ListInterface. From the typed
|
||||
* data API perspective sequences are handled as ordered mappings without
|
||||
* metadata about existing properties.
|
||||
*/
|
||||
class Sequence extends ArrayElement {
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
use Drupal\Core\TypedData\ListDataDefinition;
|
||||
|
||||
/**
|
||||
* A typed data definition class for defining sequences in configuration.
|
||||
*/
|
||||
class SequenceDataDefinition extends ListDataDefinition {
|
||||
|
||||
/**
|
||||
* Gets the description of how the sequence should be sorted.
|
||||
*
|
||||
* Only the top level of the array should be sorted. Top-level keys should be
|
||||
* discarded when using 'value' sorting. If the sequence is an associative
|
||||
* array 'key' sorting is recommended, if not 'value' sorting is recommended.
|
||||
*
|
||||
* @return string|null
|
||||
* May be 'key' (to sort by key), 'value' (to sort by value, discarding
|
||||
* keys), or NULL (if the schema does not describe how the sequence should
|
||||
* be sorted).
|
||||
*/
|
||||
public function getOrderBy() {
|
||||
return isset($this->definition['orderby']) ? $this->definition['orderby'] : NULL;
|
||||
}
|
||||
|
||||
}
|
|
@ -3,6 +3,8 @@
|
|||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Core\Config\Schema\Ignore;
|
||||
use Drupal\Core\Config\Schema\Sequence;
|
||||
use Drupal\Core\Config\Schema\SequenceDataDefinition;
|
||||
use Drupal\Core\TypedData\PrimitiveInterface;
|
||||
use Drupal\Core\TypedData\Type\FloatInterface;
|
||||
use Drupal\Core\TypedData\Type\IntegerInterface;
|
||||
|
@ -129,9 +131,7 @@ abstract class StorableConfigBase extends ConfigBase {
|
|||
*/
|
||||
protected function getSchemaWrapper() {
|
||||
if (!isset($this->schemaWrapper)) {
|
||||
$definition = $this->typedConfigManager->getDefinition($this->name);
|
||||
$data_definition = $this->typedConfigManager->buildDataDefinition($definition, $this->data);
|
||||
$this->schemaWrapper = $this->typedConfigManager->create($data_definition, $this->data);
|
||||
$this->schemaWrapper = $this->typedConfigManager->createFromNameAndData($this->name, $this->data);
|
||||
}
|
||||
return $this->schemaWrapper;
|
||||
}
|
||||
|
@ -210,6 +210,29 @@ abstract class StorableConfigBase extends ConfigBase {
|
|||
foreach ($value as $nested_value_key => $nested_value) {
|
||||
$value[$nested_value_key] = $this->castValue($key . '.' . $nested_value_key, $nested_value);
|
||||
}
|
||||
|
||||
if ($element instanceof Sequence) {
|
||||
$data_definition = $element->getDataDefinition();
|
||||
if ($data_definition instanceof SequenceDataDefinition) {
|
||||
// Apply any sorting defined on the schema.
|
||||
switch ($data_definition->getOrderBy()) {
|
||||
case 'key':
|
||||
ksort($value);
|
||||
break;
|
||||
|
||||
case 'value':
|
||||
// The PHP documentation notes that "Be careful when sorting
|
||||
// arrays with mixed types values because sort() can produce
|
||||
// unpredictable results". There is no risk here because
|
||||
// \Drupal\Core\Config\StorableConfigBase::castValue() has
|
||||
// already cast all values to the same type using the
|
||||
// configuration schema.
|
||||
sort($value);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
|
|
@ -433,8 +433,8 @@ class StorageComparer implements StorageComparerInterface {
|
|||
*
|
||||
* @see \Drupal\Core\Config\StorageComparerInterface::extractRenameNames()
|
||||
*/
|
||||
protected function createRenameName($name1, $name2) {
|
||||
return $name1 . '::' . $name2;
|
||||
protected function createRenameName($old_name, $new_name) {
|
||||
return $old_name . '::' . $new_name;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue