Update to Drupal 8.0.0 beta 14. For more information, see https://drupal.org/node/2544542

This commit is contained in:
Pantheon Automation 2015-08-27 12:03:05 -07:00 committed by Greg Anderson
parent 3b2511d96d
commit 81ccda77eb
2155 changed files with 54307 additions and 46870 deletions

View file

@ -14,7 +14,7 @@ use Drupal\Component\Plugin\PluginManagerInterface;
/**
* Defines a class which is capable of clearing the cache on plugin managers.
*/
class CachedDiscoveryClearer {
class CachedDiscoveryClearer implements CachedDiscoveryClearerInterface {
/**
* The stored discoveries.
@ -24,18 +24,14 @@ class CachedDiscoveryClearer {
protected $cachedDiscoveries = array();
/**
* Adds a plugin manager to the active list.
*
* @param \Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface $cached_discovery
* An object that implements the cached discovery interface, typically a
* plugin manager.
* {@inheritdoc}
*/
public function addCachedDiscovery(CachedDiscoveryInterface $cached_discovery) {
$this->cachedDiscoveries[] = $cached_discovery;
}
/**
* Clears the cache on all cached discoveries.
* {@inheritdoc}
*/
public function clearCachedDefinitions() {
foreach ($this->cachedDiscoveries as $cached_discovery) {

View file

@ -0,0 +1,31 @@
<?php
/**
* @file
* Contains \Drupal\Core\Plugin\CachedDiscoveryClearerInterface.
*/
namespace Drupal\Core\Plugin;
use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
/**
* Provides a way to clear static caches of all plugin managers.
*/
interface CachedDiscoveryClearerInterface {
/**
* Adds a plugin manager to the active list.
*
* @param \Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface $cached_discovery
* An object that implements the cached discovery interface, typically a
* plugin manager.
*/
public function addCachedDiscovery(CachedDiscoveryInterface $cached_discovery);
/**
* Clears the cache on all cached discoveries.
*/
public function clearCachedDefinitions();
}

View file

@ -9,7 +9,8 @@ namespace Drupal\Core\Plugin\Context;
use Drupal\Component\Plugin\Context\Context as ComponentContext;
use Drupal\Component\Plugin\Exception\ContextException;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\Core\TypedData\TypedDataTrait;
@ -34,6 +35,21 @@ class Context extends ComponentContext implements ContextInterface {
*/
protected $contextDefinition;
/**
* The cacheability metadata.
*
* @var \Drupal\Core\Cache\CacheableMetadata
*/
protected $cacheabilityMetadata;
/**
* {@inheritdoc}
*/
public function __construct(ContextDefinitionInterface $context_definition) {
parent::__construct($context_definition);
$this->cacheabilityMetadata = new CacheableMetadata();
}
/**
* {@inheritdoc}
*/
@ -49,17 +65,29 @@ class Context extends ComponentContext implements ContextInterface {
}
elseif ($definition->isRequired()) {
$type = $definition->getDataType();
throw new ContextException(SafeMarkup::format("The @type context is required and not present.", array('@type' => $type)));
throw new ContextException("The '$type' context is required and not present.");
}
return $default_value;
}
return $this->getTypedDataManager()->getCanonicalRepresentation($this->contextData);
}
/**
* {@inheritdoc}
*/
public function hasContextValue() {
return (bool) $this->contextData || parent::hasContextValue();
}
/**
* {@inheritdoc}
*/
public function setContextValue($value) {
// Add the value as a cacheable dependency only if implements the interface
// to prevent it from disabling caching with a max-age 0.
if ($value instanceof CacheableDependencyInterface) {
$this->addCacheableDependency($value);
}
if ($value instanceof TypedDataInterface) {
return $this->setContextData($value);
}
@ -113,4 +141,33 @@ class Context extends ComponentContext implements ContextInterface {
return $this->getContextData()->validate();
}
/**
* {@inheritdoc}
*/
public function addCacheableDependency($dependency) {
$this->cacheabilityMetadata = $this->cacheabilityMetadata->merge(CacheableMetadata::createFromObject($dependency));
return $this;
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return $this->cacheabilityMetadata->getCacheContexts();
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return $this->cacheabilityMetadata->getCacheTags();
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
return $this->cacheabilityMetadata->getCacheMaxAge();
}
}

View file

@ -7,7 +7,6 @@
namespace Drupal\Core\Plugin\Context;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\TypedData\TypedDataTrait;
/**
@ -245,7 +244,7 @@ class ContextDefinition implements ContextDefinitionInterface {
}
if (!$definition) {
throw new \Exception(SafeMarkup::format('The data type "@type" is invalid', array('@type' => $this->getDataType())));
throw new \Exception("The data type '{$this->getDataType()}' is invalid");
}
$definition->setLabel($this->getLabel())
->setDescription($this->getDescription())

View file

@ -8,7 +8,7 @@
namespace Drupal\Core\Plugin\Context;
use Drupal\Component\Plugin\Exception\ContextException;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
/**
@ -74,19 +74,51 @@ class ContextHandler implements ContextHandlerInterface {
public function applyContextMapping(ContextAwarePluginInterface $plugin, $contexts, $mappings = array()) {
$mappings += $plugin->getContextMapping();
// Loop through each of the expected contexts.
foreach (array_keys($plugin->getContextDefinitions()) as $plugin_context_id) {
$missing_value = [];
foreach ($plugin->getContextDefinitions() as $plugin_context_id => $plugin_context_definition) {
// If this context was given a specific name, use that.
$context_id = isset($mappings[$plugin_context_id]) ? $mappings[$plugin_context_id] : $plugin_context_id;
if (!empty($contexts[$context_id])) {
// This assignment has been used, remove it.
unset($mappings[$plugin_context_id]);
$plugin->setContextValue($plugin_context_id, $contexts[$context_id]->getContextValue());
// Plugins have their on context objects, only the value is applied.
// They also need to know about the cacheability metadata of where that
// value is coming from, so pass them through to those objects.
$plugin_context = $plugin->getContext($plugin_context_id);
if ($plugin_context instanceof ContextInterface && $contexts[$context_id] instanceof CacheableDependencyInterface) {
$plugin_context->addCacheableDependency($contexts[$context_id]);
}
// Pass the value to the plugin if there is one.
if ($contexts[$context_id]->hasContextValue()) {
$plugin->setContextValue($plugin_context_id, $contexts[$context_id]->getContextValue());
}
elseif ($plugin_context_definition->isRequired()) {
// Collect required contexts that exist but are missing a value.
$missing_value[] = $plugin_context_id;
}
}
elseif ($plugin_context_definition->isRequired()) {
// Collect required contexts that are missing.
$missing_value[] = $plugin_context_id;
}
else {
// Ignore mappings for optional missing context.
unset($mappings[$plugin_context_id]);
}
}
// If there are any required contexts without a value, throw an exception.
if ($missing_value) {
throw new ContextException(sprintf('Required contexts without a value: %s.', implode(', ', $missing_value)));
}
// If there are any mappings that were not satisfied, throw an exception.
if (!empty($mappings)) {
throw new ContextException(SafeMarkup::format('Assigned contexts were not satisfied: @mappings', ['@mappings' => implode(',', array_keys($mappings))]));
throw new ContextException('Assigned contexts were not satisfied: ' . implode(',', array_keys($mappings)));
}
}

View file

@ -8,12 +8,13 @@
namespace Drupal\Core\Plugin\Context;
use Drupal\Component\Plugin\Context\ContextInterface as ComponentContextInterface;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\TypedData\TypedDataInterface;
/**
* Interface for context.
*/
interface ContextInterface extends ComponentContextInterface {
interface ContextInterface extends ComponentContextInterface, CacheableDependencyInterface {
/**
* Gets the context value as typed data object.
@ -32,4 +33,22 @@ interface ContextInterface extends ComponentContextInterface {
*/
public function setContextData(TypedDataInterface $data);
/**
* Adds a dependency on an object: merges its cacheability metadata.
*
* E.g. when a context depends on some configuration, an entity, or an access
* result, we must make sure their cacheability metadata is present on the
* response. This method makes doing that simple.
*
* @param \Drupal\Core\Cache\CacheableDependencyInterface|mixed $dependency
* The dependency. If the object implements CacheableDependencyInterface,
* then its cacheability metadata will be used. Otherwise, the passed in
* object must be assumed to be uncacheable, so max-age 0 is set.
*
* @return $this
*
* @see \Drupal\Core\Cache\CacheableMetadata::createFromObject()
*/
public function addCacheableDependency($dependency);
}

View file

@ -0,0 +1,82 @@
<?php
/**
* @file
* Contains \Drupal\Core\Plugin\Context\ContextProviderInterface.
*/
namespace Drupal\Core\Plugin\Context;
/**
* Defines an interface for providing plugin contexts.
*
* Implementations only need to deal with unqualified context IDs so they only
* need to be unique in the context of a given service provider.
*
* The fully qualified context ID then includes the service ID:
* @{service_id}:{unqualified_context_id}.
*
* @see \Drupal\Core\Plugin\Context\ContextRepositoryInterface
*/
interface ContextProviderInterface {
/**
* Gets runtime context values for the given context IDs.
*
* For context-aware plugins to function correctly, all of the contexts that
* they require must be populated with values. So this method should set a
* value for each context that it adds. For example:
*
* @code
* // Determine a specific node to pass as context to a block.
* $node = ...
*
* // Set that specific node as the value of the 'node' context.
* $context = new Context(new ContextDefinition('entity:node'));
* $context->setContextValue($node);
* return ['node' => $context];
* @endcode
*
* On the other hand, there are cases, on which providers no longer are
* possible to provide context objects, even without the value, so the caller
* should not expect it.
*
* @param string[] $unqualified_context_ids
* The requested context IDs. The context provider must only return contexts
* for those IDs.
*
* @return \Drupal\Core\Plugin\Context\ContextInterface[]
* The determined available contexts, keyed by the unqualified context_id.
*
* @see \Drupal\Core\Plugin\Context\ContextProviderInterface:getAvailableContexts()
*/
public function getRuntimeContexts(array $unqualified_context_ids);
/**
* Gets all available contexts for the purposes of configuration.
*
* When a context aware plugin is being configured, the configuration UI must
* know which named contexts are potentially available, but does not care
* about the value, since the value can be different for each request, and
* might not be available at all during the configuration UI's request.
*
* For example:
* @code
* // During configuration, there is no specific node to pass as context.
* // However, inform the system that a context named 'node' is
* // available, and provide its definition, so that context aware plugins
* // can be configured to use it. When the plugin, for example a block,
* // needs to evaluate the context, the value of this context will be
* // supplied by getRuntimeContexts().
* $context = new Context(new ContextDefinition('entity:node'));
* return ['node' => $context];
* @endcode
*
* @return \Drupal\Core\Plugin\Context\ContextInterface[]
* All available contexts keyed by the unqualified context ID.
*
* @see \Drupal\Core\Plugin\Context\ContextProviderInterface::getRuntimeContext()
*/
public function getAvailableContexts();
}

View file

@ -0,0 +1,46 @@
<?php
/**
* @file
* Contains \Drupal\Core\Plugin\Context\ContextRepositoryInterface.
*/
namespace Drupal\Core\Plugin\Context;
/**
* Offers a global context repository.
*
* Provides a list of all available contexts, which is mostly useful for
* configuration on forms, as well as a method to get the concrete contexts with
* their values, given a list of fully qualified context IDs.
*
* @see \Drupal\Core\Plugin\Context\ContextProviderInterface
*/
interface ContextRepositoryInterface {
/**
* Gets runtime context values for the given context IDs.
*
* Given that context providers might not return contexts for the given
* context IDs, it is also not guaranteed that the context repository returns
* contexts for all specified IDs.
*
* @param string[] $context_ids
* Fully qualified context IDs, which looks like
* @{service_id}:{unqualified_context_id}, so for example
* node.node_route_context:node.
*
* @return \Drupal\Core\Plugin\Context\ContextInterface[]
* The determined contexts, keyed by the fully qualified context ID.
*/
public function getRuntimeContexts(array $context_ids);
/**
* Gets all available contexts for the purposes of configuration.
*
* @return \Drupal\Core\Plugin\Context\ContextInterface[]
* All available contexts.
*/
public function getAvailableContexts();
}

View file

@ -0,0 +1,112 @@
<?php
/**
* @file
* Contains \Drupal\Core\Plugin\Context\LazyContextRepository.
*/
namespace Drupal\Core\Plugin\Context;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a context repository which uses context provider services.
*/
class LazyContextRepository implements ContextRepositoryInterface {
/**
* The set of available context providers service IDs.
*
* @var string[]
* Context provider service IDs.
*/
protected $contextProviderServiceIDs = [];
/**
* The service container.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
/**
* The statically cached contexts.
*
* @var \Drupal\Core\Plugin\Context\ContextInterface[]
*/
protected $contexts = [];
/**
* Constructs a LazyContextRepository object.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* The current service container.
* @param string[] $context_provider_service_ids
* The set of the available context provider service IDs.
*/
public function __construct(ContainerInterface $container, array $context_provider_service_ids) {
$this->container = $container;
$this->contextProviderServiceIDs = $context_provider_service_ids;
}
/**
* {@inheritdoc}
*/
public function getRuntimeContexts(array $context_ids) {
$contexts = [];
// Create a map of context providers (service IDs) to unqualified context
// IDs.
$context_ids_by_service = [];
foreach ($context_ids as $id) {
if (isset($this->contexts[$id])) {
$contexts[$id] = $this->contexts[$id];
continue;
}
// The IDs have been passed in @{service_id}:{unqualified_context_id}
// format.
// @todo Convert to an assert once https://www.drupal.org/node/2408013 is
// in.
if ($id[0] === '@' && strpos($id, ':') !== FALSE) {
list($service_id, $unqualified_context_id) = explode(':', $id, 2);
// Remove the leading '@'.
$service_id = substr($service_id, 1);
}
else {
throw new \InvalidArgumentException('You must provide the context IDs in the @{service_id}:{unqualified_context_id} format.');
}
$context_ids_by_service[$service_id][] = $unqualified_context_id;
}
// Iterate over all missing context providers (services), gather the
// runtime contexts and assign them as requested.
foreach ($context_ids_by_service as $service_id => $unqualified_context_ids) {
$contexts_by_service = $this->container->get($service_id)->getRuntimeContexts($unqualified_context_ids);
$wanted_contexts = array_intersect_key($contexts_by_service, array_flip($unqualified_context_ids));
foreach ($wanted_contexts as $unqualified_context_id => $context) {
$context_id = '@' . $service_id . ':' . $unqualified_context_id;
$this->contexts[$context_id] = $contexts[$context_id] = $context;
}
}
return $contexts;
}
/**
* {@inheritdoc}
*/
public function getAvailableContexts() {
$contexts = [];
foreach ($this->contextProviderServiceIDs as $service_id) {
$contexts_by_service = $this->container->get($service_id)->getAvailableContexts();
foreach ($contexts_by_service as $unqualified_context_id => $context) {
$context_id = '@' . $service_id . ':' . $unqualified_context_id;
$contexts[$context_id] = $context;
}
}
return $contexts;
}
}

View file

@ -10,6 +10,8 @@ namespace Drupal\Core\Plugin;
use Drupal\Component\Plugin\ConfigurablePluginInterface;
use Drupal\Component\Plugin\ContextAwarePluginBase as ComponentContextAwarePluginBase;
use Drupal\Component\Plugin\Exception\ContextException;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\StringTranslation\StringTranslationTrait;
@ -91,4 +93,53 @@ abstract class ContextAwarePluginBase extends ComponentContextAwarePluginBase im
return \Drupal::service('context.handler');
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
$cache_contexts = [];
// Applied contexts can affect the cache contexts when this plugin is
// involved in caching, collect and return them.
foreach ($this->getContexts() as $context) {
/** @var $context \Drupal\Core\Cache\CacheableDependencyInterface */
if ($context instanceof CacheableDependencyInterface) {
$cache_contexts = Cache::mergeContexts($cache_contexts, $context->getCacheContexts());
}
}
return $cache_contexts;
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
$tags = [];
// Applied contexts can affect the cache tags when this plugin is
// involved in caching, collect and return them.
foreach ($this->getContexts() as $context) {
/** @var $context \Drupal\Core\Cache\CacheableDependencyInterface */
if ($context instanceof CacheableDependencyInterface) {
$tags = Cache::mergeTags($tags, $context->getCacheTags());
}
}
return $tags;
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
$max_age = Cache::PERMANENT;
// Applied contexts can affect the cache max age when this plugin is
// involved in caching, collect and return them.
foreach ($this->getContexts() as $context) {
/** @var $context \Drupal\Core\Cache\CacheableDependencyInterface */
if ($context instanceof CacheableDependencyInterface) {
$max_age = Cache::mergeMaxAges($max_age, $context->getCacheMaxAge());
}
}
return $max_age;
}
}

View file

@ -336,7 +336,7 @@ class DefaultPluginManager extends PluginManagerBase implements PluginManagerInt
/**
* Determines if the provider of a definition exists.
*
* @return boolean
* @return bool
* TRUE if provider exists, FALSE otherwise.
*/
protected function providerExists($provider) {

View file

@ -10,9 +10,18 @@ namespace Drupal\Core\Plugin\Discovery;
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
use Drupal\Component\Discovery\YamlDiscovery as ComponentYamlDiscovery;
use Drupal\Component\Plugin\Discovery\DiscoveryTrait;
use Drupal\Core\StringTranslation\TranslationWrapper;
/**
* Allows YAML files to define plugin definitions.
*
* If the value of a key (like title) in the definition is translatable then
* the addTranslatableProperty() method can be used to mark it as such and also
* to add translation context. Then
* \Drupal\Core\StringTranslation\TranslationWrapper will be used to translate
* the string and also to mark it safe. Only strings written in the YAML files
* should be marked as safe, strings coming from dynamic plugin definitions
* potentially containing user input should not.
*/
class YamlDiscovery implements DiscoveryInterface {
@ -25,6 +34,15 @@ class YamlDiscovery implements DiscoveryInterface {
*/
protected $discovery;
/**
* Contains an array of translatable properties passed along to t().
*
* @see \Drupal\Core\Plugin\Discovery\YamlDiscovery::addTranslatableProperty()
*
* @var array
*/
protected $translatableProperties = [];
/**
* Construct a YamlDiscovery object.
*
@ -38,6 +56,23 @@ class YamlDiscovery implements DiscoveryInterface {
$this->discovery = new ComponentYamlDiscovery($name, $directories);
}
/**
* Set one of the YAML values as being translatable.
*
* @param string $value_key
* The key corresponding to the value in the YAML that contains a
* translatable string.
* @param string $context_key
* (Optional) the translation context for the value specified by the
* $value_key.
*
* @return $this
*/
public function addTranslatableProperty($value_key, $context_key = '') {
$this->translatableProperties[$value_key] = $context_key;
return $this;
}
/**
* {@inheritdoc}
*/
@ -48,6 +83,20 @@ class YamlDiscovery implements DiscoveryInterface {
$definitions = array();
foreach ($plugins as $provider => $list) {
foreach ($list as $id => $definition) {
// Add translation wrappers.
foreach ($this->translatableProperties as $property => $context_key) {
if (isset($definition[$property])) {
$options = [];
// Move the t() context from the definition to the translation
// wrapper.
if ($context_key && isset($definition[$context_key])) {
$options['context'] = $definition[$context_key];
unset($definition[$context_key]);
}
$definition[$property] = new TranslationWrapper($definition[$property], [], $options);
}
}
// Add ID and provider.
$definitions[$id] = $definition + array(
'provider' => $provider,
'id' => $id,

View file

@ -10,18 +10,7 @@ namespace Drupal\Core\Plugin;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides an interface for a plugin that contains a form.
*
* Plugin forms are usually contained in other forms. In order to know where the
* plugin form is located in the parent form, #parents and #array_parents must
* be known, but these are not available during the initial build phase. In
* order to have these properties available when building the plugin form's
* elements, let buildConfigurationForm() return a form element that has a
* #process callback and build the rest of the form in the callback. By the time
* the callback is executed, the element's #parents and #array_parents
* properties will have been set by the form API. For more documentation on
* #parents and #array_parents, see
* https://api.drupal.org/api/drupal/developer!topics!forms_api_reference.html/8.
* Provides an interface for an embeddable plugin form.
*
* @ingroup plugin_api
*/
@ -30,10 +19,21 @@ interface PluginFormInterface {
/**
* Form constructor.
*
* Plugin forms are embedded in other forms. In order to know where the plugin
* form is located in the parent form, #parents and #array_parents must be
* known, but these are not available during the initial build phase. In order
* to have these properties available when building the plugin form's
* elements, let this method return a form element that has a #process
* callback and build the rest of the form in the callback. By the time the
* callback is executed, the element's #parents and #array_parents properties
* will have been set by the form API. For more documentation on #parents and
* #array_parents, see
* https://api.drupal.org/api/drupal/developer!topics!forms_api_reference.html/8.
*
* @param array $form
* An associative array containing the structure of the form.
* An associative array containing the initial structure of the plugin form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* The current state of the complete form.
*
* @return array
* The form structure.
@ -44,9 +44,10 @@ interface PluginFormInterface {
* Form validation handler.
*
* @param array $form
* An associative array containing the structure of the form.
* An associative array containing the structure of the plugin form as built
* by static::buildConfigurationForm().
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* The current state of the complete form.
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state);
@ -54,9 +55,10 @@ interface PluginFormInterface {
* Form submission handler.
*
* @param array $form
* An associative array containing the structure of the form.
* An associative array containing the structure of the plugin form as built
* by static::buildConfigurationForm().
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* The current state of the complete form.
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state);