Update to Drupal 8.0-dev-2015-11-17. Commits through da81cd220, Tue Nov 17 15:53:49 2015 +0000, Issue #2617224 by Wim Leers: Move around/fix some documentation.
This commit is contained in:
parent
4afb23bbd3
commit
7784f4c23d
929 changed files with 19798 additions and 5304 deletions
|
@ -79,8 +79,7 @@ class FileStorage implements PhpStorageInterface {
|
|||
public static function htaccessLines($private = TRUE) {
|
||||
$lines = <<<EOF
|
||||
# Turn off all options we don't need.
|
||||
Options None
|
||||
Options +FollowSymLinks
|
||||
Options -Indexes -ExecCGI -Includes -MultiViews
|
||||
|
||||
# Set the catch-all handler to prevent scripts from being executed.
|
||||
SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
|
||||
|
|
|
@ -116,8 +116,9 @@ class AssetResolver implements AssetResolverInterface {
|
|||
public function getCssAssets(AttachedAssetsInterface $assets, $optimize) {
|
||||
$theme_info = $this->themeManager->getActiveTheme();
|
||||
// Add the theme name to the cache key since themes may implement
|
||||
// hook_css_alter().
|
||||
$cid = 'css:' . $theme_info->getName() . ':' . Crypt::hashBase64(serialize($assets)) . (int) $optimize;
|
||||
// hook_library_info_alter().
|
||||
$libraries_to_load = $this->getLibrariesToLoad($assets);
|
||||
$cid = 'css:' . $theme_info->getName() . ':' . Crypt::hashBase64(serialize($libraries_to_load)) . (int) $optimize;
|
||||
if ($cached = $this->cache->get($cid)) {
|
||||
return $cached->data;
|
||||
}
|
||||
|
@ -132,7 +133,7 @@ class AssetResolver implements AssetResolverInterface {
|
|||
'browsers' => [],
|
||||
];
|
||||
|
||||
foreach ($this->getLibrariesToLoad($assets) as $library) {
|
||||
foreach ($libraries_to_load as $library) {
|
||||
list($extension, $name) = explode('/', $library, 2);
|
||||
$definition = $this->libraryDiscovery->getLibraryByName($extension, $name);
|
||||
if (isset($definition['css'])) {
|
||||
|
@ -187,9 +188,7 @@ class AssetResolver implements AssetResolverInterface {
|
|||
* Returns the JavaScript settings assets for this response's libraries.
|
||||
*
|
||||
* Gathers all drupalSettings from all libraries in the attached assets
|
||||
* collection and merges them, then it merges individual attached settings,
|
||||
* and finally invokes hook_js_settings_alter() to allow alterations of
|
||||
* JavaScript settings by modules and themes.
|
||||
* collection and merges them.
|
||||
*
|
||||
* @param \Drupal\Core\Asset\AttachedAssetsInterface $assets
|
||||
* The assets attached to the current response.
|
||||
|
@ -207,9 +206,6 @@ class AssetResolver implements AssetResolverInterface {
|
|||
}
|
||||
}
|
||||
|
||||
// Attached settings win over settings in libraries.
|
||||
$settings = NestedArray::mergeDeepArray([$settings, $assets->getSettings()], TRUE);
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
|
@ -219,9 +215,10 @@ class AssetResolver implements AssetResolverInterface {
|
|||
public function getJsAssets(AttachedAssetsInterface $assets, $optimize) {
|
||||
$theme_info = $this->themeManager->getActiveTheme();
|
||||
// Add the theme name to the cache key since themes may implement
|
||||
// hook_js_alter(). Additionally add the current language to support
|
||||
// translation of JavaScript files.
|
||||
$cid = 'js:' . $theme_info->getName() . ':' . $this->languageManager->getCurrentLanguage()->getId() . ':' . Crypt::hashBase64(serialize($assets)) . (int) $optimize;
|
||||
// hook_library_info_alter(). Additionally add the current language to
|
||||
// support translation of JavaScript files via hook_js_alter().
|
||||
$libraries_to_load = $this->getLibrariesToLoad($assets);
|
||||
$cid = 'js:' . $theme_info->getName() . ':' . $this->languageManager->getCurrentLanguage()->getId() . ':' . Crypt::hashBase64(serialize($libraries_to_load)) . (int) (count($assets->getSettings()) > 0) . (int) $optimize;
|
||||
|
||||
if ($cached = $this->cache->get($cid)) {
|
||||
list($js_assets_header, $js_assets_footer, $settings, $settings_in_header) = $cached->data;
|
||||
|
@ -239,8 +236,6 @@ class AssetResolver implements AssetResolverInterface {
|
|||
'browsers' => [],
|
||||
];
|
||||
|
||||
$libraries_to_load = $this->getLibrariesToLoad($assets);
|
||||
|
||||
// Collect all libraries that contain JS assets and are in the header.
|
||||
$header_js_libraries = [];
|
||||
foreach ($libraries_to_load as $library) {
|
||||
|
@ -329,8 +324,10 @@ class AssetResolver implements AssetResolverInterface {
|
|||
$this->cache->set($cid, [$js_assets_header, $js_assets_footer, $settings, $settings_in_header], CacheBackendInterface::CACHE_PERMANENT, ['library_info']);
|
||||
}
|
||||
|
||||
|
||||
if ($settings !== FALSE) {
|
||||
// Attached settings override both library definitions and
|
||||
// hook_js_settings_build().
|
||||
$settings = NestedArray::mergeDeepArray([$settings, $assets->getSettings()], TRUE);
|
||||
// Allow modules and themes to alter the JavaScript settings.
|
||||
$this->moduleHandler->alter('js_settings', $settings, $assets);
|
||||
$this->themeManager->alter('js_settings', $settings, $assets);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\Core\Cache;
|
||||
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Core\DestructableInterface;
|
||||
use Drupal\Core\Lock\LockBackendInterface;
|
||||
|
||||
|
@ -232,7 +233,7 @@ abstract class CacheCollector implements CacheCollectorInterface, DestructableIn
|
|||
|
||||
// Lock cache writes to help avoid stampedes.
|
||||
$cid = $this->getCid();
|
||||
$lock_name = $cid . ':' . __CLASS__;
|
||||
$lock_name = $this->normalizeLockName($cid . ':' . __CLASS__);
|
||||
if (!$lock || $this->lock->acquire($lock_name)) {
|
||||
// Set and delete operations invalidate the cache item. Try to also load
|
||||
// an eventually invalidated cache entry, only update an invalidated cache
|
||||
|
@ -264,6 +265,30 @@ abstract class CacheCollector implements CacheCollectorInterface, DestructableIn
|
|||
$this->keysToRemove = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a cache ID in order to comply with database limitations.
|
||||
*
|
||||
* @param string $cid
|
||||
* The passed in cache ID.
|
||||
*
|
||||
* @return string
|
||||
* An ASCII-encoded cache ID that is at most 255 characters long.
|
||||
*/
|
||||
protected function normalizeLockName($cid) {
|
||||
// Nothing to do if the ID is a US ASCII string of 255 characters or less.
|
||||
$cid_is_ascii = mb_check_encoding($cid, 'ASCII');
|
||||
if (strlen($cid) <= 255 && $cid_is_ascii) {
|
||||
return $cid;
|
||||
}
|
||||
// Return a string that uses as much as possible of the original cache ID
|
||||
// with the hash appended.
|
||||
$hash = Crypt::hashBase64($cid);
|
||||
if (!$cid_is_ascii) {
|
||||
return $hash;
|
||||
}
|
||||
return substr($cid, 0, 255 - strlen($hash)) . $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -10,6 +10,7 @@ namespace Drupal\Core\Composer;
|
|||
use Drupal\Component\PhpStorage\FileStorage;
|
||||
use Composer\Script\Event;
|
||||
use Composer\Installer\PackageEvent;
|
||||
use Composer\Semver\Constraint\Constraint;
|
||||
|
||||
/**
|
||||
* Provides static functions for composer script events.
|
||||
|
@ -71,23 +72,38 @@ class Composer {
|
|||
];
|
||||
|
||||
/**
|
||||
* Add vendor classes to composers static classmap.
|
||||
* Add vendor classes to Composer's static classmap.
|
||||
*/
|
||||
public static function preAutoloadDump(Event $event) {
|
||||
$composer = $event->getComposer();
|
||||
$package = $composer->getPackage();
|
||||
$autoload = $package->getAutoload();
|
||||
$autoload['classmap'] = array_merge($autoload['classmap'], array(
|
||||
'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/symfony/http-kernel/HttpKernel.php',
|
||||
'vendor/symfony/http-kernel/HttpKernelInterface.php',
|
||||
'vendor/symfony/http-kernel/TerminableInterface.php',
|
||||
));
|
||||
$package->setAutoload($autoload);
|
||||
// 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.
|
||||
$constraint = new Constraint('>', '');
|
||||
// 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'], array(
|
||||
'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',
|
||||
));
|
||||
$package->setAutoload($autoload);
|
||||
}
|
||||
if ($repository->findPackage('symfony/http-kernel', $constraint)) {
|
||||
$autoload = $package->getAutoload();
|
||||
$autoload['classmap'] = array_merge($autoload['classmap'], array(
|
||||
'vendor/symfony/http-kernel/HttpKernel.php',
|
||||
'vendor/symfony/http-kernel/HttpKernelInterface.php',
|
||||
'vendor/symfony/http-kernel/TerminableInterface.php',
|
||||
));
|
||||
$package->setAutoload($autoload);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -112,13 +112,13 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
|||
$prefix = $name . '.';
|
||||
}
|
||||
|
||||
// Gets a profile storage to search for overrides if necessary.
|
||||
$profile_storage = $this->getProfileStorage($name);
|
||||
// Gets profile storages to search for overrides if necessary.
|
||||
$profile_storages = $this->getProfileStorages($name);
|
||||
|
||||
// Gather information about all the supported collections.
|
||||
$collection_info = $this->configManager->getConfigCollectionInfo();
|
||||
foreach ($collection_info->getCollectionNames() as $collection) {
|
||||
$config_to_create = $this->getConfigToCreate($storage, $collection, $prefix, $profile_storage);
|
||||
$config_to_create = $this->getConfigToCreate($storage, $collection, $prefix, $profile_storages);
|
||||
// If we're installing a profile ensure configuration that is overriding
|
||||
// is excluded.
|
||||
if ($name == $this->drupalGetProfile()) {
|
||||
|
@ -223,19 +223,22 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
|||
* The configuration collection to use.
|
||||
* @param string $prefix
|
||||
* (optional) Limit to configuration starting with the provided string.
|
||||
* @param \Drupal\Core\Config\StorageInterface[] $profile_storages
|
||||
* An array of storage interfaces containing profile configuration to check
|
||||
* for overrides.
|
||||
*
|
||||
* @return array
|
||||
* An array of configuration data read from the source storage keyed by the
|
||||
* configuration object name.
|
||||
*/
|
||||
protected function getConfigToCreate(StorageInterface $storage, $collection, $prefix = '', StorageInterface $profile_storage = NULL) {
|
||||
protected function getConfigToCreate(StorageInterface $storage, $collection, $prefix = '', array $profile_storages = []) {
|
||||
if ($storage->getCollectionName() != $collection) {
|
||||
$storage = $storage->createCollection($collection);
|
||||
}
|
||||
$data = $storage->readMultiple($storage->listAll($prefix));
|
||||
|
||||
// Check to see if the corresponding override storage has any overrides.
|
||||
if ($profile_storage) {
|
||||
foreach ($profile_storages as $profile_storage) {
|
||||
if ($profile_storage->getCollectionName() != $collection) {
|
||||
$profile_storage = $profile_storage->createCollection($collection);
|
||||
}
|
||||
|
@ -435,11 +438,11 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
|||
$enabled_extensions = $this->getEnabledExtensions();
|
||||
// Add the extension that will be enabled to the list of enabled extensions.
|
||||
$enabled_extensions[] = $name;
|
||||
// Gets a profile storage to search for overrides if necessary.
|
||||
$profile_storage = $this->getProfileStorage($name);
|
||||
// Gets profile storages to search for overrides if necessary.
|
||||
$profile_storages = $this->getProfileStorages($name);
|
||||
|
||||
// Check the dependencies of configuration provided by the module.
|
||||
$invalid_default_config = $this->findDefaultConfigWithUnmetDependencies($storage, $enabled_extensions, $profile_storage);
|
||||
$invalid_default_config = $this->findDefaultConfigWithUnmetDependencies($storage, $enabled_extensions, $profile_storages);
|
||||
if (!empty($invalid_default_config)) {
|
||||
throw UnmetDependenciesException::create($name, $invalid_default_config);
|
||||
}
|
||||
|
@ -460,14 +463,19 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
|||
/**
|
||||
* Finds default configuration with unmet dependencies.
|
||||
*
|
||||
* @param \Drupal\Core\Config\StorageInterface $storage
|
||||
* The storage containing the default configuration.
|
||||
* @param array $enabled_extensions
|
||||
* A list of all the currently enabled modules and themes.
|
||||
* @param \Drupal\Core\Config\StorageInterface[] $profile_storages
|
||||
* An array of storage interfaces containing profile configuration to check
|
||||
* for overrides.
|
||||
*
|
||||
* @return array
|
||||
* List of configuration that has unmet dependencies
|
||||
*/
|
||||
protected function findDefaultConfigWithUnmetDependencies(StorageInterface $storage, array $enabled_extensions, StorageInterface $profile_storage = NULL) {
|
||||
$config_to_create = $this->getConfigToCreate($storage, StorageInterface::DEFAULT_COLLECTION, '', $profile_storage);
|
||||
protected function findDefaultConfigWithUnmetDependencies(StorageInterface $storage, array $enabled_extensions, array $profile_storages = []) {
|
||||
$config_to_create = $this->getConfigToCreate($storage, StorageInterface::DEFAULT_COLLECTION, '', $profile_storages);
|
||||
$all_config = array_merge($this->configFactory->listAll(), array_keys($config_to_create));
|
||||
return array_filter(array_keys($config_to_create), function($config_name) use ($enabled_extensions, $all_config, $config_to_create) {
|
||||
return !$this->validateDependencies($config_name, $config_to_create[$config_name], $enabled_extensions, $all_config);
|
||||
|
@ -550,27 +558,31 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
|||
/**
|
||||
* Gets the profile storage to use to check for profile overrides.
|
||||
*
|
||||
* The install profile can override module configuration during a module
|
||||
* install. Both the install and optional directories are checked for matching
|
||||
* configuration. This allows profiles to override default configuration for
|
||||
* modules they do not depend on.
|
||||
*
|
||||
* @param string $installing_name
|
||||
* (optional) The name of the extension currently being installed.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageInterface|null
|
||||
* A storage to access configuration from the installation profile. If a
|
||||
* Drupal installation is not in progress or we're installing the profile
|
||||
* itself, then it will return NULL as the profile storage should not be
|
||||
* used.
|
||||
* @return \Drupal\Core\Config\StorageInterface[]|null
|
||||
* Storages to access configuration from the installation profile. If we're
|
||||
* installing the profile itself, then it will return an empty array as the
|
||||
* profile storage should not be used.
|
||||
*/
|
||||
protected function getProfileStorage($installing_name = '') {
|
||||
protected function getProfileStorages($installing_name = '') {
|
||||
$profile = $this->drupalGetProfile();
|
||||
if ($this->drupalInstallationAttempted() && $profile != $installing_name) {
|
||||
// Profiles should not contain optional configuration so always use the
|
||||
// install directory.
|
||||
$profile_install_path = $this->getDefaultConfigDirectory('module', $profile);
|
||||
$profile_storage = new FileStorage($profile_install_path, StorageInterface::DEFAULT_COLLECTION);
|
||||
$profile_storages = [];
|
||||
if ($profile && $profile != $installing_name) {
|
||||
$profile_path = $this->drupalGetPath('module', $profile);
|
||||
foreach ([InstallStorage::CONFIG_INSTALL_DIRECTORY, InstallStorage::CONFIG_OPTIONAL_DIRECTORY] as $directory) {
|
||||
if (is_dir($profile_path . '/' . $directory)) {
|
||||
$profile_storages[] = new FileStorage($profile_path . '/' . $directory, StorageInterface::DEFAULT_COLLECTION);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$profile_storage = NULL;
|
||||
}
|
||||
return $profile_storage;
|
||||
return $profile_storages;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -387,6 +387,7 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function url($rel = 'edit-form', $options = array()) {
|
||||
// Do not remove this override: the default value of $rel is different.
|
||||
return parent::url($rel, $options);
|
||||
}
|
||||
|
||||
|
@ -394,9 +395,19 @@ abstract class ConfigEntityBase extends Entity implements ConfigEntityInterface
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function link($text = NULL, $rel = 'edit-form', array $options = []) {
|
||||
// Do not remove this override: the default value of $rel is different.
|
||||
return parent::link($text, $rel, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toUrl($rel = 'edit-form', array $options = []) {
|
||||
// Unless language was already provided, avoid setting an explicit language.
|
||||
$options += ['language' => NULL];
|
||||
return parent::toUrl($rel, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -49,6 +49,13 @@ abstract class ControllerBase implements ContainerInjectionInterface {
|
|||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The entity form builder.
|
||||
*
|
||||
|
@ -117,6 +124,10 @@ abstract class ControllerBase implements ContainerInjectionInterface {
|
|||
*
|
||||
* @return \Drupal\Core\Entity\EntityManagerInterface
|
||||
* The entity manager service.
|
||||
*
|
||||
* @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
|
||||
* Most of the time static::entityTypeManager() is supposed to be used
|
||||
* instead.
|
||||
*/
|
||||
protected function entityManager() {
|
||||
if (!$this->entityManager) {
|
||||
|
@ -125,6 +136,19 @@ abstract class ControllerBase implements ContainerInjectionInterface {
|
|||
return $this->entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the entity type manager.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
* The entity type manager.
|
||||
*/
|
||||
protected function entityTypeManager() {
|
||||
if (!isset($this->entityTypeManager)) {
|
||||
$this->entityTypeManager = $this->container()->get('entity_type.manager');
|
||||
}
|
||||
return $this->entityTypeManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the entity form builder.
|
||||
*
|
||||
|
|
|
@ -254,14 +254,24 @@ class DateHelper {
|
|||
* An array of weekdays.
|
||||
*
|
||||
* @return array
|
||||
* An array of weekdays reordered to match the first day of the week.
|
||||
* An array of weekdays reordered to match the first day of the week. The
|
||||
* keys will remain unchanged. For example, if the first day of the week is
|
||||
* set to be Monday, the array keys will be [1, 2, 3, 4, 5, 6, 0].
|
||||
*/
|
||||
public static function weekDaysOrdered($weekdays) {
|
||||
$first_day = \Drupal::config('system.date')->get('first_day');
|
||||
if ($first_day > 0) {
|
||||
for ($i = 1; $i <= $first_day; $i++) {
|
||||
$last = array_shift($weekdays);
|
||||
array_push($weekdays, $last);
|
||||
// Reset the array to the first element.
|
||||
reset($weekdays);
|
||||
// Retrieve the first week day value.
|
||||
$last = current($weekdays);
|
||||
// Store the corresponding key.
|
||||
$key = key($weekdays);
|
||||
// Remove this week day from the beginning of the array.
|
||||
unset($weekdays[$key]);
|
||||
// Add this week day to the end of the array.
|
||||
$weekdays[$key] = $last;
|
||||
}
|
||||
}
|
||||
return $weekdays;
|
||||
|
|
|
@ -72,33 +72,6 @@ class ContainerBuilder extends SymfonyContainerBuilder {
|
|||
parent::setParameter($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes a service change.
|
||||
*
|
||||
* This method is a copy of the ContainerBuilder of symfony.
|
||||
*
|
||||
* This method updates all services that depend on the given
|
||||
* service by calling all methods referencing it.
|
||||
*
|
||||
* @param string $id A service id
|
||||
*/
|
||||
private function synchronize($id) {
|
||||
foreach ($this->getDefinitions() as $definitionId => $definition) {
|
||||
// only check initialized services
|
||||
if (!$this->initialized($definitionId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($definition->getMethodCalls() as $call) {
|
||||
foreach ($call[1] as $argument) {
|
||||
if ($argument instanceof Reference && $id == (string) $argument) {
|
||||
$this->callMethod($this->get($definitionId), $call);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A 1to1 copy of parent::callMethod.
|
||||
*/
|
||||
|
|
|
@ -821,13 +821,6 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
|
|||
// If there is no container and no cached container definition, build a new
|
||||
// one from scratch.
|
||||
if (!isset($container) && !isset($container_definition)) {
|
||||
if (version_compare(phpversion(), '7.0.0-dev') >= 0) {
|
||||
// The service graph implementation is prone to corruption during GC.
|
||||
// Collect cycles now then disable the GC for the time of the compiler
|
||||
// run.
|
||||
// @see https://bugs.php.net/bug.php?id=70805
|
||||
gc_collect_cycles();
|
||||
}
|
||||
$container = $this->compileContainer();
|
||||
|
||||
// Only dump the container if dumping is allowed. This is useful for
|
||||
|
|
|
@ -10,10 +10,10 @@ namespace Drupal\Core\Entity\Element;
|
|||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Component\Utility\Tags;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityReferenceSelection\SelectionWithAutocreateInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Render\Element\Textfield;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\user\EntityOwnerInterface;
|
||||
|
||||
/**
|
||||
* Provides an entity autocomplete form element.
|
||||
|
@ -147,7 +147,7 @@ class EntityAutocomplete extends Textfield {
|
|||
'handler_settings' => $element['#selection_settings'],
|
||||
);
|
||||
$handler = \Drupal::service('plugin.manager.entity_reference_selection')->getInstance($options);
|
||||
$autocreate = (bool) $element['#autocreate'];
|
||||
$autocreate = (bool) $element['#autocreate'] && $handler instanceof SelectionWithAutocreateInterface;
|
||||
|
||||
$input_values = $element['#tags'] ? Tags::explode($element['#value']) : array($element['#value']);
|
||||
foreach ($input_values as $input) {
|
||||
|
@ -167,13 +167,14 @@ class EntityAutocomplete extends Textfield {
|
|||
// Auto-create item. See an example of how this is handled in
|
||||
// \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::presave().
|
||||
$value[] = array(
|
||||
'entity' => static::createNewEntity($element['#target_type'], $element['#autocreate']['bundle'], $input, $element['#autocreate']['uid'])
|
||||
'entity' => $handler->createNewEntity($element['#target_type'], $element['#autocreate']['bundle'], $input, $element['#autocreate']['uid']),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the referenced entities are valid, if needed.
|
||||
if ($element['#validate_reference'] && !$autocreate && !empty($value)) {
|
||||
if ($element['#validate_reference'] && !empty($value)) {
|
||||
// Validate existing entities.
|
||||
$ids = array_reduce($value, function ($return, $item) {
|
||||
if (isset($item['target_id'])) {
|
||||
$return[] = $item['target_id'];
|
||||
|
@ -189,6 +190,30 @@ class EntityAutocomplete extends Textfield {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate newly created entities.
|
||||
$new_entities = array_reduce($value, function ($return, $item) {
|
||||
if (isset($item['entity'])) {
|
||||
$return[] = $item['entity'];
|
||||
}
|
||||
return $return;
|
||||
});
|
||||
|
||||
if ($new_entities) {
|
||||
if ($autocreate) {
|
||||
$valid_new_entities = $handler->validateReferenceableNewEntities($new_entities);
|
||||
$invalid_new_entities = array_diff_key($new_entities, $valid_new_entities);
|
||||
}
|
||||
else {
|
||||
// If the selection handler does not support referencing newly
|
||||
// created entities, all of them should be invalidated.
|
||||
$invalid_new_entities = $new_entities;
|
||||
}
|
||||
|
||||
foreach ($invalid_new_entities as $entity) {
|
||||
$form_state->setError($element, t('This entity (%type: %label) cannot be referenced.', array('%type' => $element['#target_type'], '%label' => $entity->label())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use only the last value if the form element does not support multiple
|
||||
|
@ -310,37 +335,4 @@ class EntityAutocomplete extends Textfield {
|
|||
return $match;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new entity from a label entered in the autocomplete input.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID.
|
||||
* @param string $bundle
|
||||
* The bundle name.
|
||||
* @param string $label
|
||||
* The entity label.
|
||||
* @param int $uid
|
||||
* The entity owner ID.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
*/
|
||||
protected static function createNewEntity($entity_type_id, $bundle, $label, $uid) {
|
||||
$entity_manager = \Drupal::entityManager();
|
||||
|
||||
$entity_type = $entity_manager->getDefinition($entity_type_id);
|
||||
$bundle_key = $entity_type->getKey('bundle');
|
||||
$label_key = $entity_type->getKey('label');
|
||||
|
||||
$entity = $entity_manager->getStorage($entity_type_id)->create(array(
|
||||
$bundle_key => $bundle,
|
||||
$label_key => $label,
|
||||
));
|
||||
|
||||
if ($entity instanceof EntityOwnerInterface) {
|
||||
$entity->setOwnerId($uid);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -72,11 +72,26 @@ abstract class Entity implements EntityInterface {
|
|||
* Gets the entity manager.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityManagerInterface
|
||||
*
|
||||
* @deprecated in Drupal 8.0.0 and will be removed before Drupal 9.0.0.
|
||||
* Use \Drupal::entityTypeManager() instead in most cases. If the needed
|
||||
* method is not on \Drupal\Core\Entity\EntityTypeManagerInterface, see the
|
||||
* deprecated \Drupal\Core\Entity\EntityManager to find the
|
||||
* correct interface or service.
|
||||
*/
|
||||
protected function entityManager() {
|
||||
return \Drupal::entityManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity type manager.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected function entityTypeManager() {
|
||||
return \Drupal::entityTypeManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the language manager.
|
||||
*
|
||||
|
@ -158,6 +173,13 @@ abstract class Entity implements EntityInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function urlInfo($rel = 'canonical', array $options = []) {
|
||||
return $this->toUrl($rel, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toUrl($rel = 'canonical', array $options = []) {
|
||||
if ($this->id() === NULL) {
|
||||
throw new EntityMalformedException(sprintf('The "%s" entity cannot have a URI as it does not have an ID', $this->getEntityTypeId()));
|
||||
}
|
||||
|
@ -237,26 +259,33 @@ abstract class Entity implements EntityInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function link($text = NULL, $rel = 'canonical', array $options = []) {
|
||||
if (is_null($text)) {
|
||||
return $this->toLink($text, $rel, $options)->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toLink($text = NULL, $rel = 'canonical', array $options = []) {
|
||||
if (!isset($text)) {
|
||||
$text = $this->label();
|
||||
}
|
||||
$url = $this->urlInfo($rel);
|
||||
$url = $this->toUrl($rel);
|
||||
$options += $url->getOptions();
|
||||
$url->setOptions($options);
|
||||
return (new Link($text, $url))->toString();
|
||||
return new Link($text, $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function url($rel = 'canonical', $options = array()) {
|
||||
// While self::urlInfo() will throw an exception if the entity is new,
|
||||
// While self::toUrl() will throw an exception if the entity has no id,
|
||||
// the expected result for a URL is always a string.
|
||||
if ($this->isNew() || !$this->hasLinkTemplate($rel)) {
|
||||
if ($this->id() === NULL || !$this->hasLinkTemplate($rel)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$uri = $this->urlInfo($rel);
|
||||
$uri = $this->toUrl($rel);
|
||||
$options += $uri->getOptions();
|
||||
$uri->setOptions($options);
|
||||
return $uri->toString();
|
||||
|
|
|
@ -101,7 +101,29 @@ interface EntityInterface extends AccessibleInterface, CacheableDependencyInterf
|
|||
public function label();
|
||||
|
||||
/**
|
||||
* Gets the URI elements of the entity.
|
||||
* Gets the URL object for the entity.
|
||||
*
|
||||
* @param string $rel
|
||||
* The link relationship type, for example: canonical or edit-form.
|
||||
* @param array $options
|
||||
* See \Drupal\Core\Routing\UrlGeneratorInterface::generateFromRoute() for
|
||||
* the available options.
|
||||
*
|
||||
* @return \Drupal\Core\Url
|
||||
* The URL object.
|
||||
*
|
||||
* @deprecated in Drupal 8.0.0, intended to be removed in Drupal 9.0.0
|
||||
* Use toUrl() instead.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityInterface::toUrl
|
||||
*/
|
||||
public function urlInfo($rel = 'canonical', array $options = array());
|
||||
|
||||
/**
|
||||
* Gets the URL object for the entity.
|
||||
*
|
||||
* The entity must have an id already. Content entities usually get their IDs
|
||||
* by saving them.
|
||||
*
|
||||
* URI templates might be set in the links array in an annotation, for
|
||||
* example:
|
||||
|
@ -128,8 +150,12 @@ interface EntityInterface extends AccessibleInterface, CacheableDependencyInterf
|
|||
* the available options.
|
||||
*
|
||||
* @return \Drupal\Core\Url
|
||||
* The URL object.
|
||||
*
|
||||
* @throws \Drupal\Core\Entity\EntityMalformedException
|
||||
* @throws \Drupal\Core\Entity\Exception\UndefinedLinkTemplateException
|
||||
*/
|
||||
public function urlInfo($rel = 'canonical', array $options = array());
|
||||
public function toUrl($rel = 'canonical', array $options = array());
|
||||
|
||||
/**
|
||||
* Gets the public URL for this entity.
|
||||
|
@ -142,9 +168,36 @@ interface EntityInterface extends AccessibleInterface, CacheableDependencyInterf
|
|||
*
|
||||
* @return string
|
||||
* The URL for this entity.
|
||||
*
|
||||
* @deprecated in Drupal 8.0.0, intended to be removed in Drupal 9.0.0
|
||||
* Please use toUrl() instead.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityInterface::toUrl
|
||||
*/
|
||||
public function url($rel = 'canonical', $options = array());
|
||||
|
||||
/**
|
||||
* Deprecated way of generating a link to the entity. See toLink().
|
||||
*
|
||||
* @param string|null $text
|
||||
* (optional) The link text for the anchor tag as a translated string.
|
||||
* If NULL, it will use the entity's label. Defaults to NULL.
|
||||
* @param string $rel
|
||||
* (optional) The link relationship type. Defaults to 'canonical'.
|
||||
* @param array $options
|
||||
* See \Drupal\Core\Routing\UrlGeneratorInterface::generateFromRoute() for
|
||||
* the available options.
|
||||
*
|
||||
* @return string
|
||||
* An HTML string containing a link to the entity.
|
||||
*
|
||||
* @deprecated in Drupal 8.0.0, intended to be removed in Drupal 9.0.0
|
||||
* Please use toLink() instead.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityInterface::toLink
|
||||
*/
|
||||
public function link($text = NULL, $rel = 'canonical', array $options = []);
|
||||
|
||||
/**
|
||||
* Generates the HTML for a link to this entity.
|
||||
*
|
||||
|
@ -157,10 +210,13 @@ interface EntityInterface extends AccessibleInterface, CacheableDependencyInterf
|
|||
* See \Drupal\Core\Routing\UrlGeneratorInterface::generateFromRoute() for
|
||||
* the available options.
|
||||
*
|
||||
* @return string
|
||||
* An HTML string containing a link to the entity.
|
||||
* @return \Drupal\Core\Link
|
||||
* A Link to the entity.
|
||||
*
|
||||
* @throws \Drupal\Core\Entity\EntityMalformedException
|
||||
* @throws \Drupal\Core\Entity\Exception\UndefinedLinkTemplateException
|
||||
*/
|
||||
public function link($text = NULL, $rel = 'canonical', array $options = []);
|
||||
public function toLink($text = NULL, $rel = 'canonical', array $options = []);
|
||||
|
||||
/**
|
||||
* Indicates if a link template exists for a given key.
|
||||
|
|
|
@ -30,7 +30,7 @@ interface SelectionInterface extends PluginFormInterface {
|
|||
public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0);
|
||||
|
||||
/**
|
||||
* Counts entities that are referenceable by a given field.
|
||||
* Counts entities that are referenceable.
|
||||
*
|
||||
* @return int
|
||||
* The number of referenceable entities.
|
||||
|
@ -38,7 +38,7 @@ interface SelectionInterface extends PluginFormInterface {
|
|||
public function countReferenceableEntities($match = NULL, $match_operator = 'CONTAINS');
|
||||
|
||||
/**
|
||||
* Validates that entities can be referenced by this field.
|
||||
* Validates which existing entities can be referenced.
|
||||
*
|
||||
* @return array
|
||||
* An array of valid entity IDs.
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Entity\EntityReferenceSelection\SelectionWithAutocreateInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Entity\EntityReferenceSelection;
|
||||
|
||||
/**
|
||||
* Interface for Selection plugins that support newly created entities.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManager
|
||||
* @see \Drupal\Core\Entity\Annotation\EntityReferenceSelection
|
||||
* @see plugin_api
|
||||
*/
|
||||
interface SelectionWithAutocreateInterface {
|
||||
|
||||
/**
|
||||
* Creates a new entity object that can be used as a valid reference.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID.
|
||||
* @param string $bundle
|
||||
* The bundle name.
|
||||
* @param string $label
|
||||
* The entity label.
|
||||
* @param int $uid
|
||||
* The entity owner ID, if the entity type supports it.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
* An unsaved entity object.
|
||||
*/
|
||||
public function createNewEntity($entity_type_id, $bundle, $label, $uid);
|
||||
|
||||
/**
|
||||
* Validates which newly created entities can be referenced.
|
||||
*
|
||||
* This method should replicate the logic implemented by
|
||||
* \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface::validateReferenceableEntities(),
|
||||
* but applied to newly created entities that have not been saved yet.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface[] $entities
|
||||
* An array of entities to check.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface[]
|
||||
* The incoming $entities parameter, filtered for valid entities. Array keys
|
||||
* are preserved.
|
||||
*/
|
||||
public function validateReferenceableNewEntities(array $entities);
|
||||
|
||||
}
|
|
@ -97,18 +97,18 @@ class EntityTypeBundleInfo implements EntityTypeBundleInfoInterface {
|
|||
}
|
||||
else {
|
||||
$this->bundleInfo = $this->moduleHandler->invokeAll('entity_bundle_info');
|
||||
// First look for entity types that act as bundles for others, load them
|
||||
// and add them as bundles.
|
||||
foreach ($this->entityTypeManager->getDefinitions() as $type => $entity_type) {
|
||||
if ($entity_type->getBundleOf()) {
|
||||
foreach ($this->entityTypeManager->getStorage($type)->loadMultiple() as $entity) {
|
||||
$this->bundleInfo[$entity_type->getBundleOf()][$entity->id()]['label'] = $entity->label();
|
||||
// First look for entity types that act as bundles for others, load them
|
||||
// and add them as bundles.
|
||||
if ($bundle_entity_type = $entity_type->getBundleEntityType()) {
|
||||
foreach ($this->entityTypeManager->getStorage($bundle_entity_type)->loadMultiple() as $entity) {
|
||||
$this->bundleInfo[$type][$entity->id()]['label'] = $entity->label();
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($this->entityTypeManager->getDefinitions() as $type => $entity_type) {
|
||||
// If no bundles are provided, use the entity type name and label.
|
||||
if (!isset($this->bundleInfo[$type])) {
|
||||
// If entity type bundles are not supported and
|
||||
// hook_entity_bundle_info() has not already set up bundle
|
||||
// information, use the entity type name and label.
|
||||
elseif (!isset($this->bundleInfo[$type])) {
|
||||
$this->bundleInfo[$type][$type]['label'] = $entity_type->getLabel();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ use Drupal\Component\Utility\Html;
|
|||
use Drupal\Core\Database\Query\AlterableInterface;
|
||||
use Drupal\Core\Database\Query\SelectInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Entity\EntityReferenceSelection\SelectionWithAutocreateInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
@ -18,6 +19,7 @@ use Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface;
|
|||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\user\EntityOwnerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
|
@ -40,7 +42,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
|||
* deriver = "Drupal\Core\Entity\Plugin\Derivative\DefaultSelectionDeriver"
|
||||
* )
|
||||
*/
|
||||
class DefaultSelection extends PluginBase implements SelectionInterface, ContainerFactoryPluginInterface {
|
||||
class DefaultSelection extends PluginBase implements SelectionInterface, SelectionWithAutocreateInterface, ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
|
@ -288,6 +290,38 @@ class DefaultSelection extends PluginBase implements SelectionInterface, Contain
|
|||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createNewEntity($entity_type_id, $bundle, $label, $uid) {
|
||||
$entity_type = $this->entityManager->getDefinition($entity_type_id);
|
||||
$bundle_key = $entity_type->getKey('bundle');
|
||||
$label_key = $entity_type->getKey('label');
|
||||
|
||||
$entity = $this->entityManager->getStorage($entity_type_id)->create(array(
|
||||
$bundle_key => $bundle,
|
||||
$label_key => $label,
|
||||
));
|
||||
|
||||
if ($entity instanceof EntityOwnerInterface) {
|
||||
$entity->setOwnerId($uid);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateReferenceableNewEntities(array $entities) {
|
||||
return array_filter($entities, function ($entity) {
|
||||
if (isset($this->configuration['handler_settings']['target_bundles'])) {
|
||||
return in_array($entity->bundle(), $this->configuration['handler_settings']['target_bundles']);
|
||||
}
|
||||
return TRUE;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an EntityQuery to get referenceable entities.
|
||||
*
|
||||
|
|
|
@ -26,10 +26,24 @@ class ValidReferenceConstraint extends Constraint {
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
public $message = 'The referenced entity (%type: %id) does not exist.';
|
||||
public $message = 'This entity (%type: %id) cannot be referenced.';
|
||||
|
||||
/**
|
||||
* Validation message when the target_id is empty.
|
||||
* Violation message when the entity does not exist.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $nonExistingMessage = 'The referenced entity (%type: %id) does not exist.';
|
||||
|
||||
/**
|
||||
* Violation message when a new entity ("autocreate") is invalid.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $invalidAutocreateMessage = 'This entity (%type: %label) cannot be referenced.';
|
||||
|
||||
/**
|
||||
* Violation message when the target_id is empty.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
|
|
|
@ -7,39 +7,142 @@
|
|||
|
||||
namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface;
|
||||
use Drupal\Core\Entity\EntityReferenceSelection\SelectionWithAutocreateInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
|
||||
/**
|
||||
* Checks if referenced entities are valid.
|
||||
*/
|
||||
class ValidReferenceConstraintValidator extends ConstraintValidator {
|
||||
class ValidReferenceConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* The selection plugin manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface
|
||||
*/
|
||||
protected $selectionManager;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a ValidReferenceConstraintValidator object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface $selection_manager
|
||||
* The selection plugin manager.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(SelectionPluginManagerInterface $selection_manager, EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->selectionManager = $selection_manager;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('plugin.manager.entity_reference_selection'),
|
||||
$container->get('entity_type.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($value, Constraint $constraint) {
|
||||
/** @var \Drupal\Core\Field\FieldItemInterface $value */
|
||||
/** @var \Drupal\Core\Field\FieldItemListInterface $value */
|
||||
/** @var ValidReferenceConstraint $constraint */
|
||||
if (!isset($value)) {
|
||||
return;
|
||||
}
|
||||
// We don't use a regular NotNull constraint for the target_id property as
|
||||
// a NULL value is valid if the entity property contains an unsaved entity.
|
||||
// @see \Drupal\Core\TypedData\DataReferenceTargetDefinition::getConstraints
|
||||
if (!$value->isEmpty() && $value->target_id === NULL && !$value->entity->isNew()) {
|
||||
$this->context->addViolation($constraint->nullMessage);
|
||||
|
||||
// Collect new entities and IDs of existing entities across the field items.
|
||||
$new_entities = [];
|
||||
$target_ids = [];
|
||||
foreach ($value as $delta => $item) {
|
||||
$target_id = $item->target_id;
|
||||
// We don't use a regular NotNull constraint for the target_id property as
|
||||
// NULL is allowed if the entity property contains an unsaved entity.
|
||||
// @see \Drupal\Core\TypedData\DataReferenceTargetDefinition::getConstraints()
|
||||
if (!$item->isEmpty() && $target_id === NULL) {
|
||||
if (!$item->entity->isNew()) {
|
||||
$this->context->buildViolation($constraint->nullMessage)
|
||||
->atPath((string) $delta)
|
||||
->addViolation();
|
||||
return;
|
||||
}
|
||||
$new_entities[$delta] = $item->entity;
|
||||
}
|
||||
|
||||
// '0' or NULL are considered valid empty references.
|
||||
if (!empty($target_id)) {
|
||||
$target_ids[$delta] = $target_id;
|
||||
}
|
||||
}
|
||||
|
||||
// Early opt-out if nothing to validate.
|
||||
if (!$new_entities && !$target_ids) {
|
||||
return;
|
||||
}
|
||||
$id = $value->get('target_id')->getValue();
|
||||
// '0' or NULL are considered valid empty references.
|
||||
if (empty($id)) {
|
||||
return;
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface $handler * */
|
||||
$handler = $this->selectionManager->getSelectionHandler($value->getFieldDefinition());
|
||||
$target_type_id = $value->getFieldDefinition()->getSetting('target_type');
|
||||
|
||||
// Add violations on deltas with a new entity that is not valid.
|
||||
if ($new_entities) {
|
||||
if ($handler instanceof SelectionWithAutocreateInterface) {
|
||||
$valid_new_entities = $handler->validateReferenceableNewEntities($new_entities);
|
||||
$invalid_new_entities = array_diff_key($new_entities, $valid_new_entities);
|
||||
}
|
||||
else {
|
||||
// If the selection handler does not support referencing newly created
|
||||
// entities, all of them should be invalidated.
|
||||
$invalid_new_entities = $new_entities;
|
||||
}
|
||||
|
||||
foreach ($invalid_new_entities as $delta => $entity) {
|
||||
$this->context->buildViolation($constraint->invalidAutocreateMessage)
|
||||
->setParameter('%type', $target_type_id)
|
||||
->setParameter('%label', $entity->label())
|
||||
->atPath((string) $delta . '.entity')
|
||||
->setInvalidValue($entity)
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
$referenced_entity = $value->get('entity')->getValue();
|
||||
if (!$referenced_entity) {
|
||||
$type = $value->getFieldDefinition()->getSetting('target_type');
|
||||
$this->context->addViolation($constraint->message, array('%type' => $type, '%id' => $id));
|
||||
|
||||
// Add violations on deltas with a target_id that is not valid.
|
||||
if ($target_ids) {
|
||||
$valid_target_ids = $handler->validateReferenceableEntities($target_ids);
|
||||
if ($invalid_target_ids = array_diff($target_ids, $valid_target_ids)) {
|
||||
// For accuracy of the error message, differentiate non-referenceable
|
||||
// and non-existent entities.
|
||||
$target_type = $this->entityTypeManager->getDefinition($target_type_id);
|
||||
$existing_ids = $this->entityTypeManager->getStorage($target_type_id)->getQuery()
|
||||
->condition($target_type->getKey('id'), $invalid_target_ids, 'IN')
|
||||
->execute();
|
||||
foreach ($invalid_target_ids as $delta => $target_id) {
|
||||
$message = in_array($target_id, $existing_ids) ? $constraint->message : $constraint->nonExistingMessage;
|
||||
$this->context->buildViolation($message)
|
||||
->setParameter('%type', $target_type_id)
|
||||
->setParameter('%id', $target_id)
|
||||
->atPath((string) $delta . '.target_id')
|
||||
->setInvalidValue($target_id)
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,7 +7,11 @@
|
|||
|
||||
namespace Drupal\Core\Entity\Routing;
|
||||
|
||||
use Drupal\Core\Entity\EntityHandlerInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
|
@ -24,7 +28,33 @@ use Symfony\Component\Routing\RouteCollection;
|
|||
*
|
||||
* @internal
|
||||
*/
|
||||
class DefaultHtmlRouteProvider implements EntityRouteProviderInterface {
|
||||
class DefaultHtmlRouteProvider implements EntityRouteProviderInterface, EntityHandlerInterface {
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* Constructs a new DefaultHtmlRouteProvider.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $entity_manager) {
|
||||
$this->entityManager = $entity_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$container->get('entity.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -71,6 +101,12 @@ class DefaultHtmlRouteProvider implements EntityRouteProviderInterface {
|
|||
->setOption('parameters', [
|
||||
$entity_type_id => ['type' => 'entity:' . $entity_type_id],
|
||||
]);
|
||||
|
||||
// Entity types with serial IDs can specify this in their route
|
||||
// requirements, improving the matching process.
|
||||
if ($this->getEntityTypeIdKeyType($entity_type) === 'integer') {
|
||||
$route->setRequirement($entity_type_id, '\d+');
|
||||
}
|
||||
return $route;
|
||||
}
|
||||
}
|
||||
|
@ -102,6 +138,12 @@ class DefaultHtmlRouteProvider implements EntityRouteProviderInterface {
|
|||
->setOption('parameters', [
|
||||
$entity_type_id => ['type' => 'entity:' . $entity_type_id],
|
||||
]);
|
||||
|
||||
// Entity types with serial IDs can specify this in their route
|
||||
// requirements, improving the matching process.
|
||||
if ($this->getEntityTypeIdKeyType($entity_type) === 'integer') {
|
||||
$route->setRequirement($entity_type_id, '\d+');
|
||||
}
|
||||
return $route;
|
||||
}
|
||||
}
|
||||
|
@ -128,8 +170,33 @@ class DefaultHtmlRouteProvider implements EntityRouteProviderInterface {
|
|||
->setOption('parameters', [
|
||||
$entity_type_id => ['type' => 'entity:' . $entity_type_id],
|
||||
]);
|
||||
|
||||
// Entity types with serial IDs can specify this in their route
|
||||
// requirements, improving the matching process.
|
||||
if ($this->getEntityTypeIdKeyType($entity_type) === 'integer') {
|
||||
$route->setRequirement($entity_type_id, '\d+');
|
||||
}
|
||||
return $route;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type of the ID key for a given entity type.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* An entity type.
|
||||
*
|
||||
* @return string|null
|
||||
* The type of the ID key for a given entity type, or NULL if the entity
|
||||
* type does not support fields.
|
||||
*/
|
||||
protected function getEntityTypeIdKeyType(EntityTypeInterface $entity_type) {
|
||||
if (!$entity_type->isSubclassOf(FieldableEntityInterface::class)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type->id());
|
||||
return $field_storage_definitions[$entity_type->getKey('id')]->getType();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1916,16 +1916,16 @@ function hook_entity_extra_field_info() {
|
|||
// Visibility of the ordering of the language selector is the same as on the
|
||||
// node/add form.
|
||||
if ($module_language_enabled) {
|
||||
$configuration = ContentLanguageSettings::loadByEntityTypeBundle('node', $bundle->type);
|
||||
$configuration = ContentLanguageSettings::loadByEntityTypeBundle('node', $bundle->id());
|
||||
if ($configuration->isLanguageAlterable()) {
|
||||
$extra['node'][$bundle->type]['form']['language'] = array(
|
||||
$extra['node'][$bundle->id()]['form']['language'] = array(
|
||||
'label' => t('Language'),
|
||||
'description' => $description,
|
||||
'weight' => 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
$extra['node'][$bundle->type]['display']['language'] = array(
|
||||
$extra['node'][$bundle->id()]['display']['language'] = array(
|
||||
'label' => t('Language'),
|
||||
'description' => $description,
|
||||
'weight' => 0,
|
||||
|
@ -1948,8 +1948,8 @@ function hook_entity_extra_field_info() {
|
|||
function hook_entity_extra_field_info_alter(&$info) {
|
||||
// Force node title to always be at the top of the list by default.
|
||||
foreach (NodeType::loadMultiple() as $bundle) {
|
||||
if (isset($info['node'][$bundle->type]['form']['title'])) {
|
||||
$info['node'][$bundle->type]['form']['title']['weight'] = -20;
|
||||
if (isset($info['node'][$bundle->id()]['form']['title'])) {
|
||||
$info['node'][$bundle->id()]['form']['title']['weight'] = -20;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\EventSubscriber\ContentControllerSubscriber.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\EventSubscriber;
|
||||
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
/**
|
||||
* Sets the request format onto the request object.
|
||||
*
|
||||
* @todo Remove this event subscriber after
|
||||
* https://www.drupal.org/node/2092647 has landed.
|
||||
*/
|
||||
class ContentControllerSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* Sets the _controller on a request when a _form is defined.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
|
||||
* The event to process.
|
||||
*/
|
||||
public function onRequestDeriveFormWrapper(GetResponseEvent $event) {
|
||||
$request = $event->getRequest();
|
||||
|
||||
if ($request->attributes->has('_form')) {
|
||||
$request->attributes->set('_controller', 'controller.form:getContentResult');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the methods in this class that should be listeners.
|
||||
*
|
||||
* @return array
|
||||
* An array of event listener definitions.
|
||||
*/
|
||||
static function getSubscribedEvents() {
|
||||
$events[KernelEvents::REQUEST][] = array('onRequestDeriveFormWrapper', 25);
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
|
@ -147,7 +147,12 @@ class DefaultExceptionHtmlSubscriber extends HttpExceptionSubscriberBase {
|
|||
}
|
||||
|
||||
$response = $this->httpKernel->handle($sub_request, HttpKernelInterface::SUB_REQUEST);
|
||||
$response->setStatusCode($status_code);
|
||||
// Only 2xx responses should have their status code overridden; any
|
||||
// other status code should be passed on: redirects (3xx), error (5xx)…
|
||||
// @see https://www.drupal.org/node/2603788#comment-10504916
|
||||
if ($response->isSuccessful()) {
|
||||
$response->setStatusCode($status_code);
|
||||
}
|
||||
|
||||
// Persist any special HTTP headers that were set on the exception.
|
||||
if ($exception instanceof HttpExceptionInterface) {
|
||||
|
|
|
@ -48,10 +48,6 @@ class HtmlResponsePlaceholderStrategySubscriber implements EventSubscriberInterf
|
|||
* The event to process.
|
||||
*/
|
||||
public function onRespond(FilterResponseEvent $event) {
|
||||
if (!$event->isMasterRequest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$response = $event->getResponse();
|
||||
if (!$response instanceof HtmlResponse) {
|
||||
return;
|
||||
|
|
|
@ -42,10 +42,6 @@ class HtmlResponseSubscriber implements EventSubscriberInterface {
|
|||
* The event to process.
|
||||
*/
|
||||
public function onRespond(FilterResponseEvent $event) {
|
||||
if (!$event->isMasterRequest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$response = $event->getResponse();
|
||||
if (!$response instanceof HtmlResponse) {
|
||||
return;
|
||||
|
|
|
@ -483,4 +483,18 @@ class ThemeHandler implements ThemeHandlerInterface {
|
|||
throw new \InvalidArgumentException(sprintf('The theme %s does not exist.', $name));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasUi($name) {
|
||||
$themes = $this->listInfo();
|
||||
if (isset($themes[$name])) {
|
||||
if (!empty($themes[$name]->info['hidden'])) {
|
||||
$theme_config = $this->configFactory->get('system.theme');
|
||||
return $name == $theme_config->get('default') || $name == $theme_config->get('admin');
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -208,4 +208,18 @@ interface ThemeHandlerInterface {
|
|||
*/
|
||||
public function getTheme($name);
|
||||
|
||||
/**
|
||||
* Determines if a theme should be shown in the user interface.
|
||||
*
|
||||
* To be shown in the UI the theme has to be installed. If the theme is hidden
|
||||
* it will not be shown unless it is the default or admin theme.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the theme to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the theme should be shown in the UI, FALSE if not.
|
||||
*/
|
||||
public function hasUi($name);
|
||||
|
||||
}
|
||||
|
|
|
@ -15,6 +15,16 @@ use Drupal\Core\Form\FormStateInterface;
|
|||
*/
|
||||
class EntityReferenceFieldItemList extends FieldItemList implements EntityReferenceFieldItemListInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConstraints() {
|
||||
$constraints = parent::getConstraints();
|
||||
$constraint_manager = $this->getTypedDataManager()->getValidationConstraintManager();
|
||||
$constraints[] = $constraint_manager->create('ValidReference', []);
|
||||
return $constraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -40,9 +40,6 @@ use Drupal\Core\Validation\Plugin\Validation\Constraint\AllowedValuesConstraint;
|
|||
* default_widget = "entity_reference_autocomplete",
|
||||
* default_formatter = "entity_reference_label",
|
||||
* list_class = "\Drupal\Core\Field\EntityReferenceFieldItemList",
|
||||
* default_widget = "entity_reference_autocomplete",
|
||||
* default_formatter = "entity_reference_label",
|
||||
* constraints = {"ValidReference" = {}}
|
||||
* )
|
||||
*/
|
||||
class EntityReferenceItem extends FieldItemBase implements OptionsProviderInterface, PreconfiguredFieldUiOptionsInterface {
|
||||
|
@ -165,20 +162,6 @@ class EntityReferenceItem extends FieldItemBase implements OptionsProviderInterf
|
|||
unset($constraints[$key]);
|
||||
}
|
||||
}
|
||||
list($current_handler) = explode(':', $this->getSetting('handler'), 2);
|
||||
if ($current_handler === 'default') {
|
||||
$handler_settings = $this->getSetting('handler_settings');
|
||||
if (isset($handler_settings['target_bundles'])) {
|
||||
$constraint_manager = \Drupal::typedDataManager()->getValidationConstraintManager();
|
||||
$constraints[] = $constraint_manager->create('ComplexData', [
|
||||
'entity' => [
|
||||
'Bundle' => [
|
||||
'bundle' => $handler_settings['target_bundles'],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
return $constraints;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
namespace Drupal\Core\Field\Plugin\Field\FieldType;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\TypedData\DataDefinition;
|
||||
|
||||
|
@ -77,4 +79,16 @@ class UriItem extends StringItem {
|
|||
return parent::isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
|
||||
$values = parent::generateSampleValue($field_definition);
|
||||
$suffix_length = $field_definition->getSetting('max_length') - 7;
|
||||
foreach ($values as $key => $value) {
|
||||
$values[$key] = 'http://' . Unicode::substr($value, 0, $suffix_length);
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -304,7 +304,9 @@ class LocalTaskManager extends DefaultPluginManager implements LocalTaskManagerI
|
|||
}
|
||||
// Pre-fetch all routes involved in the tree. This reduces the number
|
||||
// of SQL queries that would otherwise be triggered by the access manager.
|
||||
$routes = $route_names ? $this->routeProvider->getRoutesByNames($route_names) : array();
|
||||
if ($route_names) {
|
||||
$this->routeProvider->getRoutesByNames($route_names);
|
||||
}
|
||||
|
||||
foreach ($tree as $level => $instances) {
|
||||
/** @var $instances \Drupal\Core\Menu\LocalTaskInterface[] */
|
||||
|
|
|
@ -351,8 +351,11 @@ class MenuLinkManager implements MenuLinkManagerInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function addDefinition($id, array $definition) {
|
||||
if ($this->treeStorage->load($id) || $id === '') {
|
||||
throw new PluginException("The ID $id already exists as a plugin definition or is not valid");
|
||||
if ($this->treeStorage->load($id)) {
|
||||
throw new PluginException("The menu link ID $id already exists as a plugin definition");
|
||||
}
|
||||
elseif ($id === '') {
|
||||
throw new PluginException("The menu link ID cannot be empty");
|
||||
}
|
||||
// Add defaults, so there is no requirement to specify everything.
|
||||
$this->processDefinition($definition, $id);
|
||||
|
|
|
@ -177,6 +177,7 @@ class MenuLinkTree implements MenuLinkTreeInterface {
|
|||
// Add the theme wrapper for outer markup.
|
||||
// Allow menu-specific theme overrides.
|
||||
$build['#theme'] = 'menu__' . strtr($menu_name, '-', '_');
|
||||
$build['#menu_name'] = $menu_name;
|
||||
$build['#items'] = $items;
|
||||
// Set cache tag.
|
||||
$build['#cache']['tags'][] = 'config:system.menu.' . $menu_name;
|
||||
|
|
|
@ -11,9 +11,14 @@ use Drupal\Core\Cache\Cache;
|
|||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Database\Query\Condition;
|
||||
|
||||
/**
|
||||
* Provides a class for CRUD operations on path aliases.
|
||||
*
|
||||
* All queries perform case-insensitive matching on the 'source' and 'alias'
|
||||
* fields, so the aliases '/test-alias' and '/test-Alias' are considered to be
|
||||
* the same, and will both refer to the same internal system path.
|
||||
*/
|
||||
class AliasStorage implements AliasStorageInterface {
|
||||
/**
|
||||
|
@ -98,7 +103,13 @@ class AliasStorage implements AliasStorageInterface {
|
|||
public function load($conditions) {
|
||||
$select = $this->connection->select('url_alias');
|
||||
foreach ($conditions as $field => $value) {
|
||||
$select->condition($field, $value);
|
||||
if ($field == 'source' || $field == 'alias') {
|
||||
// Use LIKE for case-insensitive matching.
|
||||
$select->condition($field, $this->connection->escapeLike($value), 'LIKE');
|
||||
}
|
||||
else {
|
||||
$select->condition($field, $value);
|
||||
}
|
||||
}
|
||||
return $select
|
||||
->fields('url_alias')
|
||||
|
@ -115,7 +126,13 @@ class AliasStorage implements AliasStorageInterface {
|
|||
$path = $this->load($conditions);
|
||||
$query = $this->connection->delete('url_alias');
|
||||
foreach ($conditions as $field => $value) {
|
||||
$query->condition($field, $value);
|
||||
if ($field == 'source' || $field == 'alias') {
|
||||
// Use LIKE for case-insensitive matching.
|
||||
$query->condition($field, $this->connection->escapeLike($value), 'LIKE');
|
||||
}
|
||||
else {
|
||||
$query->condition($field, $value);
|
||||
}
|
||||
}
|
||||
$deleted = $query->execute();
|
||||
// @todo Switch to using an event for this instead of a hook.
|
||||
|
@ -128,90 +145,101 @@ class AliasStorage implements AliasStorageInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function preloadPathAlias($preloaded, $langcode) {
|
||||
$args = array(
|
||||
':system[]' => $preloaded,
|
||||
':langcode' => $langcode,
|
||||
':langcode_undetermined' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
);
|
||||
$langcode_list = [$langcode, LanguageInterface::LANGCODE_NOT_SPECIFIED];
|
||||
$select = $this->connection->select('url_alias')
|
||||
->fields('url_alias', ['source', 'alias']);
|
||||
|
||||
if (!empty($preloaded)) {
|
||||
$conditions = new Condition('OR');
|
||||
foreach ($preloaded as $preloaded_item) {
|
||||
$conditions->condition('source', $this->connection->escapeLike($preloaded_item), 'LIKE');
|
||||
}
|
||||
$select->condition($conditions);
|
||||
}
|
||||
|
||||
// Always get the language-specific alias before the language-neutral one.
|
||||
// For example 'de' is less than 'und' so the order needs to be ASC, while
|
||||
// 'xx-lolspeak' is more than 'und' so the order needs to be DESC. We also
|
||||
// order by pid ASC so that fetchAllKeyed() returns the most recently
|
||||
// created alias for each source. Subsequent queries using fetchField() must
|
||||
// use pid DESC to have the same effect. For performance reasons, the query
|
||||
// builder is not used here.
|
||||
// use pid DESC to have the same effect.
|
||||
if ($langcode == LanguageInterface::LANGCODE_NOT_SPECIFIED) {
|
||||
// Prevent PDO from complaining about a token the query doesn't use.
|
||||
unset($args[':langcode']);
|
||||
$result = $this->connection->query('SELECT source, alias FROM {url_alias} WHERE source IN ( :system[] ) AND langcode = :langcode_undetermined ORDER BY pid ASC', $args);
|
||||
array_pop($langcode_list);
|
||||
}
|
||||
elseif ($langcode < LanguageInterface::LANGCODE_NOT_SPECIFIED) {
|
||||
$result = $this->connection->query('SELECT source, alias FROM {url_alias} WHERE source IN ( :system[] ) AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode ASC, pid ASC', $args);
|
||||
$select->orderBy('langcode', 'ASC');
|
||||
}
|
||||
else {
|
||||
$result = $this->connection->query('SELECT source, alias FROM {url_alias} WHERE source IN ( :system[] ) AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode DESC, pid ASC', $args);
|
||||
$select->orderBy('langcode', 'DESC');
|
||||
}
|
||||
|
||||
return $result->fetchAllKeyed();
|
||||
$select->orderBy('pid', 'ASC');
|
||||
$select->condition('langcode', $langcode_list, 'IN');
|
||||
return $select->execute()->fetchAllKeyed();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lookupPathAlias($path, $langcode) {
|
||||
$args = array(
|
||||
':source' => $path,
|
||||
':langcode' => $langcode,
|
||||
':langcode_undetermined' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
);
|
||||
// See the queries above.
|
||||
$source = $this->connection->escapeLike($path);
|
||||
$langcode_list = [$langcode, LanguageInterface::LANGCODE_NOT_SPECIFIED];
|
||||
|
||||
// See the queries above. Use LIKE for case-insensitive matching.
|
||||
$select = $this->connection->select('url_alias')
|
||||
->fields('url_alias', ['alias'])
|
||||
->condition('source', $source, 'LIKE');
|
||||
if ($langcode == LanguageInterface::LANGCODE_NOT_SPECIFIED) {
|
||||
unset($args[':langcode']);
|
||||
$alias = $this->connection->query("SELECT alias FROM {url_alias} WHERE source = :source AND langcode = :langcode_undetermined ORDER BY pid DESC", $args)->fetchField();
|
||||
array_pop($langcode_list);
|
||||
}
|
||||
elseif ($langcode > LanguageInterface::LANGCODE_NOT_SPECIFIED) {
|
||||
$alias = $this->connection->query("SELECT alias FROM {url_alias} WHERE source = :source AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode DESC, pid DESC", $args)->fetchField();
|
||||
$select->orderBy('langcode', 'DESC');
|
||||
}
|
||||
else {
|
||||
$alias = $this->connection->query("SELECT alias FROM {url_alias} WHERE source = :source AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode ASC, pid DESC", $args)->fetchField();
|
||||
$select->orderBy('langcode', 'ASC');
|
||||
}
|
||||
|
||||
return $alias;
|
||||
$select->orderBy('pid', 'DESC');
|
||||
$select->condition('langcode', $langcode_list, 'IN');
|
||||
return $select->execute()->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lookupPathSource($path, $langcode) {
|
||||
$args = array(
|
||||
':alias' => $path,
|
||||
':langcode' => $langcode,
|
||||
':langcode_undetermined' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
);
|
||||
// See the queries above.
|
||||
$alias = $this->connection->escapeLike($path);
|
||||
$langcode_list = [$langcode, LanguageInterface::LANGCODE_NOT_SPECIFIED];
|
||||
|
||||
// See the queries above. Use LIKE for case-insensitive matching.
|
||||
$select = $this->connection->select('url_alias')
|
||||
->fields('url_alias', ['source'])
|
||||
->condition('alias', $alias, 'LIKE');
|
||||
if ($langcode == LanguageInterface::LANGCODE_NOT_SPECIFIED) {
|
||||
unset($args[':langcode']);
|
||||
$result = $this->connection->query("SELECT source FROM {url_alias} WHERE alias = :alias AND langcode = :langcode_undetermined ORDER BY pid DESC", $args);
|
||||
array_pop($langcode_list);
|
||||
}
|
||||
elseif ($langcode > LanguageInterface::LANGCODE_NOT_SPECIFIED) {
|
||||
$result = $this->connection->query("SELECT source FROM {url_alias} WHERE alias = :alias AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode DESC, pid DESC", $args);
|
||||
$select->orderBy('langcode', 'DESC');
|
||||
}
|
||||
else {
|
||||
$result = $this->connection->query("SELECT source FROM {url_alias} WHERE alias = :alias AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode ASC, pid DESC", $args);
|
||||
$select->orderBy('langcode', 'ASC');
|
||||
}
|
||||
|
||||
return $result->fetchField();
|
||||
$select->orderBy('pid', 'DESC');
|
||||
$select->condition('langcode', $langcode_list, 'IN');
|
||||
return $select->execute()->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function aliasExists($alias, $langcode, $source = NULL) {
|
||||
// Use LIKE and NOT LIKE for case-insensitive matching.
|
||||
$query = $this->connection->select('url_alias')
|
||||
->condition('alias', $alias)
|
||||
->condition('alias', $this->connection->escapeLike($alias), 'LIKE')
|
||||
->condition('langcode', $langcode);
|
||||
if (!empty($source)) {
|
||||
$query->condition('source', $source, '<>');
|
||||
$query->condition('source', $this->connection->escapeLike($source), 'NOT LIKE');
|
||||
}
|
||||
$query->addExpression('1');
|
||||
$query->range(0, 1);
|
||||
|
|
|
@ -44,6 +44,9 @@ interface AliasStorageInterface {
|
|||
/**
|
||||
* Fetches a specific URL alias from the database.
|
||||
*
|
||||
* The default implementation performs case-insensitive matching on the
|
||||
* 'source' and 'alias' strings.
|
||||
*
|
||||
* @param array $conditions
|
||||
* An array of query conditions.
|
||||
*
|
||||
|
@ -60,6 +63,9 @@ interface AliasStorageInterface {
|
|||
/**
|
||||
* Deletes a URL alias.
|
||||
*
|
||||
* The default implementation performs case-insensitive matching on the
|
||||
* 'source' and 'alias' strings.
|
||||
*
|
||||
* @param array $conditions
|
||||
* An array of criteria.
|
||||
*/
|
||||
|
@ -82,6 +88,9 @@ interface AliasStorageInterface {
|
|||
/**
|
||||
* Returns an alias of Drupal system URL.
|
||||
*
|
||||
* The default implementation performs case-insensitive matching on the
|
||||
* 'source' and 'alias' strings.
|
||||
*
|
||||
* @param string $path
|
||||
* The path to investigate for corresponding path aliases.
|
||||
* @param string $langcode
|
||||
|
@ -96,6 +105,9 @@ interface AliasStorageInterface {
|
|||
/**
|
||||
* Returns Drupal system URL of an alias.
|
||||
*
|
||||
* The default implementation performs case-insensitive matching on the
|
||||
* 'source' and 'alias' strings.
|
||||
*
|
||||
* @param string $path
|
||||
* The path to investigate for corresponding system URLs.
|
||||
* @param string $langcode
|
||||
|
@ -110,6 +122,9 @@ interface AliasStorageInterface {
|
|||
/**
|
||||
* Checks if alias already exists.
|
||||
*
|
||||
* The default implementation performs case-insensitive matching on the
|
||||
* 'source' and 'alias' strings.
|
||||
*
|
||||
* @param string $alias
|
||||
* Alias to check against.
|
||||
* @param string $langcode
|
||||
|
@ -135,8 +150,9 @@ interface AliasStorageInterface {
|
|||
*
|
||||
* @param array $header
|
||||
* Table header.
|
||||
* @param string[]|null $keys
|
||||
* (optional) Search keys.
|
||||
* @param string|null $keys
|
||||
* (optional) Search keyword that may include one or more '*' as wildcard
|
||||
* values.
|
||||
*
|
||||
* @return array
|
||||
* Array of items to be displayed on the current page.
|
||||
|
|
|
@ -21,8 +21,27 @@ interface OutboundPathProcessorInterface {
|
|||
* @param string $path
|
||||
* The path to process, with a leading slash.
|
||||
* @param array $options
|
||||
* An array of options such as would be passed to the generator's
|
||||
* generateFromRoute() method.
|
||||
* (optional) An associative array of additional options, with the following
|
||||
* elements:
|
||||
* - 'query': An array of query key/value-pairs (without any URL-encoding)
|
||||
* to append to the URL.
|
||||
* - 'fragment': A fragment identifier (named anchor) to append to the URL.
|
||||
* Do not include the leading '#' character.
|
||||
* - 'absolute': Defaults to FALSE. Whether to force the output to be an
|
||||
* absolute link (beginning with http:). Useful for links that will be
|
||||
* displayed outside the site, such as in an RSS feed.
|
||||
* - 'language': An optional language object used to look up the alias
|
||||
* for the URL. If $options['language'] is omitted, it defaults to the
|
||||
* current language for the language type LanguageInterface::TYPE_URL.
|
||||
* - 'https': Whether this URL should point to a secure location. If not
|
||||
* defined, the current scheme is used, so the user stays on HTTP or HTTPS
|
||||
* respectively. TRUE enforces HTTPS and FALSE enforces HTTP.
|
||||
* - 'base_url': Only used internally by a path processor, for example, to
|
||||
* modify the base URL when a language dependent URL requires so.
|
||||
* - 'prefix': Only used internally, to modify the path when a language
|
||||
* dependent URL requires so.
|
||||
* - 'route': The route object for the given path. It will be set by
|
||||
* \Drupal\Core\Routing\UrlGenerator::generateFromRoute().
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The HttpRequest object representing the current request.
|
||||
* @param \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata
|
||||
|
|
|
@ -269,6 +269,11 @@ abstract class RenderElement extends PluginBase implements ElementInterface {
|
|||
return $element;
|
||||
}
|
||||
|
||||
// Add a data attribute to disable automatic refocus after ajax call.
|
||||
if (!empty($element['#ajax']['disable-refocus'])) {
|
||||
$element['#attributes']['data-disable-refocus'] = "true";
|
||||
}
|
||||
|
||||
// Add a reasonable default event handler if none was specified.
|
||||
if (isset($element['#ajax']) && !isset($element['#ajax']['event'])) {
|
||||
switch ($element['#type']) {
|
||||
|
|
33
core/lib/Drupal/Core/Routing/Enhancer/FormRouteEnhancer.php
Normal file
33
core/lib/Drupal/Core/Routing/Enhancer/FormRouteEnhancer.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Routing\Enhancer\FormRouteEnhancer.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Routing\Enhancer;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Enhancer to add a wrapping controller for _form routes.
|
||||
*/
|
||||
class FormRouteEnhancer implements RouteEnhancerInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applies(Route $route) {
|
||||
return $route->hasDefault('_form') && !$route->hasDefault('_controller');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function enhance(array $defaults, Request $request) {
|
||||
$defaults['_controller'] = 'controller.form:getContentResult';
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
}
|
|
@ -44,8 +44,12 @@ class ParamConversionEnhancer implements RouteEnhancerInterface, EventSubscriber
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function enhance(array $defaults, Request $request) {
|
||||
$defaults['_raw_variables'] = $this->copyRawVariables($defaults);
|
||||
return $this->paramConverterManager->convert($defaults);
|
||||
// Just run the parameter conversion once per request.
|
||||
if (!isset($defaults['_raw_variables'])) {
|
||||
$defaults['_raw_variables'] = $this->copyRawVariables($defaults);
|
||||
$defaults = $this->paramConverterManager->convert($defaults);
|
||||
}
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -246,7 +246,7 @@ class RouteProvider implements PreloadableRouteProviderInterface, PagedRouteProv
|
|||
* @return array
|
||||
* An array of outlines that could match the specified path parts.
|
||||
*/
|
||||
public function getCandidateOutlines(array $parts) {
|
||||
protected function getCandidateOutlines(array $parts) {
|
||||
$number_parts = count($parts);
|
||||
$ancestors = array();
|
||||
$length = $number_parts - 1;
|
||||
|
@ -355,7 +355,7 @@ class RouteProvider implements PreloadableRouteProviderInterface, PagedRouteProv
|
|||
/**
|
||||
* Comparison function for usort on routes.
|
||||
*/
|
||||
public function routeProviderRouteCompare(array $a, array $b) {
|
||||
protected function routeProviderRouteCompare(array $a, array $b) {
|
||||
if ($a['fit'] == $b['fit']) {
|
||||
return strcmp($a['name'], $b['name']);
|
||||
}
|
||||
|
|
|
@ -309,6 +309,9 @@ class UrlGenerator implements UrlGeneratorInterface {
|
|||
$name = $this->getRouteDebugMessage($name);
|
||||
$this->processRoute($name, $route, $parameters, $generated_url);
|
||||
$path = $this->getInternalPathFromRoute($name, $route, $parameters, $query_params);
|
||||
// Outbound path processors might need the route object for the path, e.g.
|
||||
// to get the path pattern.
|
||||
$options['route'] = $route;
|
||||
$path = $this->processPath($path, $options, $generated_url);
|
||||
|
||||
if (!empty($options['prefix'])) {
|
||||
|
|
|
@ -77,6 +77,10 @@ interface UrlGeneratorInterface extends VersatileGeneratorInterface {
|
|||
* @throws \Symfony\Component\Routing\Exception\InvalidParameterException
|
||||
* Thrown when a parameter value for a placeholder is not correct because it
|
||||
* does not match the requirement.
|
||||
*
|
||||
* @internal
|
||||
* Should not be used in user code.
|
||||
* Use \Drupal\Core\Url instead.
|
||||
*/
|
||||
public function generateFromRoute($name, $parameters = array(), $options = array(), $collect_bubbleable_metadata = FALSE);
|
||||
|
||||
|
|
|
@ -47,28 +47,41 @@ class ReverseProxyMiddleware implements HttpKernelInterface {
|
|||
*/
|
||||
public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {
|
||||
// Initialize proxy settings.
|
||||
if ($this->settings->get('reverse_proxy', FALSE)) {
|
||||
$ip_header = $this->settings->get('reverse_proxy_header', 'X_FORWARDED_FOR');
|
||||
static::setSettingsOnRequest($request, $this->settings);
|
||||
return $this->httpKernel->handle($request, $type, $catch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets reverse proxy settings on Request object.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* A Request instance.
|
||||
* @param \Drupal\Core\Site\Settings $settings
|
||||
* The site settings.
|
||||
*/
|
||||
public static function setSettingsOnRequest(Request $request, Settings $settings) {
|
||||
// Initialize proxy settings.
|
||||
if ($settings->get('reverse_proxy', FALSE)) {
|
||||
$ip_header = $settings->get('reverse_proxy_header', 'X_FORWARDED_FOR');
|
||||
$request::setTrustedHeaderName($request::HEADER_CLIENT_IP, $ip_header);
|
||||
|
||||
$proto_header = $this->settings->get('reverse_proxy_proto_header', 'X_FORWARDED_PROTO');
|
||||
$proto_header = $settings->get('reverse_proxy_proto_header', 'X_FORWARDED_PROTO');
|
||||
$request::setTrustedHeaderName($request::HEADER_CLIENT_PROTO, $proto_header);
|
||||
|
||||
$host_header = $this->settings->get('reverse_proxy_host_header', 'X_FORWARDED_HOST');
|
||||
$host_header = $settings->get('reverse_proxy_host_header', 'X_FORWARDED_HOST');
|
||||
$request::setTrustedHeaderName($request::HEADER_CLIENT_HOST, $host_header);
|
||||
|
||||
$port_header = $this->settings->get('reverse_proxy_port_header', 'X_FORWARDED_PORT');
|
||||
$port_header = $settings->get('reverse_proxy_port_header', 'X_FORWARDED_PORT');
|
||||
$request::setTrustedHeaderName($request::HEADER_CLIENT_PORT, $port_header);
|
||||
|
||||
$forwarded_header = $this->settings->get('reverse_proxy_forwarded_header', 'FORWARDED');
|
||||
$forwarded_header = $settings->get('reverse_proxy_forwarded_header', 'FORWARDED');
|
||||
$request::setTrustedHeaderName($request::HEADER_FORWARDED, $forwarded_header);
|
||||
|
||||
$proxies = $this->settings->get('reverse_proxy_addresses', array());
|
||||
$proxies = $settings->get('reverse_proxy_addresses', array());
|
||||
if (count($proxies) > 0) {
|
||||
$request::setTrustedProxies($proxies);
|
||||
}
|
||||
}
|
||||
return $this->httpKernel->handle($request, $type, $catch);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -134,6 +134,11 @@ class TranslationManager implements TranslationInterface, TranslatorInterface {
|
|||
* The translated string.
|
||||
*/
|
||||
protected function doTranslate($string, array $options = array()) {
|
||||
// If a NULL langcode has been provided, unset it.
|
||||
if (!isset($options['langcode']) && array_key_exists('langcode', $options)) {
|
||||
unset($options['langcode']);
|
||||
}
|
||||
|
||||
// Merge in options defaults.
|
||||
$options = $options + [
|
||||
'langcode' => $this->defaultLangcode,
|
||||
|
|
|
@ -117,10 +117,11 @@ class Attribute implements \ArrayAccess, \IteratorAggregate, MarkupInterface {
|
|||
* An AttributeValueBase representation of the attribute's value.
|
||||
*/
|
||||
protected function createAttributeValue($name, $value) {
|
||||
// If the value is already an AttributeValueBase object, return it
|
||||
// straight away.
|
||||
// If the value is already an AttributeValueBase object,
|
||||
// return a new instance of the same class, but with the new name.
|
||||
if ($value instanceof AttributeValueBase) {
|
||||
return $value;
|
||||
$class = get_class($value);
|
||||
return new $class($name, $value->value());
|
||||
}
|
||||
// An array value or 'class' attribute name are forced to always be an
|
||||
// AttributeArray value for consistency.
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Theme\MissingThemeDependencyException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Theme;
|
||||
|
||||
/**
|
||||
* Exception to be thrown when base theme for installed theme is not installed.
|
||||
*
|
||||
* @see \Drupal\Core\Theme\ThemeInitialization::getActiveThemeByName().
|
||||
*/
|
||||
class MissingThemeDependencyException extends \Exception {
|
||||
|
||||
/**
|
||||
* The missing theme dependency.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $theme;
|
||||
|
||||
/**
|
||||
* Constructs the exception.
|
||||
*
|
||||
* @param string $message
|
||||
* The exception message.
|
||||
* @param string $theme
|
||||
* The missing theme dependency.
|
||||
*/
|
||||
public function __construct($message, $theme) {
|
||||
parent::__construct($message);
|
||||
$this->theme = $theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the machine name of the missing theme.
|
||||
*
|
||||
* @return string
|
||||
* The machine name of the theme that is missing.
|
||||
*/
|
||||
public function getMissingThemeName() {
|
||||
return $this->theme;
|
||||
}
|
||||
|
||||
}
|
|
@ -109,6 +109,16 @@ class ThemeInitialization implements ThemeInitializationInterface {
|
|||
$ancestor = $theme_name;
|
||||
while ($ancestor && isset($themes[$ancestor]->base_theme)) {
|
||||
$ancestor = $themes[$ancestor]->base_theme;
|
||||
if (!$this->themeHandler->themeExists($ancestor)) {
|
||||
if ($ancestor == 'stable') {
|
||||
// Themes that depend on Stable will be fixed by system_update_8014().
|
||||
// There is no harm in not adding it as an ancestor since at worst
|
||||
// some people might experience slight visual regressions on
|
||||
// update.php.
|
||||
continue;
|
||||
}
|
||||
throw new MissingThemeDependencyException(sprintf('Base theme %s has not been installed.', $ancestor), $ancestor);
|
||||
}
|
||||
$base_themes[] = $themes[$ancestor];
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,9 @@ interface ThemeInitializationInterface {
|
|||
*
|
||||
* @return \Drupal\Core\Theme\ActiveTheme
|
||||
* An active theme object instance for the given theme.
|
||||
*
|
||||
* @throws \Drupal\Core\Theme\MissingThemeDependencyException
|
||||
* Thrown when base theme for installed theme is not installed.
|
||||
*/
|
||||
public function getActiveThemeByName($theme_name);
|
||||
|
||||
|
@ -54,8 +57,8 @@ interface ThemeInitializationInterface {
|
|||
* @param \Drupal\Core\Extension\Extension $theme
|
||||
* The theme extension object.
|
||||
* @param \Drupal\Core\Extension\Extension[] $base_themes
|
||||
* An array of extension objects of base theme and its bases. It is ordered
|
||||
* by 'oldest first', meaning the top level of the chain will be first.
|
||||
* An array of extension objects of base theme and its bases. It is ordered
|
||||
* by 'next parent first', meaning the top level of the chain will be first.
|
||||
*
|
||||
* @return \Drupal\Core\Theme\ActiveTheme
|
||||
* The active theme instance for the passed in $theme.
|
||||
|
|
|
@ -72,6 +72,9 @@ interface LinkGeneratorInterface {
|
|||
* @throws \Symfony\Component\Routing\Exception\InvalidParameterException
|
||||
* Thrown when a parameter value for a placeholder is not correct because it
|
||||
* does not match the requirement.
|
||||
*
|
||||
* @internal
|
||||
* Should not be used in user code. Use \Drupal\Core\Link instead.
|
||||
*/
|
||||
public function generate($text, Url $url);
|
||||
|
||||
|
@ -84,6 +87,10 @@ interface LinkGeneratorInterface {
|
|||
* @return \Drupal\Core\GeneratedLink
|
||||
* A GeneratedLink object containing a link to the given route and
|
||||
* parameters and bubbleable metadata.
|
||||
*
|
||||
* @internal
|
||||
* Should not be used in user code.
|
||||
* Use \Drupal\Core\Link instead.
|
||||
*/
|
||||
public function generateFromLink(Link $link);
|
||||
|
||||
|
|
Reference in a new issue