Initial Drupal 11 with DDEV setup
This commit is contained in:
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Component\Utility\ArgumentsResolver;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Resolves the arguments to pass to an access check callable.
|
||||
*/
|
||||
class AccessArgumentsResolverFactory implements AccessArgumentsResolverFactoryInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getArgumentsResolver(RouteMatchInterface $route_match, AccountInterface $account, ?Request $request = NULL) {
|
||||
$route = $route_match->getRouteObject();
|
||||
|
||||
// Defaults for the parameters defined on the route object need to be added
|
||||
// to the raw arguments.
|
||||
$raw_route_arguments = $route_match->getRawParameters()->all() + $route->getDefaults();
|
||||
|
||||
$upcasted_route_arguments = $route_match->getParameters()->all();
|
||||
|
||||
// Parameters which are not defined on the route object, but still are
|
||||
// essential for access checking are passed as wildcards to the argument
|
||||
// resolver. An access-check method with a parameter of type Route,
|
||||
// RouteMatchInterface, AccountInterface or Request will receive those
|
||||
// arguments regardless of the parameter name.
|
||||
$wildcard_arguments = [$route, $route_match, $account];
|
||||
if (isset($request)) {
|
||||
$wildcard_arguments[] = $request;
|
||||
}
|
||||
|
||||
return new ArgumentsResolver($raw_route_arguments, $upcasted_route_arguments, $wildcard_arguments);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Constructs the arguments resolver instance to use when running access checks.
|
||||
*/
|
||||
interface AccessArgumentsResolverFactoryInterface {
|
||||
|
||||
/**
|
||||
* Returns the arguments resolver to use when running access checks.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match object to be checked.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The account being checked.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* Optional, the request object.
|
||||
*
|
||||
* @return \Drupal\Component\Utility\ArgumentsResolverInterface
|
||||
* The parametrized arguments resolver instance.
|
||||
*/
|
||||
public function getArgumentsResolver(RouteMatchInterface $route_match, AccountInterface $account, ?Request $request = NULL);
|
||||
|
||||
}
|
||||
24
web/core/lib/Drupal/Core/Access/AccessCheckInterface.php
Normal file
24
web/core/lib/Drupal/Core/Access/AccessCheckInterface.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
|
||||
|
||||
/**
|
||||
* An access check service determines access rules for particular routes.
|
||||
*/
|
||||
interface AccessCheckInterface extends RoutingAccessInterface {
|
||||
|
||||
/**
|
||||
* Declares whether the access check applies to a specific route or not.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route to consider attaching to.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if this access checker applies to this route.
|
||||
*/
|
||||
public function applies(Route $route);
|
||||
|
||||
}
|
||||
12
web/core/lib/Drupal/Core/Access/AccessException.php
Normal file
12
web/core/lib/Drupal/Core/Access/AccessException.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
/**
|
||||
* An exception thrown for access errors.
|
||||
*
|
||||
* Examples could be invalid access callback return values, or invalid access
|
||||
* objects being used.
|
||||
*/
|
||||
class AccessException extends \RuntimeException {
|
||||
}
|
||||
55
web/core/lib/Drupal/Core/Access/AccessGroupAnd.php
Normal file
55
web/core/lib/Drupal/Core/Access/AccessGroupAnd.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* An access group where all the dependencies must be allowed.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class AccessGroupAnd implements AccessibleInterface {
|
||||
|
||||
/**
|
||||
* The access dependencies.
|
||||
*
|
||||
* @var \Drupal\Core\Access\AccessibleInterface[]
|
||||
*/
|
||||
protected $dependencies = [];
|
||||
|
||||
/**
|
||||
* Adds an access dependency.
|
||||
*
|
||||
* @param \Drupal\Core\Access\AccessibleInterface $dependency
|
||||
* The access dependency to be added.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addDependency(AccessibleInterface $dependency) {
|
||||
$this->dependencies[] = $dependency;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($operation, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$access_result = AccessResult::neutral();
|
||||
foreach (array_slice($this->dependencies, 1) as $dependency) {
|
||||
$access_result = $access_result->andIf($dependency->access($operation, $account, TRUE));
|
||||
}
|
||||
return $return_as_object ? $access_result : $access_result->isAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the access dependencies.
|
||||
*
|
||||
* @return list<\Drupal\Core\Access\AccessibleInterface>
|
||||
* The list of access dependencies.
|
||||
*/
|
||||
public function getDependencies() {
|
||||
return $this->dependencies;
|
||||
}
|
||||
|
||||
}
|
||||
169
web/core/lib/Drupal/Core/Access/AccessManager.php
Normal file
169
web/core/lib/Drupal/Core/Access/AccessManager.php
Normal file
@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\ParamConverter\ParamConverterManagerInterface;
|
||||
use Drupal\Core\ParamConverter\ParamNotConvertedException;
|
||||
use Drupal\Core\Routing\RouteMatch;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Routing\RouteProviderInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Component\Utility\ArgumentsResolverInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Exception\RouteNotFoundException;
|
||||
use Drupal\Core\Routing\RouteObjectInterface;
|
||||
|
||||
/**
|
||||
* Attaches access check services to routes and runs them on request.
|
||||
*
|
||||
* @see \Drupal\Tests\Core\Access\AccessManagerTest
|
||||
*/
|
||||
class AccessManager implements AccessManagerInterface {
|
||||
/**
|
||||
* The route provider.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteProviderInterface
|
||||
*/
|
||||
protected $routeProvider;
|
||||
|
||||
/**
|
||||
* The paramconverter manager.
|
||||
*
|
||||
* @var \Drupal\Core\ParamConverter\ParamConverterManagerInterface
|
||||
*/
|
||||
protected $paramConverterManager;
|
||||
|
||||
/**
|
||||
* The access arguments resolver.
|
||||
*
|
||||
* @var \Drupal\Core\Access\AccessArgumentsResolverFactoryInterface
|
||||
*/
|
||||
protected $argumentsResolverFactory;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* The check provider.
|
||||
*
|
||||
* @var \Drupal\Core\Access\CheckProviderInterface
|
||||
*/
|
||||
protected $checkProvider;
|
||||
|
||||
/**
|
||||
* Constructs an AccessManager instance.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
|
||||
* The route provider.
|
||||
* @param \Drupal\Core\ParamConverter\ParamConverterManagerInterface $paramconverter_manager
|
||||
* The param converter manager.
|
||||
* @param \Drupal\Core\Access\AccessArgumentsResolverFactoryInterface $arguments_resolver_factory
|
||||
* The access arguments resolver.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user.
|
||||
* @param CheckProviderInterface $check_provider
|
||||
* The check access provider.
|
||||
*/
|
||||
public function __construct(RouteProviderInterface $route_provider, ParamConverterManagerInterface $paramconverter_manager, AccessArgumentsResolverFactoryInterface $arguments_resolver_factory, AccountInterface $current_user, CheckProviderInterface $check_provider) {
|
||||
$this->routeProvider = $route_provider;
|
||||
$this->paramConverterManager = $paramconverter_manager;
|
||||
$this->argumentsResolverFactory = $arguments_resolver_factory;
|
||||
$this->currentUser = $current_user;
|
||||
$this->checkProvider = $check_provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkNamedRoute($route_name, array $parameters = [], ?AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
try {
|
||||
$route = $this->routeProvider->getRouteByName($route_name);
|
||||
|
||||
// ParamConverterManager relies on the route name and object being
|
||||
// available from the parameters array.
|
||||
$parameters[RouteObjectInterface::ROUTE_NAME] = $route_name;
|
||||
$parameters[RouteObjectInterface::ROUTE_OBJECT] = $route;
|
||||
$upcasted_parameters = $this->paramConverterManager->convert($parameters + $route->getDefaults());
|
||||
|
||||
$route_match = new RouteMatch($route_name, $route, $upcasted_parameters, $parameters);
|
||||
return $this->check($route_match, $account, NULL, $return_as_object);
|
||||
}
|
||||
catch (RouteNotFoundException) {
|
||||
// Cacheable until extensions change.
|
||||
$result = AccessResult::forbidden()->addCacheTags(['config:core.extension']);
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
catch (ParamNotConvertedException) {
|
||||
// Uncacheable because conversion of the parameter may not have been
|
||||
// possible due to dynamic circumstances.
|
||||
$result = AccessResult::forbidden()->setCacheMaxAge(0);
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkRequest(Request $request, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$route_match = RouteMatch::createFromRequest($request);
|
||||
return $this->check($route_match, $account, $request, $return_as_object);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(RouteMatchInterface $route_match, ?AccountInterface $account = NULL, ?Request $request = NULL, $return_as_object = FALSE) {
|
||||
if (!isset($account)) {
|
||||
$account = $this->currentUser;
|
||||
}
|
||||
$route = $route_match->getRouteObject();
|
||||
$checks = $route->getOption('_access_checks') ?: [];
|
||||
|
||||
// Filter out checks which require the incoming request.
|
||||
if (!isset($request)) {
|
||||
$checks = array_diff($checks, $this->checkProvider->getChecksNeedRequest());
|
||||
}
|
||||
|
||||
$result = AccessResult::neutral();
|
||||
if (!empty($checks)) {
|
||||
$arguments_resolver = $this->argumentsResolverFactory->getArgumentsResolver($route_match, $account, $request);
|
||||
$result = AccessResult::allowed();
|
||||
foreach ($checks as $service_id) {
|
||||
$result = $result->andIf($this->performCheck($service_id, $arguments_resolver));
|
||||
}
|
||||
}
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the specified access check.
|
||||
*
|
||||
* @param string $service_id
|
||||
* The access check service ID to use.
|
||||
* @param \Drupal\Component\Utility\ArgumentsResolverInterface $arguments_resolver
|
||||
* The parametrized arguments resolver instance.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*
|
||||
* @throws \Drupal\Core\Access\AccessException
|
||||
* Thrown when the access check returns an invalid value.
|
||||
*/
|
||||
protected function performCheck($service_id, ArgumentsResolverInterface $arguments_resolver) {
|
||||
$callable = $this->checkProvider->loadCheck($service_id);
|
||||
$arguments = $arguments_resolver->getArguments($callable);
|
||||
/** @var \Drupal\Core\Access\AccessResultInterface $service_access **/
|
||||
$service_access = call_user_func_array($callable, $arguments);
|
||||
|
||||
if (!$service_access instanceof AccessResultInterface) {
|
||||
throw new AccessException("Access error in $service_id. Access services must return an object that implements AccessResultInterface.");
|
||||
}
|
||||
|
||||
return $service_access;
|
||||
}
|
||||
|
||||
}
|
||||
83
web/core/lib/Drupal/Core/Access/AccessManagerInterface.php
Normal file
83
web/core/lib/Drupal/Core/Access/AccessManagerInterface.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface for attaching and running access check services.
|
||||
*/
|
||||
interface AccessManagerInterface {
|
||||
|
||||
/**
|
||||
* Checks a named route with parameters against access check services.
|
||||
*
|
||||
* Determines whether the route is accessible or not.
|
||||
*
|
||||
* @param string $route_name
|
||||
* The route to check access to.
|
||||
* @param array $parameters
|
||||
* Optional array of values to substitute into the route path pattern.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* (optional) Run access checks for this account. Defaults to the current
|
||||
* user.
|
||||
* @param bool $return_as_object
|
||||
* (optional) Defaults to FALSE.
|
||||
*
|
||||
* @return bool|\Drupal\Core\Access\AccessResultInterface
|
||||
* The access result. Returns a boolean if $return_as_object is FALSE (this
|
||||
* is the default) and otherwise an AccessResultInterface object.
|
||||
* When a boolean is returned, the result of AccessInterface::isAllowed() is
|
||||
* returned, i.e. TRUE means access is explicitly allowed, FALSE means
|
||||
* access is either explicitly forbidden or "no opinion".
|
||||
*/
|
||||
public function checkNamedRoute($route_name, array $parameters = [], ?AccountInterface $account = NULL, $return_as_object = FALSE);
|
||||
|
||||
/**
|
||||
* Execute access checks against the incoming request.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The incoming request.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* (optional) Run access checks for this account. Defaults to the current
|
||||
* user.
|
||||
* @param bool $return_as_object
|
||||
* (optional) Defaults to FALSE.
|
||||
*
|
||||
* @return bool|\Drupal\Core\Access\AccessResultInterface
|
||||
* The access result. Returns a boolean if $return_as_object is FALSE (this
|
||||
* is the default) and otherwise an AccessResultInterface object.
|
||||
* When a boolean is returned, the result of AccessInterface::isAllowed() is
|
||||
* returned, i.e. TRUE means access is explicitly allowed, FALSE means
|
||||
* access is either explicitly forbidden or "no opinion".
|
||||
*/
|
||||
public function checkRequest(Request $request, ?AccountInterface $account = NULL, $return_as_object = FALSE);
|
||||
|
||||
/**
|
||||
* Checks a route against applicable access check services.
|
||||
*
|
||||
* Determines whether the route is accessible or not.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* (optional) Run access checks for this account. Defaults to the current
|
||||
* user.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* Optional, a request. Only supply this parameter when checking the
|
||||
* incoming request, do not specify when checking routes on output.
|
||||
* @param bool $return_as_object
|
||||
* (optional) Defaults to FALSE.
|
||||
*
|
||||
* @return bool|\Drupal\Core\Access\AccessResultInterface
|
||||
* The access result. Returns a boolean if $return_as_object is FALSE (this
|
||||
* is the default) and otherwise an AccessResultInterface object.
|
||||
* When a boolean is returned, the result of AccessInterface::isAllowed() is
|
||||
* returned, i.e. TRUE means access is explicitly allowed, FALSE means
|
||||
* access is either explicitly forbidden or "no opinion".
|
||||
*/
|
||||
public function check(RouteMatchInterface $route_match, ?AccountInterface $account = NULL, ?Request $request = NULL, $return_as_object = FALSE);
|
||||
|
||||
}
|
||||
424
web/core/lib/Drupal/Core/Access/AccessResult.php
Normal file
424
web/core/lib/Drupal/Core/Access/AccessResult.php
Normal file
@ -0,0 +1,424 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Value object for passing an access result with cacheability metadata.
|
||||
*
|
||||
* The access result itself — excluding the cacheability metadata — is
|
||||
* immutable. There are subclasses for each of the three possible access results
|
||||
* themselves:
|
||||
*
|
||||
* @see \Drupal\Core\Access\AccessResultAllowed
|
||||
* @see \Drupal\Core\Access\AccessResultForbidden
|
||||
* @see \Drupal\Core\Access\AccessResultNeutral
|
||||
*
|
||||
* When using ::orIf() and ::andIf(), cacheability metadata will be merged
|
||||
* accordingly as well.
|
||||
*/
|
||||
abstract class AccessResult implements AccessResultInterface, RefinableCacheableDependencyInterface {
|
||||
|
||||
use RefinableCacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* Creates an AccessResultInterface object with isNeutral() === TRUE.
|
||||
*
|
||||
* @param string|null $reason
|
||||
* (optional) The reason why access is neutral. Intended for developers,
|
||||
* hence not translatable.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultNeutral
|
||||
* isNeutral() will be TRUE.
|
||||
*/
|
||||
public static function neutral($reason = NULL) {
|
||||
assert(is_string($reason) || is_null($reason));
|
||||
return new AccessResultNeutral($reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an AccessResultInterface object with isAllowed() === TRUE.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultAllowed
|
||||
* isAllowed() will be TRUE.
|
||||
*/
|
||||
public static function allowed() {
|
||||
return new AccessResultAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an AccessResultInterface object with isForbidden() === TRUE.
|
||||
*
|
||||
* @param string|null $reason
|
||||
* (optional) The reason why access is forbidden. Intended for developers,
|
||||
* hence not translatable.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultForbidden
|
||||
* isForbidden() will be TRUE.
|
||||
*/
|
||||
public static function forbidden($reason = NULL) {
|
||||
assert(is_string($reason) || is_null($reason));
|
||||
return new AccessResultForbidden($reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an allowed or neutral access result.
|
||||
*
|
||||
* @param bool $condition
|
||||
* The condition to evaluate.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResult
|
||||
* If $condition is TRUE, isAllowed() will be TRUE, otherwise isNeutral()
|
||||
* will be TRUE.
|
||||
*/
|
||||
public static function allowedIf($condition) {
|
||||
return $condition ? static::allowed() : static::neutral();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a forbidden or neutral access result.
|
||||
*
|
||||
* @param bool $condition
|
||||
* The condition to evaluate.
|
||||
* @param string|null $reason
|
||||
* (optional) The reason why access is forbidden. Intended for developers,
|
||||
* hence not translatable.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResult
|
||||
* If $condition is TRUE, isForbidden() will be TRUE, otherwise isNeutral()
|
||||
* will be TRUE.
|
||||
*/
|
||||
public static function forbiddenIf($condition, $reason = NULL) {
|
||||
return $condition ? static::forbidden($reason) : static::neutral();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an access result if the permission is present, neutral otherwise.
|
||||
*
|
||||
* Checks the permission and adds a 'user.permissions' cache context.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The account for which to check a permission.
|
||||
* @param string $permission
|
||||
* The permission to check for.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResult
|
||||
* If the account has the permission, isAllowed() will be TRUE, otherwise
|
||||
* isNeutral() will be TRUE.
|
||||
*/
|
||||
public static function allowedIfHasPermission(AccountInterface $account, $permission) {
|
||||
$access_result = static::allowedIf($account->hasPermission($permission))->addCacheContexts(['user.permissions']);
|
||||
|
||||
if ($access_result instanceof AccessResultReasonInterface) {
|
||||
$access_result->setReason("The '$permission' permission is required.");
|
||||
}
|
||||
return $access_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an access result if the permissions are present, neutral otherwise.
|
||||
*
|
||||
* Checks the permission and adds a 'user.permissions' cache contexts.
|
||||
*
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The account for which to check permissions.
|
||||
* @param array $permissions
|
||||
* The permissions to check.
|
||||
* @param string $conjunction
|
||||
* (optional) 'AND' if all permissions are required, 'OR' in case just one.
|
||||
* Defaults to 'AND'.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResult
|
||||
* If the account has the permissions, isAllowed() will be TRUE, otherwise
|
||||
* isNeutral() will be TRUE.
|
||||
*/
|
||||
public static function allowedIfHasPermissions(AccountInterface $account, array $permissions, $conjunction = 'AND') {
|
||||
$access = FALSE;
|
||||
|
||||
if ($conjunction == 'AND' && !empty($permissions)) {
|
||||
$access = TRUE;
|
||||
foreach ($permissions as $permission) {
|
||||
if (!$account->hasPermission($permission)) {
|
||||
$access = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach ($permissions as $permission) {
|
||||
if ($account->hasPermission($permission)) {
|
||||
$access = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$access_result = static::allowedIf($access)->addCacheContexts(empty($permissions) ? [] : ['user.permissions']);
|
||||
|
||||
if ($access_result instanceof AccessResultReasonInterface) {
|
||||
if (count($permissions) === 1) {
|
||||
$access_result->setReason("The '$permission' permission is required.");
|
||||
}
|
||||
elseif (count($permissions) > 1) {
|
||||
$quote = function ($s) {
|
||||
return "'$s'";
|
||||
};
|
||||
$access_result->setReason(sprintf("The following permissions are required: %s.", implode(" $conjunction ", array_map($quote, $permissions))));
|
||||
}
|
||||
}
|
||||
|
||||
return $access_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Drupal\Core\Access\AccessResultAllowed
|
||||
*/
|
||||
public function isAllowed() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Drupal\Core\Access\AccessResultForbidden
|
||||
*/
|
||||
public function isForbidden() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Drupal\Core\Access\AccessResultNeutral
|
||||
*/
|
||||
public function isNeutral() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
return $this->cacheContexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
return $this->cacheTags;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
return $this->cacheMaxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets cache contexts (to the empty array).
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function resetCacheContexts() {
|
||||
$this->cacheContexts = [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets cache tags (to the empty array).
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function resetCacheTags() {
|
||||
$this->cacheTags = [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum age for which this access result may be cached.
|
||||
*
|
||||
* @param int $max_age
|
||||
* The maximum time in seconds that this access result may be cached.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setCacheMaxAge($max_age) {
|
||||
$this->cacheMaxAge = $max_age;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method, adds the "user.permissions" cache context.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function cachePerPermissions() {
|
||||
$this->addCacheContexts(['user.permissions']);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method, adds the "user" cache context.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function cachePerUser() {
|
||||
$this->addCacheContexts(['user']);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function orIf(AccessResultInterface $other) {
|
||||
$merge_other = FALSE;
|
||||
// $other's cacheability metadata is merged if $merge_other gets set to TRUE
|
||||
// and this happens in three cases:
|
||||
// 1. $other's access result is the one that determines the combined access
|
||||
// result.
|
||||
// 2. This access result is not cacheable and $other's access result is the
|
||||
// same. i.e. attempt to return a cacheable access result.
|
||||
// 3. Neither access result is 'forbidden' and both are cacheable: inherit
|
||||
// the other's cacheability metadata because it may turn into a
|
||||
// 'forbidden' for another value of the cache contexts in the
|
||||
// cacheability metadata. In other words: this is necessary to respect
|
||||
// the contagious nature of the 'forbidden' access result.
|
||||
// e.g. we have two access results A and B. Neither is forbidden. A is
|
||||
// globally cacheable (no cache contexts). B is cacheable per role. If we
|
||||
// don't have merging case 3, then A->orIf(B) will be globally cacheable,
|
||||
// which means that even if a user of a different role logs in, the
|
||||
// cached access result will be used, even though for that other role, B
|
||||
// is forbidden!
|
||||
if ($this->isForbidden() || $other->isForbidden()) {
|
||||
$result = static::forbidden();
|
||||
if (!$this->isForbidden() || ($this->getCacheMaxAge() === 0 && $other->isForbidden())) {
|
||||
$merge_other = TRUE;
|
||||
}
|
||||
|
||||
if ($this->isForbidden() && $this instanceof AccessResultReasonInterface && $this->getReason() !== '') {
|
||||
$result->setReason($this->getReason());
|
||||
}
|
||||
elseif ($other->isForbidden() && $other instanceof AccessResultReasonInterface && $other->getReason() !== '') {
|
||||
$result->setReason($other->getReason());
|
||||
}
|
||||
}
|
||||
elseif ($this->isAllowed() || $other->isAllowed()) {
|
||||
$result = static::allowed();
|
||||
if (!$this->isAllowed() || ($this->getCacheMaxAge() === 0 && $other->isAllowed()) || ($this->getCacheMaxAge() !== 0 && $other instanceof CacheableDependencyInterface && $other->getCacheMaxAge() !== 0)) {
|
||||
$merge_other = TRUE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$result = static::neutral();
|
||||
if ($this->getCacheMaxAge() === 0 || ($other instanceof CacheableDependencyInterface && $other->getCacheMaxAge() !== 0)) {
|
||||
$merge_other = TRUE;
|
||||
}
|
||||
|
||||
if ($this instanceof AccessResultReasonInterface && $this->getReason() !== '') {
|
||||
$result->setReason($this->getReason());
|
||||
}
|
||||
elseif ($other instanceof AccessResultReasonInterface && $other->getReason() !== '') {
|
||||
$result->setReason($other->getReason());
|
||||
}
|
||||
}
|
||||
$result->inheritCacheability($this);
|
||||
if ($merge_other) {
|
||||
$result->inheritCacheability($other);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function andIf(AccessResultInterface $other) {
|
||||
// The other access result's cacheability metadata is merged if $merge_other
|
||||
// gets set to TRUE. It gets set to TRUE in one case: if the other access
|
||||
// result is used.
|
||||
$merge_other = FALSE;
|
||||
if ($this->isForbidden() || $other->isForbidden()) {
|
||||
$result = static::forbidden();
|
||||
if (!$this->isForbidden()) {
|
||||
if ($other instanceof AccessResultReasonInterface) {
|
||||
$result->setReason($other->getReason());
|
||||
}
|
||||
$merge_other = TRUE;
|
||||
}
|
||||
else {
|
||||
if ($this instanceof AccessResultReasonInterface) {
|
||||
$result->setReason($this->getReason());
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($this->isAllowed() && $other->isAllowed()) {
|
||||
$result = static::allowed();
|
||||
$merge_other = TRUE;
|
||||
}
|
||||
else {
|
||||
$result = static::neutral();
|
||||
if (!$this->isNeutral()) {
|
||||
$merge_other = TRUE;
|
||||
if ($other instanceof AccessResultReasonInterface) {
|
||||
$result->setReason($other->getReason());
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($this instanceof AccessResultReasonInterface) {
|
||||
$result->setReason($this->getReason());
|
||||
}
|
||||
}
|
||||
}
|
||||
$result->inheritCacheability($this);
|
||||
if ($merge_other) {
|
||||
$result->inheritCacheability($other);
|
||||
// If this access result is not cacheable, then an AND with another access
|
||||
// result must also not be cacheable, except if the other access result
|
||||
// has isForbidden() === TRUE. isForbidden() access results are contagious
|
||||
// in that they propagate regardless of the other value.
|
||||
if ($this->getCacheMaxAge() === 0 && !$result->isForbidden()) {
|
||||
$result->setCacheMaxAge(0);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherits the cacheability of the other access result, if any.
|
||||
*
|
||||
* This method differs from addCacheableDependency() in how it handles
|
||||
* max-age, because it is designed to inherit the cacheability of the second
|
||||
* operand in the andIf() and orIf() operations. There, the situation
|
||||
* "allowed, max-age=0 OR allowed, max-age=1000" needs to yield max-age 1000
|
||||
* as the end result.
|
||||
*
|
||||
* @param \Drupal\Core\Access\AccessResultInterface $other
|
||||
* The other access result, whose cacheability (if any) to inherit.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function inheritCacheability(AccessResultInterface $other) {
|
||||
if ($other instanceof CacheableDependencyInterface) {
|
||||
$this->addCacheableDependency($other);
|
||||
if ($this->getCacheMaxAge() !== 0 && $other->getCacheMaxAge() !== 0) {
|
||||
$this->setCacheMaxAge(Cache::mergeMaxAges($this->getCacheMaxAge(), $other->getCacheMaxAge()));
|
||||
}
|
||||
else {
|
||||
$this->setCacheMaxAge($other->getCacheMaxAge());
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->setCacheMaxAge(0);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
17
web/core/lib/Drupal/Core/Access/AccessResultAllowed.php
Normal file
17
web/core/lib/Drupal/Core/Access/AccessResultAllowed.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
/**
|
||||
* Value object indicating an allowed access result, with cacheability metadata.
|
||||
*/
|
||||
class AccessResultAllowed extends AccessResult {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isAllowed() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
49
web/core/lib/Drupal/Core/Access/AccessResultForbidden.php
Normal file
49
web/core/lib/Drupal/Core/Access/AccessResultForbidden.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
/**
|
||||
* Value object for a forbidden access result, with cacheability metadata.
|
||||
*/
|
||||
class AccessResultForbidden extends AccessResult implements AccessResultReasonInterface {
|
||||
|
||||
/**
|
||||
* The reason why access is forbidden. For use in error messages.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason;
|
||||
|
||||
/**
|
||||
* Constructs a new AccessResultForbidden instance.
|
||||
*
|
||||
* @param null|string $reason
|
||||
* (optional) A message to provide details about this access result.
|
||||
*/
|
||||
public function __construct($reason = NULL) {
|
||||
$this->reason = $reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isForbidden() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getReason() {
|
||||
return (string) $this->reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setReason($reason) {
|
||||
$this->reason = $reason;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
108
web/core/lib/Drupal/Core/Access/AccessResultInterface.php
Normal file
108
web/core/lib/Drupal/Core/Access/AccessResultInterface.php
Normal file
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
/**
|
||||
* Interface for access result value objects.
|
||||
*
|
||||
* IMPORTANT NOTE: You have to call isAllowed() when you want to know whether
|
||||
* someone has access. Just using
|
||||
* @code
|
||||
* if ($access_result) {
|
||||
* // The user has access!
|
||||
* }
|
||||
* else {
|
||||
* // The user doesn't have access!
|
||||
* }
|
||||
* @endcode
|
||||
* would never enter the else-statement and hence introduce a critical security
|
||||
* issue.
|
||||
*/
|
||||
interface AccessResultInterface {
|
||||
|
||||
/**
|
||||
* Checks whether this access result indicates access is explicitly allowed.
|
||||
*
|
||||
* Call this method to check whether someone has access, to convert an access
|
||||
* result object to boolean.
|
||||
*
|
||||
* @return bool
|
||||
* When TRUE then isForbidden() and isNeutral() are FALSE.
|
||||
*/
|
||||
public function isAllowed();
|
||||
|
||||
/**
|
||||
* Checks whether this access result indicates access is explicitly forbidden.
|
||||
*
|
||||
* Call this when optimizing an access checker (for hook_entity_access() or a
|
||||
* route requirement): if this is TRUE, the final result will be forbidden and
|
||||
* no further checking is necessary.
|
||||
*
|
||||
* Do not use this method to decide whether someone has access, to convert an
|
||||
* access result to boolean: just because this returns FALSE, the end result
|
||||
* might be neutral which is not allowed. Always use isAllowed() for this.
|
||||
*
|
||||
* @return bool
|
||||
* When TRUE then isAllowed() and isNeutral() are FALSE.
|
||||
*/
|
||||
public function isForbidden();
|
||||
|
||||
/**
|
||||
* Checks whether this access result indicates access is not yet determined.
|
||||
*
|
||||
* @return bool
|
||||
* When TRUE then isAllowed() and isForbidden() are FALSE.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function isNeutral();
|
||||
|
||||
/**
|
||||
* Combine this access result with another using OR.
|
||||
*
|
||||
* When ORing two access results, the result is:
|
||||
* - isForbidden() in either ⇒ isForbidden()
|
||||
* - otherwise if isAllowed() in either ⇒ isAllowed()
|
||||
* - otherwise both must be isNeutral() ⇒ isNeutral()
|
||||
*
|
||||
* Truth table:
|
||||
* @code
|
||||
* |A N F
|
||||
* --+-----
|
||||
* A |A A F
|
||||
* N |A N F
|
||||
* F |F F F
|
||||
* @endcode
|
||||
*
|
||||
* @param \Drupal\Core\Access\AccessResultInterface $other
|
||||
* The other access result to OR this one with.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function orIf(AccessResultInterface $other);
|
||||
|
||||
/**
|
||||
* Combine this access result with another using AND.
|
||||
*
|
||||
* When AND is performed on two access results, the result is:
|
||||
* - isForbidden() in either ⇒ isForbidden()
|
||||
* - otherwise, if isAllowed() in both ⇒ isAllowed()
|
||||
* - otherwise, one of them is isNeutral() ⇒ isNeutral()
|
||||
*
|
||||
* Truth table:
|
||||
* @code
|
||||
* |A N F
|
||||
* --+-----
|
||||
* A |A N F
|
||||
* N |N N F
|
||||
* F |F F F
|
||||
* @endcode
|
||||
*
|
||||
* @param \Drupal\Core\Access\AccessResultInterface $other
|
||||
* The other access result to AND this one with.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function andIf(AccessResultInterface $other);
|
||||
|
||||
}
|
||||
49
web/core/lib/Drupal/Core/Access/AccessResultNeutral.php
Normal file
49
web/core/lib/Drupal/Core/Access/AccessResultNeutral.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
/**
|
||||
* Value object indicating a neutral access result, with cacheability metadata.
|
||||
*/
|
||||
class AccessResultNeutral extends AccessResult implements AccessResultReasonInterface {
|
||||
|
||||
/**
|
||||
* The reason why access is neutral. For use in messages.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reason;
|
||||
|
||||
/**
|
||||
* Constructs a new AccessResultNeutral instance.
|
||||
*
|
||||
* @param null|string $reason
|
||||
* (optional) A message to provide details about this access result.
|
||||
*/
|
||||
public function __construct($reason = NULL) {
|
||||
$this->reason = $reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isNeutral() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getReason() {
|
||||
return (string) $this->reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setReason($reason) {
|
||||
$this->reason = $reason;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
/**
|
||||
* Interface for access result value objects with stored reason for developers.
|
||||
*
|
||||
* For example, a developer can specify the reason for forbidden access:
|
||||
* @code
|
||||
* new AccessResultForbidden('You are not authorized to hack core');
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\Core\Access\AccessResultInterface
|
||||
*/
|
||||
interface AccessResultReasonInterface extends AccessResultInterface {
|
||||
|
||||
/**
|
||||
* Gets the reason for this access result.
|
||||
*
|
||||
* @return string
|
||||
* The reason of this access result or an empty string if no reason is
|
||||
* provided.
|
||||
*/
|
||||
public function getReason();
|
||||
|
||||
/**
|
||||
* Sets the reason for this access result.
|
||||
*
|
||||
* @param string|null $reason
|
||||
* The reason of this access result or NULL if no reason is provided.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result instance.
|
||||
*/
|
||||
public function setReason($reason);
|
||||
|
||||
}
|
||||
34
web/core/lib/Drupal/Core/Access/AccessibleInterface.php
Normal file
34
web/core/lib/Drupal/Core/Access/AccessibleInterface.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Interface for checking access.
|
||||
*
|
||||
* @ingroup entity_api
|
||||
*/
|
||||
interface AccessibleInterface {
|
||||
|
||||
/**
|
||||
* Checks data value access.
|
||||
*
|
||||
* @param string $operation
|
||||
* The operation to be performed.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* (optional) The user for which to check access, or NULL to check access
|
||||
* for the current user. Defaults to NULL.
|
||||
* @param bool $return_as_object
|
||||
* (optional) Defaults to FALSE.
|
||||
*
|
||||
* @return bool|\Drupal\Core\Access\AccessResultInterface
|
||||
* The access result. Returns a boolean if $return_as_object is FALSE (this
|
||||
* is the default) and otherwise an AccessResultInterface object.
|
||||
* When a boolean is returned, the result of AccessInterface::isAllowed() is
|
||||
* returned, i.e. TRUE means access is explicitly allowed, FALSE means
|
||||
* access is either explicitly forbidden or "no opinion".
|
||||
*/
|
||||
public function access($operation, ?AccountInterface $account = NULL, $return_as_object = FALSE);
|
||||
|
||||
}
|
||||
170
web/core/lib/Drupal/Core/Access/CheckProvider.php
Normal file
170
web/core/lib/Drupal/Core/Access/CheckProvider.php
Normal file
@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\Routing\Access\AccessInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Loads access checkers from the container.
|
||||
*/
|
||||
class CheckProvider implements CheckProviderInterface {
|
||||
|
||||
/**
|
||||
* Array of registered access check service ids.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $checkIds = [];
|
||||
|
||||
/**
|
||||
* Array of access check objects keyed by service id.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\Access\AccessInterface[]
|
||||
*/
|
||||
protected $checks;
|
||||
|
||||
/**
|
||||
* Array of access check method names keyed by service ID.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $checkMethods = [];
|
||||
|
||||
/**
|
||||
* Array of access checks which only will be run on the incoming request.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $checksNeedsRequest = [];
|
||||
|
||||
/**
|
||||
* An array to map static requirement keys to service IDs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $staticRequirementMap;
|
||||
|
||||
/**
|
||||
* An array to map dynamic requirement keys to service IDs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dynamicRequirementMap;
|
||||
|
||||
/**
|
||||
* Constructs a CheckProvider object.
|
||||
*
|
||||
* @param array $dynamic_requirements_map
|
||||
* An array to map dynamic requirement keys to service IDs.
|
||||
* @param \Psr\Container\ContainerInterface $container
|
||||
* The check provider service locator.
|
||||
*/
|
||||
public function __construct(
|
||||
array $dynamic_requirements_map,
|
||||
protected ContainerInterface $container,
|
||||
) {
|
||||
$this->dynamicRequirementMap = $dynamic_requirements_map;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addCheckService($service_id, $service_method, array $applies_checks = [], $needs_incoming_request = FALSE) {
|
||||
$this->checkIds[] = $service_id;
|
||||
$this->checkMethods[$service_id] = $service_method;
|
||||
if ($needs_incoming_request) {
|
||||
$this->checksNeedsRequest[$service_id] = $service_id;
|
||||
}
|
||||
foreach ($applies_checks as $applies_check) {
|
||||
$this->staticRequirementMap[$applies_check][] = $service_id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getChecksNeedRequest() {
|
||||
return $this->checksNeedsRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setChecks(RouteCollection $routes) {
|
||||
foreach ($routes as $route) {
|
||||
if ($checks = $this->applies($route)) {
|
||||
$route->setOption('_access_checks', $checks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadCheck($service_id) {
|
||||
if (empty($this->checks[$service_id])) {
|
||||
if (!in_array($service_id, $this->checkIds)) {
|
||||
throw new \InvalidArgumentException(sprintf('No check has been registered for %s', $service_id));
|
||||
}
|
||||
|
||||
$check = $this->container->get($service_id);
|
||||
|
||||
if (!($check instanceof AccessInterface)) {
|
||||
throw new AccessException('All access checks must implement AccessInterface.');
|
||||
}
|
||||
if (!is_callable([$check, $this->checkMethods[$service_id]])) {
|
||||
throw new AccessException(sprintf('Access check method %s in service %s must be callable.', $this->checkMethods[$service_id], $service_id));
|
||||
}
|
||||
|
||||
$this->checks[$service_id] = $check;
|
||||
}
|
||||
return [$this->checks[$service_id], $this->checkMethods[$service_id]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine which registered access checks apply to a route.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route to get list of access checks for.
|
||||
*
|
||||
* @return array
|
||||
* An array of service ids for the access checks that apply to passed
|
||||
* route.
|
||||
*/
|
||||
protected function applies(Route $route) {
|
||||
$checks = [];
|
||||
|
||||
// Iterate through map requirements from appliesTo() on access checkers.
|
||||
// Only iterate through all checkIds if this is not used.
|
||||
foreach ($route->getRequirements() as $key => $value) {
|
||||
if (isset($this->staticRequirementMap[$key])) {
|
||||
foreach ($this->staticRequirementMap[$key] as $service_id) {
|
||||
$this->loadCheck($service_id);
|
||||
$checks[] = $service_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finally, see if any dynamic access checkers apply.
|
||||
foreach ($this->dynamicRequirementMap as $service_id) {
|
||||
$this->loadCheck($service_id);
|
||||
if ($this->checks[$service_id]->applies($route)) {
|
||||
$checks[] = $service_id;
|
||||
}
|
||||
}
|
||||
|
||||
return $checks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles a mapping of requirement keys to access checker service IDs.
|
||||
*/
|
||||
protected function loadDynamicRequirementMap() {
|
||||
if (!isset($this->dynamicRequirementMap)) {
|
||||
$this->dynamicRequirementMap = $this->container->getParameter('dynamic_access_check_services');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
65
web/core/lib/Drupal/Core/Access/CheckProviderInterface.php
Normal file
65
web/core/lib/Drupal/Core/Access/CheckProviderInterface.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Provides the available access checkers by service IDs.
|
||||
*
|
||||
* Access checker services are added by ::addCheckService calls and are loaded
|
||||
* by ::loadCheck.
|
||||
*
|
||||
* The checker provider service and the actual checking is separated in order
|
||||
* to not require the full access manager on route build time.
|
||||
*/
|
||||
interface CheckProviderInterface {
|
||||
|
||||
/**
|
||||
* For each route, saves a list of applicable access checks to the route.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\RouteCollection $routes
|
||||
* A collection of routes to apply checks to.
|
||||
*/
|
||||
public function setChecks(RouteCollection $routes);
|
||||
|
||||
/**
|
||||
* Registers a new AccessCheck by service ID.
|
||||
*
|
||||
* @param string $service_id
|
||||
* The ID of the service in the Container that provides a check.
|
||||
* @param string $service_method
|
||||
* The method to invoke on the service object for performing the check.
|
||||
* @param array $applies_checks
|
||||
* (optional) An array of route requirement keys the checker service applies
|
||||
* to.
|
||||
* @param bool $needs_incoming_request
|
||||
* (optional) True if access-check method only acts on an incoming request.
|
||||
*/
|
||||
public function addCheckService($service_id, $service_method, array $applies_checks = [], $needs_incoming_request = FALSE);
|
||||
|
||||
/**
|
||||
* Lazy-loads access check services.
|
||||
*
|
||||
* @param string $service_id
|
||||
* The service id of the access check service to load.
|
||||
*
|
||||
* @return callable
|
||||
* A callable access check.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown when the service hasn't been registered in addCheckService().
|
||||
* @throws \Drupal\Core\Access\AccessException
|
||||
* Thrown when the service doesn't implement the required interface.
|
||||
*/
|
||||
public function loadCheck($service_id);
|
||||
|
||||
/**
|
||||
* A list of checks that needs the request.
|
||||
*
|
||||
* @return array
|
||||
* Array of access checks which will only be run on the incoming request.
|
||||
*/
|
||||
public function getChecksNeedRequest();
|
||||
|
||||
}
|
||||
69
web/core/lib/Drupal/Core/Access/CsrfAccessCheck.php
Normal file
69
web/core/lib/Drupal/Core/Access/CsrfAccessCheck.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Access protection against CSRF attacks.
|
||||
*
|
||||
* The CsrfAccessCheck is added to any route with the '_csrf_token' route
|
||||
* requirement. If a link/url to a protected route is generated using the
|
||||
* url_generator service, a valid token will be added automatically. Otherwise,
|
||||
* a valid token can be generated by the csrf_token service using the route's
|
||||
* path (without leading slash) as the argument when generating the token. This
|
||||
* token can then be added as the 'token' query parameter when accessing the
|
||||
* protected route.
|
||||
*
|
||||
* @see \Drupal\Core\Access\RouteProcessorCsrf
|
||||
* @see \Drupal\Core\Access\CsrfTokenGenerator
|
||||
* @see https://www.drupal.org/docs/8/api/routing-system/access-checking-on-routes/csrf-access-checking
|
||||
*/
|
||||
class CsrfAccessCheck implements RoutingAccessInterface {
|
||||
|
||||
use RoutePathGenerationTrait;
|
||||
|
||||
/**
|
||||
* The CSRF token generator.
|
||||
*/
|
||||
protected CsrfTokenGenerator $csrfToken;
|
||||
|
||||
/**
|
||||
* Constructs a CsrfAccessCheck object.
|
||||
*
|
||||
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
|
||||
* The CSRF token generator.
|
||||
*/
|
||||
public function __construct(CsrfTokenGenerator $csrf_token) {
|
||||
$this->csrfToken = $csrf_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks access based on a CSRF token for the request.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route to check against.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request object.
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match object.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function access(Route $route, Request $request, RouteMatchInterface $route_match) {
|
||||
$path = $this->generateRoutePath($route, $route_match->getRawParameters()->all());
|
||||
if ($this->csrfToken->validate($request->query->get('token', ''), $path)) {
|
||||
$result = AccessResult::allowed();
|
||||
}
|
||||
else {
|
||||
$result = AccessResult::forbidden($request->query->has('token') ? "'csrf_token' URL query argument is invalid." : "'csrf_token' URL query argument is missing.");
|
||||
}
|
||||
// Not cacheable because the CSRF token is highly dynamic.
|
||||
return $result->setCacheMaxAge(0);
|
||||
}
|
||||
|
||||
}
|
||||
109
web/core/lib/Drupal/Core/Access/CsrfRequestHeaderAccessCheck.php
Normal file
109
web/core/lib/Drupal/Core/Access/CsrfRequestHeaderAccessCheck.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Session\SessionConfigurationInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Access protection against CSRF attacks.
|
||||
*/
|
||||
class CsrfRequestHeaderAccessCheck implements AccessCheckInterface {
|
||||
|
||||
/**
|
||||
* A string key that will used to designate the token used by this class.
|
||||
*/
|
||||
const TOKEN_KEY = 'X-CSRF-Token request header';
|
||||
|
||||
/**
|
||||
* The session configuration.
|
||||
*
|
||||
* @var \Drupal\Core\Session\SessionConfigurationInterface
|
||||
*/
|
||||
protected $sessionConfiguration;
|
||||
|
||||
/**
|
||||
* The token generator.
|
||||
*
|
||||
* @var \Drupal\Core\Access\CsrfTokenGenerator
|
||||
*/
|
||||
protected $csrfToken;
|
||||
|
||||
/**
|
||||
* Constructs a new rest CSRF access check.
|
||||
*
|
||||
* @param \Drupal\Core\Session\SessionConfigurationInterface $session_configuration
|
||||
* The session configuration.
|
||||
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
|
||||
* The token generator.
|
||||
*/
|
||||
public function __construct(SessionConfigurationInterface $session_configuration, CsrfTokenGenerator $csrf_token) {
|
||||
$this->sessionConfiguration = $session_configuration;
|
||||
$this->csrfToken = $csrf_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applies(Route $route) {
|
||||
$requirements = $route->getRequirements();
|
||||
if (array_key_exists('_csrf_request_header_token', $requirements)) {
|
||||
if (isset($requirements['_method'])) {
|
||||
// There could be more than one method requirement separated with '|'.
|
||||
$methods = explode('|', $requirements['_method']);
|
||||
// CSRF protection only applies to write operations, so we can filter
|
||||
// out any routes that require reading methods only.
|
||||
$write_methods = array_diff($methods, ['GET', 'HEAD', 'OPTIONS', 'TRACE']);
|
||||
if (empty($write_methods)) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
// No method requirement given, so we run this access check to be on the
|
||||
// safe side.
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks access.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request object.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The currently logged in account.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function access(Request $request, AccountInterface $account) {
|
||||
$method = $request->getMethod();
|
||||
|
||||
// Read-only operations are always allowed.
|
||||
if (in_array($method, ['GET', 'HEAD', 'OPTIONS', 'TRACE'], TRUE)) {
|
||||
return AccessResult::allowed();
|
||||
}
|
||||
|
||||
// This check only applies if
|
||||
// 1. the user was successfully authenticated and
|
||||
// 2. the request comes with a session cookie.
|
||||
if ($account->isAuthenticated()
|
||||
&& $this->sessionConfiguration->hasSession($request)
|
||||
) {
|
||||
if (!$request->headers->has('X-CSRF-Token')) {
|
||||
return AccessResult::forbidden()->setReason('X-CSRF-Token request header is missing')->setCacheMaxAge(0);
|
||||
}
|
||||
$csrf_token = $request->headers->get('X-CSRF-Token');
|
||||
// @todo Remove validate call using 'rest' in 8.3.
|
||||
// Kept here for sessions active during update.
|
||||
if (!$this->csrfToken->validate($csrf_token, self::TOKEN_KEY)
|
||||
&& !$this->csrfToken->validate($csrf_token, 'rest')) {
|
||||
return AccessResult::forbidden()->setReason('X-CSRF-Token request header is invalid')->setCacheMaxAge(0);
|
||||
}
|
||||
}
|
||||
// Let other access checkers decide if the request is legit.
|
||||
return AccessResult::allowed()->setCacheMaxAge(0);
|
||||
}
|
||||
|
||||
}
|
||||
118
web/core/lib/Drupal/Core/Access/CsrfTokenGenerator.php
Normal file
118
web/core/lib/Drupal/Core/Access/CsrfTokenGenerator.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Core\PrivateKey;
|
||||
use Drupal\Core\Session\MetadataBag;
|
||||
use Drupal\Core\Site\Settings;
|
||||
|
||||
/**
|
||||
* Generates and validates CSRF tokens.
|
||||
*
|
||||
* @see \Drupal\Tests\Core\Access\CsrfTokenGeneratorTest
|
||||
*/
|
||||
class CsrfTokenGenerator {
|
||||
|
||||
/**
|
||||
* The private key service.
|
||||
*
|
||||
* @var \Drupal\Core\PrivateKey
|
||||
*/
|
||||
protected $privateKey;
|
||||
|
||||
/**
|
||||
* The session metadata bag.
|
||||
*
|
||||
* @var \Drupal\Core\Session\MetadataBag
|
||||
*/
|
||||
protected $sessionMetadata;
|
||||
|
||||
/**
|
||||
* Constructs the token generator.
|
||||
*
|
||||
* @param \Drupal\Core\PrivateKey $private_key
|
||||
* The private key service.
|
||||
* @param \Drupal\Core\Session\MetadataBag $session_metadata
|
||||
* The session metadata bag.
|
||||
*/
|
||||
public function __construct(PrivateKey $private_key, MetadataBag $session_metadata) {
|
||||
$this->privateKey = $private_key;
|
||||
$this->sessionMetadata = $session_metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a token based on $value, the user session, and the private key.
|
||||
*
|
||||
* The generated token is based on the session of the current user. Normally,
|
||||
* anonymous users do not have a session, so the generated token will be
|
||||
* different on every page request. To generate a token for users without a
|
||||
* session, manually start a session prior to calling this function.
|
||||
*
|
||||
* @param string $value
|
||||
* (optional) An additional value to base the token on.
|
||||
*
|
||||
* @return string
|
||||
* A 43-character URL-safe token for validation, based on the token seed,
|
||||
* the hash salt provided by Settings::getHashSalt(), and the
|
||||
* 'drupal_private_key' configuration variable.
|
||||
*
|
||||
* @see \Drupal\Core\Site\Settings::getHashSalt()
|
||||
* @see \Symfony\Component\HttpFoundation\Session\SessionInterface::start()
|
||||
*/
|
||||
public function get($value = '') {
|
||||
$seed = $this->sessionMetadata->getCsrfTokenSeed();
|
||||
if (empty($seed)) {
|
||||
$seed = Crypt::randomBytesBase64();
|
||||
$this->sessionMetadata->setCsrfTokenSeed($seed);
|
||||
}
|
||||
|
||||
return $this->computeToken($seed, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a token based on $value, the user session, and the private key.
|
||||
*
|
||||
* @param string $token
|
||||
* The token to be validated.
|
||||
* @param string $value
|
||||
* (optional) An additional value to base the token on.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE for a valid token, FALSE for an invalid token.
|
||||
*/
|
||||
public function validate($token, $value = '') {
|
||||
$seed = $this->sessionMetadata->getCsrfTokenSeed();
|
||||
if (empty($seed)) {
|
||||
return FALSE;
|
||||
}
|
||||
$value = $this->computeToken($seed, $value);
|
||||
// PHP 8.0 strictly type hints for hash_equals. Maintain BC until we can
|
||||
// enforce scalar type hints on this method.
|
||||
if (!is_string($token)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return hash_equals($value, $token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a token based on $value, the token seed, and the private key.
|
||||
*
|
||||
* @param string $seed
|
||||
* The per-session token seed.
|
||||
* @param string $value
|
||||
* (optional) An additional value to base the token on.
|
||||
*
|
||||
* @return string
|
||||
* A 43-character URL-safe token for validation, based on the token seed,
|
||||
* the hash salt provided by Settings::getHashSalt(), and the site private
|
||||
* key.
|
||||
*
|
||||
* @see \Drupal\Core\Site\Settings::getHashSalt()
|
||||
*/
|
||||
protected function computeToken($seed, $value = '') {
|
||||
return Crypt::hmacBase64($value, $seed . $this->privateKey->get() . Settings::getHashSalt());
|
||||
}
|
||||
|
||||
}
|
||||
69
web/core/lib/Drupal/Core/Access/CustomAccessCheck.php
Normal file
69
web/core/lib/Drupal/Core/Access/CustomAccessCheck.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Utility\CallableResolver;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Defines an access checker that allows specifying a custom method for access.
|
||||
*
|
||||
* You should only use it when you are sure that the access callback will not be
|
||||
* reused. Good examples in core are Edit or Toolbar module.
|
||||
*
|
||||
* The method is called on another instance of the controller class, so you
|
||||
* cannot reuse any stored property of your actual controller instance used
|
||||
* to generate the output.
|
||||
*/
|
||||
class CustomAccessCheck implements RoutingAccessInterface {
|
||||
|
||||
/**
|
||||
* Constructs a CustomAccessCheck instance.
|
||||
*
|
||||
* @param \Drupal\Core\Utility\CallableResolver $callableResolver
|
||||
* The callable resolver.
|
||||
* @param \Drupal\Core\Access\AccessArgumentsResolverFactoryInterface $argumentsResolverFactory
|
||||
* The arguments resolver factory.
|
||||
*/
|
||||
public function __construct(
|
||||
protected CallableResolver $callableResolver,
|
||||
protected AccessArgumentsResolverFactoryInterface $argumentsResolverFactory,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks access for the account and route using the custom access checker.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route.
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match object to be checked.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The account being checked.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* Optional, a request. Only supply this parameter when checking the
|
||||
* incoming request.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account, ?Request $request = NULL) {
|
||||
try {
|
||||
$callable = $this->callableResolver->getCallableFromDefinition($route->getRequirement('_custom_access'));
|
||||
}
|
||||
catch (\InvalidArgumentException) {
|
||||
// The custom access controller method was not found.
|
||||
throw new \BadMethodCallException(sprintf('The "%s" method is not callable as a _custom_access callback in route "%s"', $route->getRequirement('_custom_access'), $route->getPath()));
|
||||
}
|
||||
|
||||
$arguments_resolver = $this->argumentsResolverFactory->getArgumentsResolver($route_match, $account, $request);
|
||||
$arguments = $arguments_resolver->getArguments($callable);
|
||||
|
||||
return call_user_func_array($callable, $arguments);
|
||||
}
|
||||
|
||||
}
|
||||
34
web/core/lib/Drupal/Core/Access/DefaultAccessCheck.php
Normal file
34
web/core/lib/Drupal/Core/Access/DefaultAccessCheck.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Allows access to routes to be controlled by an '_access' boolean parameter.
|
||||
*/
|
||||
class DefaultAccessCheck implements RoutingAccessInterface {
|
||||
|
||||
/**
|
||||
* Checks access to the route based on the _access parameter.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route to check against.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function access(Route $route) {
|
||||
if ($route->getRequirement('_access') === 'TRUE') {
|
||||
return AccessResult::allowed();
|
||||
}
|
||||
elseif ($route->getRequirement('_access') === 'FALSE') {
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
else {
|
||||
return AccessResult::neutral();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
35
web/core/lib/Drupal/Core/Access/DependentAccessInterface.php
Normal file
35
web/core/lib/Drupal/Core/Access/DependentAccessInterface.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
/**
|
||||
* Interface for AccessibleInterface objects that have an access dependency.
|
||||
*
|
||||
* Objects should implement this interface when their access depends on access
|
||||
* to another object that implements \Drupal\Core\Access\AccessibleInterface.
|
||||
* This interface simply provides the getter method for the access
|
||||
* dependency object. Objects that implement this interface are responsible for
|
||||
* checking access of the access dependency because the dependency may not take
|
||||
* effect in all cases. For instance an entity may only need the access
|
||||
* dependency set when it is embedded within another entity and its access
|
||||
* should be dependent on access to the entity in which it is embedded.
|
||||
*
|
||||
* To check the access to the dependency the object implementing this interface
|
||||
* can use code like this:
|
||||
* @code
|
||||
* $accessible->getAccessDependency()->access($op, $account, TRUE);
|
||||
* @endcode
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface DependentAccessInterface {
|
||||
|
||||
/**
|
||||
* Gets the access dependency.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessibleInterface|null
|
||||
* The access dependency or NULL if none has been set.
|
||||
*/
|
||||
public function getAccessDependency();
|
||||
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
/**
|
||||
* An interface to allow adding an access dependency.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface RefinableDependentAccessInterface extends DependentAccessInterface {
|
||||
|
||||
/**
|
||||
* Sets the access dependency.
|
||||
*
|
||||
* If an access dependency is already set this will replace the existing
|
||||
* dependency.
|
||||
*
|
||||
* @param \Drupal\Core\Access\AccessibleInterface $access_dependency
|
||||
* The object upon which access depends.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAccessDependency(AccessibleInterface $access_dependency);
|
||||
|
||||
/**
|
||||
* Adds an access dependency into the existing access dependency.
|
||||
*
|
||||
* If no existing dependency is currently set this will set the dependency
|
||||
* will be set to the new value.
|
||||
*
|
||||
* If there is an existing dependency and it is not an instance of
|
||||
* AccessGroupAnd the dependency will be set as a new AccessGroupAnd
|
||||
* instance with the existing and new dependencies as the members of the
|
||||
* group.
|
||||
*
|
||||
* If there is an existing dependency and it is an instance of AccessGroupAnd
|
||||
* the dependency will be added to the existing access group.
|
||||
*
|
||||
* @param \Drupal\Core\Access\AccessibleInterface $access_dependency
|
||||
* The access dependency to merge.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addAccessDependency(AccessibleInterface $access_dependency);
|
||||
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
/**
|
||||
* Trait for \Drupal\Core\Access\RefinableDependentAccessInterface.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait RefinableDependentAccessTrait {
|
||||
|
||||
/**
|
||||
* The access dependency.
|
||||
*
|
||||
* @var \Drupal\Core\Access\AccessibleInterface
|
||||
*/
|
||||
protected $accessDependency;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setAccessDependency(AccessibleInterface $access_dependency) {
|
||||
$this->accessDependency = $access_dependency;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAccessDependency() {
|
||||
return $this->accessDependency;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addAccessDependency(AccessibleInterface $access_dependency) {
|
||||
if (empty($this->accessDependency)) {
|
||||
$this->accessDependency = $access_dependency;
|
||||
return $this;
|
||||
}
|
||||
if (!$this->accessDependency instanceof AccessGroupAnd) {
|
||||
$accessGroup = new AccessGroupAnd();
|
||||
$this->accessDependency = $accessGroup->addDependency($this->accessDependency);
|
||||
}
|
||||
$this->accessDependency->addDependency($access_dependency);
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
58
web/core/lib/Drupal/Core/Access/RoutePathGenerationTrait.php
Normal file
58
web/core/lib/Drupal/Core/Access/RoutePathGenerationTrait.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Provides a method for generating route paths.
|
||||
*/
|
||||
trait RoutePathGenerationTrait {
|
||||
|
||||
/**
|
||||
* Generates a route path by replacing placeholders with their values.
|
||||
*
|
||||
* Placeholders without corresponding values in the parameters array
|
||||
* are removed from the resulting path.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
* The route object containing the path with placeholders.
|
||||
* @param array $parameters
|
||||
* An associative array of parameters to replace in the route path,
|
||||
* where the keys are placeholders and the values are the replacement
|
||||
* values.
|
||||
* @code
|
||||
* Example:
|
||||
* [
|
||||
* 'parameter1' => 'value1',
|
||||
* ]
|
||||
* @endcode
|
||||
* This will transform a route path such as
|
||||
* '/route/path/{parameter1}{parameter2}' into '/route/path/value1'.
|
||||
*
|
||||
* @return string
|
||||
* The generated path with all placeholders either replaced by their
|
||||
* corresponding values or removed if no matching parameter exists.
|
||||
*/
|
||||
public function generateRoutePath(Route $route, array $parameters): string {
|
||||
$path = ltrim($route->getPath(), '/');
|
||||
|
||||
// Replace path parameters with their corresponding values from the
|
||||
// parameters array.
|
||||
foreach ($parameters as $param => $value) {
|
||||
if (NULL !== $value) {
|
||||
$path = str_replace("{{$param}}", $value, $path);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove placeholders that were not replaced.
|
||||
$path = preg_replace('/\/{[^}]+}/', '', $path);
|
||||
|
||||
// Remove trailing slashes (multiple slashes may result from the removal of
|
||||
// unreplaced placeholders).
|
||||
$path = rtrim($path, '/');
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
}
|
||||
93
web/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php
Normal file
93
web/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Core\Security\TrustedCallbackInterface;
|
||||
use Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Processes the outbound route to handle the CSRF token.
|
||||
*/
|
||||
class RouteProcessorCsrf implements OutboundRouteProcessorInterface, TrustedCallbackInterface {
|
||||
|
||||
use RoutePathGenerationTrait;
|
||||
|
||||
/**
|
||||
* Constructs a RouteProcessorCsrf object.
|
||||
*
|
||||
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrfToken
|
||||
* The CSRF token generator.
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack|null $requestStack
|
||||
* The request stack.
|
||||
*/
|
||||
public function __construct(
|
||||
protected CsrfTokenGenerator $csrfToken,
|
||||
protected ?RequestStack $requestStack = NULL,
|
||||
) {
|
||||
if ($requestStack === NULL) {
|
||||
@trigger_error('Calling ' . __CLASS__ . ' constructor without the $requestStack argument is deprecated in drupal:11.2.0 and it will be required in drupal:12.0.0. See https://www.drupal.org/project/drupal/issues/3485174', E_USER_DEPRECATED);
|
||||
$this->requestStack = \Drupal::service('request_stack');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processOutbound($route_name, Route $route, array &$parameters, ?BubbleableMetadata $bubbleable_metadata = NULL) {
|
||||
if ($route->hasRequirement('_csrf_token')) {
|
||||
$path = $this->generateRoutePath($route, $parameters);
|
||||
// Adding this to the parameters means it will get merged into the query
|
||||
// string when the route is compiled.
|
||||
if (!$bubbleable_metadata || $this->requestStack->getCurrentRequest()->getRequestFormat() !== 'html') {
|
||||
$parameters['token'] = $this->csrfToken->get($path);
|
||||
}
|
||||
else {
|
||||
// Generate a placeholder and a render array to replace it.
|
||||
$placeholder = Crypt::hashBase64($path);
|
||||
$placeholder_render_array = [
|
||||
'#lazy_builder' => ['route_processor_csrf:renderPlaceholderCsrfToken', [$path]],
|
||||
];
|
||||
// Instead of setting an actual CSRF token as the query string, we set
|
||||
// the placeholder, which will be replaced at the very last moment. This
|
||||
// ensures links with CSRF tokens don't break cacheability.
|
||||
$parameters['token'] = $placeholder;
|
||||
$bubbleable_metadata->addAttachments(['placeholders' => [$placeholder => $placeholder_render_array]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render API callback: Adds a CSRF token for the given path to the markup.
|
||||
*
|
||||
* This function is assigned as a #lazy_builder callback.
|
||||
*
|
||||
* @param string $path
|
||||
* The path to get a CSRF token for.
|
||||
*
|
||||
* @return array
|
||||
* A renderable array representing the CSRF token.
|
||||
*/
|
||||
public function renderPlaceholderCsrfToken($path) {
|
||||
return [
|
||||
'#markup' => $this->csrfToken->get($path),
|
||||
// Tokens are per session.
|
||||
'#cache' => [
|
||||
'contexts' => [
|
||||
'session',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function trustedCallbacks() {
|
||||
return ['renderPlaceholderCsrfToken'];
|
||||
}
|
||||
|
||||
}
|
||||
26
web/core/lib/Drupal/Core/Action/ActionBase.php
Normal file
26
web/core/lib/Drupal/Core/Action/ActionBase.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Action;
|
||||
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
|
||||
/**
|
||||
* Provides a base implementation for an Action plugin.
|
||||
*
|
||||
* @see \Drupal\Core\Annotation\Action
|
||||
* @see \Drupal\Core\Action\ActionManager
|
||||
* @see \Drupal\Core\Action\ActionInterface
|
||||
* @see plugin_api
|
||||
*/
|
||||
abstract class ActionBase extends PluginBase implements ActionInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function executeMultiple(array $entities) {
|
||||
foreach ($entities as $entity) {
|
||||
$this->execute($entity);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
61
web/core/lib/Drupal/Core/Action/ActionInterface.php
Normal file
61
web/core/lib/Drupal/Core/Action/ActionInterface.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Action;
|
||||
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\Core\Executable\ExecutableInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface for an Action plugin.
|
||||
*
|
||||
* @todo WARNING: The action API is going to receive some additions before
|
||||
* release. The following additions are likely to happen:
|
||||
* - The way configuration is handled and configuration forms are built is
|
||||
* likely to change in order for the plugin to be of use for Rules.
|
||||
* - Actions are going to become context-aware in
|
||||
* https://www.drupal.org/node/2011038, what will deprecated the 'type'
|
||||
* annotation.
|
||||
* - Instead of action implementations saving entities, support for marking
|
||||
* required context as to be saved by the execution manager will be added as
|
||||
* part of https://www.drupal.org/node/2347017.
|
||||
* - Actions will receive a data processing API that allows for token
|
||||
* replacements to happen outside of the action plugin implementations,
|
||||
* see https://www.drupal.org/node/2347023.
|
||||
*
|
||||
* @see \Drupal\Core\Annotation\Action
|
||||
* @see \Drupal\Core\Action\ActionManager
|
||||
* @see \Drupal\Core\Action\ActionBase
|
||||
* @see plugin_api
|
||||
*/
|
||||
interface ActionInterface extends ExecutableInterface, PluginInspectionInterface {
|
||||
|
||||
/**
|
||||
* Executes the plugin for an array of objects.
|
||||
*
|
||||
* @param array $objects
|
||||
* An array of entities.
|
||||
*/
|
||||
public function executeMultiple(array $objects);
|
||||
|
||||
/**
|
||||
* Checks object access.
|
||||
*
|
||||
* @param mixed $object
|
||||
* The object to execute the action on.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* (optional) The user for which to check access, or NULL to check access
|
||||
* for the current user. Defaults to NULL.
|
||||
* @param bool $return_as_object
|
||||
* (optional) Defaults to FALSE.
|
||||
*
|
||||
* @return bool|\Drupal\Core\Access\AccessResultInterface
|
||||
* The access result. Returns a boolean if $return_as_object is FALSE (this
|
||||
* is the default) and otherwise an AccessResultInterface object.
|
||||
* When a boolean is returned, the result of AccessInterface::isAllowed() is
|
||||
* returned, i.e. TRUE means access is explicitly allowed, FALSE means
|
||||
* access is either explicitly forbidden or "no opinion".
|
||||
*/
|
||||
public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE);
|
||||
|
||||
}
|
||||
56
web/core/lib/Drupal/Core/Action/ActionManager.php
Normal file
56
web/core/lib/Drupal/Core/Action/ActionManager.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Action;
|
||||
|
||||
use Drupal\Component\Plugin\CategorizingPluginManagerInterface;
|
||||
use Drupal\Core\Action\Attribute\Action;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\CategorizingPluginManagerTrait;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
|
||||
/**
|
||||
* Provides an Action plugin manager.
|
||||
*
|
||||
* @see \Drupal\Core\Annotation\Action
|
||||
* @see \Drupal\Core\Action\ActionInterface
|
||||
* @see \Drupal\Core\Action\ActionBase
|
||||
* @see plugin_api
|
||||
*/
|
||||
class ActionManager extends DefaultPluginManager implements CategorizingPluginManagerInterface {
|
||||
|
||||
use CategorizingPluginManagerTrait;
|
||||
|
||||
/**
|
||||
* Constructs a new class instance.
|
||||
*
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* Cache backend instance to use.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler to invoke the alter hook with.
|
||||
*/
|
||||
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
|
||||
parent::__construct('Plugin/Action', $namespaces, $module_handler, ActionInterface::class, Action::class, 'Drupal\Core\Annotation\Action');
|
||||
$this->alterInfo('action_info');
|
||||
$this->setCacheBackend($cache_backend, 'action_info');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin definitions for this entity type.
|
||||
*
|
||||
* @param string $type
|
||||
* The entity type name.
|
||||
*
|
||||
* @return array
|
||||
* An array of plugin definitions for this entity type.
|
||||
*/
|
||||
public function getDefinitionsByType($type) {
|
||||
return array_filter($this->getDefinitions(), function ($definition) use ($type) {
|
||||
return $definition['type'] === $type;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
22
web/core/lib/Drupal/Core/Action/ActionPluginCollection.php
Normal file
22
web/core/lib/Drupal/Core/Action/ActionPluginCollection.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Action;
|
||||
|
||||
use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection;
|
||||
|
||||
/**
|
||||
* Provides a container for lazily loading Action plugins.
|
||||
*/
|
||||
class ActionPluginCollection extends DefaultSingleLazyPluginCollection {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return \Drupal\Core\Action\ActionInterface
|
||||
* The action plugin instance.
|
||||
*/
|
||||
public function &get($instance_id) {
|
||||
return parent::get($instance_id);
|
||||
}
|
||||
|
||||
}
|
||||
51
web/core/lib/Drupal/Core/Action/Attribute/Action.php
Normal file
51
web/core/lib/Drupal/Core/Action/Attribute/Action.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Attribute;
|
||||
|
||||
use Drupal\Component\Plugin\Attribute\Plugin;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Defines an Action attribute object.
|
||||
*
|
||||
* Plugin Namespace: Plugin\Action
|
||||
*
|
||||
* @see \Drupal\Core\Action\ActionInterface
|
||||
* @see \Drupal\Core\Action\ActionManager
|
||||
* @see \Drupal\Core\Action\ActionBase
|
||||
* @see \Drupal\Core\Action\Plugin\Action\UnpublishAction
|
||||
* @see plugin_api
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
class Action extends Plugin {
|
||||
|
||||
/**
|
||||
* Constructs an Action attribute.
|
||||
*
|
||||
* @param string $id
|
||||
* The plugin ID.
|
||||
* @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $label
|
||||
* The label of the action.
|
||||
* @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $action_label
|
||||
* (optional) A label that can be used by the action deriver.
|
||||
* @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $category
|
||||
* (optional) The category under which the action should be listed in the
|
||||
* UI.
|
||||
* @param class-string|null $deriver
|
||||
* (optional) The deriver class.
|
||||
* @param string|null $confirm_form_route_name
|
||||
* (optional) The route name for a confirmation form for this action.
|
||||
* @param string|null $type
|
||||
* (optional) The entity type the action can apply to.
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $id,
|
||||
public readonly ?TranslatableMarkup $label = NULL,
|
||||
public readonly ?TranslatableMarkup $action_label = NULL,
|
||||
public readonly ?TranslatableMarkup $category = NULL,
|
||||
public readonly ?string $deriver = NULL,
|
||||
public readonly ?string $confirm_form_route_name = NULL,
|
||||
public readonly ?string $type = NULL,
|
||||
) {}
|
||||
|
||||
}
|
||||
58
web/core/lib/Drupal/Core/Action/ConfigurableActionBase.php
Normal file
58
web/core/lib/Drupal/Core/Action/ConfigurableActionBase.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Action;
|
||||
|
||||
use Drupal\Component\Plugin\ConfigurableInterface;
|
||||
use Drupal\Component\Plugin\DependentPluginInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\PluginFormInterface;
|
||||
|
||||
/**
|
||||
* Provides a base implementation for a configurable Action plugin.
|
||||
*/
|
||||
abstract class ConfigurableActionBase extends ActionBase implements ConfigurableInterface, DependentPluginInterface, PluginFormInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->setConfiguration($configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfiguration() {
|
||||
return $this->configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setConfiguration(array $configuration) {
|
||||
$this->configuration = $configuration + $this->defaultConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
101
web/core/lib/Drupal/Core/Action/Plugin/Action/DeleteAction.php
Normal file
101
web/core/lib/Drupal/Core/Action/Plugin/Action/DeleteAction.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Action\Plugin\Action\Derivative\EntityDeleteActionDeriver;
|
||||
use Drupal\Core\Action\Attribute\Action;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\TempStore\PrivateTempStoreFactory;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Redirects to an entity deletion form.
|
||||
*/
|
||||
#[Action(
|
||||
id: 'entity:delete_action',
|
||||
action_label: new TranslatableMarkup('Delete'),
|
||||
deriver: EntityDeleteActionDeriver::class
|
||||
)]
|
||||
class DeleteAction extends EntityActionBase {
|
||||
|
||||
/**
|
||||
* The tempstore object.
|
||||
*
|
||||
* @var \Drupal\Core\TempStore\PrivateTempStoreFactory
|
||||
*/
|
||||
protected $tempStore;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* Constructs a new DeleteAction object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
|
||||
* The tempstore factory.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* Current user.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) {
|
||||
$this->currentUser = $current_user;
|
||||
$this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm');
|
||||
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('tempstore.private'),
|
||||
$container->get('current_user')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function executeMultiple(array $entities) {
|
||||
/** @var \Drupal\Core\Entity\EntityInterface[] $entities */
|
||||
$selection = [];
|
||||
foreach ($entities as $entity) {
|
||||
$langcode = $entity->language()->getId();
|
||||
$selection[$entity->id()][$langcode] = $langcode;
|
||||
}
|
||||
$this->tempStore->set($this->currentUser->id() . ':' . $this->getPluginDefinition()['type'], $selection);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($object = NULL) {
|
||||
$this->executeMultiple([$object]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
return $object->access('delete', $account, $return_as_object);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Plugin\Action\Derivative;
|
||||
|
||||
use Drupal\Component\Plugin\Derivative\DeriverBase;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\StringTranslation\TranslationInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a base action for each entity type with specific interfaces.
|
||||
*/
|
||||
abstract class EntityActionDeriverBase extends DeriverBase implements ContainerDeriverInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a new EntityActionDeriverBase object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
|
||||
* The string translation service.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->stringTranslation = $string_translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('string_translation')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the deriver can be used for the provided entity type.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the entity type can be used, FALSE otherwise.
|
||||
*/
|
||||
abstract protected function isApplicable(EntityTypeInterface $entity_type);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
if (empty($this->derivatives)) {
|
||||
$definitions = [];
|
||||
foreach ($this->getApplicableEntityTypes() as $entity_type_id => $entity_type) {
|
||||
$definition = $base_plugin_definition;
|
||||
$definition['type'] = $entity_type_id;
|
||||
$definition['label'] = sprintf('%s %s', $base_plugin_definition['action_label'], $entity_type->getSingularLabel());
|
||||
$definitions[$entity_type_id] = $definition;
|
||||
}
|
||||
$this->derivatives = $definitions;
|
||||
}
|
||||
|
||||
return parent::getDerivativeDefinitions($base_plugin_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of applicable entity types.
|
||||
*
|
||||
* The list consists of all entity types which match the conditions for the
|
||||
* given deriver.
|
||||
* For example, if the action applies to entities that are publishable,
|
||||
* this method will find all entity types that are publishable.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityTypeInterface[]
|
||||
* The applicable entity types, keyed by entity type ID.
|
||||
*/
|
||||
protected function getApplicableEntityTypes() {
|
||||
$entity_types = $this->entityTypeManager->getDefinitions();
|
||||
$entity_types = array_filter($entity_types, function (EntityTypeInterface $entity_type) {
|
||||
return $this->isApplicable($entity_type);
|
||||
});
|
||||
|
||||
return $entity_types;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Plugin\Action\Derivative;
|
||||
|
||||
use Drupal\Core\Entity\EntityChangedInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
|
||||
/**
|
||||
* Provides an action deriver that finds entity types of EntityChangedInterface.
|
||||
*
|
||||
* @see \Drupal\Core\Action\Plugin\Action\SaveAction
|
||||
*/
|
||||
class EntityChangedActionDeriver extends EntityActionDeriverBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function isApplicable(EntityTypeInterface $entity_type) {
|
||||
return $entity_type->entityClassImplements(EntityChangedInterface::class);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Plugin\Action\Derivative;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
|
||||
/**
|
||||
* Provides an action deriver that finds entity types with delete form.
|
||||
*
|
||||
* @see \Drupal\Core\Action\Plugin\Action\DeleteAction
|
||||
*/
|
||||
class EntityDeleteActionDeriver extends EntityActionDeriverBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
if (empty($this->derivatives)) {
|
||||
$definitions = [];
|
||||
foreach ($this->getApplicableEntityTypes() as $entity_type_id => $entity_type) {
|
||||
$definition = $base_plugin_definition;
|
||||
$definition['type'] = $entity_type_id;
|
||||
$definition['label'] = $this->t('Delete @entity_type', ['@entity_type' => $entity_type->getSingularLabel()]);
|
||||
$definition['confirm_form_route_name'] = 'entity.' . $entity_type->id() . '.delete_multiple_form';
|
||||
$definitions[$entity_type_id] = $definition;
|
||||
}
|
||||
$this->derivatives = $definitions;
|
||||
}
|
||||
|
||||
return $this->derivatives;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function isApplicable(EntityTypeInterface $entity_type) {
|
||||
return $entity_type->hasLinkTemplate('delete-multiple-form');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Plugin\Action\Derivative;
|
||||
|
||||
use Drupal\Core\Entity\EntityPublishedInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
|
||||
/**
|
||||
* Provides an action deriver that finds publishable entity types.
|
||||
*
|
||||
* @see \Drupal\Core\Action\Plugin\Action\PublishAction
|
||||
* @see \Drupal\Core\Action\Plugin\Action\UnpublishAction
|
||||
*/
|
||||
class EntityPublishedActionDeriver extends EntityActionDeriverBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function isApplicable(EntityTypeInterface $entity_type) {
|
||||
return $entity_type->entityClassImplements(EntityPublishedInterface::class);
|
||||
}
|
||||
|
||||
}
|
||||
218
web/core/lib/Drupal/Core/Action/Plugin/Action/EmailAction.php
Normal file
218
web/core/lib/Drupal/Core/Action/Plugin/Action/EmailAction.php
Normal file
@ -0,0 +1,218 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Plugin\Action;
|
||||
|
||||
use Drupal\Component\Render\PlainTextOutput;
|
||||
use Drupal\Component\Utility\EmailValidatorInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Action\ConfigurableActionBase;
|
||||
use Drupal\Core\Action\Attribute\Action;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Mail\MailManagerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Utility\Token;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Sends an email message.
|
||||
*/
|
||||
#[Action(
|
||||
id: 'action_send_email_action',
|
||||
label: new TranslatableMarkup('Send email'),
|
||||
type: 'system'
|
||||
)]
|
||||
class EmailAction extends ConfigurableActionBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The token service.
|
||||
*
|
||||
* @var \Drupal\Core\Utility\Token
|
||||
*/
|
||||
protected $token;
|
||||
|
||||
/**
|
||||
* The user storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* A logger instance.
|
||||
*
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* The mail manager.
|
||||
*
|
||||
* @var \Drupal\Core\Mail\MailManagerInterface
|
||||
*/
|
||||
protected $mailManager;
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* The email validator.
|
||||
*
|
||||
* @var \Drupal\Component\Utility\EmailValidatorInterface
|
||||
*/
|
||||
protected $emailValidator;
|
||||
|
||||
/**
|
||||
* Constructs an EmailAction object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Utility\Token $token
|
||||
* The token service.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Psr\Log\LoggerInterface $logger
|
||||
* A logger instance.
|
||||
* @param \Drupal\Core\Mail\MailManagerInterface $mail_manager
|
||||
* The mail manager.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Component\Utility\EmailValidatorInterface $email_validator
|
||||
* The email validator.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, Token $token, EntityTypeManagerInterface $entity_type_manager, LoggerInterface $logger, MailManagerInterface $mail_manager, LanguageManagerInterface $language_manager, EmailValidatorInterface $email_validator) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->token = $token;
|
||||
$this->storage = $entity_type_manager->getStorage('user');
|
||||
$this->logger = $logger;
|
||||
$this->mailManager = $mail_manager;
|
||||
$this->languageManager = $language_manager;
|
||||
$this->emailValidator = $email_validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static($configuration, $plugin_id, $plugin_definition,
|
||||
$container->get('token'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('logger.factory')->get('action'),
|
||||
$container->get('plugin.manager.mail'),
|
||||
$container->get('language_manager'),
|
||||
$container->get('email.validator')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($entity = NULL) {
|
||||
if (empty($this->configuration['node'])) {
|
||||
$this->configuration['node'] = $entity;
|
||||
}
|
||||
|
||||
$recipient = PlainTextOutput::renderFromHtml($this->token->replace($this->configuration['recipient'], $this->configuration));
|
||||
|
||||
// If the recipient is a registered user with a language preference, use
|
||||
// the recipient's preferred language. Otherwise, use the system default
|
||||
// language.
|
||||
$recipient_accounts = $this->storage->loadByProperties(['mail' => $recipient]);
|
||||
$recipient_account = reset($recipient_accounts);
|
||||
if ($recipient_account) {
|
||||
$langcode = $recipient_account->getPreferredLangcode();
|
||||
}
|
||||
else {
|
||||
$langcode = $this->languageManager->getDefaultLanguage()->getId();
|
||||
}
|
||||
$params = ['context' => $this->configuration];
|
||||
|
||||
$message = $this->mailManager->mail('system', 'action_send_email', $recipient, $langcode, $params);
|
||||
// Error logging is handled by \Drupal\Core\Mail\MailManager::mail().
|
||||
if ($message['result']) {
|
||||
$this->logger->info('Sent email to %recipient', ['%recipient' => $recipient]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'recipient' => '',
|
||||
'subject' => '',
|
||||
'message' => '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form['recipient'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Recipient email address'),
|
||||
'#default_value' => $this->configuration['recipient'],
|
||||
'#maxlength' => '254',
|
||||
'#description' => $this->t('You may also use tokens: [node:author:mail], [comment:author:mail], etc. Separate recipients with a comma.'),
|
||||
];
|
||||
$form['subject'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Subject'),
|
||||
'#default_value' => $this->configuration['subject'],
|
||||
'#maxlength' => '254',
|
||||
'#description' => $this->t('The subject of the message.'),
|
||||
];
|
||||
$form['message'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->t('Message'),
|
||||
'#default_value' => $this->configuration['message'],
|
||||
'#cols' => '80',
|
||||
'#rows' => '20',
|
||||
'#description' => $this->t('The message that should be sent. You may include placeholders like [node:title], [user:account-name], [user:display-name] and [comment:body] to represent data that will be different each time message is sent. Not all placeholders will be available in all contexts.'),
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
if (!$this->emailValidator->isValid($form_state->getValue('recipient')) && !str_contains($form_state->getValue('recipient'), ':mail')) {
|
||||
// We want the literal %author placeholder to be emphasized in the error
|
||||
// message.
|
||||
$form_state->setErrorByName('recipient', $this->t('Enter a valid email address or use a token email address such as %author.', ['%author' => '[node:author:mail]']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->configuration['recipient'] = $form_state->getValue('recipient');
|
||||
$this->configuration['subject'] = $form_state->getValue('subject');
|
||||
$this->configuration['message'] = $form_state->getValue('message');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$result = AccessResult::allowed();
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Plugin\Action;
|
||||
|
||||
use Drupal\Component\Plugin\DependentPluginInterface;
|
||||
use Drupal\Core\Action\ActionBase;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Base class for entity-based actions.
|
||||
*/
|
||||
abstract class EntityActionBase extends ActionBase implements DependentPluginInterface, ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs an EntityActionBase object.
|
||||
*
|
||||
* @param mixed[] $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
$module_name = $this->entityTypeManager
|
||||
->getDefinition($this->getPluginDefinition()['type'])
|
||||
->getProvider();
|
||||
return ['module' => [$module_name]];
|
||||
}
|
||||
|
||||
}
|
||||
141
web/core/lib/Drupal/Core/Action/Plugin/Action/GotoAction.php
Normal file
141
web/core/lib/Drupal/Core/Action/Plugin/Action/GotoAction.php
Normal file
@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Plugin\Action;
|
||||
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Action\ConfigurableActionBase;
|
||||
use Drupal\Core\Action\Attribute\Action;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Utility\UnroutedUrlAssemblerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
/**
|
||||
* Redirects to a different URL.
|
||||
*/
|
||||
#[Action(
|
||||
id: 'action_goto_action',
|
||||
label: new TranslatableMarkup('Redirect to URL'),
|
||||
type: 'system'
|
||||
)]
|
||||
class GotoAction extends ConfigurableActionBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The event dispatcher service.
|
||||
*
|
||||
* @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface
|
||||
*/
|
||||
protected $dispatcher;
|
||||
|
||||
/**
|
||||
* The unrouted URL assembler service.
|
||||
*
|
||||
* @var \Drupal\Core\Utility\UnroutedUrlAssemblerInterface
|
||||
*/
|
||||
protected $unroutedUrlAssembler;
|
||||
|
||||
/**
|
||||
* Constructs a GotoAction object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $dispatcher
|
||||
* The tempstore factory.
|
||||
* @param \Drupal\Core\Utility\UnroutedUrlAssemblerInterface $url_assembler
|
||||
* The unrouted URL assembler service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EventDispatcherInterface $dispatcher, UnroutedUrlAssemblerInterface $url_assembler) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->unroutedUrlAssembler = $url_assembler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static($configuration, $plugin_id, $plugin_definition, $container->get('event_dispatcher'), $container->get('unrouted_url_assembler'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($object = NULL) {
|
||||
$url = $this->configuration['url'];
|
||||
// Leave external URLs unchanged, and assemble others as absolute URLs
|
||||
// relative to the site's base URL.
|
||||
if (!UrlHelper::isExternal($url)) {
|
||||
$parts = UrlHelper::parse($url);
|
||||
// @todo '<front>' is valid input for BC reasons, may be removed by
|
||||
// https://www.drupal.org/node/2421941
|
||||
if ($parts['path'] === '<front>') {
|
||||
$parts['path'] = '';
|
||||
}
|
||||
$uri = 'base:' . $parts['path'];
|
||||
$options = [
|
||||
'query' => $parts['query'],
|
||||
'fragment' => $parts['fragment'],
|
||||
'absolute' => TRUE,
|
||||
];
|
||||
// Treat this as if it's user input of a path relative to the site's
|
||||
// base URL.
|
||||
$url = $this->unroutedUrlAssembler->assemble($uri, $options);
|
||||
}
|
||||
$response = new RedirectResponse($url);
|
||||
$listener = function ($event) use ($response) {
|
||||
$event->setResponse($response);
|
||||
};
|
||||
// Add the listener to the event dispatcher.
|
||||
$this->dispatcher->addListener(KernelEvents::RESPONSE, $listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'url' => '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form['url'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('URL'),
|
||||
'#description' => $this->t('The URL to which the user should be redirected. This can be an internal URL like /node/1234 or an external URL like @url.', ['@url' => 'https://example.com']),
|
||||
'#default_value' => $this->configuration['url'],
|
||||
'#required' => TRUE,
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->configuration['url'] = $form_state->getValue('url');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$access = AccessResult::allowed();
|
||||
return $return_as_object ? $access : $access->isAllowed();
|
||||
}
|
||||
|
||||
}
|
||||
135
web/core/lib/Drupal/Core/Action/Plugin/Action/MessageAction.php
Normal file
135
web/core/lib/Drupal/Core/Action/Plugin/Action/MessageAction.php
Normal file
@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Action\ConfigurableActionBase;
|
||||
use Drupal\Core\Action\Attribute\Action;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Utility\Token;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Sends a message to the current user's screen.
|
||||
*/
|
||||
#[Action(
|
||||
id: 'action_message_action',
|
||||
label: new TranslatableMarkup('Display a message to the user'),
|
||||
type: 'system'
|
||||
)]
|
||||
class MessageAction extends ConfigurableActionBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The token service.
|
||||
*
|
||||
* @var \Drupal\Core\Utility\Token
|
||||
*/
|
||||
protected $token;
|
||||
|
||||
/**
|
||||
* The renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* The messenger.
|
||||
*
|
||||
* @var \Drupal\Core\Messenger\MessengerInterface
|
||||
*/
|
||||
protected $messenger;
|
||||
|
||||
/**
|
||||
* Constructs a MessageAction object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Utility\Token $token
|
||||
* The token service.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer.
|
||||
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
|
||||
* The messenger.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, Token $token, RendererInterface $renderer, MessengerInterface $messenger) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->token = $token;
|
||||
$this->renderer = $renderer;
|
||||
$this->messenger = $messenger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static($configuration, $plugin_id, $plugin_definition, $container->get('token'), $container->get('renderer'), $container->get('messenger'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($entity = NULL) {
|
||||
if (empty($this->configuration['node'])) {
|
||||
$this->configuration['node'] = $entity;
|
||||
}
|
||||
$message = $this->token->replace($this->configuration['message'], $this->configuration);
|
||||
$build = [
|
||||
'#markup' => $message,
|
||||
];
|
||||
|
||||
// @todo Fix in https://www.drupal.org/node/2577827
|
||||
$this->messenger->addStatus($this->renderer->renderInIsolation($build));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'message' => '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form['message'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => $this->t('Message'),
|
||||
'#default_value' => $this->configuration['message'],
|
||||
'#required' => TRUE,
|
||||
'#rows' => '8',
|
||||
'#description' => $this->t('The message to be displayed to the current user. You may include placeholders like [node:title], [user:account-name], [user:display-name] and [comment:body] to represent data that will be different each time message is sent. Not all placeholders will be available in all contexts.'),
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->configuration['message'] = $form_state->getValue('message');
|
||||
unset($this->configuration['node']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$result = AccessResult::allowed();
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Action\Plugin\Action\Derivative\EntityPublishedActionDeriver;
|
||||
use Drupal\Core\Action\Attribute\Action;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Publishes an entity.
|
||||
*/
|
||||
#[Action(
|
||||
id: 'entity:publish_action',
|
||||
action_label: new TranslatableMarkup('Publish'),
|
||||
deriver: EntityPublishedActionDeriver::class
|
||||
)]
|
||||
class PublishAction extends EntityActionBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($entity = NULL) {
|
||||
$entity->setPublished()->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$key = $object->getEntityType()->getKey('published');
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityInterface $object */
|
||||
$result = $object->access('update', $account, TRUE)
|
||||
->andIf($object->$key->access('edit', $account, TRUE));
|
||||
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
}
|
||||
81
web/core/lib/Drupal/Core/Action/Plugin/Action/SaveAction.php
Normal file
81
web/core/lib/Drupal/Core/Action/Plugin/Action/SaveAction.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Plugin\Action;
|
||||
|
||||
use Drupal\Component\Datetime\TimeInterface;
|
||||
use Drupal\Core\Action\Plugin\Action\Derivative\EntityChangedActionDeriver;
|
||||
use Drupal\Core\Action\Attribute\Action;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides an action that can save any entity.
|
||||
*/
|
||||
#[Action(
|
||||
id: 'entity:save_action',
|
||||
action_label: new TranslatableMarkup('Save'),
|
||||
deriver: EntityChangedActionDeriver::class
|
||||
)]
|
||||
class SaveAction extends EntityActionBase {
|
||||
|
||||
/**
|
||||
* The time service.
|
||||
*
|
||||
* @var \Drupal\Component\Datetime\TimeInterface
|
||||
*/
|
||||
protected $time;
|
||||
|
||||
/**
|
||||
* Constructs a SaveAction object.
|
||||
*
|
||||
* @param mixed[] $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Component\Datetime\TimeInterface $time
|
||||
* The time service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, TimeInterface $time) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager);
|
||||
$this->time = $time;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('datetime.time')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($entity = NULL) {
|
||||
$entity->setChangedTime($this->time->getRequestTime())->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
// It's not necessary to check the changed field access here, because
|
||||
// Drupal\Core\Field\ChangedFieldItemList would anyway return 'not allowed'.
|
||||
// Also changing the changed field value is only a workaround to trigger an
|
||||
// entity resave. Without a field change, this would not be possible.
|
||||
/** @var \Drupal\Core\Entity\EntityInterface $object */
|
||||
return $object->access('update', $account, $return_as_object);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Action\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Action\Plugin\Action\Derivative\EntityPublishedActionDeriver;
|
||||
use Drupal\Core\Action\Attribute\Action;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Unpublishes an entity.
|
||||
*/
|
||||
#[Action(
|
||||
id: 'entity:unpublish_action',
|
||||
action_label: new TranslatableMarkup('Unpublish'),
|
||||
deriver: EntityPublishedActionDeriver::class
|
||||
)]
|
||||
class UnpublishAction extends EntityActionBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($entity = NULL) {
|
||||
$entity->setUnpublished()->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$key = $object->getEntityType()->getKey('published');
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityInterface $object */
|
||||
$result = $object->access('update', $account, TRUE)
|
||||
->andIf($object->$key->access('edit', $account, TRUE));
|
||||
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
}
|
||||
45
web/core/lib/Drupal/Core/Ajax/AddCssCommand.php
Normal file
45
web/core/lib/Drupal/Core/Ajax/AddCssCommand.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* An AJAX command for adding css to the page via ajax.
|
||||
*
|
||||
* This command is implemented by Drupal.AjaxCommands.prototype.add_css()
|
||||
* defined in misc/ajax.js.
|
||||
*
|
||||
* @see misc/ajax.js
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class AddCssCommand implements CommandInterface {
|
||||
|
||||
/**
|
||||
* Arrays containing attributes of the stylesheets to be added to the page.
|
||||
*
|
||||
* @var string[][]
|
||||
*/
|
||||
protected $styles;
|
||||
|
||||
/**
|
||||
* Constructs an AddCssCommand.
|
||||
*
|
||||
* @param string[][] $styles
|
||||
* Arrays containing attributes of the stylesheets to be added to the page.
|
||||
* i.e. `['href' => 'someURL']` becomes `<link href="someURL">`.
|
||||
*/
|
||||
public function __construct(array $styles) {
|
||||
$this->styles = $styles;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
return [
|
||||
'command' => 'add_css',
|
||||
'data' => $this->styles,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
61
web/core/lib/Drupal/Core/Ajax/AddJsCommand.php
Normal file
61
web/core/lib/Drupal/Core/Ajax/AddJsCommand.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* An AJAX command for adding JS to the page via AJAX.
|
||||
*
|
||||
* This command will make sure all the files are loaded before continuing
|
||||
* executing the next AJAX command. This command is implemented by
|
||||
* Drupal.AjaxCommands.prototype.add_js() defined in misc/ajax.js.
|
||||
*
|
||||
* @see misc/ajax.js
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class AddJsCommand implements CommandInterface {
|
||||
|
||||
/**
|
||||
* An array containing attributes of the scripts to be added to the page.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $scripts;
|
||||
|
||||
/**
|
||||
* A CSS selector string.
|
||||
*
|
||||
* If the command is a response to a request from an #ajax form element then
|
||||
* this value will default to 'body'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $selector;
|
||||
|
||||
/**
|
||||
* Constructs an AddJsCommand.
|
||||
*
|
||||
* @param array $scripts
|
||||
* An array containing the attributes of the 'script' tags to be added to
|
||||
* the page. i.e. `['src' => 'someURL', 'defer' => TRUE]` becomes
|
||||
* `<script src="someURL" defer>`.
|
||||
* @param string $selector
|
||||
* A CSS selector of the element where the script tags will be appended.
|
||||
*/
|
||||
public function __construct(array $scripts, string $selector = 'body') {
|
||||
$this->scripts = $scripts;
|
||||
$this->selector = $selector;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
return [
|
||||
'command' => 'add_js',
|
||||
'selector' => $this->selector,
|
||||
'data' => $this->scripts,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
35
web/core/lib/Drupal/Core/Ajax/AfterCommand.php
Normal file
35
web/core/lib/Drupal/Core/Ajax/AfterCommand.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* An AJAX command for calling the jQuery after() method.
|
||||
*
|
||||
* The 'insert/after' command instructs the client to use jQuery's after()
|
||||
* method to insert the given render array or HTML content after each element
|
||||
* matched by the given selector.
|
||||
*
|
||||
* This command is implemented by Drupal.AjaxCommands.prototype.insert()
|
||||
* defined in misc/ajax.js.
|
||||
*
|
||||
* @see http://docs.jquery.com/Manipulation/after#content
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class AfterCommand extends InsertCommand {
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Ajax\CommandInterface:render().
|
||||
*/
|
||||
public function render() {
|
||||
|
||||
return [
|
||||
'command' => 'insert',
|
||||
'method' => 'after',
|
||||
'selector' => $this->selector,
|
||||
'data' => $this->getRenderedContent(),
|
||||
'settings' => $this->settings,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
57
web/core/lib/Drupal/Core/Ajax/AjaxFormHelperTrait.php
Normal file
57
web/core/lib/Drupal/Core/Ajax/AjaxFormHelperTrait.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Provides a helper to for submitting an AJAX form.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait AjaxFormHelperTrait {
|
||||
|
||||
use AjaxHelperTrait;
|
||||
|
||||
/**
|
||||
* Submit form dialog #ajax callback.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*
|
||||
* @return \Drupal\Core\Ajax\AjaxResponse
|
||||
* An AJAX response that display validation error messages or represents a
|
||||
* successful submission.
|
||||
*/
|
||||
public function ajaxSubmit(array &$form, FormStateInterface $form_state) {
|
||||
if ($form_state->hasAnyErrors()) {
|
||||
$form['status_messages'] = [
|
||||
'#type' => 'status_messages',
|
||||
'#weight' => -1000,
|
||||
];
|
||||
$form['#sorted'] = FALSE;
|
||||
$response = new AjaxResponse();
|
||||
$response->addCommand(new ReplaceCommand('[data-drupal-selector="' . $form['#attributes']['data-drupal-selector'] . '"]', $form));
|
||||
}
|
||||
else {
|
||||
$response = $this->successfulAjaxSubmit($form, $form_state);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the form to respond to a successful AJAX submission.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*
|
||||
* @return \Drupal\Core\Ajax\AjaxResponse
|
||||
* An AJAX response.
|
||||
*/
|
||||
abstract protected function successfulAjaxSubmit(array $form, FormStateInterface $form_state);
|
||||
|
||||
}
|
||||
37
web/core/lib/Drupal/Core/Ajax/AjaxHelperTrait.php
Normal file
37
web/core/lib/Drupal/Core/Ajax/AjaxHelperTrait.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
|
||||
|
||||
/**
|
||||
* Provides a helper to determine if the current request is via AJAX.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait AjaxHelperTrait {
|
||||
|
||||
/**
|
||||
* Determines if the current request is via AJAX.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the current request is via AJAX, FALSE otherwise.
|
||||
*/
|
||||
protected function isAjax() {
|
||||
$wrapper_format = $this->getRequestWrapperFormat() ?? '';
|
||||
return str_contains($wrapper_format, 'drupal_ajax') ||
|
||||
str_contains($wrapper_format, 'drupal_modal') ||
|
||||
str_contains($wrapper_format, 'drupal_dialog');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the wrapper format of the current request.
|
||||
*
|
||||
* @return string|null
|
||||
* The wrapper format. NULL if the wrapper format is not set.
|
||||
*/
|
||||
protected function getRequestWrapperFormat() {
|
||||
return \Drupal::request()->get(MainContentViewSubscriber::WRAPPER_FORMAT);
|
||||
}
|
||||
|
||||
}
|
||||
68
web/core/lib/Drupal/Core/Ajax/AjaxResponse.php
Normal file
68
web/core/lib/Drupal/Core/Ajax/AjaxResponse.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Core\Render\AttachmentsInterface;
|
||||
use Drupal\Core\Render\AttachmentsTrait;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
|
||||
/**
|
||||
* JSON response object for AJAX requests.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class AjaxResponse extends JsonResponse implements AttachmentsInterface {
|
||||
|
||||
use AttachmentsTrait;
|
||||
|
||||
/**
|
||||
* The array of ajax commands.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $commands = [];
|
||||
|
||||
/**
|
||||
* Add an AJAX command to the response.
|
||||
*
|
||||
* @param \Drupal\Core\Ajax\CommandInterface $command
|
||||
* An AJAX command object implementing CommandInterface.
|
||||
* @param bool $prepend
|
||||
* A boolean which determines whether the new command should be executed
|
||||
* before previously added commands. Defaults to FALSE.
|
||||
*
|
||||
* @return $this
|
||||
* The current AjaxResponse.
|
||||
*/
|
||||
public function addCommand(CommandInterface $command, $prepend = FALSE) {
|
||||
if ($prepend) {
|
||||
array_unshift($this->commands, $command->render());
|
||||
}
|
||||
else {
|
||||
$this->commands[] = $command->render();
|
||||
}
|
||||
if ($command instanceof CommandWithAttachedAssetsInterface) {
|
||||
$assets = $command->getAttachedAssets();
|
||||
$attachments = [
|
||||
'library' => $assets->getLibraries(),
|
||||
'drupalSettings' => $assets->getSettings(),
|
||||
];
|
||||
$attachments = BubbleableMetadata::mergeAttachments($this->getAttachments(), $attachments);
|
||||
$this->setAttachments($attachments);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all AJAX commands.
|
||||
*
|
||||
* @return array
|
||||
* Returns render arrays for all previously added commands.
|
||||
*/
|
||||
public function &getCommands() {
|
||||
return $this->commands;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Asset\AssetCollectionRendererInterface;
|
||||
use Drupal\Core\Asset\AssetResolverInterface;
|
||||
use Drupal\Core\Asset\AttachedAssets;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Render\AttachmentsInterface;
|
||||
use Drupal\Core\Render\AttachmentsResponseProcessorInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Processes attachments of AJAX responses.
|
||||
*
|
||||
* @see \Drupal\Core\Ajax\AjaxResponse
|
||||
* @see \Drupal\Core\Render\MainContent\AjaxRenderer
|
||||
*/
|
||||
class AjaxResponseAttachmentsProcessor implements AttachmentsResponseProcessorInterface {
|
||||
|
||||
/**
|
||||
* A config object for the system performance configuration.
|
||||
*
|
||||
* @var \Drupal\Core\Config\Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* Constructs an AjaxResponseAttachmentsProcessor object.
|
||||
*
|
||||
* @param \Drupal\Core\Asset\AssetResolverInterface $assetResolver
|
||||
* An asset resolver.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* A config factory for retrieving required config objects.
|
||||
* @param \Drupal\Core\Asset\AssetCollectionRendererInterface $cssCollectionRenderer
|
||||
* The CSS asset collection renderer.
|
||||
* @param \Drupal\Core\Asset\AssetCollectionRendererInterface $jsCollectionRenderer
|
||||
* The JS asset collection renderer.
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
|
||||
* The request stack.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
|
||||
* The module handler.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
|
||||
* The language manager.
|
||||
*/
|
||||
public function __construct(
|
||||
protected AssetResolverInterface $assetResolver,
|
||||
protected ConfigFactoryInterface $config_factory,
|
||||
protected AssetCollectionRendererInterface $cssCollectionRenderer,
|
||||
protected AssetCollectionRendererInterface $jsCollectionRenderer,
|
||||
protected RequestStack $requestStack,
|
||||
protected RendererInterface $renderer,
|
||||
protected ModuleHandlerInterface $moduleHandler,
|
||||
protected LanguageManagerInterface $languageManager,
|
||||
) {
|
||||
$this->config = $config_factory->get('system.performance');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processAttachments(AttachmentsInterface $response) {
|
||||
assert($response instanceof AjaxResponse, '\Drupal\Core\Ajax\AjaxResponse instance expected.');
|
||||
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
|
||||
if ($response->getContent() == '{}') {
|
||||
$response->setData($this->buildAttachmentsCommands($response, $request));
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the AJAX commands to attach assets.
|
||||
*
|
||||
* @param \Drupal\Core\Ajax\AjaxResponse $response
|
||||
* The AJAX response to update.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request object that the AJAX is responding to.
|
||||
*
|
||||
* @return array
|
||||
* An array of commands ready to be returned as JSON.
|
||||
*/
|
||||
protected function buildAttachmentsCommands(AjaxResponse $response, Request $request) {
|
||||
$ajax_page_state = $request->get('ajax_page_state');
|
||||
$maintenance_mode = defined('MAINTENANCE_MODE') || \Drupal::state()->get('system.maintenance_mode');
|
||||
|
||||
// Aggregate CSS/JS if necessary, but only during normal site operation.
|
||||
$optimize_css = !$maintenance_mode && $this->config->get('css.preprocess');
|
||||
$optimize_js = !$maintenance_mode && $this->config->get('js.preprocess');
|
||||
|
||||
$attachments = $response->getAttachments();
|
||||
|
||||
// Resolve the attached libraries into asset collections.
|
||||
$assets = new AttachedAssets();
|
||||
$assets->setLibraries($attachments['library'] ?? [])
|
||||
->setAlreadyLoadedLibraries(isset($ajax_page_state['libraries']) ? explode(',', $ajax_page_state['libraries']) : [])
|
||||
->setSettings($attachments['drupalSettings'] ?? []);
|
||||
$css_assets = $this->assetResolver->getCssAssets($assets, $optimize_css, $this->languageManager->getCurrentLanguage());
|
||||
[$js_assets_header, $js_assets_footer] = $this->assetResolver->getJsAssets($assets, $optimize_js, $this->languageManager->getCurrentLanguage());
|
||||
|
||||
// First, AttachedAssets::setLibraries() ensures duplicate libraries are
|
||||
// removed: it converts it to a set of libraries if necessary. Second,
|
||||
// AssetResolver::getJsSettings() ensures $assets contains the final set of
|
||||
// JavaScript settings. AttachmentsResponseProcessorInterface also mandates
|
||||
// that the response it processes contains the final attachment values, so
|
||||
// update both the 'library' and 'drupalSettings' attachments accordingly.
|
||||
$attachments['library'] = $assets->getLibraries();
|
||||
$attachments['drupalSettings'] = $assets->getSettings();
|
||||
$response->setAttachments($attachments);
|
||||
|
||||
// Render the HTML to load these files, and add AJAX commands to insert this
|
||||
// HTML in the page. Settings are handled separately, afterwards.
|
||||
$settings = [];
|
||||
if (isset($js_assets_header['drupalSettings'])) {
|
||||
$settings = $js_assets_header['drupalSettings']['data'];
|
||||
unset($js_assets_header['drupalSettings']);
|
||||
}
|
||||
if (isset($js_assets_footer['drupalSettings'])) {
|
||||
$settings = $js_assets_footer['drupalSettings']['data'];
|
||||
unset($js_assets_footer['drupalSettings']);
|
||||
}
|
||||
|
||||
// Prepend commands to add the assets, preserving their relative order.
|
||||
$resource_commands = [];
|
||||
if ($css_assets) {
|
||||
$css_render_array = $this->cssCollectionRenderer->render($css_assets);
|
||||
$resource_commands[] = new AddCssCommand(array_column($css_render_array, '#attributes'));
|
||||
}
|
||||
if ($js_assets_header) {
|
||||
$js_header_render_array = $this->jsCollectionRenderer->render($js_assets_header);
|
||||
$resource_commands[] = new AddJsCommand(array_column($js_header_render_array, '#attributes'), 'head');
|
||||
}
|
||||
if ($js_assets_footer) {
|
||||
$js_footer_render_array = $this->jsCollectionRenderer->render($js_assets_footer);
|
||||
$resource_commands[] = new AddJsCommand(array_column($js_footer_render_array, '#attributes'));
|
||||
}
|
||||
foreach (array_reverse($resource_commands) as $resource_command) {
|
||||
$response->addCommand($resource_command, TRUE);
|
||||
}
|
||||
|
||||
// Prepend a command to merge changes and additions to drupalSettings.
|
||||
if (!empty($settings)) {
|
||||
// During Ajax requests basic path-specific settings are excluded from
|
||||
// new drupalSettings values. The original page where this request comes
|
||||
// from already has the right values. An Ajax request would update them
|
||||
// with values for the Ajax request and incorrectly override the page's
|
||||
// values.
|
||||
// @see system_js_settings_alter()
|
||||
unset($settings['path']);
|
||||
$response->addCommand(new SettingsCommand($settings, TRUE), TRUE);
|
||||
}
|
||||
|
||||
$commands = $response->getCommands();
|
||||
$this->moduleHandler->alter('ajax_render', $commands);
|
||||
|
||||
return $commands;
|
||||
}
|
||||
|
||||
}
|
||||
40
web/core/lib/Drupal/Core/Ajax/AlertCommand.php
Normal file
40
web/core/lib/Drupal/Core/Ajax/AlertCommand.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* AJAX command for a javascript alert box.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class AlertCommand implements CommandInterface {
|
||||
|
||||
/**
|
||||
* The text to be displayed in the alert box.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $text;
|
||||
|
||||
/**
|
||||
* Constructs an AlertCommand object.
|
||||
*
|
||||
* @param string $text
|
||||
* The text to be displayed in the alert box.
|
||||
*/
|
||||
public function __construct($text) {
|
||||
$this->text = $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Ajax\CommandInterface:render().
|
||||
*/
|
||||
public function render() {
|
||||
|
||||
return [
|
||||
'command' => 'alert',
|
||||
'text' => $this->text,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
90
web/core/lib/Drupal/Core/Ajax/AnnounceCommand.php
Normal file
90
web/core/lib/Drupal/Core/Ajax/AnnounceCommand.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Asset\AttachedAssets;
|
||||
|
||||
/**
|
||||
* AJAX command for a JavaScript Drupal.announce() call.
|
||||
*
|
||||
* Developers should be extra careful if this command and
|
||||
* \Drupal\Core\Ajax\MessageCommand are included in the same response. By
|
||||
* default, MessageCommand will also call Drupal.announce() and announce the
|
||||
* message to the screen reader (unless the option to suppress announcements is
|
||||
* passed to the constructor). Manual testing with a screen reader is strongly
|
||||
* recommended.
|
||||
*
|
||||
* @see \Drupal\Core\Ajax\MessageCommand
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class AnnounceCommand implements CommandInterface, CommandWithAttachedAssetsInterface {
|
||||
|
||||
/**
|
||||
* The assertive priority attribute value.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PRIORITY_ASSERTIVE = 'assertive';
|
||||
|
||||
/**
|
||||
* The polite priority attribute value.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PRIORITY_POLITE = 'polite';
|
||||
|
||||
/**
|
||||
* The text to be announced.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $text;
|
||||
|
||||
/**
|
||||
* The priority that will be used for the announcement.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $priority;
|
||||
|
||||
/**
|
||||
* Constructs an AnnounceCommand object.
|
||||
*
|
||||
* @param string $text
|
||||
* The text to be announced.
|
||||
* @param string|null $priority
|
||||
* (optional) The priority that will be used for the announcement. Defaults
|
||||
* to NULL which will not set a 'priority' in the response sent to the
|
||||
* client and therefore the JavaScript Drupal.announce() default of 'polite'
|
||||
* will be used for the message.
|
||||
*/
|
||||
public function __construct($text, $priority = NULL) {
|
||||
$this->text = $text;
|
||||
$this->priority = $priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
$render = [
|
||||
'command' => 'announce',
|
||||
'text' => $this->text,
|
||||
];
|
||||
if ($this->priority !== NULL) {
|
||||
$render['priority'] = $this->priority;
|
||||
}
|
||||
return $render;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAttachedAssets() {
|
||||
$assets = new AttachedAssets();
|
||||
$assets->setLibraries(['core/drupal.announce']);
|
||||
return $assets;
|
||||
}
|
||||
|
||||
}
|
||||
35
web/core/lib/Drupal/Core/Ajax/AppendCommand.php
Normal file
35
web/core/lib/Drupal/Core/Ajax/AppendCommand.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* An AJAX command for calling the jQuery append() method.
|
||||
*
|
||||
* The 'insert/append' command instructs the client to use jQuery's append()
|
||||
* method to append the given render array or HTML content to the inside of each
|
||||
* element matched by the given selector.
|
||||
*
|
||||
* This command is implemented by Drupal.AjaxCommands.prototype.insert()
|
||||
* defined in misc/ajax.js.
|
||||
*
|
||||
* @see http://docs.jquery.com/Manipulation/append#content
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class AppendCommand extends InsertCommand {
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Ajax\CommandInterface:render().
|
||||
*/
|
||||
public function render() {
|
||||
|
||||
return [
|
||||
'command' => 'insert',
|
||||
'method' => 'append',
|
||||
'selector' => $this->selector,
|
||||
'data' => $this->getRenderedContent(),
|
||||
'settings' => $this->settings,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
47
web/core/lib/Drupal/Core/Ajax/BaseCommand.php
Normal file
47
web/core/lib/Drupal/Core/Ajax/BaseCommand.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* Base command that only exists to simplify AJAX commands.
|
||||
*/
|
||||
class BaseCommand implements CommandInterface {
|
||||
|
||||
/**
|
||||
* The name of the command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $command;
|
||||
|
||||
/**
|
||||
* The data to pass on to the client side.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* Constructs a BaseCommand object.
|
||||
*
|
||||
* @param string $command
|
||||
* The name of the command.
|
||||
* @param string $data
|
||||
* The data to pass on to the client side.
|
||||
*/
|
||||
public function __construct($command, $data) {
|
||||
$this->command = $command;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
return [
|
||||
'command' => $this->command,
|
||||
'data' => $this->data,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
35
web/core/lib/Drupal/Core/Ajax/BeforeCommand.php
Normal file
35
web/core/lib/Drupal/Core/Ajax/BeforeCommand.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* An AJAX command for calling the jQuery before() method.
|
||||
*
|
||||
* The 'insert/before' command instructs the client to use jQuery's before()
|
||||
* method to insert the given render array or HTML content before each of
|
||||
* elements matched by the given selector.
|
||||
*
|
||||
* This command is implemented by Drupal.AjaxCommands.prototype.insert()
|
||||
* defined in misc/ajax.js.
|
||||
*
|
||||
* @see http://docs.jquery.com/Manipulation/before#content
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class BeforeCommand extends InsertCommand {
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Ajax\CommandInterface:render().
|
||||
*/
|
||||
public function render() {
|
||||
|
||||
return [
|
||||
'command' => 'insert',
|
||||
'method' => 'before',
|
||||
'selector' => $this->selector,
|
||||
'data' => $this->getRenderedContent(),
|
||||
'settings' => $this->settings,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
60
web/core/lib/Drupal/Core/Ajax/ChangedCommand.php
Normal file
60
web/core/lib/Drupal/Core/Ajax/ChangedCommand.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* An AJAX command for marking HTML elements as changed.
|
||||
*
|
||||
* This command instructs the client to mark each of the elements matched by the
|
||||
* given selector as 'ajax-changed'.
|
||||
*
|
||||
* This command is implemented by Drupal.AjaxCommands.prototype.changed()
|
||||
* defined in misc/ajax.js.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class ChangedCommand implements CommandInterface {
|
||||
|
||||
/**
|
||||
* A CSS selector string.
|
||||
*
|
||||
* If the command is a response to a request from an #ajax form element then
|
||||
* this value can be NULL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $selector;
|
||||
|
||||
/**
|
||||
* An optional CSS selector for elements to which asterisks will be appended.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $asterisk;
|
||||
|
||||
/**
|
||||
* Constructs a ChangedCommand object.
|
||||
*
|
||||
* @param string $selector
|
||||
* CSS selector for elements to be marked as changed.
|
||||
* @param string $asterisk
|
||||
* CSS selector for elements to which an asterisk will be appended.
|
||||
*/
|
||||
public function __construct($selector, $asterisk = '') {
|
||||
$this->selector = $selector;
|
||||
$this->asterisk = $asterisk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Ajax\CommandInterface:render().
|
||||
*/
|
||||
public function render() {
|
||||
|
||||
return [
|
||||
'command' => 'changed',
|
||||
'selector' => $this->selector,
|
||||
'asterisk' => $this->asterisk,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
50
web/core/lib/Drupal/Core/Ajax/CloseDialogCommand.php
Normal file
50
web/core/lib/Drupal/Core/Ajax/CloseDialogCommand.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* Defines an AJAX command that closes the current active dialog.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class CloseDialogCommand implements CommandInterface {
|
||||
|
||||
/**
|
||||
* A CSS selector string of the dialog to close.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $selector;
|
||||
|
||||
/**
|
||||
* Whether to persist the dialog in the DOM or not.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $persist;
|
||||
|
||||
/**
|
||||
* Constructs a CloseDialogCommand object.
|
||||
*
|
||||
* @param string $selector
|
||||
* A CSS selector string of the dialog to close.
|
||||
* @param bool $persist
|
||||
* (optional) Whether to persist the dialog in the DOM or not.
|
||||
*/
|
||||
public function __construct($selector = NULL, $persist = FALSE) {
|
||||
$this->selector = $selector ?: '#drupal-modal';
|
||||
$this->persist = $persist;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
return [
|
||||
'command' => 'closeDialog',
|
||||
'selector' => $this->selector,
|
||||
'persist' => $this->persist,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
23
web/core/lib/Drupal/Core/Ajax/CloseModalDialogCommand.php
Normal file
23
web/core/lib/Drupal/Core/Ajax/CloseModalDialogCommand.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* Defines an AJAX command that closes the currently visible modal dialog.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class CloseModalDialogCommand extends CloseDialogCommand {
|
||||
|
||||
/**
|
||||
* Constructs a CloseModalDialogCommand object.
|
||||
*
|
||||
* @param bool $persist
|
||||
* (optional) Whether to persist the dialog in the DOM or not.
|
||||
*/
|
||||
public function __construct($persist = FALSE) {
|
||||
$this->selector = '#drupal-modal';
|
||||
$this->persist = $persist;
|
||||
}
|
||||
|
||||
}
|
||||
20
web/core/lib/Drupal/Core/Ajax/CommandInterface.php
Normal file
20
web/core/lib/Drupal/Core/Ajax/CommandInterface.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* AJAX command interface.
|
||||
*
|
||||
* All AJAX commands passed to AjaxResponse objects should implement these
|
||||
* methods.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
interface CommandInterface {
|
||||
|
||||
/**
|
||||
* Return an array to be run through json_encode and sent to the client.
|
||||
*/
|
||||
public function render();
|
||||
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* Interface for Ajax commands that render content and attach assets.
|
||||
*
|
||||
* All Ajax commands that render HTML should implement these methods
|
||||
* to be able to return attached assets to the calling AjaxResponse object.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
interface CommandWithAttachedAssetsInterface {
|
||||
|
||||
/**
|
||||
* Gets the attached assets.
|
||||
*
|
||||
* @return \Drupal\Core\Asset\AttachedAssets|null
|
||||
* The attached assets for this command.
|
||||
*/
|
||||
public function getAttachedAssets();
|
||||
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Asset\AttachedAssets;
|
||||
|
||||
/**
|
||||
* Trait for Ajax commands that render content and attach assets.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
trait CommandWithAttachedAssetsTrait {
|
||||
|
||||
/**
|
||||
* The attached assets for this Ajax command.
|
||||
*
|
||||
* @var \Drupal\Core\Asset\AttachedAssets
|
||||
*/
|
||||
protected $attachedAssets;
|
||||
|
||||
/**
|
||||
* Processes the content for output.
|
||||
*
|
||||
* If content is a render array, it may contain attached assets to be
|
||||
* processed.
|
||||
*
|
||||
* @return string|\Drupal\Component\Render\MarkupInterface
|
||||
* HTML rendered content.
|
||||
*/
|
||||
protected function getRenderedContent() {
|
||||
$this->attachedAssets = new AttachedAssets();
|
||||
if (is_array($this->content)) {
|
||||
if (!$this->content) {
|
||||
return '';
|
||||
}
|
||||
$html = \Drupal::service('renderer')->renderRoot($this->content);
|
||||
$this->attachedAssets = AttachedAssets::createFromRenderArray($this->content);
|
||||
return $html;
|
||||
}
|
||||
else {
|
||||
return $this->content;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the attached assets.
|
||||
*
|
||||
* @return \Drupal\Core\Asset\AttachedAssets|null
|
||||
* The attached assets for this command.
|
||||
*/
|
||||
public function getAttachedAssets() {
|
||||
return $this->attachedAssets;
|
||||
}
|
||||
|
||||
}
|
||||
77
web/core/lib/Drupal/Core/Ajax/CssCommand.php
Normal file
77
web/core/lib/Drupal/Core/Ajax/CssCommand.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* An AJAX command for calling the jQuery css() method.
|
||||
*
|
||||
* The 'css' command will instruct the client to use the jQuery css() method to
|
||||
* apply the CSS arguments to elements matched by the given selector.
|
||||
*
|
||||
* This command is implemented by Drupal.AjaxCommands.prototype.css() defined
|
||||
* in misc/ajax.js.
|
||||
*
|
||||
* @see http://docs.jquery.com/CSS/css#properties
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class CssCommand implements CommandInterface {
|
||||
|
||||
/**
|
||||
* A CSS selector string.
|
||||
*
|
||||
* If the command is a response to a request from an #ajax form element then
|
||||
* this value can be NULL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $selector;
|
||||
|
||||
/**
|
||||
* An array of property/value pairs to set in the CSS for the selector.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $css = [];
|
||||
|
||||
/**
|
||||
* Constructs a CssCommand object.
|
||||
*
|
||||
* @param string $selector
|
||||
* A CSS selector for elements to which the CSS will be applied.
|
||||
* @param array $css
|
||||
* An array of CSS property/value pairs to set.
|
||||
*/
|
||||
public function __construct($selector, array $css = []) {
|
||||
$this->selector = $selector;
|
||||
$this->css = $css;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a property/value pair to the CSS to be added to this element.
|
||||
*
|
||||
* @param string $property
|
||||
* The CSS property to be changed.
|
||||
* @param string $value
|
||||
* The new value of the CSS property.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setProperty($property, $value) {
|
||||
$this->css[$property] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Ajax\CommandInterface:render().
|
||||
*/
|
||||
public function render() {
|
||||
|
||||
return [
|
||||
'command' => 'css',
|
||||
'selector' => $this->selector,
|
||||
'argument' => $this->css,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
73
web/core/lib/Drupal/Core/Ajax/DataCommand.php
Normal file
73
web/core/lib/Drupal/Core/Ajax/DataCommand.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* An AJAX command for implementing jQuery's data() method.
|
||||
*
|
||||
* This instructs the client to attach the name=value pair of data to the
|
||||
* selector via jQuery's data cache.
|
||||
*
|
||||
* This command is implemented by Drupal.AjaxCommands.prototype.data() defined
|
||||
* in misc/ajax.js.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class DataCommand implements CommandInterface {
|
||||
|
||||
/**
|
||||
* A CSS selector string for elements to which data will be attached.
|
||||
*
|
||||
* If the command is a response to a request from an #ajax form element then
|
||||
* this value can be NULL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $selector;
|
||||
|
||||
/**
|
||||
* The key of the data attached to elements matched by the selector.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The value of the data to be attached to elements matched by the selector.
|
||||
*
|
||||
* The data is not limited to strings; it can be any format.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* Constructs a DataCommand object.
|
||||
*
|
||||
* @param string $selector
|
||||
* A CSS selector for the elements to which the data will be attached.
|
||||
* @param string $name
|
||||
* The key of the data to be attached to elements matched by the selector.
|
||||
* @param mixed $value
|
||||
* The value of the data to be attached to elements matched by the selector.
|
||||
*/
|
||||
public function __construct($selector, $name, $value) {
|
||||
$this->selector = $selector;
|
||||
$this->name = $name;
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Ajax\CommandInterface:render().
|
||||
*/
|
||||
public function render() {
|
||||
|
||||
return [
|
||||
'command' => 'data',
|
||||
'selector' => $this->selector,
|
||||
'name' => $this->name,
|
||||
'value' => $this->value,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
51
web/core/lib/Drupal/Core/Ajax/FocusFirstCommand.php
Normal file
51
web/core/lib/Drupal/Core/Ajax/FocusFirstCommand.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* AJAX command for focusing an element.
|
||||
*
|
||||
* This command is provided a selector then does the following:
|
||||
* - The first element matching the provided selector will become the container
|
||||
* where the search for tabbable elements is conducted.
|
||||
* - If one or more tabbable elements are found within the container, the first
|
||||
* of those will receive focus.
|
||||
* - If no tabbable elements are found within the container, but the container
|
||||
* itself is focusable, then the container will receive focus.
|
||||
* - If the container is not focusable and contains no tabbable elements, the
|
||||
* triggering element will remain focused.
|
||||
*
|
||||
* @see Drupal.AjaxCommands.focusFirst
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class FocusFirstCommand implements CommandInterface {
|
||||
|
||||
/**
|
||||
* The selector of the container with tabbable elements.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $selector;
|
||||
|
||||
/**
|
||||
* Constructs an FocusFirstCommand object.
|
||||
*
|
||||
* @param string $selector
|
||||
* The selector of the container with tabbable elements.
|
||||
*/
|
||||
public function __construct($selector) {
|
||||
$this->selector = $selector;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
return [
|
||||
'command' => 'focusFirst',
|
||||
'selector' => $this->selector,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
35
web/core/lib/Drupal/Core/Ajax/HtmlCommand.php
Normal file
35
web/core/lib/Drupal/Core/Ajax/HtmlCommand.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* AJAX command for calling the jQuery html() method.
|
||||
*
|
||||
* The 'insert/html' command instructs the client to use jQuery's html() method
|
||||
* to set the HTML content of each element matched by the given selector while
|
||||
* leaving the outer tags intact using a given render array or HTML markup.
|
||||
*
|
||||
* This command is implemented by Drupal.AjaxCommands.prototype.insert()
|
||||
* defined in misc/ajax.js.
|
||||
*
|
||||
* @see http://docs.jquery.com/Attributes/html#val
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class HtmlCommand extends InsertCommand {
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Ajax\CommandInterface:render().
|
||||
*/
|
||||
public function render() {
|
||||
|
||||
return [
|
||||
'command' => 'insert',
|
||||
'method' => 'html',
|
||||
'selector' => $this->selector,
|
||||
'data' => $this->getRenderedContent(),
|
||||
'settings' => $this->settings,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
78
web/core/lib/Drupal/Core/Ajax/InsertCommand.php
Normal file
78
web/core/lib/Drupal/Core/Ajax/InsertCommand.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* Generic AJAX command for inserting content.
|
||||
*
|
||||
* This command instructs the client to insert the given render array or HTML
|
||||
* using whichever jQuery DOM manipulation method has been specified in the
|
||||
* #ajax['method'] variable of the element that triggered the request.
|
||||
*
|
||||
* This command is implemented by Drupal.AjaxCommands.prototype.insert()
|
||||
* defined in misc/ajax.js.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class InsertCommand implements CommandInterface, CommandWithAttachedAssetsInterface {
|
||||
|
||||
use CommandWithAttachedAssetsTrait;
|
||||
|
||||
/**
|
||||
* A CSS selector string.
|
||||
*
|
||||
* If the command is a response to a request from an #ajax form element then
|
||||
* this value can be NULL.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $selector;
|
||||
|
||||
/**
|
||||
* The content for the matched element(s).
|
||||
*
|
||||
* Either a render array or an HTML string.
|
||||
*
|
||||
* @var string|array
|
||||
*/
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* A settings array to be passed to any attached JavaScript behavior.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* Constructs an InsertCommand object.
|
||||
*
|
||||
* @param string|null $selector
|
||||
* A CSS selector.
|
||||
* @param string|array $content
|
||||
* The content that will be inserted in the matched element(s), either a
|
||||
* render array or an HTML string.
|
||||
* @param array $settings
|
||||
* An array of JavaScript settings to be passed to any attached behaviors.
|
||||
*/
|
||||
public function __construct($selector, $content, ?array $settings = NULL) {
|
||||
$this->selector = $selector;
|
||||
$this->content = $content;
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Ajax\CommandInterface:render().
|
||||
*/
|
||||
public function render() {
|
||||
|
||||
return [
|
||||
'command' => 'insert',
|
||||
'method' => NULL,
|
||||
'selector' => $this->selector,
|
||||
'data' => $this->getRenderedContent(),
|
||||
'settings' => $this->settings,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
73
web/core/lib/Drupal/Core/Ajax/InvokeCommand.php
Normal file
73
web/core/lib/Drupal/Core/Ajax/InvokeCommand.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* AJAX command for invoking an arbitrary jQuery method.
|
||||
*
|
||||
* The 'invoke' command will instruct the client to invoke the given jQuery
|
||||
* method with the supplied arguments on the elements matched by the given
|
||||
* selector. Intended for simple jQuery commands, such as attr(), addClass(),
|
||||
* removeClass(), toggleClass(), etc.
|
||||
*
|
||||
* This command is implemented by Drupal.AjaxCommands.prototype.invoke()
|
||||
* defined in misc/ajax.js.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class InvokeCommand implements CommandInterface {
|
||||
|
||||
/**
|
||||
* A CSS selector string.
|
||||
*
|
||||
* If the command is a response to a request from an #ajax form element then
|
||||
* this value can be NULL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $selector;
|
||||
|
||||
/**
|
||||
* A jQuery method to invoke.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $method;
|
||||
|
||||
/**
|
||||
* An optional list of arguments to pass to the method.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments;
|
||||
|
||||
/**
|
||||
* Constructs an InvokeCommand object.
|
||||
*
|
||||
* @param string $selector
|
||||
* A jQuery selector.
|
||||
* @param string $method
|
||||
* The name of a jQuery method to invoke.
|
||||
* @param array $arguments
|
||||
* An optional array of arguments to pass to the method.
|
||||
*/
|
||||
public function __construct($selector, $method, array $arguments = []) {
|
||||
$this->selector = $selector;
|
||||
$this->method = $method;
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Ajax\CommandInterface:render().
|
||||
*/
|
||||
public function render() {
|
||||
|
||||
return [
|
||||
'command' => 'invoke',
|
||||
'selector' => $this->selector,
|
||||
'method' => $this->method,
|
||||
'args' => $this->arguments,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
145
web/core/lib/Drupal/Core/Ajax/MessageCommand.php
Normal file
145
web/core/lib/Drupal/Core/Ajax/MessageCommand.php
Normal file
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Component\Render\MarkupInterface;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Asset\AttachedAssets;
|
||||
|
||||
/**
|
||||
* AJAX command for a JavaScript Drupal.message() call.
|
||||
*
|
||||
* AJAX command that allows you to add messages from an Ajax response. The
|
||||
* command will create a new Drupal.Message() object and call its addMessage()
|
||||
* method.
|
||||
*
|
||||
* Usage examples:
|
||||
* Here are examples of how to suppress announcements:
|
||||
* @code
|
||||
* $response = new AjaxResponse();
|
||||
*
|
||||
* // A status message added in the default location.
|
||||
* $response->addCommand(new MessageCommand('Your changes have been saved.'));
|
||||
*
|
||||
* // A warning message added in the default location.
|
||||
* $response->addCommand(new MessageCommand('There was a problem. Save your work.', NULL, ['type' => 'warning']));
|
||||
*
|
||||
* // A status message added an alternate location.
|
||||
* $response->addCommand(new MessageCommand('Hey look over here!', '#alternate-message-container'));
|
||||
*
|
||||
* // An error added in an alternate location.
|
||||
* $response->addCommand(new MessageCommand('Open the pod bay doors, HAL.', '#alternate-message-container', ['type' => 'error']));
|
||||
* @endcode
|
||||
*
|
||||
* By default, previous messages in a location are cleared before the message
|
||||
* is added. If you would like to leave the previous messages in a location,
|
||||
* you may do so by setting the fourth parameter to FALSE:
|
||||
* Here are examples of how to suppress announcements:
|
||||
* @code
|
||||
* $response->addCommand(new MessageCommand('Hey look over here.', NULL, ['type' => 'error'], FALSE));
|
||||
* @endcode
|
||||
*
|
||||
* Developers should take care when using MessageCommand and AnnounceCommand
|
||||
* together in the same AJAX response. Unless the "announce" option is set to
|
||||
* an empty string (''), this command will result in the message being
|
||||
* announced to screen readers. When combined with AnnounceCommand, this may
|
||||
* result in unexpected behavior. Manual testing with a screen reader is
|
||||
* strongly recommended.
|
||||
*
|
||||
* If you wish to display a message without the text being announced to screen
|
||||
* readers, add options.announce = '' (i.e. an empty string):
|
||||
* @code
|
||||
* $command = new MessageCommand("I won't be announced", NULL, [
|
||||
* 'announce' => '',
|
||||
* ]);
|
||||
* @endcode
|
||||
*
|
||||
* If you wish to set the announcement priority to assertive, you can do that
|
||||
* this way:
|
||||
* @code
|
||||
* $response->addCommand(new MessageCommand('You added 3 cat pics.', '.js-media-library-messages', [
|
||||
* 'priority' => 'assertive',
|
||||
* ]);
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\Core\Ajax\AnnounceCommand
|
||||
* @see https://www.drupal.org/docs/develop/drupal-apis/ajax-api/core-ajax-callback-commands#s-messagecommand
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class MessageCommand implements CommandInterface, CommandWithAttachedAssetsInterface {
|
||||
|
||||
/**
|
||||
* The message text.
|
||||
*
|
||||
* @var string|\Drupal\Component\Render\MarkupInterface
|
||||
*/
|
||||
protected $message;
|
||||
|
||||
/**
|
||||
* Whether to clear previous messages.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $clearPrevious;
|
||||
|
||||
/**
|
||||
* The query selector for the element the message will appear in.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $wrapperQuerySelector;
|
||||
|
||||
/**
|
||||
* The options passed to Drupal.message().add().
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* Constructs a MessageCommand object.
|
||||
*
|
||||
* @param string|\Drupal\Component\Render\MarkupInterface $message
|
||||
* The text of the message.
|
||||
* @param string|null $wrapper_query_selector
|
||||
* The query selector of the element to display messages in when they
|
||||
* should be displayed somewhere other than the default.
|
||||
* @see Drupal.Message.defaultWrapper()
|
||||
* @param array $options
|
||||
* The options passed to Drupal.message().add().
|
||||
* @param bool $clear_previous
|
||||
* If TRUE, previous messages will be cleared first.
|
||||
*/
|
||||
public function __construct($message, $wrapper_query_selector = NULL, array $options = [], $clear_previous = TRUE) {
|
||||
$this->message = $message;
|
||||
$this->wrapperQuerySelector = $wrapper_query_selector;
|
||||
$this->options = $options;
|
||||
$this->clearPrevious = $clear_previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
return [
|
||||
'command' => 'message',
|
||||
'message' => $this->message instanceof MarkupInterface
|
||||
? (string) $this->message
|
||||
: Xss::filterAdmin($this->message),
|
||||
'messageWrapperQuerySelector' => $this->wrapperQuerySelector,
|
||||
'messageOptions' => $this->options,
|
||||
'clearPrevious' => $this->clearPrevious,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAttachedAssets() {
|
||||
$assets = new AttachedAssets();
|
||||
$assets->setLibraries(['core/drupal.message']);
|
||||
return $assets;
|
||||
}
|
||||
|
||||
}
|
||||
151
web/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php
Normal file
151
web/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php
Normal file
@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Component\Render\PlainTextOutput;
|
||||
|
||||
/**
|
||||
* Defines an AJAX command to open certain content in a dialog.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class OpenDialogCommand implements CommandInterface, CommandWithAttachedAssetsInterface {
|
||||
|
||||
use CommandWithAttachedAssetsTrait;
|
||||
|
||||
/**
|
||||
* The selector of the dialog.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $selector;
|
||||
|
||||
/**
|
||||
* The title of the dialog.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $title;
|
||||
|
||||
/**
|
||||
* The content for the dialog.
|
||||
*
|
||||
* Either a render array or an HTML string.
|
||||
*
|
||||
* @var string|array
|
||||
*/
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* Stores dialog-specific options passed directly to jQuery UI dialogs.
|
||||
*
|
||||
* Any jQuery UI option can be used.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @see http://api.jqueryui.com/dialog.
|
||||
*/
|
||||
protected $dialogOptions;
|
||||
|
||||
/**
|
||||
* Custom settings passed to Drupal behaviors on the content of the dialog.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* Constructs an OpenDialogCommand object.
|
||||
*
|
||||
* @param string $selector
|
||||
* The selector of the dialog.
|
||||
* @param string|\Stringable|null $title
|
||||
* The title of the dialog.
|
||||
* @param string|array $content
|
||||
* The content that will be placed in the dialog, either a render array
|
||||
* or an HTML string.
|
||||
* @param array $dialog_options
|
||||
* (optional) Options to be passed to the dialog implementation. Any
|
||||
* jQuery UI option can be used. See http://api.jqueryui.com/dialog.
|
||||
* @param array|null $settings
|
||||
* (optional) Custom settings that will be passed to the Drupal behaviors
|
||||
* on the content of the dialog. If left empty, the settings will be
|
||||
* populated automatically from the current request.
|
||||
*/
|
||||
public function __construct($selector, string|\Stringable|null $title, $content, array $dialog_options = [], $settings = NULL) {
|
||||
$title = PlainTextOutput::renderFromHtml($title);
|
||||
|
||||
$dialog_options += ['title' => $title];
|
||||
if (isset($dialog_options['dialogClass'])) {
|
||||
@trigger_error('Passing $dialog_options[\'dialogClass\'] to OpenDialogCommand::__construct() is deprecated in drupal:10.3.0 and will be removed in drupal:12.0.0. Use $dialog_options[\'classes\'] instead. See https://www.drupal.org/node/3440844', E_USER_DEPRECATED);
|
||||
$dialog_options['classes']['ui-dialog'] = $dialog_options['dialogClass'];
|
||||
unset($dialog_options['dialogClass']);
|
||||
}
|
||||
|
||||
$this->selector = $selector;
|
||||
$this->content = $content;
|
||||
$this->dialogOptions = $dialog_options;
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the dialog options.
|
||||
*
|
||||
* @return array
|
||||
* An array of the dialog-specific options passed directly to jQuery UI
|
||||
* dialogs.
|
||||
*/
|
||||
public function getDialogOptions() {
|
||||
return $this->dialogOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the dialog options array.
|
||||
*
|
||||
* @param array $dialog_options
|
||||
* Options to be passed to the dialog implementation. Any jQuery UI option
|
||||
* can be used. See http://api.jqueryui.com/dialog.
|
||||
*/
|
||||
public function setDialogOptions($dialog_options) {
|
||||
$this->dialogOptions = $dialog_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a single dialog option value.
|
||||
*
|
||||
* @param string $key
|
||||
* Key of the dialog option. Any jQuery UI option can be used.
|
||||
* See http://api.jqueryui.com/dialog.
|
||||
* @param mixed $value
|
||||
* Option to be passed to the dialog implementation.
|
||||
*/
|
||||
public function setDialogOption($key, $value) {
|
||||
$this->dialogOptions[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the dialog title (an alias of setDialogOptions).
|
||||
*
|
||||
* @param string $title
|
||||
* The new title of the dialog.
|
||||
*/
|
||||
public function setDialogTitle($title) {
|
||||
$this->setDialogOption('title', $title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\Core\Ajax\CommandInterface:render().
|
||||
*/
|
||||
public function render() {
|
||||
// For consistency ensure the modal option is set to TRUE or FALSE.
|
||||
$this->dialogOptions['modal'] = isset($this->dialogOptions['modal']) && $this->dialogOptions['modal'];
|
||||
return [
|
||||
'command' => 'openDialog',
|
||||
'selector' => $this->selector,
|
||||
'settings' => $this->settings,
|
||||
'data' => $this->getRenderedContent(),
|
||||
'dialogOptions' => $this->dialogOptions,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
38
web/core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php
Normal file
38
web/core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* An AJAX command to open certain content in a dialog in a modal dialog.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class OpenModalDialogCommand extends OpenDialogCommand {
|
||||
|
||||
/**
|
||||
* Constructs an OpenModalDialog object.
|
||||
*
|
||||
* The modal dialog differs from the normal modal provided by
|
||||
* OpenDialogCommand in that a modal prevents other interactions on the page
|
||||
* until the modal has been completed. Drupal provides a built-in modal for
|
||||
* this purpose, so no selector needs to be provided.
|
||||
*
|
||||
* @param string|\Stringable|null $title
|
||||
* The title of the dialog.
|
||||
* @param string|array $content
|
||||
* The content that will be placed in the dialog, either a render array
|
||||
* or an HTML string.
|
||||
* @param array $dialog_options
|
||||
* (optional) Settings to be passed to the dialog implementation. Any
|
||||
* jQuery UI option can be used. See http://api.jqueryui.com/dialog.
|
||||
* @param array|null $settings
|
||||
* (optional) Custom settings that will be passed to the Drupal behaviors
|
||||
* on the content of the dialog. If left empty, the settings will be
|
||||
* populated automatically from the current request.
|
||||
*/
|
||||
public function __construct(string|\Stringable|null $title, $content, array $dialog_options = [], $settings = NULL) {
|
||||
$dialog_options['modal'] = TRUE;
|
||||
parent::__construct('#drupal-modal', $title, $content, $dialog_options, $settings);
|
||||
}
|
||||
|
||||
}
|
||||
57
web/core/lib/Drupal/Core/Ajax/OpenModalDialogWithUrl.php
Normal file
57
web/core/lib/Drupal/Core/Ajax/OpenModalDialogWithUrl.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
|
||||
/**
|
||||
* Provides an AJAX command for opening a modal with URL.
|
||||
*
|
||||
* OpenDialogCommand is a similar class which opens modals but works
|
||||
* differently as it needs all data to be passed through dialogOptions while
|
||||
* OpenModalDialogWithUrl fetches the data from routing info of the URL.
|
||||
*
|
||||
* @see \Drupal\Core\Ajax\OpenDialogCommand
|
||||
*/
|
||||
class OpenModalDialogWithUrl implements CommandInterface {
|
||||
|
||||
/**
|
||||
* Constructs a OpenModalDialogWithUrl object.
|
||||
*
|
||||
* @param string $url
|
||||
* Only Internal URLs or URLs with the same domain and base path are
|
||||
* allowed.
|
||||
* @param array $settings
|
||||
* The dialog settings.
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $url,
|
||||
protected array $settings,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
// @see \Drupal\Core\Routing\LocalAwareRedirectResponseTrait::isLocal()
|
||||
if (!UrlHelper::isExternal($this->url) || UrlHelper::externalIsLocal($this->url, $this->getBaseURL())) {
|
||||
return [
|
||||
'command' => 'openModalDialogWithUrl',
|
||||
'url' => $this->url,
|
||||
'dialogOptions' => $this->settings,
|
||||
];
|
||||
}
|
||||
throw new \LogicException('External URLs are not allowed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the complete base URL.
|
||||
*/
|
||||
private function getBaseUrl() {
|
||||
$requestContext = \Drupal::service('router.request_context');
|
||||
return $requestContext->getCompleteBaseUrl();
|
||||
}
|
||||
|
||||
}
|
||||
86
web/core/lib/Drupal/Core/Ajax/OpenOffCanvasDialogCommand.php
Normal file
86
web/core/lib/Drupal/Core/Ajax/OpenOffCanvasDialogCommand.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* Defines an AJAX command to open content in a dialog in an off-canvas tray.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class OpenOffCanvasDialogCommand extends OpenDialogCommand {
|
||||
|
||||
/**
|
||||
* The dialog width to use if none is provided.
|
||||
*/
|
||||
const DEFAULT_DIALOG_WIDTH = 300;
|
||||
|
||||
/**
|
||||
* Constructs an OpenOffCanvasDialogCommand object.
|
||||
*
|
||||
* The off-canvas dialog differs from the normal modal provided by
|
||||
* OpenDialogCommand in that an off-canvas has built in positioning and
|
||||
* behaviors. Drupal provides a built-in off-canvas dialog for this purpose,
|
||||
* so the selector is hard-coded in the call to the parent constructor.
|
||||
*
|
||||
* @param string|\Stringable|null $title
|
||||
* The title of the dialog.
|
||||
* @param string|array $content
|
||||
* The content that will be placed in the dialog, either a render array
|
||||
* or an HTML string.
|
||||
* @param array $dialog_options
|
||||
* (optional) Settings to be passed to the dialog implementation. Any
|
||||
* jQuery UI option can be used. See http://api.jqueryui.com/dialog.
|
||||
* @param array|null $settings
|
||||
* (optional) Custom settings that will be passed to the Drupal behaviors
|
||||
* on the content of the dialog. If left empty, the settings will be
|
||||
* populated automatically from the current request.
|
||||
* @param string $position
|
||||
* (optional) The position to render the off-canvas dialog.
|
||||
*/
|
||||
public function __construct(string|\Stringable|null $title, $content, array $dialog_options = [], $settings = NULL, $position = 'side') {
|
||||
$dialog_class = FALSE;
|
||||
if (isset($dialog_options['classes']['ui-dialog'])) {
|
||||
$dialog_class = $dialog_options['classes']['ui-dialog'];
|
||||
}
|
||||
elseif (isset($dialog_options['dialogClass'])) {
|
||||
@trigger_error('Passing $dialog_options[\'dialogClass\'] to OpenOffCanvasDialogCommand::__construct() is deprecated in drupal:10.3.0 and will be removed in drupal:12.0.0. Use $dialog_options[\'classes\'] instead. See https://www.drupal.org/node/3440844', E_USER_DEPRECATED);
|
||||
$dialog_class = $dialog_options['dialogClass'];
|
||||
unset($dialog_options['dialogClass']);
|
||||
}
|
||||
if ($dialog_class) {
|
||||
$dialog_options['classes']['ui-dialog'] = $dialog_class . ' ' . "ui-dialog-off-canvas ui-dialog-position-$position";
|
||||
}
|
||||
else {
|
||||
$dialog_options['classes']['ui-dialog'] = "ui-dialog-off-canvas ui-dialog-position-$position";
|
||||
}
|
||||
parent::__construct('#drupal-off-canvas', $title, $content, $dialog_options, $settings);
|
||||
$this->dialogOptions['modal'] = FALSE;
|
||||
$this->dialogOptions['autoResize'] = FALSE;
|
||||
$this->dialogOptions['resizable'] = 'w';
|
||||
$this->dialogOptions['draggable'] = FALSE;
|
||||
$this->dialogOptions['drupalAutoButtons'] = FALSE;
|
||||
$this->dialogOptions['drupalOffCanvasPosition'] = $position;
|
||||
|
||||
// Add CSS class to #drupal-off-canvas element. This enables developers to
|
||||
// select previous versions of off-canvas styles by using custom selector:
|
||||
// #drupal-off-canvas:not(.drupal-off-canvas-reset).
|
||||
$this->dialogOptions['classes']['ui-dialog-content'] = 'drupal-off-canvas-reset';
|
||||
// If no width option is provided then use the default width to avoid the
|
||||
// dialog staying at the width of the previous instance when opened
|
||||
// more than once, with different widths, on a single page.
|
||||
if (!isset($this->dialogOptions['width'])) {
|
||||
$this->dialogOptions['width'] = static::DEFAULT_DIALOG_WIDTH;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
$build = parent::render();
|
||||
$build['effect'] = 'fade';
|
||||
$build['speed'] = 1000;
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
35
web/core/lib/Drupal/Core/Ajax/PrependCommand.php
Normal file
35
web/core/lib/Drupal/Core/Ajax/PrependCommand.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* AJAX command for calling the jQuery insert() method.
|
||||
*
|
||||
* The 'insert/prepend' command instructs the client to use jQuery's prepend()
|
||||
* method to prepend the given render array or HTML content to the inside each
|
||||
* element matched by the given selector.
|
||||
*
|
||||
* This command is implemented by Drupal.AjaxCommands.prototype.insert()
|
||||
* defined in misc/ajax.js.
|
||||
*
|
||||
* @see http://docs.jquery.com/Manipulation/prepend#content
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class PrependCommand extends InsertCommand {
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Ajax\CommandInterface:render().
|
||||
*/
|
||||
public function render() {
|
||||
|
||||
return [
|
||||
'command' => 'insert',
|
||||
'method' => 'prepend',
|
||||
'selector' => $this->selector,
|
||||
'data' => $this->getRenderedContent(),
|
||||
'settings' => $this->settings,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
40
web/core/lib/Drupal/Core/Ajax/RedirectCommand.php
Normal file
40
web/core/lib/Drupal/Core/Ajax/RedirectCommand.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* Defines an AJAX command to set the window.location, loading that URL.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class RedirectCommand implements CommandInterface {
|
||||
|
||||
/**
|
||||
* The URL that will be loaded into window.location.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* Constructs an RedirectCommand object.
|
||||
*
|
||||
* @param string $url
|
||||
* The URL that will be loaded into window.location. This should be a full
|
||||
* URL.
|
||||
*/
|
||||
public function __construct($url) {
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\Core\Ajax\CommandInterface:render().
|
||||
*/
|
||||
public function render() {
|
||||
return [
|
||||
'command' => 'redirect',
|
||||
'url' => $this->url,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
48
web/core/lib/Drupal/Core/Ajax/RemoveCommand.php
Normal file
48
web/core/lib/Drupal/Core/Ajax/RemoveCommand.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* AJAX command for calling the jQuery remove() method.
|
||||
*
|
||||
* The 'remove' command instructs the client to use jQuery's remove() method
|
||||
* to remove each of elements matched by the given selector, and everything
|
||||
* within them.
|
||||
*
|
||||
* This command is implemented by Drupal.AjaxCommands.prototype.remove()
|
||||
* defined in misc/ajax.js.
|
||||
*
|
||||
* @see http://docs.jquery.com/Manipulation/remove#expr
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class RemoveCommand implements CommandInterface {
|
||||
|
||||
/**
|
||||
* The CSS selector for the element(s) to be removed.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $selector;
|
||||
|
||||
/**
|
||||
* Constructs a RemoveCommand object.
|
||||
*
|
||||
* @param string $selector
|
||||
* The selector.
|
||||
*/
|
||||
public function __construct($selector) {
|
||||
$this->selector = $selector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Ajax\CommandInterface:render().
|
||||
*/
|
||||
public function render() {
|
||||
return [
|
||||
'command' => 'remove',
|
||||
'selector' => $this->selector,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
36
web/core/lib/Drupal/Core/Ajax/ReplaceCommand.php
Normal file
36
web/core/lib/Drupal/Core/Ajax/ReplaceCommand.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* AJAX command for calling the jQuery replace() method.
|
||||
*
|
||||
* The 'insert/replaceWith' command instructs the client to use jQuery's
|
||||
* replaceWith() method to replace each element matched by the given selector
|
||||
* with the given render array or HTML.
|
||||
*
|
||||
* This command is implemented by Drupal.AjaxCommands.prototype.insert()
|
||||
* defined in misc/ajax.js.
|
||||
*
|
||||
* See
|
||||
* @link http://docs.jquery.com/Manipulation/replaceWith#content jQuery replaceWith command @endlink
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class ReplaceCommand extends InsertCommand {
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Ajax\CommandInterface:render().
|
||||
*/
|
||||
public function render() {
|
||||
|
||||
return [
|
||||
'command' => 'insert',
|
||||
'method' => 'replaceWith',
|
||||
'selector' => $this->selector,
|
||||
'data' => $this->getRenderedContent(),
|
||||
'settings' => $this->settings,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
49
web/core/lib/Drupal/Core/Ajax/RestripeCommand.php
Normal file
49
web/core/lib/Drupal/Core/Ajax/RestripeCommand.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* AJAX command for resetting the striping on a table.
|
||||
*
|
||||
* The 'restripe' command instructs the client to restripe a table. This is
|
||||
* usually used after a table has been modified by a replace or append command.
|
||||
*
|
||||
* This command is implemented by Drupal.AjaxCommands.prototype.restripe()
|
||||
* defined in misc/ajax.js.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class RestripeCommand implements CommandInterface {
|
||||
|
||||
/**
|
||||
* A CSS selector string.
|
||||
*
|
||||
* If the command is a response to a request from an #ajax form element then
|
||||
* this value can be NULL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $selector;
|
||||
|
||||
/**
|
||||
* Constructs a RestripeCommand object.
|
||||
*
|
||||
* @param string $selector
|
||||
* A CSS selector for the table to be restriped.
|
||||
*/
|
||||
public function __construct($selector) {
|
||||
$this->selector = $selector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Ajax\CommandInterface:render().
|
||||
*/
|
||||
public function render() {
|
||||
|
||||
return [
|
||||
'command' => 'restripe',
|
||||
'selector' => $this->selector,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
39
web/core/lib/Drupal/Core/Ajax/ScrollTopCommand.php
Normal file
39
web/core/lib/Drupal/Core/Ajax/ScrollTopCommand.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* Provides an AJAX command for scrolling to the top of an element.
|
||||
*
|
||||
* This command is implemented in Drupal.AjaxCommands.prototype.scrollTop.
|
||||
*/
|
||||
class ScrollTopCommand implements CommandInterface {
|
||||
|
||||
/**
|
||||
* A CSS selector string.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $selector;
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\Core\Ajax\ScrollTopCommand object.
|
||||
*
|
||||
* @param string $selector
|
||||
* A CSS selector.
|
||||
*/
|
||||
public function __construct($selector) {
|
||||
$this->selector = $selector;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render(): array {
|
||||
return [
|
||||
'command' => 'scrollTop',
|
||||
'selector' => $this->selector,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
63
web/core/lib/Drupal/Core/Ajax/SetDialogOptionCommand.php
Normal file
63
web/core/lib/Drupal/Core/Ajax/SetDialogOptionCommand.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* Defines an AJAX command that sets jQuery UI dialog properties.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class SetDialogOptionCommand implements CommandInterface {
|
||||
|
||||
/**
|
||||
* A CSS selector string.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $selector;
|
||||
|
||||
/**
|
||||
* A jQuery UI dialog option name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $optionName;
|
||||
|
||||
/**
|
||||
* A jQuery UI dialog option value.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $optionValue;
|
||||
|
||||
/**
|
||||
* Constructs a SetDialogOptionCommand object.
|
||||
*
|
||||
* @param string $selector
|
||||
* The selector of the dialog whose title will be set. If set to an empty
|
||||
* value, the default modal dialog will be selected.
|
||||
* @param string $option_name
|
||||
* The name of the option to set. May be any jQuery UI dialog option.
|
||||
* See http://api.jqueryui.com/dialog.
|
||||
* @param mixed $option_value
|
||||
* The value of the option to be passed to the dialog.
|
||||
*/
|
||||
public function __construct($selector, $option_name, $option_value) {
|
||||
$this->selector = $selector ?: '#drupal-modal';
|
||||
$this->optionName = $option_name;
|
||||
$this->optionValue = $option_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
return [
|
||||
'command' => 'setDialogOption',
|
||||
'selector' => $this->selector,
|
||||
'optionName' => $this->optionName,
|
||||
'optionValue' => $this->optionValue,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
27
web/core/lib/Drupal/Core/Ajax/SetDialogTitleCommand.php
Normal file
27
web/core/lib/Drupal/Core/Ajax/SetDialogTitleCommand.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* Defines an AJAX command that sets jQuery UI dialog properties.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class SetDialogTitleCommand extends SetDialogOptionCommand {
|
||||
|
||||
/**
|
||||
* Constructs a SetDialogTitleCommand object.
|
||||
*
|
||||
* @param string $selector
|
||||
* The selector of the dialog whose title will be set. If set to an empty
|
||||
* value, the default modal dialog will be selected.
|
||||
* @param string $title
|
||||
* The title that will be set on the dialog.
|
||||
*/
|
||||
public function __construct($selector, $title) {
|
||||
$this->selector = $selector ?: '#drupal-modal';
|
||||
$this->optionName = 'title';
|
||||
$this->optionValue = $title;
|
||||
}
|
||||
|
||||
}
|
||||
69
web/core/lib/Drupal/Core/Ajax/SettingsCommand.php
Normal file
69
web/core/lib/Drupal/Core/Ajax/SettingsCommand.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
|
||||
/**
|
||||
* AJAX command for adjusting Drupal's JavaScript settings.
|
||||
*
|
||||
* The 'settings' command instructs the client either to use the given array as
|
||||
* the settings for ajax-loaded content or to extend drupalSettings with the
|
||||
* given array, depending on the value of the $merge parameter.
|
||||
*
|
||||
* This command is implemented by Drupal.AjaxCommands.prototype.settings()
|
||||
* defined in misc/ajax.js.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class SettingsCommand implements CommandInterface {
|
||||
|
||||
/**
|
||||
* An array of key/value pairs of JavaScript settings.
|
||||
*
|
||||
* This will be used for all commands after this if they do not include their
|
||||
* own settings array.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* Whether the settings should be merged into the global drupalSettings.
|
||||
*
|
||||
* By default (FALSE), the settings that are passed to Drupal.attachBehaviors
|
||||
* will not include the global drupalSettings.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $merge;
|
||||
|
||||
/**
|
||||
* Constructs a SettingsCommand object.
|
||||
*
|
||||
* @param array $settings
|
||||
* An array of key/value pairs of JavaScript settings.
|
||||
* @param bool $merge
|
||||
* Whether the settings should be merged into the global drupalSettings.
|
||||
*/
|
||||
public function __construct(array $settings, $merge = FALSE) {
|
||||
$this->settings = $settings;
|
||||
$this->merge = $merge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Ajax\CommandInterface:render().
|
||||
*/
|
||||
public function render() {
|
||||
if (isset($this->settings['ajax_page_state']['libraries'])) {
|
||||
$this->settings['ajax_page_state']['libraries'] = UrlHelper::compressQueryParameter($this->settings['ajax_page_state']['libraries']);
|
||||
}
|
||||
|
||||
return [
|
||||
'command' => 'settings',
|
||||
'settings' => $this->settings,
|
||||
'merge' => $this->merge,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
54
web/core/lib/Drupal/Core/Ajax/TabledragWarningCommand.php
Normal file
54
web/core/lib/Drupal/Core/Ajax/TabledragWarningCommand.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Asset\AttachedAssets;
|
||||
|
||||
/**
|
||||
* AJAX command for conveying changed tabledrag rows.
|
||||
*
|
||||
* This command is provided an id of a table row then does the following:
|
||||
* - Marks the row as changed.
|
||||
* - If a message generated by the tableDragChangedWarning is not present above
|
||||
* the table the row belongs to, that message is added there.
|
||||
*
|
||||
* @see Drupal.AjaxCommands.prototype.tabledragChanged
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class TabledragWarningCommand implements CommandInterface, CommandWithAttachedAssetsInterface {
|
||||
|
||||
/**
|
||||
* Constructs a TableDragWarningCommand object.
|
||||
*
|
||||
* @param string $id
|
||||
* The id of the changed row.
|
||||
* @param string $tabledrag_instance
|
||||
* The identifier of the tabledrag instance.
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $id,
|
||||
protected string $tabledrag_instance,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
return [
|
||||
'command' => 'tabledragChanged',
|
||||
'id' => $this->id,
|
||||
'tabledrag_instance' => $this->tabledrag_instance,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAttachedAssets() {
|
||||
$assets = new AttachedAssets();
|
||||
$assets->setLibraries(['core/drupal.tabledrag.ajax']);
|
||||
return $assets;
|
||||
}
|
||||
|
||||
}
|
||||
62
web/core/lib/Drupal/Core/Ajax/UpdateBuildIdCommand.php
Normal file
62
web/core/lib/Drupal/Core/Ajax/UpdateBuildIdCommand.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* Ajax command for updating the form build ID.
|
||||
*
|
||||
* Used for updating the value of a hidden form_build_id input element on a
|
||||
* form. It requires the form passed in to have keys for both the old build ID
|
||||
* in #build_id_old and the new build ID in #build_id.
|
||||
*
|
||||
* The primary use case for this Ajax command is to serve a new build ID to a
|
||||
* form served from the cache to an anonymous user, preventing one anonymous
|
||||
* user from accessing the form state of another anonymous user on Ajax enabled
|
||||
* forms.
|
||||
*
|
||||
* This command is implemented by
|
||||
* Drupal.AjaxCommands.prototype.update_build_id() defined in misc/ajax.js.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class UpdateBuildIdCommand implements CommandInterface {
|
||||
|
||||
/**
|
||||
* Old build id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $old;
|
||||
|
||||
/**
|
||||
* New build id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $new;
|
||||
|
||||
/**
|
||||
* Constructs an UpdateBuildIdCommand object.
|
||||
*
|
||||
* @param string $old
|
||||
* The old build_id.
|
||||
* @param string $new
|
||||
* The new build_id.
|
||||
*/
|
||||
public function __construct($old, $new) {
|
||||
$this->old = $old;
|
||||
$this->new = $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
return [
|
||||
'command' => 'update_build_id',
|
||||
'old' => $this->old,
|
||||
'new' => $this->new,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
67
web/core/lib/Drupal/Core/Annotation/Action.php
Normal file
67
web/core/lib/Drupal/Core/Annotation/Action.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines an Action annotation object.
|
||||
*
|
||||
* Plugin Namespace: Plugin\Action
|
||||
*
|
||||
* @see \Drupal\Core\Action\ActionInterface
|
||||
* @see \Drupal\Core\Action\ActionManager
|
||||
* @see \Drupal\Core\Action\ActionBase
|
||||
* @see \Drupal\Core\Action\Plugin\Action\UnpublishAction
|
||||
* @see plugin_api
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class Action extends Plugin {
|
||||
|
||||
/**
|
||||
* The plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The human-readable name of the action plugin.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $label;
|
||||
|
||||
/**
|
||||
* The route name for a confirmation form for this action.
|
||||
*
|
||||
* This property is optional and it does not need to be declared.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @todo Provide a more generic way to allow an action to be confirmed first.
|
||||
*/
|
||||
public $confirm_form_route_name = '';
|
||||
|
||||
/**
|
||||
* The entity type the action can apply to.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @todo Replace with \Drupal\Core\Plugin\Context\Context.
|
||||
*/
|
||||
public $type = '';
|
||||
|
||||
/**
|
||||
* The category under which the action should be listed in the UI.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $category;
|
||||
|
||||
}
|
||||
164
web/core/lib/Drupal/Core/Annotation/ContextDefinition.php
Normal file
164
web/core/lib/Drupal/Core/Annotation/ContextDefinition.php
Normal file
@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* @defgroup plugin_context Annotation for context definition
|
||||
* @{
|
||||
* Describes how to use ContextDefinition annotation.
|
||||
*
|
||||
* When providing plugin annotations, contexts can be defined to support UI
|
||||
* interactions through providing limits, and mapping contexts to appropriate
|
||||
* plugins. Context definitions can be provided as such:
|
||||
* @code
|
||||
* context_definitions = {
|
||||
* "node" = @ContextDefinition("entity:node")
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* To add a label to a context definition use the "label" key:
|
||||
* @code
|
||||
* context_definitions = {
|
||||
* "node" = @ContextDefinition("entity:node", label = @Translation("Node"))
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* Contexts are required unless otherwise specified. To make an optional
|
||||
* context use the "required" key:
|
||||
* @code
|
||||
* context_definitions = {
|
||||
* "node" = @ContextDefinition("entity:node", required = FALSE, label = @Translation("Node"))
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* To define multiple contexts, simply provide different key names in the
|
||||
* context array:
|
||||
* @code
|
||||
* context_definitions = {
|
||||
* "artist" = @ContextDefinition("entity:node", label = @Translation("Artist")),
|
||||
* "album" = @ContextDefinition("entity:node", label = @Translation("Album"))
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* Specifying a default value for the context definition:
|
||||
* @code
|
||||
* context_definitions = {
|
||||
* "message" = @ContextDefinition("string",
|
||||
* label = @Translation("Message"),
|
||||
* default_value = @Translation("Checkout complete! Thank you for your purchase.")
|
||||
* )
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @see annotation
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines a context definition annotation object.
|
||||
*
|
||||
* Some plugins require various data contexts in order to function. This class
|
||||
* supports that need by allowing the contexts to be easily defined within an
|
||||
* annotation and return a ContextDefinitionInterface implementing class.
|
||||
*
|
||||
* @Annotation
|
||||
*
|
||||
* @ingroup plugin_context
|
||||
*/
|
||||
class ContextDefinition extends Plugin {
|
||||
|
||||
/**
|
||||
* The ContextDefinitionInterface object.
|
||||
*
|
||||
* @var \Drupal\Core\Plugin\Context\ContextDefinitionInterface
|
||||
*/
|
||||
protected $definition;
|
||||
|
||||
/**
|
||||
* Constructs a new context definition object.
|
||||
*
|
||||
* @param array $values
|
||||
* An associative array with the following keys:
|
||||
* - value: The required data type.
|
||||
* - label: (optional) The UI label of this context definition.
|
||||
* - required: (optional) Whether the context definition is required.
|
||||
* - multiple: (optional) Whether the context definition is multivalue.
|
||||
* - description: (optional) The UI description of this context definition.
|
||||
* - default_value: (optional) The default value in case the underlying
|
||||
* value is not set.
|
||||
* - class: (optional) A custom ContextDefinitionInterface class.
|
||||
*
|
||||
* @throws \Exception
|
||||
* Thrown when the class key is specified with a non
|
||||
* ContextDefinitionInterface implementing class.
|
||||
*/
|
||||
public function __construct(array $values) {
|
||||
$values += [
|
||||
'required' => TRUE,
|
||||
'multiple' => FALSE,
|
||||
'default_value' => NULL,
|
||||
];
|
||||
// Annotation classes extract data from passed annotation classes directly
|
||||
// used in the classes they pass to.
|
||||
foreach (['label', 'description'] as $key) {
|
||||
// @todo Remove this workaround in https://www.drupal.org/node/2362727.
|
||||
if (isset($values[$key]) && $values[$key] instanceof Translation) {
|
||||
$values[$key] = (string) $values[$key]->get();
|
||||
}
|
||||
else {
|
||||
$values[$key] = NULL;
|
||||
}
|
||||
}
|
||||
if (isset($values['class']) && !in_array('Drupal\Core\Plugin\Context\ContextDefinitionInterface', class_implements($values['class']))) {
|
||||
throw new \Exception('ContextDefinition class must implement \Drupal\Core\Plugin\Context\ContextDefinitionInterface.');
|
||||
}
|
||||
|
||||
$class = $this->getDefinitionClass($values);
|
||||
$this->definition = new $class($values['value'], $values['label'], $values['required'], $values['multiple'], $values['description'], $values['default_value']);
|
||||
|
||||
if (isset($values['constraints'])) {
|
||||
foreach ($values['constraints'] as $constraint_name => $options) {
|
||||
$this->definition->addConstraint($constraint_name, $options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the context definition class to use.
|
||||
*
|
||||
* If the annotation specifies a specific context definition class, we use
|
||||
* that. Otherwise, we use \Drupal\Core\Plugin\Context\EntityContextDefinition
|
||||
* if the data type starts with 'entity:', since it contains specialized logic
|
||||
* specific to entities. Otherwise, we fall back to the generic
|
||||
* \Drupal\Core\Plugin\Context\ContextDefinition class.
|
||||
*
|
||||
* @param array $values
|
||||
* The annotation values.
|
||||
*
|
||||
* @return string
|
||||
* The fully-qualified name of the context definition class.
|
||||
*/
|
||||
protected function getDefinitionClass(array $values) {
|
||||
if (isset($values['class'])) {
|
||||
return $values['class'];
|
||||
}
|
||||
if (str_starts_with($values['value'], 'entity:')) {
|
||||
return 'Drupal\Core\Plugin\Context\EntityContextDefinition';
|
||||
}
|
||||
return 'Drupal\Core\Plugin\Context\ContextDefinition';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of an annotation.
|
||||
*
|
||||
* @return \Drupal\Core\Plugin\Context\ContextDefinitionInterface
|
||||
* The context definition object.
|
||||
*/
|
||||
public function get() {
|
||||
return $this->definition;
|
||||
}
|
||||
|
||||
}
|
||||
47
web/core/lib/Drupal/Core/Annotation/Mail.php
Normal file
47
web/core/lib/Drupal/Core/Annotation/Mail.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a Mail annotation object.
|
||||
*
|
||||
* Plugin Namespace: Plugin\Mail
|
||||
*
|
||||
* For a working example, see \Drupal\Core\Mail\Plugin\Mail\PhpMail
|
||||
*
|
||||
* @see \Drupal\Core\Mail\MailInterface
|
||||
* @see \Drupal\Core\Mail\MailManager
|
||||
* @see plugin_api
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class Mail extends Plugin {
|
||||
|
||||
/**
|
||||
* The plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The human-readable name of the mail plugin.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $label;
|
||||
|
||||
/**
|
||||
* A short description of the mail plugin.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $description;
|
||||
|
||||
}
|
||||
105
web/core/lib/Drupal/Core/Annotation/PluralTranslation.php
Normal file
105
web/core/lib/Drupal/Core/Annotation/PluralTranslation.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\AnnotationBase;
|
||||
|
||||
/**
|
||||
* Defines an annotation object for strings that require plural forms.
|
||||
*
|
||||
* Note that the return values for both 'singular' and 'plural' keys needs to be
|
||||
* passed to
|
||||
* \Drupal\Core\StringTranslation\TranslationInterface::formatPlural().
|
||||
*
|
||||
* For example, the annotation can look like this:
|
||||
* @code
|
||||
* label_count = @ PluralTranslation(
|
||||
* singular = "@count item",
|
||||
* plural = "@count items",
|
||||
* context = "cart_items",
|
||||
* ),
|
||||
* @endcode
|
||||
* Remove spaces after @ in your actual plugin - these are put into this sample
|
||||
* code so that it is not recognized as annotation.
|
||||
*
|
||||
* Code samples that make use of this annotation class and the definition sample
|
||||
* above:
|
||||
* @code
|
||||
* // Returns: 1 item
|
||||
* $entity_type->getCountLabel(1);
|
||||
*
|
||||
* // Returns: 5 items
|
||||
* $entity_type->getCountLabel(5);
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityType::getSingularLabel()
|
||||
* @see \Drupal\Core\Entity\EntityType::getPluralLabel()
|
||||
* @see \Drupal\Core\Entity\EntityType::getCountLabel()
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class PluralTranslation extends AnnotationBase {
|
||||
|
||||
/**
|
||||
* The string for the singular case.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $singular;
|
||||
|
||||
/**
|
||||
* The string for the plural case.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $plural;
|
||||
|
||||
/**
|
||||
* The context the source strings belong to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* Constructs a new class instance.
|
||||
*
|
||||
* @param array $values
|
||||
* An associative array with the following keys:
|
||||
* - singular: The string for the singular case.
|
||||
* - plural: The string for the plural case.
|
||||
* - context: The context the source strings belong to.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown when the keys 'singular' or 'plural' are missing from the $values
|
||||
* array.
|
||||
*/
|
||||
public function __construct(array $values) {
|
||||
if (!isset($values['singular'])) {
|
||||
throw new \InvalidArgumentException('Missing "singular" value in the PluralTranslation annotation');
|
||||
}
|
||||
if (!isset($values['plural'])) {
|
||||
throw new \InvalidArgumentException('Missing "plural" value in the PluralTranslation annotation');
|
||||
}
|
||||
|
||||
$this->singular = $values['singular'];
|
||||
$this->plural = $values['plural'];
|
||||
if (isset($values['context'])) {
|
||||
$this->context = $values['context'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get() {
|
||||
return [
|
||||
'singular' => $this->singular,
|
||||
'plural' => $this->plural,
|
||||
'context' => $this->context,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
68
web/core/lib/Drupal/Core/Annotation/QueueWorker.php
Normal file
68
web/core/lib/Drupal/Core/Annotation/QueueWorker.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Declare a worker class for processing a queue item.
|
||||
*
|
||||
* Worker plugins are used by some queues for processing the individual items
|
||||
* in the queue. In that case, the ID of the worker plugin needs to match the
|
||||
* machine name of a queue, so that you can retrieve the queue back end by
|
||||
* calling \Drupal\Core\Queue\QueueFactory::get($plugin_id).
|
||||
*
|
||||
* \Drupal\Core\Cron::processQueues() processes queues that use workers; they
|
||||
* can also be processed outside of the cron process.
|
||||
*
|
||||
* Some queues do not use worker plugins: you can create queues, add items to
|
||||
* them, claim them, etc. without using a QueueWorker plugin. However, you will
|
||||
* need to take care of processing the items in the queue in that case. You can
|
||||
* look at \Drupal\Core\Cron::processQueues() for an example of how to process
|
||||
* a queue that uses workers, and adapt it to your queue.
|
||||
*
|
||||
* Plugin Namespace: Plugin\QueueWorker
|
||||
*
|
||||
* For a working example, see
|
||||
* \Drupal\locale\Plugin\QueueWorker\LocaleTranslation.
|
||||
*
|
||||
* @see \Drupal\Core\Queue\QueueWorkerInterface
|
||||
* @see \Drupal\Core\Queue\QueueWorkerBase
|
||||
* @see \Drupal\Core\Queue\QueueWorkerManager
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup queue
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class QueueWorker extends Plugin {
|
||||
|
||||
/**
|
||||
* The plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The human-readable title of the plugin.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $title;
|
||||
|
||||
/**
|
||||
* An optional associative array of settings for cron.
|
||||
*
|
||||
* @var array
|
||||
* The array has one key, time, which is set to the time Drupal cron should
|
||||
* spend on calling this worker in seconds. The default is set in
|
||||
* \Drupal\Core\Queue\QueueWorkerManager::processDefinition().
|
||||
*
|
||||
* @see \Drupal\Core\Queue\QueueWorkerManager::processDefinition()
|
||||
*/
|
||||
public $cron;
|
||||
|
||||
}
|
||||
95
web/core/lib/Drupal/Core/Annotation/Translation.php
Normal file
95
web/core/lib/Drupal/Core/Annotation/Translation.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\AnnotationBase;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* @defgroup plugin_translatable Annotation for translatable text
|
||||
* @{
|
||||
* Describes how to put translatable UI text into annotations.
|
||||
*
|
||||
* When providing plugin annotation, properties whose values are displayed in
|
||||
* the user interface should be made translatable. Much the same as how user
|
||||
* interface text elsewhere is wrapped in t() to make it translatable, in plugin
|
||||
* annotation, wrap translatable strings in the @ Translation() annotation.
|
||||
* For example:
|
||||
* @code
|
||||
* title = @ Translation("Title of the plugin"),
|
||||
* @endcode
|
||||
* Remove spaces after @ in your actual plugin - these are put into this sample
|
||||
* code so that it is not recognized as annotation.
|
||||
*
|
||||
* To provide replacement values for placeholders, use the "arguments" array:
|
||||
* @code
|
||||
* title = @ Translation("Bundle @title", arguments = {"@title" = "Foo"}),
|
||||
* @endcode
|
||||
*
|
||||
* It is also possible to provide a context with the text, similar to t():
|
||||
* @code
|
||||
* title = @ Translation("Bundle", context = "Validation"),
|
||||
* @endcode
|
||||
* Other t() arguments like language code are not valid to pass in. Only
|
||||
* context is supported.
|
||||
*
|
||||
* @see i18n
|
||||
* @see annotation
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines a translatable annotation object.
|
||||
*
|
||||
* Some metadata within an annotation needs to be translatable. This class
|
||||
* supports that need by allowing both the translatable string and, if
|
||||
* specified, a context for that string. The string (with optional context)
|
||||
* is passed into t().
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class Translation extends AnnotationBase {
|
||||
|
||||
/**
|
||||
* The string translation object.
|
||||
*
|
||||
* @var \Drupal\Core\StringTranslation\TranslatableMarkup
|
||||
*/
|
||||
protected $translation;
|
||||
|
||||
/**
|
||||
* Constructs a new class instance.
|
||||
*
|
||||
* Parses values passed into this class through the t() function in Drupal and
|
||||
* handles an optional context for the string.
|
||||
*
|
||||
* @param array $values
|
||||
* Possible array keys:
|
||||
* - value (required): the string that is to be translated.
|
||||
* - arguments (optional): an array with placeholder replacements, keyed by
|
||||
* placeholder.
|
||||
* - context (optional): a string that describes the context of "value".
|
||||
*/
|
||||
public function __construct(array $values) {
|
||||
$string = $values['value'];
|
||||
$arguments = $values['arguments'] ?? [];
|
||||
$options = [];
|
||||
if (!empty($values['context'])) {
|
||||
$options = [
|
||||
'context' => $values['context'],
|
||||
];
|
||||
}
|
||||
// phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
|
||||
$this->translation = new TranslatableMarkup($string, $arguments, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get() {
|
||||
return $this->translation;
|
||||
}
|
||||
|
||||
}
|
||||
55
web/core/lib/Drupal/Core/Archiver/Annotation/Archiver.php
Normal file
55
web/core/lib/Drupal/Core/Archiver/Annotation/Archiver.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Archiver\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines an archiver annotation object.
|
||||
*
|
||||
* Plugin Namespace: Plugin\Archiver
|
||||
*
|
||||
* For a working example, see \Drupal\system\Plugin\Archiver\Zip
|
||||
*
|
||||
* @see \Drupal\Core\Archiver\ArchiverManager
|
||||
* @see \Drupal\Core\Archiver\ArchiverInterface
|
||||
* @see plugin_api
|
||||
* @see hook_archiver_info_alter()
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class Archiver extends Plugin {
|
||||
|
||||
/**
|
||||
* The archiver plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The human-readable name of the archiver plugin.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $title;
|
||||
|
||||
/**
|
||||
* The description of the archiver plugin.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $description;
|
||||
|
||||
/**
|
||||
* An array of valid extensions for this archiver.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $extensions;
|
||||
|
||||
}
|
||||
24
web/core/lib/Drupal/Core/Archiver/ArchiveTar.php
Normal file
24
web/core/lib/Drupal/Core/Archiver/ArchiveTar.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Archiver;
|
||||
|
||||
/**
|
||||
* Extends Pear's Archive_Tar to use exceptions.
|
||||
*/
|
||||
class ArchiveTar extends \Archive_Tar {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function _error($p_message) {
|
||||
throw new \Exception($p_message);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function _warning($p_message) {
|
||||
throw new \Exception($p_message);
|
||||
}
|
||||
|
||||
}
|
||||
8
web/core/lib/Drupal/Core/Archiver/ArchiverException.php
Normal file
8
web/core/lib/Drupal/Core/Archiver/ArchiverException.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Archiver;
|
||||
|
||||
/**
|
||||
* Defines an exception class for Drupal\Core\Archiver\ArchiverInterface.
|
||||
*/
|
||||
class ArchiverException extends \Exception {}
|
||||
60
web/core/lib/Drupal/Core/Archiver/ArchiverInterface.php
Normal file
60
web/core/lib/Drupal/Core/Archiver/ArchiverInterface.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Archiver;
|
||||
|
||||
/**
|
||||
* Defines the common interface for all Archiver classes.
|
||||
*
|
||||
* @see \Drupal\Core\Archiver\ArchiverManager
|
||||
* @see \Drupal\Core\Archiver\Attribute\Archiver
|
||||
* @see plugin_api
|
||||
*/
|
||||
interface ArchiverInterface {
|
||||
|
||||
/**
|
||||
* Adds the specified file or directory to the archive.
|
||||
*
|
||||
* @param string $file_path
|
||||
* The full system path of the file or directory to add. Only local files
|
||||
* and directories are supported.
|
||||
*
|
||||
* @return $this
|
||||
* The called object.
|
||||
*/
|
||||
public function add($file_path);
|
||||
|
||||
/**
|
||||
* Removes the specified file from the archive.
|
||||
*
|
||||
* @param string $path
|
||||
* The file name relative to the root of the archive to remove.
|
||||
*
|
||||
* @return $this
|
||||
* The called object.
|
||||
*/
|
||||
public function remove($path);
|
||||
|
||||
/**
|
||||
* Extracts multiple files in the archive to the specified path.
|
||||
*
|
||||
* @param string $path
|
||||
* A full system path of the directory to which to extract files.
|
||||
* @param array $files
|
||||
* Optionally specify a list of files to be extracted. Files are
|
||||
* relative to the root of the archive. If not specified, all files
|
||||
* in the archive will be extracted.
|
||||
*
|
||||
* @return $this
|
||||
* The called object.
|
||||
*/
|
||||
public function extract($path, array $files = []);
|
||||
|
||||
/**
|
||||
* Lists all files in the archive.
|
||||
*
|
||||
* @return array
|
||||
* An array of file names relative to the root of the archive.
|
||||
*/
|
||||
public function listContents();
|
||||
|
||||
}
|
||||
97
web/core/lib/Drupal/Core/Archiver/ArchiverManager.php
Normal file
97
web/core/lib/Drupal/Core/Archiver/ArchiverManager.php
Normal file
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Archiver;
|
||||
|
||||
use Drupal\Component\Plugin\Factory\DefaultFactory;
|
||||
use Drupal\Core\Archiver\Attribute\Archiver;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\File\FileSystemInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
|
||||
/**
|
||||
* Provides an Archiver plugin manager.
|
||||
*
|
||||
* @see \Drupal\Core\Archiver\Attribute\Archiver
|
||||
* @see \Drupal\Core\Archiver\ArchiverInterface
|
||||
* @see plugin_api
|
||||
*/
|
||||
class ArchiverManager extends DefaultPluginManager {
|
||||
|
||||
/**
|
||||
* The file system service.
|
||||
*
|
||||
* @var \Drupal\Core\File\FileSystemInterface
|
||||
*/
|
||||
protected $fileSystem;
|
||||
|
||||
/**
|
||||
* Constructs an ArchiverManager object.
|
||||
*
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* Cache backend instance to use.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler to invoke the alter hook with.
|
||||
* @param \Drupal\Core\File\FileSystemInterface $file_system
|
||||
* The file handler.
|
||||
*/
|
||||
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, FileSystemInterface $file_system) {
|
||||
parent::__construct('Plugin/Archiver', $namespaces, $module_handler, ArchiverInterface::class, Archiver::class, 'Drupal\Core\Archiver\Annotation\Archiver');
|
||||
$this->alterInfo('archiver_info');
|
||||
$this->setCacheBackend($cache_backend, 'archiver_info_plugins');
|
||||
$this->fileSystem = $file_system;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createInstance($plugin_id, array $configuration = []) {
|
||||
$plugin_definition = $this->getDefinition($plugin_id);
|
||||
$plugin_class = DefaultFactory::getPluginClass($plugin_id, $plugin_definition, 'Drupal\Core\Archiver\ArchiverInterface');
|
||||
return new $plugin_class($this->fileSystem->realpath($configuration['filepath']), $configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInstance(array $options) {
|
||||
$filepath = $options['filepath'];
|
||||
foreach ($this->getDefinitions() as $plugin_id => $definition) {
|
||||
foreach ($definition['extensions'] as $extension) {
|
||||
// Because extensions may be multi-part, such as .tar.gz,
|
||||
// we cannot use simpler approaches like substr() or pathinfo().
|
||||
// This method isn't quite as clean but gets the job done.
|
||||
// Also note that the file may not yet exist, so we cannot rely
|
||||
// on fileinfo() or other disk-level utilities.
|
||||
if (strrpos($filepath, '.' . $extension) === strlen($filepath) - strlen('.' . $extension)) {
|
||||
return $this->createInstance($plugin_id, $options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string of supported archive extensions.
|
||||
*
|
||||
* @return string
|
||||
* A space-separated string of extensions suitable for use by the file
|
||||
* validation system.
|
||||
*/
|
||||
public function getExtensions() {
|
||||
$valid_extensions = [];
|
||||
foreach ($this->getDefinitions() as $archive) {
|
||||
foreach ($archive['extensions'] as $extension) {
|
||||
foreach (explode('.', $extension) as $part) {
|
||||
if (!in_array($part, $valid_extensions)) {
|
||||
$valid_extensions[] = $part;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return implode(' ', $valid_extensions);
|
||||
}
|
||||
|
||||
}
|
||||
45
web/core/lib/Drupal/Core/Archiver/Attribute/Archiver.php
Normal file
45
web/core/lib/Drupal/Core/Archiver/Attribute/Archiver.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Archiver\Attribute;
|
||||
|
||||
use Drupal\Component\Plugin\Attribute\Plugin;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Defines an archiver attribute object.
|
||||
*
|
||||
* Plugin Namespace: Plugin\Archiver
|
||||
*
|
||||
* For a working example, see \Drupal\system\Plugin\Archiver\Zip
|
||||
*
|
||||
* @see \Drupal\Core\Archiver\ArchiverManager
|
||||
* @see \Drupal\Core\Archiver\ArchiverInterface
|
||||
* @see plugin_api
|
||||
* @see hook_archiver_info_alter()
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
class Archiver extends Plugin {
|
||||
|
||||
/**
|
||||
* Constructs an archiver plugin attribute object.
|
||||
*
|
||||
* @param string $id
|
||||
* The archiver plugin ID.
|
||||
* @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $title
|
||||
* The human-readable name of the archiver plugin.
|
||||
* @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $description
|
||||
* The description of the archiver plugin.
|
||||
* @param array $extensions
|
||||
* An array of valid extensions for this archiver.
|
||||
* @param class-string|null $deriver
|
||||
* (optional) The deriver class.
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $id,
|
||||
public readonly ?TranslatableMarkup $title = NULL,
|
||||
public readonly ?TranslatableMarkup $description = NULL,
|
||||
public readonly array $extensions = [],
|
||||
public readonly ?string $deriver = NULL,
|
||||
) {}
|
||||
|
||||
}
|
||||
98
web/core/lib/Drupal/Core/Archiver/Tar.php
Normal file
98
web/core/lib/Drupal/Core/Archiver/Tar.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Archiver;
|
||||
|
||||
/**
|
||||
* Defines an archiver implementation for .tar files.
|
||||
*/
|
||||
class Tar implements ArchiverInterface {
|
||||
|
||||
/**
|
||||
* The underlying ArchiveTar instance that does the heavy lifting.
|
||||
*
|
||||
* @var \Drupal\Core\Archiver\ArchiveTar
|
||||
*/
|
||||
protected $tar;
|
||||
|
||||
/**
|
||||
* Constructs a Tar object.
|
||||
*
|
||||
* @param string $file_path
|
||||
* The full system path of the archive to manipulate. Only local files
|
||||
* are supported. If the file does not yet exist, it will be created if
|
||||
* appropriate.
|
||||
* @param array $configuration
|
||||
* (Optional) settings to open the archive with the following keys:
|
||||
* - 'compress': Indicates if the 'gzip', 'bz2', or 'lzma2' compression is
|
||||
* required.
|
||||
* - 'buffer_length': Length of the read buffer in bytes.
|
||||
*
|
||||
* @throws \Drupal\Core\Archiver\ArchiverException
|
||||
*/
|
||||
public function __construct($file_path, array $configuration = []) {
|
||||
$compress = $configuration['compress'] ?? NULL;
|
||||
$buffer = $configuration['buffer_length'] ?? 512;
|
||||
$this->tar = new ArchiveTar($file_path, $compress, $buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function add($file_path) {
|
||||
$this->tar->add($file_path);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function remove($file_path) {
|
||||
// @todo Archive_Tar doesn't have a remove operation
|
||||
// so we'll have to simulate it somehow, probably by
|
||||
// creating a new archive with everything but the removed
|
||||
// file.
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function extract($path, array $files = []) {
|
||||
if ($files) {
|
||||
$this->tar->extractList($files, $path, '', FALSE, FALSE);
|
||||
}
|
||||
else {
|
||||
$this->tar->extract($path, FALSE, FALSE);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function listContents() {
|
||||
$files = [];
|
||||
foreach ($this->tar->listContent() as $file_data) {
|
||||
$files[] = $file_data['filename'];
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the tar engine itself.
|
||||
*
|
||||
* In some cases it may be necessary to directly access the underlying
|
||||
* ArchiveTar object for implementation-specific logic. This is for advanced
|
||||
* use only as it is not shared by other implementations of ArchiveInterface.
|
||||
*
|
||||
* @return ArchiveTar
|
||||
* The ArchiveTar object used by this object.
|
||||
*/
|
||||
public function getArchive() {
|
||||
return $this->tar;
|
||||
}
|
||||
|
||||
}
|
||||
96
web/core/lib/Drupal/Core/Archiver/Zip.php
Normal file
96
web/core/lib/Drupal/Core/Archiver/Zip.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Archiver;
|
||||
|
||||
/**
|
||||
* Defines an archiver implementation for .zip files.
|
||||
*
|
||||
* @link http://php.net/zip
|
||||
*/
|
||||
class Zip implements ArchiverInterface {
|
||||
|
||||
/**
|
||||
* The underlying ZipArchive instance that does the heavy lifting.
|
||||
*
|
||||
* @var \ZipArchive
|
||||
*/
|
||||
protected $zip;
|
||||
|
||||
/**
|
||||
* Constructs a Zip object.
|
||||
*
|
||||
* @param string $file_path
|
||||
* The full system path of the archive to manipulate. Only local files
|
||||
* are supported. If the file does not yet exist, it will be created if
|
||||
* appropriate.
|
||||
* @param array $configuration
|
||||
* (Optional) settings to open the archive with the following keys:
|
||||
* - 'flags': The mode to open the archive with \ZipArchive::open().
|
||||
*
|
||||
* @throws \Drupal\Core\Archiver\ArchiverException
|
||||
*/
|
||||
public function __construct($file_path, array $configuration = []) {
|
||||
$this->zip = new \ZipArchive();
|
||||
if ($this->zip->open($file_path, $configuration['flags'] ?? 0) !== TRUE) {
|
||||
throw new ArchiverException("Cannot open '$file_path'");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function add($file_path) {
|
||||
$this->zip->addFile($file_path);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function remove($file_path) {
|
||||
$this->zip->deleteName($file_path);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function extract($path, array $files = []) {
|
||||
if ($files) {
|
||||
$this->zip->extractTo($path, $files);
|
||||
}
|
||||
else {
|
||||
$this->zip->extractTo($path);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function listContents() {
|
||||
$files = [];
|
||||
for ($i = 0; $i < $this->zip->numFiles; $i++) {
|
||||
$files[] = $this->zip->getNameIndex($i);
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the zip engine itself.
|
||||
*
|
||||
* In some cases it may be necessary to directly access the underlying
|
||||
* ZipArchive object for implementation-specific logic. This is for advanced
|
||||
* use only as it is not shared by other implementations of ArchiveInterface.
|
||||
*
|
||||
* @return \ZipArchive
|
||||
* The ZipArchive object used by this object.
|
||||
*/
|
||||
public function getArchive() {
|
||||
return $this->zip;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
/**
|
||||
* Interface defining a service that optimizes a collection of assets.
|
||||
*
|
||||
* Contains an additional method to allow for optimizing an asset group.
|
||||
*/
|
||||
interface AssetCollectionGroupOptimizerInterface extends AssetCollectionOptimizerInterface {
|
||||
|
||||
/**
|
||||
* Optimizes a specific group of assets.
|
||||
*
|
||||
* @param array $group
|
||||
* An asset group.
|
||||
*
|
||||
* @return string
|
||||
* The optimized string for the group.
|
||||
*/
|
||||
public function optimizeGroup(array $group): string;
|
||||
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
/**
|
||||
* Interface defining a service that logically groups a collection of assets.
|
||||
*/
|
||||
interface AssetCollectionGrouperInterface {
|
||||
|
||||
/**
|
||||
* Groups a collection of assets into logical groups of asset collections.
|
||||
*
|
||||
* @param array $assets
|
||||
* An asset collection.
|
||||
*
|
||||
* @return array
|
||||
* A sorted array of asset groups.
|
||||
*/
|
||||
public function group(array $assets);
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user