Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessArgumentsResolverFactory.
|
||||
*/
|
||||
|
||||
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,34 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessArgumentsResolverFactoryInterface.
|
||||
*/
|
||||
|
||||
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);
|
||||
|
||||
}
|
29
core/lib/Drupal/Core/Access/AccessCheckInterface.php
Normal file
29
core/lib/Drupal/Core/Access/AccessCheckInterface.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessCheckInterface.
|
||||
*/
|
||||
|
||||
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 array
|
||||
* An array of route requirement keys this access checker applies to.
|
||||
*/
|
||||
public function applies(Route $route);
|
||||
|
||||
}
|
17
core/lib/Drupal/Core/Access/AccessException.php
Normal file
17
core/lib/Drupal/Core/Access/AccessException.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessException.
|
||||
*/
|
||||
|
||||
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 {
|
||||
}
|
178
core/lib/Drupal/Core/Access/AccessManager.php
Normal file
178
core/lib/Drupal/Core/Access/AccessManager.php
Normal file
|
@ -0,0 +1,178 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessManager.
|
||||
*/
|
||||
|
||||
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 Symfony\Cmf\Component\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 a 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
|
||||
*/
|
||||
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 = array(), AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
try {
|
||||
$route = $this->routeProvider->getRouteByName($route_name, $parameters);
|
||||
|
||||
// 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 $e) {
|
||||
// Cacheable until extensions change.
|
||||
$result = AccessResult::forbidden()->addCacheTags(['config:core.extension']);
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
catch (ParamNotConvertedException $e) {
|
||||
// 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') ?: array();
|
||||
|
||||
// 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);
|
||||
|
||||
if (!$checks) {
|
||||
return AccessResult::neutral();
|
||||
}
|
||||
$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.
|
||||
*
|
||||
* @throws \Drupal\Core\Access\AccessException
|
||||
* Thrown when the access check returns an invalid value.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
88
core/lib/Drupal/Core/Access/AccessManagerInterface.php
Normal file
88
core/lib/Drupal/Core/Access/AccessManagerInterface.php
Normal file
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessManagerInterface.
|
||||
*/
|
||||
|
||||
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 applicable 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 = array(), AccountInterface $account = NULL, $return_as_object = FALSE);
|
||||
|
||||
/**
|
||||
* Execute access checks against the incoming request.
|
||||
*
|
||||
* @param 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);
|
||||
|
||||
}
|
451
core/lib/Drupal/Core/Access/AccessResult.php
Normal file
451
core/lib/Drupal/Core/Access/AccessResult.php
Normal file
|
@ -0,0 +1,451 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessResult.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Config\ConfigBase;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
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, CacheableDependencyInterface {
|
||||
|
||||
/**
|
||||
* The cache context IDs (to vary a cache item ID based on active contexts).
|
||||
*
|
||||
* @see \Drupal\Core\Cache\Context\CacheContextInterface
|
||||
* @see \Drupal\Core\Cache\Context\CacheContextsManager::convertTokensToKeys()
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $contexts;
|
||||
|
||||
/**
|
||||
* The cache tags.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $tags;
|
||||
|
||||
/**
|
||||
* The maximum caching time in seconds.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $maxAge;
|
||||
|
||||
/**
|
||||
* Constructs a new AccessResult object.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->resetCacheContexts()
|
||||
->resetCacheTags()
|
||||
// Max-age must be non-zero for an access result to be cacheable.
|
||||
// Typically, cache items are invalidated via associated cache tags, not
|
||||
// via a maximum age.
|
||||
->setCacheMaxAge(Cache::PERMANENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an AccessResultInterface object with isNeutral() === TRUE.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResult
|
||||
* isNeutral() will be TRUE.
|
||||
*/
|
||||
public static function neutral() {
|
||||
return new AccessResultNeutral();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an AccessResultInterface object with isAllowed() === TRUE.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResult
|
||||
* isAllowed() will be TRUE.
|
||||
*/
|
||||
public static function allowed() {
|
||||
return new AccessResultAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an AccessResultInterface object with isForbidden() === TRUE.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResult
|
||||
* isForbidden() will be TRUE.
|
||||
*/
|
||||
public static function forbidden() {
|
||||
return new AccessResultForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResult
|
||||
* If $condition is TRUE, isForbidden() will be TRUE, otherwise isNeutral()
|
||||
* will be TRUE.
|
||||
*/
|
||||
public static function forbiddenIf($condition) {
|
||||
return $condition ? static::forbidden(): static::neutral();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an allowed 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) {
|
||||
return static::allowedIf($account->hasPermission($permission))->addCacheContexts(['user.permissions']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an allowed 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 (!$permission_access = $account->hasPermission($permission)) {
|
||||
$access = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach ($permissions as $permission) {
|
||||
if ($permission_access = $account->hasPermission($permission)) {
|
||||
$access = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return static::allowedIf($access)->addCacheContexts(empty($permissions) ? [] : ['user.permissions']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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() {
|
||||
sort($this->contexts);
|
||||
return $this->contexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
return $this->maxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds cache contexts associated with the access result.
|
||||
*
|
||||
* @param string[] $contexts
|
||||
* An array of cache context IDs, used to generate a cache ID.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addCacheContexts(array $contexts) {
|
||||
$this->contexts = array_unique(array_merge($this->contexts, $contexts));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets cache contexts (to the empty array).
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function resetCacheContexts() {
|
||||
$this->contexts = array();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds cache tags associated with the access result.
|
||||
*
|
||||
* @param array $tags
|
||||
* An array of cache tags.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addCacheTags(array $tags) {
|
||||
$this->tags = Cache::mergeTags($this->tags, $tags);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets cache tags (to the empty array).
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function resetCacheTags() {
|
||||
$this->tags = array();
|
||||
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->maxAge = $max_age;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method, adds the "user.permissions" cache context.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function cachePerPermissions() {
|
||||
$this->addCacheContexts(array('user.permissions'));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method, adds the "user" cache context.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function cachePerUser() {
|
||||
$this->addCacheContexts(array('user'));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method, adds the entity's cache tag.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity whose cache tag to set on the access result.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function cacheUntilEntityChanges(EntityInterface $entity) {
|
||||
$this->addCacheTags($entity->getCacheTags());
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method, adds the configuration object's cache tag.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigBase $configuration
|
||||
* The configuration object whose cache tag to set on the access result.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function cacheUntilConfigurationChanges(ConfigBase $configuration) {
|
||||
$this->addCacheTags($configuration->getCacheTags());
|
||||
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;
|
||||
}
|
||||
}
|
||||
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->isNeutral() || ($this->getCacheMaxAge() === 0 && $other->isNeutral()) || ($this->getCacheMaxAge() !== 0 && $other instanceof CacheableDependencyInterface && $other->getCacheMaxAge() !== 0)) {
|
||||
$merge_other = TRUE;
|
||||
}
|
||||
}
|
||||
$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()) {
|
||||
$merge_other = TRUE;
|
||||
}
|
||||
}
|
||||
elseif ($this->isAllowed() && $other->isAllowed()) {
|
||||
$result = static::allowed();
|
||||
$merge_other = TRUE;
|
||||
}
|
||||
else {
|
||||
$result = static::neutral();
|
||||
if (!$this->isNeutral()) {
|
||||
$merge_other = TRUE;
|
||||
}
|
||||
}
|
||||
$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.
|
||||
*
|
||||
* @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) {
|
||||
if ($this->getCacheMaxAge() !== 0 && $other->getCacheMaxAge() !== 0) {
|
||||
$this->setCacheMaxAge(Cache::mergeMaxAges($this->getCacheMaxAge(), $other->getCacheMaxAge()));
|
||||
}
|
||||
else {
|
||||
$this->setCacheMaxAge($other->getCacheMaxAge());
|
||||
}
|
||||
$this->addCacheContexts($other->getCacheContexts());
|
||||
$this->addCacheTags($other->getCacheTags());
|
||||
}
|
||||
// If any of the access results don't provide cacheability metadata, then
|
||||
// we cannot cache the combined access result, for we may not make
|
||||
// assumptions.
|
||||
else {
|
||||
$this->setCacheMaxAge(0);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
22
core/lib/Drupal/Core/Access/AccessResultAllowed.php
Normal file
22
core/lib/Drupal/Core/Access/AccessResultAllowed.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessResultAllowed.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
/**
|
||||
* Value object indicating an allowed access result, with cacheability metadata.
|
||||
*/
|
||||
class AccessResultAllowed extends AccessResult {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isAllowed() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
22
core/lib/Drupal/Core/Access/AccessResultForbidden.php
Normal file
22
core/lib/Drupal/Core/Access/AccessResultForbidden.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessResultForbidden.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
/**
|
||||
* Value object indicating a forbidden access result, with cacheability metadata.
|
||||
*/
|
||||
class AccessResultForbidden extends AccessResult {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isForbidden() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
103
core/lib/Drupal/Core/Access/AccessResultInterface.php
Normal file
103
core/lib/Drupal/Core/Access/AccessResultInterface.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessResultInterface.
|
||||
*/
|
||||
|
||||
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.
|
||||
*
|
||||
* @return bool
|
||||
* When TRUE then isForbidden() and isNeutral() are FALSE.
|
||||
*/
|
||||
public function isAllowed();
|
||||
|
||||
/**
|
||||
* Checks whether this access result indicates access is explicitly forbidden.
|
||||
*
|
||||
* This is a kill switch — both orIf() and andIf() will result in
|
||||
* isForbidden() if either results are isForbidden().
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
public function isNeutral();
|
||||
|
||||
/**
|
||||
* Combine this access result with another using OR.
|
||||
*
|
||||
* When OR-ing 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-ing 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);
|
||||
|
||||
}
|
22
core/lib/Drupal/Core/Access/AccessResultNeutral.php
Normal file
22
core/lib/Drupal/Core/Access/AccessResultNeutral.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessResultNeutral.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
/**
|
||||
* Value object indicating a neutral access result, with cacheability metadata.
|
||||
*/
|
||||
class AccessResultNeutral extends AccessResult {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isNeutral() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
39
core/lib/Drupal/Core/Access/AccessibleInterface.php
Normal file
39
core/lib/Drupal/Core/Access/AccessibleInterface.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\AccessibleInterface.
|
||||
*/
|
||||
|
||||
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
core/lib/Drupal/Core/Access/CheckProvider.php
Normal file
170
core/lib/Drupal/Core/Access/CheckProvider.php
Normal file
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\CheckProvider.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\Routing\Access\AccessInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerAware;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Loads access checkers from the container.
|
||||
*/
|
||||
class CheckProvider extends ContainerAware implements CheckProviderInterface {
|
||||
|
||||
/**
|
||||
* Array of registered access check service ids.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $checkIds = array();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* Array of access checks which only will be run on the incoming request.
|
||||
*/
|
||||
protected $checksNeedsRequest = array();
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addCheckService($service_id, $service_method, array $applies_checks = array(), $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) {
|
||||
$this->loadDynamicRequirementMap();
|
||||
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(array($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 = array();
|
||||
|
||||
// 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) {
|
||||
$checks[] = $service_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finally, see if any dynamic access checkers apply.
|
||||
foreach ($this->dynamicRequirementMap as $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)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set them here, so we can use the isset() check above.
|
||||
$this->dynamicRequirementMap = array();
|
||||
|
||||
foreach ($this->checkIds as $service_id) {
|
||||
if (empty($this->checks[$service_id])) {
|
||||
$this->loadCheck($service_id);
|
||||
}
|
||||
|
||||
// Add the service ID to an array that will be iterated over.
|
||||
if ($this->checks[$service_id] instanceof AccessCheckInterface) {
|
||||
$this->dynamicRequirementMap[] = $service_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
69
core/lib/Drupal/Core/Access/CheckProviderInterface.php
Normal file
69
core/lib/Drupal/Core/Access/CheckProviderInterface.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\CheckProviderInterface.
|
||||
*/
|
||||
|
||||
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 = array(), $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
|
||||
*/
|
||||
public function getChecksNeedRequest();
|
||||
}
|
72
core/lib/Drupal/Core/Access/CsrfAccessCheck.php
Normal file
72
core/lib/Drupal/Core/Access/CsrfAccessCheck.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\CsrfAccessCheck.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Allows access to routes to be controlled by a '_csrf_token' parameter.
|
||||
*
|
||||
* To use this check, add a "token" GET parameter to URLs of which the value is
|
||||
* a token generated by \Drupal::csrfToken()->get() using the same value as the
|
||||
* "_csrf_token" parameter in the route.
|
||||
*/
|
||||
class CsrfAccessCheck implements RoutingAccessInterface {
|
||||
|
||||
/**
|
||||
* The CSRF token generator.
|
||||
*
|
||||
* @var \Drupal\Core\Access\CsrfTokenGenerator
|
||||
*/
|
||||
protected $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) {
|
||||
$parameters = $route_match->getRawParameters();
|
||||
$path = ltrim($route->getPath(), '/');
|
||||
// Replace the path parameters with values from the parameters array.
|
||||
foreach ($parameters as $param => $value) {
|
||||
$path = str_replace("{{$param}}", $value, $path);
|
||||
}
|
||||
|
||||
if ($this->csrfToken->validate($request->query->get('token'), $path)) {
|
||||
$result = AccessResult::allowed();
|
||||
}
|
||||
else {
|
||||
$result = AccessResult::forbidden();
|
||||
}
|
||||
// Not cacheable because the CSRF token is highly dynamic.
|
||||
return $result->setCacheMaxAge(0);
|
||||
}
|
||||
|
||||
}
|
117
core/lib/Drupal/Core/Access/CsrfTokenGenerator.php
Normal file
117
core/lib/Drupal/Core/Access/CsrfTokenGenerator.php
Normal file
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\CsrfTokenGenerator.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return $token === $this->computeToken($seed, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 'drupal_private_key' configuration variable.
|
||||
*
|
||||
* @see \Drupal\Core\Site\Settings::getHashSalt()
|
||||
*/
|
||||
protected function computeToken($seed, $value = '') {
|
||||
return Crypt::hmacBase64($value, $seed . $this->privateKey->get() . Settings::getHashSalt());
|
||||
}
|
||||
|
||||
}
|
74
core/lib/Drupal/Core/Access/CustomAccessCheck.php
Normal file
74
core/lib/Drupal/Core/Access/CustomAccessCheck.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\CustomAccessCheck.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\Controller\ControllerResolverInterface;
|
||||
use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
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 {
|
||||
|
||||
/**
|
||||
* The controller resolver.
|
||||
*
|
||||
* @var \Drupal\Core\Controller\ControllerResolverInterface
|
||||
*/
|
||||
protected $controllerResolver;
|
||||
|
||||
/**
|
||||
* The arguments resolver.
|
||||
*
|
||||
* @var \Drupal\Core\Access\AccessArgumentsResolverFactoryInterface
|
||||
*/
|
||||
protected $argumentsResolverFactory;
|
||||
|
||||
/**
|
||||
* Constructs a CustomAccessCheck instance.
|
||||
*
|
||||
* @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
|
||||
* The controller resolver.
|
||||
* @param \Drupal\Core\Access\AccessArgumentsResolverFactoryInterface $arguments_resolver_factory
|
||||
* The arguments resolver factory.
|
||||
*/
|
||||
public function __construct(ControllerResolverInterface $controller_resolver, AccessArgumentsResolverFactoryInterface $arguments_resolver_factory) {
|
||||
$this->controllerResolver = $controller_resolver;
|
||||
$this->argumentsResolverFactory = $arguments_resolver_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks access for the account and route using the custom access checker.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match object to be checked.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The account being checked.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessResultInterface
|
||||
* The access result.
|
||||
*/
|
||||
public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account) {
|
||||
$callable = $this->controllerResolver->getControllerFromDefinition($route->getRequirement('_custom_access'));
|
||||
$arguments_resolver = $this->argumentsResolverFactory->getArgumentsResolver($route_match, $account);
|
||||
$arguments = $arguments_resolver->getArguments($callable);
|
||||
|
||||
return call_user_func_array($callable, $arguments);
|
||||
}
|
||||
|
||||
}
|
39
core/lib/Drupal/Core/Access/DefaultAccessCheck.php
Normal file
39
core/lib/Drupal/Core/Access/DefaultAccessCheck.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\DefaultAccessCheck.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
57
core/lib/Drupal/Core/Access/RouteProcessorCsrf.php
Normal file
57
core/lib/Drupal/Core/Access/RouteProcessorCsrf.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Access\RouteProcessorCsrf.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Access;
|
||||
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Processes the outbound route to handle the CSRF token.
|
||||
*/
|
||||
class RouteProcessorCsrf implements OutboundRouteProcessorInterface {
|
||||
|
||||
/**
|
||||
* The CSRF token generator.
|
||||
*
|
||||
* @var \Drupal\Core\Access\CsrfTokenGenerator
|
||||
*/
|
||||
protected $csrfToken;
|
||||
|
||||
/**
|
||||
* Constructs a RouteProcessorCsrf object.
|
||||
*
|
||||
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
|
||||
* The CSRF token generator.
|
||||
*/
|
||||
function __construct(CsrfTokenGenerator $csrf_token) {
|
||||
$this->csrfToken = $csrf_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processOutbound($route_name, Route $route, array &$parameters, CacheableMetadata $cacheable_metadata = NULL) {
|
||||
if ($route->hasRequirement('_csrf_token')) {
|
||||
$path = ltrim($route->getPath(), '/');
|
||||
// Replace the path parameters with values from the parameters array.
|
||||
foreach ($parameters as $param => $value) {
|
||||
$path = str_replace("{{$param}}", $value, $path);
|
||||
}
|
||||
// Adding this to the parameters means it will get merged into the query
|
||||
// string when the route is compiled.
|
||||
$parameters['token'] = $this->csrfToken->get($path);
|
||||
if ($cacheable_metadata) {
|
||||
// Tokens are per user and per session, so not cacheable.
|
||||
// @todo Improve in https://www.drupal.org/node/2351015.
|
||||
$cacheable_metadata->setCacheMaxAge(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
31
core/lib/Drupal/Core/Action/ActionBase.php
Normal file
31
core/lib/Drupal/Core/Action/ActionBase.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Action\ActionBase.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
66
core/lib/Drupal/Core/Action/ActionInterface.php
Normal file
66
core/lib/Drupal/Core/Action/ActionInterface.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Action\ActionInterface.
|
||||
*/
|
||||
|
||||
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);
|
||||
|
||||
}
|
60
core/lib/Drupal/Core/Action/ActionManager.php
Normal file
60
core/lib/Drupal/Core/Action/ActionManager.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Action\ActionManager.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Action;
|
||||
|
||||
use Drupal\Component\Plugin\CategorizingPluginManagerInterface;
|
||||
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, 'Drupal\Core\Action\ActionInterface', '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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
26
core/lib/Drupal/Core/Action/ActionPluginCollection.php
Normal file
26
core/lib/Drupal/Core/Action/ActionPluginCollection.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Action\ActionPluginCollection.
|
||||
*/
|
||||
|
||||
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
|
||||
*/
|
||||
public function &get($instance_id) {
|
||||
return parent::get($instance_id);
|
||||
}
|
||||
|
||||
}
|
63
core/lib/Drupal/Core/Action/ConfigurableActionBase.php
Normal file
63
core/lib/Drupal/Core/Action/ConfigurableActionBase.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Action\ConfigurableActionBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Action;
|
||||
|
||||
use Drupal\Component\Plugin\ConfigurablePluginInterface;
|
||||
use Drupal\Core\Action\ActionBase;
|
||||
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 ConfigurablePluginInterface, PluginFormInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->configuration += $this->defaultConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfiguration() {
|
||||
return $this->configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setConfiguration(array $configuration) {
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
55
core/lib/Drupal/Core/Ajax/AddCssCommand.php
Normal file
55
core/lib/Drupal/Core/Ajax/AddCssCommand.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\AddCssCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\CommandInterface;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
* A string that contains the styles to be added to the page.
|
||||
*
|
||||
* It should include the wrapping style tag.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $styles;
|
||||
|
||||
/**
|
||||
* Constructs an AddCssCommand.
|
||||
*
|
||||
* @param string $styles
|
||||
* A string that contains the styles to be added to the page, including the
|
||||
* wrapping <style> tag.
|
||||
*/
|
||||
public function __construct($styles) {
|
||||
$this->styles = $styles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Ajax\CommandInterface:render().
|
||||
*/
|
||||
public function render() {
|
||||
|
||||
return array(
|
||||
'command' => 'add_css',
|
||||
'data' => $this->styles,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
42
core/lib/Drupal/Core/Ajax/AfterCommand.php
Normal file
42
core/lib/Drupal/Core/Ajax/AfterCommand.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\AfterCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\InsertCommand;
|
||||
|
||||
/**
|
||||
* 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 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 array(
|
||||
'command' => 'insert',
|
||||
'method' => 'after',
|
||||
'selector' => $this->selector,
|
||||
'data' => $this->getRenderedContent(),
|
||||
'settings' => $this->settings,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
78
core/lib/Drupal/Core/Ajax/AjaxResponse.php
Normal file
78
core/lib/Drupal/Core/Ajax/AjaxResponse.php
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\AjaxResponse.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Asset\AttachedAssets;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Core\Render\Renderer;
|
||||
use Drupal\Core\Render\AttachmentsInterface;
|
||||
use Drupal\Core\Render\AttachmentsTrait;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* 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 = array();
|
||||
|
||||
/**
|
||||
* 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 AjaxResponse
|
||||
* 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 \Drupal\Core\Ajax\CommandInterface[]
|
||||
* Returns all previously added AJAX commands.
|
||||
*/
|
||||
public function &getCommands() {
|
||||
return $this->commands;
|
||||
}
|
||||
|
||||
}
|
199
core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php
Normal file
199
core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php
Normal file
|
@ -0,0 +1,199 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\AjaxResponseAttachmentsProcessor.
|
||||
*/
|
||||
|
||||
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\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 {
|
||||
|
||||
/**
|
||||
* The asset resolver service.
|
||||
*
|
||||
* @var \Drupal\Core\Asset\AssetResolverInterface
|
||||
*/
|
||||
protected $assetResolver;
|
||||
|
||||
/**
|
||||
* A config object for the system performance configuration.
|
||||
*
|
||||
* @var \Drupal\Core\Config\Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* The CSS asset collection renderer service.
|
||||
*
|
||||
* @var \Drupal\Core\Asset\AssetCollectionRendererInterface
|
||||
*/
|
||||
protected $cssCollectionRenderer;
|
||||
|
||||
/**
|
||||
* The JS asset collection renderer service.
|
||||
*
|
||||
* @var \Drupal\Core\Asset\AssetCollectionRendererInterface
|
||||
*/
|
||||
protected $jsCollectionRenderer;
|
||||
|
||||
/**
|
||||
* The request stack.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* The renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* Constructs a AjaxResponseAttachmentsProcessor object.
|
||||
*
|
||||
* @param \Drupal\Core\Asset\AssetResolverInterface $asset_resolver
|
||||
* An asset resolver.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* A config factory for retrieving required config objects.
|
||||
* @param \Drupal\Core\Asset\AssetCollectionRendererInterface $css_collection_renderer
|
||||
* The CSS asset collection renderer.
|
||||
* @param \Drupal\Core\Asset\AssetCollectionRendererInterface $js_collection_renderer
|
||||
* The JS asset collection renderer.
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The request stack.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
*/
|
||||
public function __construct(AssetResolverInterface $asset_resolver, ConfigFactoryInterface $config_factory, AssetCollectionRendererInterface $css_collection_renderer, AssetCollectionRendererInterface $js_collection_renderer, RequestStack $request_stack, RendererInterface $renderer, ModuleHandlerInterface $module_handler) {
|
||||
$this->assetResolver = $asset_resolver;
|
||||
$this->config = $config_factory->get('system.performance');
|
||||
$this->cssCollectionRenderer = $css_collection_renderer;
|
||||
$this->jsCollectionRenderer = $js_collection_renderer;
|
||||
$this->requestStack = $request_stack;
|
||||
$this->renderer = $renderer;
|
||||
$this->moduleHandler = $module_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processAttachments(AttachmentsInterface $response) {
|
||||
// @todo Convert to assertion once https://www.drupal.org/node/2408013 lands
|
||||
if (!$response instanceof AjaxResponse) {
|
||||
throw new \InvalidArgumentException('\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->request->get('ajax_page_state');
|
||||
|
||||
// Aggregate CSS/JS if necessary, but only during normal site operation.
|
||||
$optimize_css = !defined('MAINTENANCE_MODE') && $this->config->get('css.preprocess');
|
||||
$optimize_js = !defined('MAINTENANCE_MODE') && $this->config->get('js.preprocess');
|
||||
|
||||
$attachments = $response->getAttachments();
|
||||
|
||||
// Resolve the attached libraries into asset collections.
|
||||
$assets = new AttachedAssets();
|
||||
$assets->setLibraries(isset($attachments['library']) ? $attachments['library'] : [])
|
||||
->setAlreadyLoadedLibraries(isset($ajax_page_state['libraries']) ? explode(',', $ajax_page_state['libraries']) : [])
|
||||
->setSettings(isset($attachments['drupalSettings']) ? $attachments['drupalSettings'] : []);
|
||||
$css_assets = $this->assetResolver->getCssAssets($assets, $optimize_css);
|
||||
list($js_assets_header, $js_assets_footer) = $this->assetResolver->getJsAssets($assets, $optimize_js);
|
||||
|
||||
// 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 = array();
|
||||
if ($css_assets) {
|
||||
$css_render_array = $this->cssCollectionRenderer->render($css_assets);
|
||||
$resource_commands[] = new AddCssCommand($this->renderer->renderPlain($css_render_array));
|
||||
}
|
||||
if ($js_assets_header) {
|
||||
$js_header_render_array = $this->jsCollectionRenderer->render($js_assets_header);
|
||||
$resource_commands[] = new PrependCommand('head', $this->renderer->renderPlain($js_header_render_array));
|
||||
}
|
||||
if ($js_assets_footer) {
|
||||
$js_footer_render_array = $this->jsCollectionRenderer->render($js_assets_footer);
|
||||
$resource_commands[] = new AppendCommand('body', $this->renderer->renderPlain($js_footer_render_array));
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
47
core/lib/Drupal/Core/Ajax/AlertCommand.php
Normal file
47
core/lib/Drupal/Core/Ajax/AlertCommand.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\AlertCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\CommandInterface;
|
||||
|
||||
/**
|
||||
* 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 array(
|
||||
'command' => 'alert',
|
||||
'text' => $this->text,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
42
core/lib/Drupal/Core/Ajax/AppendCommand.php
Normal file
42
core/lib/Drupal/Core/Ajax/AppendCommand.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\AppendCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\InsertCommand;
|
||||
|
||||
/**
|
||||
* 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 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 array(
|
||||
'command' => 'insert',
|
||||
'method' => 'append',
|
||||
'selector' => $this->selector,
|
||||
'data' => $this->getRenderedContent(),
|
||||
'settings' => $this->settings,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
42
core/lib/Drupal/Core/Ajax/BeforeCommand.php
Normal file
42
core/lib/Drupal/Core/Ajax/BeforeCommand.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\BeforeCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\InsertCommand;
|
||||
|
||||
/**
|
||||
* 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 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 array(
|
||||
'command' => 'insert',
|
||||
'method' => 'before',
|
||||
'selector' => $this->selector,
|
||||
'data' => $this->getRenderedContent(),
|
||||
'settings' => $this->settings,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
67
core/lib/Drupal/Core/Ajax/ChangedCommand.php
Normal file
67
core/lib/Drupal/Core/Ajax/ChangedCommand.php
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\ChangedCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\CommandInterface;
|
||||
|
||||
/**
|
||||
* 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 array(
|
||||
'command' => 'changed',
|
||||
'selector' => $this->selector,
|
||||
'asterisk' => $this->asterisk,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
54
core/lib/Drupal/Core/Ajax/CloseDialogCommand.php
Normal file
54
core/lib/Drupal/Core/Ajax/CloseDialogCommand.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\CloseDialogCommand.
|
||||
*/
|
||||
|
||||
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 ? $selector : '#drupal-modal';
|
||||
$this->persist = $persist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\Core\Ajax\CommandInterface::render().
|
||||
*/
|
||||
public function render() {
|
||||
return array(
|
||||
'command' => 'closeDialog',
|
||||
'selector' => $this->selector,
|
||||
'persist' => $this->persist,
|
||||
);
|
||||
}
|
||||
}
|
30
core/lib/Drupal/Core/Ajax/CloseModalDialogCommand.php
Normal file
30
core/lib/Drupal/Core/Ajax/CloseModalDialogCommand.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\CloseModalDialogCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\CloseDialogCommand;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
24
core/lib/Drupal/Core/Ajax/CommandInterface.php
Normal file
24
core/lib/Drupal/Core/Ajax/CommandInterface.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\CommandInterface.
|
||||
*/
|
||||
|
||||
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,28 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\CommandWithAttachedAssetsInterface.
|
||||
*/
|
||||
|
||||
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();
|
||||
|
||||
}
|
57
core/lib/Drupal/Core/Ajax/CommandWithAttachedAssetsTrait.php
Normal file
57
core/lib/Drupal/Core/Ajax/CommandWithAttachedAssetsTrait.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\CommandWithAttachedAssetsTrait.
|
||||
*/
|
||||
|
||||
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
|
||||
* HTML rendered content.
|
||||
*/
|
||||
protected function getRenderedContent() {
|
||||
$this->attachedAssets = new AttachedAssets();
|
||||
if (is_array($this->content)) {
|
||||
$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;
|
||||
}
|
||||
|
||||
}
|
84
core/lib/Drupal/Core/Ajax/CssCommand.php
Normal file
84
core/lib/Drupal/Core/Ajax/CssCommand.php
Normal file
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\CssCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\CommandInterface;
|
||||
|
||||
/**
|
||||
* 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 = array();
|
||||
|
||||
/**
|
||||
* 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 = array()) {
|
||||
$this->selector = $selector;
|
||||
$this->css = $css;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a property/value pair to the CSS to be added to this element.
|
||||
*
|
||||
* @param $property
|
||||
* The CSS property to be changed.
|
||||
* @param $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 array(
|
||||
'command' => 'css',
|
||||
'selector' => $this->selector,
|
||||
'argument' => $this->css,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
81
core/lib/Drupal/Core/Ajax/DataCommand.php
Normal file
81
core/lib/Drupal/Core/Ajax/DataCommand.php
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\DataCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\CommandInterface;
|
||||
|
||||
/**
|
||||
* 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 type $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 array(
|
||||
'command' => 'data',
|
||||
'selector' => $this->selector,
|
||||
'name' => $this->name,
|
||||
'value' => $this->value,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
42
core/lib/Drupal/Core/Ajax/HtmlCommand.php
Normal file
42
core/lib/Drupal/Core/Ajax/HtmlCommand.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\HtmlCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\InsertCommand;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* 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 array(
|
||||
'command' => 'insert',
|
||||
'method' => 'html',
|
||||
'selector' => $this->selector,
|
||||
'data' => $this->getRenderedContent(),
|
||||
'settings' => $this->settings,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
85
core/lib/Drupal/Core/Ajax/InsertCommand.php
Normal file
85
core/lib/Drupal/Core/Ajax/InsertCommand.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\InsertCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\CommandInterface;
|
||||
|
||||
/**
|
||||
* Generic AJAX command for inserting content.
|
||||
*
|
||||
* This command instructs the client to insert the given 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
|
||||
*/
|
||||
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 any attached JavaScript behavior.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* Constructs an InsertCommand object.
|
||||
*
|
||||
* @param string $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 array(
|
||||
'command' => 'insert',
|
||||
'method' => NULL,
|
||||
'selector' => $this->selector,
|
||||
'data' => $this->getRenderedContent(),
|
||||
'settings' => $this->settings,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
80
core/lib/Drupal/Core/Ajax/InvokeCommand.php
Normal file
80
core/lib/Drupal/Core/Ajax/InvokeCommand.php
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\InvokeCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\CommandInterface;
|
||||
|
||||
/**
|
||||
* 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 = array()) {
|
||||
$this->selector = $selector;
|
||||
$this->method = $method;
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Ajax\CommandInterface:render().
|
||||
*/
|
||||
public function render() {
|
||||
|
||||
return array(
|
||||
'command' => 'invoke',
|
||||
'selector' => $this->selector,
|
||||
'method' => $this->method,
|
||||
'args' => $this->arguments,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
144
core/lib/Drupal/Core/Ajax/OpenDialogCommand.php
Normal file
144
core/lib/Drupal/Core/Ajax/OpenDialogCommand.php
Normal file
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\OpenDialogCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\CommandInterface;
|
||||
|
||||
/**
|
||||
* 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. See http://api.jqueryui.com/dialog.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dialogOptions;
|
||||
|
||||
/**
|
||||
* Custom settings that will be passed to the 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 $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, $title, $content, array $dialog_options = array(), $settings = NULL) {
|
||||
$dialog_options += array('title' => $title);
|
||||
$this->selector = $selector;
|
||||
$this->content = $content;
|
||||
$this->dialogOptions = $dialog_options;
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the dialog options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
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->setDialogOptions('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 array(
|
||||
'command' => 'openDialog',
|
||||
'selector' => $this->selector,
|
||||
'settings' => $this->settings,
|
||||
'data' => $this->getRenderedContent(),
|
||||
'dialogOptions' => $this->dialogOptions,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
43
core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php
Normal file
43
core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\OpenModalDialogCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\OpenDialogCommand;
|
||||
|
||||
/**
|
||||
* Defines 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 $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($title, $content, array $dialog_options = array(), $settings = NULL) {
|
||||
$dialog_options['modal'] = TRUE;
|
||||
parent::__construct('#drupal-modal', $title, $content, $dialog_options, $settings);
|
||||
}
|
||||
}
|
42
core/lib/Drupal/Core/Ajax/PrependCommand.php
Normal file
42
core/lib/Drupal/Core/Ajax/PrependCommand.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\PrependCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\InsertCommand;
|
||||
|
||||
/**
|
||||
* 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 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 array(
|
||||
'command' => 'insert',
|
||||
'method' => 'prepend',
|
||||
'selector' => $this->selector,
|
||||
'data' => $this->getRenderedContent(),
|
||||
'settings' => $this->settings,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
47
core/lib/Drupal/Core/Ajax/RedirectCommand.php
Normal file
47
core/lib/Drupal/Core/Ajax/RedirectCommand.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\RedirectCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\CommandInterface;
|
||||
|
||||
/**
|
||||
* 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 array(
|
||||
'command' => 'redirect',
|
||||
'url' => $this->url,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
55
core/lib/Drupal/Core/Ajax/RemoveCommand.php
Normal file
55
core/lib/Drupal/Core/Ajax/RemoveCommand.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\RemoveCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\CommandInterface;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
public function __construct($selector) {
|
||||
$this->selector = $selector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Ajax\CommandInterface:render().
|
||||
*/
|
||||
public function render() {
|
||||
return array(
|
||||
'command' => 'remove',
|
||||
'selector' => $this->selector,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
43
core/lib/Drupal/Core/Ajax/ReplaceCommand.php
Normal file
43
core/lib/Drupal/Core/Ajax/ReplaceCommand.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\ReplaceCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\InsertCommand;
|
||||
|
||||
/**
|
||||
* 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 matched by the given
|
||||
* selector with the given 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 array(
|
||||
'command' => 'insert',
|
||||
'method' => 'replaceWith',
|
||||
'selector' => $this->selector,
|
||||
'data' => $this->getRenderedContent(),
|
||||
'settings' => $this->settings,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
56
core/lib/Drupal/Core/Ajax/RestripeCommand.php
Normal file
56
core/lib/Drupal/Core/Ajax/RestripeCommand.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\RestripeCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\CommandInterface;
|
||||
|
||||
/**
|
||||
* 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 array(
|
||||
'command' => 'restripe',
|
||||
'selector' => $this->selector,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
68
core/lib/Drupal/Core/Ajax/SetDialogOptionCommand.php
Normal file
68
core/lib/Drupal/Core/Ajax/SetDialogOptionCommand.php
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\SetDialogOptionCommand.
|
||||
*/
|
||||
|
||||
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 ? $selector : '#drupal-modal';
|
||||
$this->optionName = $option_name;
|
||||
$this->optionValue = $option_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\Core\Ajax\CommandInterface::render().
|
||||
*/
|
||||
public function render() {
|
||||
return array(
|
||||
'command' => 'setDialogOption',
|
||||
'selector' => $this->selector,
|
||||
'optionName' => $this->optionName,
|
||||
'optionValue' => $this->optionValue,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
33
core/lib/Drupal/Core/Ajax/SetDialogTitleCommand.php
Normal file
33
core/lib/Drupal/Core/Ajax/SetDialogTitleCommand.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\SetDialogTitleCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\SetDialogOptionCommand;
|
||||
|
||||
/**
|
||||
* 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 ? $selector : '#drupal-modal';
|
||||
$this->optionName = 'title';
|
||||
$this->optionValue = $title;
|
||||
}
|
||||
}
|
71
core/lib/Drupal/Core/Ajax/SettingsCommand.php
Normal file
71
core/lib/Drupal/Core/Ajax/SettingsCommand.php
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\SettingsCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\CommandInterface;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
|
||||
return array(
|
||||
'command' => 'settings',
|
||||
'settings' => $this->settings,
|
||||
'merge' => $this->merge,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
65
core/lib/Drupal/Core/Ajax/UpdateBuildIdCommand.php
Normal file
65
core/lib/Drupal/Core/Ajax/UpdateBuildIdCommand.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Ajax\UpdateBuildIdCommand.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Ajax;
|
||||
|
||||
/**
|
||||
* AJAX command 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.
|
||||
*O
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class UpdateBuildIdCommand implements CommandInterface {
|
||||
|
||||
/**
|
||||
* Old build id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $old;
|
||||
|
||||
/**
|
||||
* New build id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $new;
|
||||
|
||||
/**
|
||||
* Constructs a 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,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
71
core/lib/Drupal/Core/Annotation/Action.php
Normal file
71
core/lib/Drupal/Core/Annotation/Action.php
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Annotation\Action.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines an Action annotation object.
|
||||
*
|
||||
* Plugin Namespace: Plugin\Action
|
||||
*
|
||||
* For a working example, see \Drupal\node\Plugin\Action\UnpublishNode
|
||||
*
|
||||
* @see \Drupal\Core\Action\ActionInterface
|
||||
* @see \Drupal\Core\Action\ActionManager
|
||||
* @see \Drupal\Core\Action\ActionBase
|
||||
* @see plugin_api
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class Action extends Plugin {
|
||||
|
||||
/**
|
||||
* The plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The human-readable name of the action plugin.
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*/
|
||||
public $label;
|
||||
|
||||
/**
|
||||
* The route name for a confirmation form for this action.
|
||||
*
|
||||
* @todo Provide a more generic way to allow an action to be confirmed first.
|
||||
*
|
||||
* @var string (optional)
|
||||
*/
|
||||
public $confirm_form_route_name = '';
|
||||
|
||||
/**
|
||||
* The entity type the action can apply to.
|
||||
*
|
||||
* @todo Replace with \Drupal\Core\Plugin\Context\Context.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $type = '';
|
||||
|
||||
/**
|
||||
* The category under which the action should be listed in the UI.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $category;
|
||||
|
||||
}
|
139
core/lib/Drupal/Core/Annotation/ContextDefinition.php
Normal file
139
core/lib/Drupal/Core/Annotation/ContextDefinition.php
Normal file
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Annotation\ContextDefinition.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
use Drupal\Core\StringTranslation\TranslationWrapper;
|
||||
|
||||
/**
|
||||
* @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 = {
|
||||
* "node" = @ContextDefinition("entity:node")
|
||||
* }
|
||||
* @endcode
|
||||
* Remove spaces after @ in your actual plugin - these are put into this sample
|
||||
* code so that it is not recognized as an annotation.
|
||||
*
|
||||
* To add a label to a context definition use the "label" key:
|
||||
* @code
|
||||
* context = {
|
||||
* "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 = {
|
||||
* "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 = {
|
||||
* "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 = {
|
||||
* "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 += array(
|
||||
'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 TranslationWrapper) {
|
||||
$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 = isset($values['class']) ? $values['class'] : 'Drupal\Core\Plugin\Context\ContextDefinition';
|
||||
$this->definition = new $class($values['value'], $values['label'], $values['required'], $values['multiple'], $values['description'], $values['default_value']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of an annotation.
|
||||
*
|
||||
* @return \Drupal\Core\Plugin\Context\ContextDefinitionInterface
|
||||
*/
|
||||
public function get() {
|
||||
return $this->definition;
|
||||
}
|
||||
|
||||
}
|
52
core/lib/Drupal/Core/Annotation/Mail.php
Normal file
52
core/lib/Drupal/Core/Annotation/Mail.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Annotation\Mail.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
}
|
65
core/lib/Drupal/Core/Annotation/QueueWorker.php
Normal file
65
core/lib/Drupal/Core/Annotation/QueueWorker.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Annotation\QueueWorker.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Declare queue workers that need to be run periodically.
|
||||
*
|
||||
* While there can be only one hook_cron() process running at the same time,
|
||||
* there can be any number of processes defined here running. Because of
|
||||
* this, long running tasks are much better suited for this API. Items queued
|
||||
* in hook_cron() might be processed in the same cron run if there are not many
|
||||
* items in the queue, otherwise it might take several requests, which can be
|
||||
* run in parallel.
|
||||
*
|
||||
* You can create queues, add items to them, claim them, etc. without using a
|
||||
* QueueWorker plugin if you want, however, you need to take care of processing
|
||||
* the items in the queue in that case. See \Drupal\Core\Cron for an example.
|
||||
*
|
||||
* Plugin Namespace: Plugin\QueueWorker
|
||||
*
|
||||
* For a working example, see
|
||||
* \Drupal\aggregator\Plugin\QueueWorker\AggregatorRefresh.
|
||||
*
|
||||
* @see \Drupal\Core\Queue\QueueWorkerInterface
|
||||
* @see \Drupal\Core\Queue\QueueWorkerBase
|
||||
* @see \Drupal\Core\Queue\QueueWorkerManager
|
||||
* @see plugin_api
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class QueueWorker extends Plugin {
|
||||
|
||||
/**
|
||||
* The plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The human-readable title of the plugin.
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*/
|
||||
public $title;
|
||||
|
||||
/**
|
||||
* An associative array containing the optional key:
|
||||
* - time: (optional) How much time Drupal cron should spend on calling
|
||||
* this worker in seconds. Defaults to 15.
|
||||
*
|
||||
* @var array (optional)
|
||||
*/
|
||||
public $cron;
|
||||
|
||||
}
|
99
core/lib/Drupal/Core/Annotation/Translation.php
Normal file
99
core/lib/Drupal/Core/Annotation/Translation.php
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Annotation\Translation.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\AnnotationBase;
|
||||
use Drupal\Core\StringTranslation\TranslationWrapper;
|
||||
|
||||
/**
|
||||
* @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\TranslationWrapper
|
||||
*/
|
||||
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 = isset($values['arguments']) ? $values['arguments'] : array();
|
||||
$options = array();
|
||||
if (!empty($values['context'])) {
|
||||
$options = array(
|
||||
'context' => $values['context'],
|
||||
);
|
||||
}
|
||||
$this->translation = new TranslationWrapper($string, $arguments, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get() {
|
||||
return $this->translation;
|
||||
}
|
||||
|
||||
}
|
42
core/lib/Drupal/Core/AppRootFactory.php
Normal file
42
core/lib/Drupal/Core/AppRootFactory.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\AppRootFactory.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core;
|
||||
|
||||
/**
|
||||
* Gets the app root from the kernel.
|
||||
*/
|
||||
class AppRootFactory {
|
||||
|
||||
/**
|
||||
* The Drupal kernel.
|
||||
*
|
||||
* @var \Drupal\Core\DrupalKernelInterface
|
||||
*/
|
||||
protected $drupalKernel;
|
||||
|
||||
/**
|
||||
* Constructs an AppRootFactory instance.
|
||||
*
|
||||
* @param \Drupal\Core\DrupalKernelInterface $drupal_kernel
|
||||
* The Drupal kernel.
|
||||
*/
|
||||
public function __construct(DrupalKernelInterface $drupal_kernel) {
|
||||
$this->drupalKernel = $drupal_kernel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the app root.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get() {
|
||||
return $this->drupalKernel->getAppRoot();
|
||||
}
|
||||
|
||||
}
|
||||
|
60
core/lib/Drupal/Core/Archiver/Annotation/Archiver.php
Normal file
60
core/lib/Drupal/Core/Archiver/Annotation/Archiver.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Archiver\Annotation\Archiver.
|
||||
*/
|
||||
|
||||
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.
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*/
|
||||
public $title;
|
||||
|
||||
/**
|
||||
* The description of the archiver plugin.
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*/
|
||||
public $description;
|
||||
|
||||
/**
|
||||
* An array of valid extensions for this archiver.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $extensions;
|
||||
|
||||
}
|
1894
core/lib/Drupal/Core/Archiver/ArchiveTar.php
Normal file
1894
core/lib/Drupal/Core/Archiver/ArchiveTar.php
Normal file
File diff suppressed because it is too large
Load diff
14
core/lib/Drupal/Core/Archiver/ArchiverException.php
Normal file
14
core/lib/Drupal/Core/Archiver/ArchiverException.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Archiver\ArchiverException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Archiver;
|
||||
|
||||
/**
|
||||
* Defines an exception class for Drupal\Core\Archiver\ArchiverInterface.
|
||||
*/
|
||||
class ArchiverException extends \Exception {}
|
||||
|
64
core/lib/Drupal/Core/Archiver/ArchiverInterface.php
Normal file
64
core/lib/Drupal/Core/Archiver/ArchiverInterface.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Archiver\ArchiverInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Archiver;
|
||||
|
||||
/**
|
||||
* Defines the common interface for all Archiver classes.
|
||||
*
|
||||
* @see \Drupal\Core\Archiver\ArchiverManager
|
||||
* @see \Drupal\Core\Archiver\Annotation\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 \Drupal\Core\Archiver\ArchiverInterface
|
||||
* 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 \Drupal\Core\Archiver\ArchiverInterface
|
||||
* 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 \Drupal\Core\Archiver\ArchiverInterface
|
||||
* The called object.
|
||||
*/
|
||||
public function extract($path, array $files = array());
|
||||
|
||||
/**
|
||||
* Lists all files in the archive.
|
||||
*
|
||||
* @return array
|
||||
* An array of file names relative to the root of the archive.
|
||||
*/
|
||||
public function listContents();
|
||||
}
|
69
core/lib/Drupal/Core/Archiver/ArchiverManager.php
Normal file
69
core/lib/Drupal/Core/Archiver/ArchiverManager.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Archiver\ArchiverManager.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Archiver;
|
||||
|
||||
use Drupal\Component\Plugin\Factory\DefaultFactory;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
|
||||
/**
|
||||
* Provides an Archiver plugin manager.
|
||||
*
|
||||
* @see \Drupal\Core\Archiver\Annotation\Archiver
|
||||
* @see \Drupal\Core\Archiver\ArchiverInterface
|
||||
* @see plugin_api
|
||||
*/
|
||||
class ArchiverManager extends DefaultPluginManager {
|
||||
|
||||
/**
|
||||
* Constructs a 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.
|
||||
*/
|
||||
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
|
||||
parent::__construct('Plugin/Archiver', $namespaces, $module_handler, 'Drupal\Core\Archiver\ArchiverInterface', 'Drupal\Core\Archiver\Annotation\Archiver');
|
||||
$this->alterInfo('archiver_info');
|
||||
$this->setCacheBackend($cache_backend, 'archiver_info_plugins');
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\Component\Plugin\PluginManagerBase::createInstance().
|
||||
*/
|
||||
public function createInstance($plugin_id, array $configuration = array()) {
|
||||
$plugin_definition = $this->getDefinition($plugin_id);
|
||||
$plugin_class = DefaultFactory::getPluginClass($plugin_id, $plugin_definition, 'Drupal\Core\Archiver\ArchiverInterface');
|
||||
return new $plugin_class($configuration['filepath']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\Core\PluginManagerInterface::getInstance().
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
95
core/lib/Drupal/Core/Archiver/Tar.php
Normal file
95
core/lib/Drupal/Core/Archiver/Tar.php
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Archiver\Tar.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Archiver;
|
||||
|
||||
/**
|
||||
* Defines a 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.
|
||||
*
|
||||
* @throws \Drupal\Core\Archiver\ArchiverException
|
||||
*/
|
||||
public function __construct($file_path) {
|
||||
$this->tar = new ArchiveTar($file_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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 = array()) {
|
||||
if ($files) {
|
||||
$this->tar->extractList($files, $path);
|
||||
}
|
||||
else {
|
||||
$this->tar->extract($path);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function listContents() {
|
||||
$files = array();
|
||||
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
|
||||
* Archive_Tar object for implementation-specific logic. This is for advanced
|
||||
* use only as it is not shared by other implementations of ArchiveInterface.
|
||||
*
|
||||
* @return Archive_Tar
|
||||
* The Archive_Tar object used by this object.
|
||||
*/
|
||||
public function getArchive() {
|
||||
return $this->tar;
|
||||
}
|
||||
}
|
97
core/lib/Drupal/Core/Archiver/Zip.php
Normal file
97
core/lib/Drupal/Core/Archiver/Zip.php
Normal file
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Archiver\Zip.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Archiver;
|
||||
|
||||
/**
|
||||
* Defines a 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.
|
||||
*
|
||||
* @throws \Drupal\Core\Archiver\ArchiverException
|
||||
*/
|
||||
public function __construct($file_path) {
|
||||
$this->zip = new \ZipArchive();
|
||||
if ($this->zip->open($file_path) !== TRUE) {
|
||||
throw new ArchiverException(t('Cannot open %file_path', array('%file_path' => $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 = array()) {
|
||||
if ($files) {
|
||||
$this->zip->extractTo($path, $files);
|
||||
}
|
||||
else {
|
||||
$this->zip->extractTo($path);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function listContents() {
|
||||
$files = array();
|
||||
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,25 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\AssetCollectionGrouperInterface.
|
||||
*/
|
||||
|
||||
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);
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\AssetCollectionOptimizerInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
/**
|
||||
* Interface defining a service that optimizes a collection of assets.
|
||||
*/
|
||||
interface AssetCollectionOptimizerInterface {
|
||||
|
||||
/**
|
||||
* Optimizes a collection of assets.
|
||||
*
|
||||
* @param array $assets
|
||||
* An asset collection.
|
||||
*
|
||||
* @return array
|
||||
* An optimized asset collection.
|
||||
*/
|
||||
public function optimize(array $assets);
|
||||
|
||||
/**
|
||||
* Returns all optimized asset collections assets.
|
||||
*
|
||||
* @return string[]
|
||||
* URIs for all optimized asset collection assets.
|
||||
*/
|
||||
public function getAll();
|
||||
|
||||
/**
|
||||
* Deletes all optimized asset collections assets.
|
||||
*/
|
||||
public function deleteAll();
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\AssetCollectionRendererInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
/**
|
||||
* Interface defining a service that generates a render array to render assets.
|
||||
*/
|
||||
interface AssetCollectionRendererInterface {
|
||||
|
||||
/**
|
||||
* Renders an asset collection.
|
||||
*
|
||||
* @param array $assets
|
||||
* An asset collection.
|
||||
*
|
||||
* @return array
|
||||
* A render array to render the asset collection.
|
||||
*/
|
||||
public function render(array $assets);
|
||||
|
||||
}
|
53
core/lib/Drupal/Core/Asset/AssetDumper.php
Normal file
53
core/lib/Drupal/Core/Asset/AssetDumper.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\AssetDumper.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
use Drupal\Core\Asset\AssetDumperInterface;
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
|
||||
/**
|
||||
* Dumps a CSS or JavaScript asset.
|
||||
*/
|
||||
class AssetDumper implements AssetDumperInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* The file name for the CSS or JS cache file is generated from the hash of
|
||||
* the aggregated contents of the files in $data. This forces proxies and
|
||||
* browsers to download new CSS when the CSS changes.
|
||||
*/
|
||||
public function dump($data, $file_extension) {
|
||||
// Prefix filename to prevent blocking by firewalls which reject files
|
||||
// starting with "ad*".
|
||||
$filename = $file_extension. '_' . Crypt::hashBase64($data) . '.' . $file_extension;
|
||||
// Create the css/ or js/ path within the files folder.
|
||||
$path = 'public://' . $file_extension;
|
||||
$uri = $path . '/' . $filename;
|
||||
// Create the CSS or JS file.
|
||||
file_prepare_directory($path, FILE_CREATE_DIRECTORY);
|
||||
if (!file_exists($uri) && !file_unmanaged_save_data($data, $uri, FILE_EXISTS_REPLACE)) {
|
||||
return FALSE;
|
||||
}
|
||||
// If CSS/JS gzip compression is enabled and the zlib extension is available
|
||||
// then create a gzipped version of this file. This file is served
|
||||
// conditionally to browsers that accept gzip using .htaccess rules.
|
||||
// It's possible that the rewrite rules in .htaccess aren't working on this
|
||||
// server, but there's no harm (other than the time spent generating the
|
||||
// file) in generating the file anyway. Sites on servers where rewrite rules
|
||||
// aren't working can set css.gzip to FALSE in order to skip
|
||||
// generating a file that won't be used.
|
||||
if (extension_loaded('zlib') && \Drupal::config('system.performance')->get($file_extension . '.gzip')) {
|
||||
if (!file_exists($uri . '.gz') && !file_unmanaged_save_data(gzencode($data, 9, FORCE_GZIP), $uri . '.gz', FILE_EXISTS_REPLACE)) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return $uri;
|
||||
}
|
||||
|
||||
}
|
27
core/lib/Drupal/Core/Asset/AssetDumperInterface.php
Normal file
27
core/lib/Drupal/Core/Asset/AssetDumperInterface.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\AssetDumperInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
/**
|
||||
* Interface defining a service that dumps an (optimized) asset.
|
||||
*/
|
||||
interface AssetDumperInterface {
|
||||
|
||||
/**
|
||||
* Dumps an (optimized) asset to persistent storage.
|
||||
*
|
||||
* @param string $data
|
||||
* An (optimized) asset's contents.
|
||||
* @param string $file_extension
|
||||
* The file extension of this asset.
|
||||
*
|
||||
* @return string
|
||||
* An URI to access the dumped asset.
|
||||
*/
|
||||
public function dump($data, $file_extension);
|
||||
|
||||
}
|
36
core/lib/Drupal/Core/Asset/AssetOptimizerInterface.php
Normal file
36
core/lib/Drupal/Core/Asset/AssetOptimizerInterface.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\AssetOptimizerInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
/**
|
||||
* Interface defining a service that optimizes an asset.
|
||||
*/
|
||||
interface AssetOptimizerInterface {
|
||||
|
||||
/**
|
||||
* Optimizes an asset.
|
||||
*
|
||||
* @param array $asset
|
||||
* An asset.
|
||||
*
|
||||
* @return string
|
||||
* The optimized asset's contents.
|
||||
*/
|
||||
public function optimize(array $asset);
|
||||
|
||||
/**
|
||||
* Removes unwanted content from an asset.
|
||||
*
|
||||
* @param string $content
|
||||
* The content of an asset.
|
||||
*
|
||||
* @return string
|
||||
* The cleaned asset's contents.
|
||||
*/
|
||||
public function clean($content);
|
||||
|
||||
}
|
409
core/lib/Drupal/Core/Asset/AssetResolver.php
Normal file
409
core/lib/Drupal/Core/Asset/AssetResolver.php
Normal file
|
@ -0,0 +1,409 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\AssetResolver.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Theme\ThemeManagerInterface;
|
||||
|
||||
/**
|
||||
* The default asset resolver.
|
||||
*/
|
||||
class AssetResolver implements AssetResolverInterface {
|
||||
|
||||
/**
|
||||
* The library discovery service.
|
||||
*
|
||||
* @var \Drupal\Core\Asset\LibraryDiscoveryInterface
|
||||
*/
|
||||
protected $libraryDiscovery;
|
||||
|
||||
/**
|
||||
* The library dependency resolver.
|
||||
*
|
||||
* @var \Drupal\Core\Asset\LibraryDependencyResolverInterface
|
||||
*/
|
||||
protected $libraryDependencyResolver;
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The theme manager.
|
||||
*
|
||||
* @var \Drupal\Core\Theme\ThemeManagerInterface
|
||||
*/
|
||||
protected $themeManager;
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* The cache backend.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheBackendInterface
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* Constructs a new AssetResolver instance.
|
||||
*
|
||||
* @param \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery
|
||||
* The library discovery service.
|
||||
* @param \Drupal\Core\Asset\LibraryDependencyResolverInterface $library_dependency_resolver
|
||||
* The library dependency resolver.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
|
||||
* The theme manager.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
|
||||
* The cache backend.
|
||||
*/
|
||||
public function __construct(LibraryDiscoveryInterface $library_discovery, LibraryDependencyResolverInterface $library_dependency_resolver, ModuleHandlerInterface $module_handler, ThemeManagerInterface $theme_manager, LanguageManagerInterface $language_manager, CacheBackendInterface $cache) {
|
||||
$this->libraryDiscovery = $library_discovery;
|
||||
$this->libraryDependencyResolver = $library_dependency_resolver;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->themeManager = $theme_manager;
|
||||
$this->languageManager = $language_manager;
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the libraries that need to be loaded.
|
||||
*
|
||||
* For example, with core/a depending on core/c and core/b on core/d:
|
||||
* @code
|
||||
* $assets = new AttachedAssets();
|
||||
* $assets->setLibraries(['core/a', 'core/b', 'core/c']);
|
||||
* $assets->setAlreadyLoadedLibraries(['core/c']);
|
||||
* $resolver->getLibrariesToLoad($assets) === ['core/a', 'core/b', 'core/d']
|
||||
* @endcode
|
||||
*
|
||||
* @param \Drupal\Core\Asset\AttachedAssetsInterface $assets
|
||||
* The assets attached to the current response.
|
||||
*
|
||||
* @return string[]
|
||||
* A list of libraries and their dependencies, in the order they should be
|
||||
* loaded, excluding any libraries that have already been loaded.
|
||||
*/
|
||||
protected function getLibrariesToLoad(AttachedAssetsInterface $assets) {
|
||||
return array_diff(
|
||||
$this->libraryDependencyResolver->getLibrariesWithDependencies($assets->getLibraries()),
|
||||
$this->libraryDependencyResolver->getLibrariesWithDependencies($assets->getAlreadyLoadedLibraries())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCssAssets(AttachedAssetsInterface $assets, $optimize) {
|
||||
$theme_info = $this->themeManager->getActiveTheme();
|
||||
// Add the theme name to the cache key since themes may implement
|
||||
// hook_css_alter().
|
||||
$cid = 'css:' . $theme_info->getName() . ':' . Crypt::hashBase64(serialize($assets)) . (int) $optimize;
|
||||
if ($cached = $this->cache->get($cid)) {
|
||||
return $cached->data;
|
||||
}
|
||||
|
||||
$css = [];
|
||||
$default_options = [
|
||||
'type' => 'file',
|
||||
'group' => CSS_AGGREGATE_DEFAULT,
|
||||
'weight' => 0,
|
||||
'every_page' => FALSE,
|
||||
'media' => 'all',
|
||||
'preprocess' => TRUE,
|
||||
'browsers' => [],
|
||||
];
|
||||
|
||||
foreach ($this->getLibrariesToLoad($assets) as $library) {
|
||||
list($extension, $name) = explode('/', $library, 2);
|
||||
$definition = $this->libraryDiscovery->getLibraryByName($extension, $name);
|
||||
if (isset($definition['css'])) {
|
||||
foreach ($definition['css'] as $options) {
|
||||
$options += $default_options;
|
||||
$options['browsers'] += [
|
||||
'IE' => TRUE,
|
||||
'!IE' => TRUE,
|
||||
];
|
||||
|
||||
// Files with a query string cannot be preprocessed.
|
||||
if ($options['type'] === 'file' && $options['preprocess'] && strpos($options['data'], '?') !== FALSE) {
|
||||
$options['preprocess'] = FALSE;
|
||||
}
|
||||
|
||||
// Always add a tiny value to the weight, to conserve the insertion
|
||||
// order.
|
||||
$options['weight'] += count($css) / 1000;
|
||||
|
||||
// CSS files are being keyed by the full path.
|
||||
$css[$options['data']] = $options;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allow modules and themes to alter the CSS assets.
|
||||
$this->moduleHandler->alter('css', $css, $assets);
|
||||
$this->themeManager->alter('css', $css, $assets);
|
||||
|
||||
// Sort CSS items, so that they appear in the correct order.
|
||||
uasort($css, 'static::sort');
|
||||
|
||||
// Allow themes to remove CSS files by CSS files full path and file name.
|
||||
if ($stylesheet_remove = $theme_info->getStyleSheetsRemove()) {
|
||||
foreach ($css as $key => $options) {
|
||||
if (isset($stylesheet_remove[$key])) {
|
||||
unset($css[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($optimize) {
|
||||
$css = \Drupal::service('asset.css.collection_optimizer')->optimize($css);
|
||||
}
|
||||
$this->cache->set($cid, $css, CacheBackendInterface::CACHE_PERMANENT, ['library_info']);
|
||||
|
||||
return $css;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JavaScript settings assets for this response's libraries.
|
||||
*
|
||||
* Gathers all drupalSettings from all libraries in the attached assets
|
||||
* collection and merges them, then it merges individual attached settings,
|
||||
* and finally invokes hook_js_settings_alter() to allow alterations of
|
||||
* JavaScript settings by modules and themes.
|
||||
*
|
||||
* @param \Drupal\Core\Asset\AttachedAssetsInterface $assets
|
||||
* The assets attached to the current response.
|
||||
* @return array
|
||||
* A (possibly optimized) collection of JavaScript assets.
|
||||
*/
|
||||
protected function getJsSettingsAssets(AttachedAssetsInterface $assets) {
|
||||
$settings = [];
|
||||
|
||||
foreach ($this->getLibrariesToLoad($assets) as $library) {
|
||||
list($extension, $name) = explode('/', $library, 2);
|
||||
$definition = $this->libraryDiscovery->getLibraryByName($extension, $name);
|
||||
if (isset($definition['drupalSettings'])) {
|
||||
$settings = NestedArray::mergeDeepArray([$settings, $definition['drupalSettings']], TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
// Attached settings win over settings in libraries.
|
||||
$settings = NestedArray::mergeDeepArray([$settings, $assets->getSettings()], TRUE);
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getJsAssets(AttachedAssetsInterface $assets, $optimize) {
|
||||
$theme_info = $this->themeManager->getActiveTheme();
|
||||
// Add the theme name to the cache key since themes may implement
|
||||
// hook_js_alter(). Additionally add the current language to support
|
||||
// translation of JavaScript files.
|
||||
$cid = 'js:' . $theme_info->getName() . ':' . $this->languageManager->getCurrentLanguage()->getId() . ':' . Crypt::hashBase64(serialize($assets));
|
||||
|
||||
if ($cached = $this->cache->get($cid)) {
|
||||
list($js_assets_header, $js_assets_footer, $settings, $settings_in_header) = $cached->data;
|
||||
}
|
||||
else {
|
||||
$javascript = [];
|
||||
$default_options = [
|
||||
'type' => 'file',
|
||||
'group' => JS_DEFAULT,
|
||||
'every_page' => FALSE,
|
||||
'weight' => 0,
|
||||
'cache' => TRUE,
|
||||
'preprocess' => TRUE,
|
||||
'attributes' => [],
|
||||
'version' => NULL,
|
||||
'browsers' => [],
|
||||
];
|
||||
|
||||
$libraries_to_load = $this->getLibrariesToLoad($assets);
|
||||
|
||||
// Collect all libraries that contain JS assets and are in the header.
|
||||
$header_js_libraries = [];
|
||||
foreach ($libraries_to_load as $library) {
|
||||
list($extension, $name) = explode('/', $library, 2);
|
||||
$definition = $this->libraryDiscovery->getLibraryByName($extension, $name);
|
||||
if (isset($definition['js']) && !empty($definition['header'])) {
|
||||
$header_js_libraries[] = $library;
|
||||
}
|
||||
}
|
||||
// The current list of header JS libraries are only those libraries that
|
||||
// are in the header, but their dependencies must also be loaded for them
|
||||
// to function correctly, so update the list with those.
|
||||
$header_js_libraries = $this->libraryDependencyResolver->getLibrariesWithDependencies($header_js_libraries);
|
||||
|
||||
foreach ($libraries_to_load as $library) {
|
||||
list($extension, $name) = explode('/', $library, 2);
|
||||
$definition = $this->libraryDiscovery->getLibraryByName($extension, $name);
|
||||
if (isset($definition['js'])) {
|
||||
foreach ($definition['js'] as $options) {
|
||||
$options += $default_options;
|
||||
|
||||
// 'scope' is a calculated option, based on which libraries are
|
||||
// marked to be loaded from the header (see above).
|
||||
$options['scope'] = in_array($library, $header_js_libraries) ? 'header' : 'footer';
|
||||
|
||||
// Preprocess can only be set if caching is enabled and no
|
||||
// attributes are set.
|
||||
$options['preprocess'] = $options['cache'] && empty($options['attributes']) ? $options['preprocess'] : FALSE;
|
||||
|
||||
// Always add a tiny value to the weight, to conserve the insertion
|
||||
// order.
|
||||
$options['weight'] += count($javascript) / 1000;
|
||||
|
||||
// Local and external files must keep their name as the associative
|
||||
// key so the same JavaScript file is not added twice.
|
||||
$javascript[$options['data']] = $options;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allow modules and themes to alter the JavaScript assets.
|
||||
$this->moduleHandler->alter('js', $javascript, $assets);
|
||||
$this->themeManager->alter('js', $javascript, $assets);
|
||||
|
||||
// Sort JavaScript assets, so that they appear in the correct order.
|
||||
uasort($javascript, 'static::sort');
|
||||
|
||||
// Prepare the return value: filter JavaScript assets per scope.
|
||||
$js_assets_header = [];
|
||||
$js_assets_footer = [];
|
||||
foreach ($javascript as $key => $item) {
|
||||
if ($item['scope'] == 'header') {
|
||||
$js_assets_header[$key] = $item;
|
||||
}
|
||||
elseif ($item['scope'] == 'footer') {
|
||||
$js_assets_footer[$key] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
if ($optimize) {
|
||||
$collection_optimizer = \Drupal::service('asset.js.collection_optimizer');
|
||||
$js_assets_header = $collection_optimizer->optimize($js_assets_header);
|
||||
$js_assets_footer = $collection_optimizer->optimize($js_assets_footer);
|
||||
}
|
||||
|
||||
// If the core/drupalSettings library is being loaded or is already
|
||||
// loaded, get the JavaScript settings assets, and convert them into a
|
||||
// single "regular" JavaScript asset.
|
||||
$libraries_to_load = $this->getLibrariesToLoad($assets);
|
||||
$settings_required = in_array('core/drupalSettings', $libraries_to_load) || in_array('core/drupalSettings', $this->libraryDependencyResolver->getLibrariesWithDependencies($assets->getAlreadyLoadedLibraries()));
|
||||
$settings_have_changed = count($libraries_to_load) > 0 || count($assets->getSettings()) > 0;
|
||||
|
||||
// Initialize settings to FALSE since they are not needed by default. This
|
||||
// distinguishes between an empty array which must still allow
|
||||
// hook_js_settings_alter() to be run.
|
||||
$settings = FALSE;
|
||||
if ($settings_required && $settings_have_changed) {
|
||||
$settings = $this->getJsSettingsAssets($assets);
|
||||
// Allow modules to add cached JavaScript settings.
|
||||
foreach ($this->moduleHandler->getImplementations('js_settings_build') as $module) {
|
||||
$function = $module . '_' . 'js_settings_build';
|
||||
$function($settings, $assets);
|
||||
}
|
||||
}
|
||||
$settings_in_header = in_array('core/drupalSettings', $header_js_libraries);
|
||||
$this->cache->set($cid, [$js_assets_header, $js_assets_footer, $settings, $settings_in_header], CacheBackendInterface::CACHE_PERMANENT, ['library_info']);
|
||||
}
|
||||
|
||||
|
||||
if ($settings !== FALSE) {
|
||||
// Allow modules and themes to alter the JavaScript settings.
|
||||
$this->moduleHandler->alter('js_settings', $settings, $assets);
|
||||
$this->themeManager->alter('js_settings', $settings, $assets);
|
||||
$settings_as_inline_javascript = [
|
||||
'type' => 'setting',
|
||||
'group' => JS_SETTING,
|
||||
'every_page' => TRUE,
|
||||
'weight' => 0,
|
||||
'browsers' => [],
|
||||
'data' => $settings,
|
||||
];
|
||||
$settings_js_asset = ['drupalSettings' => $settings_as_inline_javascript];
|
||||
// Prepend to the list of JS assets, to render it first. Preferably in
|
||||
// the footer, but in the header if necessary.
|
||||
if ($settings_in_header) {
|
||||
$js_assets_header = $settings_js_asset + $js_assets_header;
|
||||
}
|
||||
else {
|
||||
$js_assets_footer = $settings_js_asset + $js_assets_footer;
|
||||
}
|
||||
}
|
||||
return [
|
||||
$js_assets_header,
|
||||
$js_assets_footer,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts CSS and JavaScript resources.
|
||||
*
|
||||
* This sort order helps optimize front-end performance while providing
|
||||
* modules and themes with the necessary control for ordering the CSS and
|
||||
* JavaScript appearing on a page.
|
||||
*
|
||||
* @param $a
|
||||
* First item for comparison. The compared items should be associative
|
||||
* arrays of member items.
|
||||
* @param $b
|
||||
* Second item for comparison.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function sort($a, $b) {
|
||||
// First order by group, so that all items in the CSS_AGGREGATE_DEFAULT
|
||||
// group appear before items in the CSS_AGGREGATE_THEME group. Modules may
|
||||
// create additional groups by defining their own constants.
|
||||
if ($a['group'] < $b['group']) {
|
||||
return -1;
|
||||
}
|
||||
elseif ($a['group'] > $b['group']) {
|
||||
return 1;
|
||||
}
|
||||
// Within a group, order all infrequently needed, page-specific files after
|
||||
// common files needed throughout the website. Separating this way allows
|
||||
// for the aggregate file generated for all of the common files to be reused
|
||||
// across a site visit without being cut by a page using a less common file.
|
||||
elseif ($a['every_page'] && !$b['every_page']) {
|
||||
return -1;
|
||||
}
|
||||
elseif (!$a['every_page'] && $b['every_page']) {
|
||||
return 1;
|
||||
}
|
||||
// Finally, order by weight.
|
||||
elseif ($a['weight'] < $b['weight']) {
|
||||
return -1;
|
||||
}
|
||||
elseif ($a['weight'] > $b['weight']) {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
85
core/lib/Drupal/Core/Asset/AssetResolverInterface.php
Normal file
85
core/lib/Drupal/Core/Asset/AssetResolverInterface.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\AssetResolverInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
/**
|
||||
* Resolves asset libraries into concrete CSS and JavaScript assets.
|
||||
*
|
||||
* Given an attached assets collection (to be loaded for the current response),
|
||||
* the asset resolver can resolve those asset libraries into a list of concrete
|
||||
* CSS and JavaScript assets.
|
||||
*
|
||||
* In other words: this allows developers to translate Drupal's asset
|
||||
* abstraction (asset libraries) into concrete assets.
|
||||
*
|
||||
* @see \Drupal\Core\Asset\AttachedAssetsInterface
|
||||
* @see \Drupal\Core\Asset\LibraryDependencyResolverInterface
|
||||
*/
|
||||
interface AssetResolverInterface {
|
||||
|
||||
/**
|
||||
* Returns the CSS assets for the current response's libraries.
|
||||
*
|
||||
* It returns the CSS assets in order, according to the SMACSS categories
|
||||
* specified in the assets' weights:
|
||||
* - CSS_BASE
|
||||
* - CSS_LAYOUT
|
||||
* - CSS_COMPONENT
|
||||
* - CSS_STATE
|
||||
* - CSS_THEME
|
||||
* @see https://www.drupal.org/node/1887918#separate-concerns
|
||||
* This ensures proper cascading of styles so themes can easily override
|
||||
* module styles through CSS selectors.
|
||||
*
|
||||
* Themes may replace module-defined CSS files by adding a stylesheet with the
|
||||
* same filename. For example, themes/bartik/system-menus.css would replace
|
||||
* modules/system/system-menus.css. This allows themes to override complete
|
||||
* CSS files, rather than specific selectors, when necessary.
|
||||
*
|
||||
* Also invokes hook_css_alter(), to allow CSS assets to be altered.
|
||||
*
|
||||
* @param \Drupal\Core\Asset\AttachedAssetsInterface $assets
|
||||
* The assets attached to the current response.
|
||||
* @param bool $optimize
|
||||
* Whether to apply the CSS asset collection optimizer, to return an
|
||||
* optimized CSS asset collection rather than an unoptimized one.
|
||||
*
|
||||
* @return array
|
||||
* A (possibly optimized) collection of CSS assets.
|
||||
*/
|
||||
public function getCssAssets(AttachedAssetsInterface $assets, $optimize);
|
||||
|
||||
/**
|
||||
* Returns the JavaScript assets for the current response's libraries.
|
||||
*
|
||||
* References to JavaScript files are placed in a certain order: first, all
|
||||
* 'core' files, then all 'module' and finally all 'theme' JavaScript files
|
||||
* are added to the page. Then, all settings are output, followed by 'inline'
|
||||
* JavaScript code. If running update.php, all preprocessing is disabled.
|
||||
*
|
||||
* Note that hook_js_alter(&$javascript) is called during this function call
|
||||
* to allow alterations of the JavaScript during its presentation. The correct
|
||||
* way to add JavaScript during hook_js_alter() is to add another element to
|
||||
* the $javascript array, deriving from drupal_js_defaults(). See
|
||||
* locale_js_alter() for an example of this.
|
||||
*
|
||||
* @param \Drupal\Core\Asset\AttachedAssetsInterface $assets
|
||||
* The assets attached to the current response.
|
||||
* @param bool $optimize
|
||||
* Whether to apply the JavaScript asset collection optimizer, to return
|
||||
* optimized JavaScript asset collections rather than an unoptimized ones.
|
||||
*
|
||||
* @return array
|
||||
* A nested array containing 2 values:
|
||||
* - at index zero: the (possibly optimized) collection of JavaScript assets
|
||||
* for the top of the page
|
||||
* - at index one: the (possibly optimized) collection of JavaScript assets
|
||||
* for the bottom of the page
|
||||
*/
|
||||
public function getJsAssets(AttachedAssetsInterface $assets, $optimize);
|
||||
|
||||
}
|
98
core/lib/Drupal/Core/Asset/AttachedAssets.php
Normal file
98
core/lib/Drupal/Core/Asset/AttachedAssets.php
Normal file
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\AttachedAssets.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
/**
|
||||
* The default attached assets collection.
|
||||
*/
|
||||
class AttachedAssets implements AttachedAssetsInterface {
|
||||
|
||||
/**
|
||||
* The (ordered) list of asset libraries attached to the current response.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public $libraries = [];
|
||||
|
||||
/**
|
||||
* The JavaScript settings attached to the current response.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $settings = [];
|
||||
|
||||
/**
|
||||
* The set of asset libraries that the client has already loaded.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $alreadyLoadedLibraries = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createFromRenderArray(array $render_array) {
|
||||
if (!isset($render_array['#attached'])) {
|
||||
throw new \LogicException('The render array has not yet been rendered, hence not all attachments have been collected yet.');
|
||||
}
|
||||
|
||||
$assets = new static();
|
||||
if (isset($render_array['#attached']['library'])) {
|
||||
$assets->setLibraries($render_array['#attached']['library']);
|
||||
}
|
||||
if (isset($render_array['#attached']['drupalSettings'])) {
|
||||
$assets->setSettings($render_array['#attached']['drupalSettings']);
|
||||
}
|
||||
return $assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setLibraries(array $libraries) {
|
||||
$this->libraries = array_unique($libraries);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLibraries() {
|
||||
return $this->libraries;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setSettings(array $settings) {
|
||||
$this->settings = $settings;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSettings() {
|
||||
return $this->settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAlreadyLoadedLibraries() {
|
||||
return $this->alreadyLoadedLibraries;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setAlreadyLoadedLibraries(array $libraries) {
|
||||
$this->alreadyLoadedLibraries = $libraries;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
85
core/lib/Drupal/Core/Asset/AttachedAssetsInterface.php
Normal file
85
core/lib/Drupal/Core/Asset/AttachedAssetsInterface.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\AttachedAssetsInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
/**
|
||||
* The attached assets collection for the current response.
|
||||
*
|
||||
* Allows for storage of:
|
||||
* - an ordered list of asset libraries (to be loaded for the current response)
|
||||
* - attached JavaScript settings (to be loaded for the current response)
|
||||
* - a set of asset libraries that the client already has loaded (as indicated
|
||||
* in the request, to *not* be loaded for the current response)
|
||||
*
|
||||
* @see \Drupal\Core\Asset\AssetResolverInterface
|
||||
*/
|
||||
interface AttachedAssetsInterface {
|
||||
|
||||
/**
|
||||
* Creates an AttachedAssetsInterface object from a render array.
|
||||
*
|
||||
* @param array $render_array
|
||||
* A render array.
|
||||
*
|
||||
* @return \Drupal\Core\Asset\AttachedAssetsInterface
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public static function createFromRenderArray(array $render_array);
|
||||
|
||||
/**
|
||||
* Sets the asset libraries attached to the current response.
|
||||
*
|
||||
* @param string[] $libraries
|
||||
* A list of libraries, in the order they should be loaded.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setLibraries(array $libraries);
|
||||
|
||||
/**
|
||||
* Returns the asset libraries attached to the current response.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getLibraries();
|
||||
|
||||
/**
|
||||
* Sets the JavaScript settings that are attached to the current response.
|
||||
*
|
||||
* @param array $settings
|
||||
* The needed JavaScript settings.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSettings(array $settings);
|
||||
|
||||
/**
|
||||
* Returns the settings attached to the current response.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSettings();
|
||||
|
||||
/**
|
||||
* Sets the asset libraries that the current request marked as already loaded.
|
||||
*
|
||||
* @param string[] $libraries
|
||||
* The set of already loaded libraries.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAlreadyLoadedLibraries(array $libraries);
|
||||
|
||||
/**
|
||||
* Returns the set of already loaded asset libraries.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAlreadyLoadedLibraries();
|
||||
|
||||
}
|
98
core/lib/Drupal/Core/Asset/CssCollectionGrouper.php
Normal file
98
core/lib/Drupal/Core/Asset/CssCollectionGrouper.php
Normal file
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\CssCollectionGrouper.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
use Drupal\Core\Asset\AssetCollectionGrouperInterface;
|
||||
|
||||
/**
|
||||
* Groups CSS assets.
|
||||
*/
|
||||
class CssCollectionGrouper implements AssetCollectionGrouperInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Puts multiple items into the same group if they are groupable and if they
|
||||
* are for the same 'media' and 'browsers'. Items of the 'file' type are
|
||||
* groupable if their 'preprocess' flag is TRUE, items of the 'inline' type
|
||||
* are always groupable, and items of the 'external' type are never groupable.
|
||||
*
|
||||
* Also ensures that the process of grouping items does not change their
|
||||
* relative order. This requirement may result in multiple groups for the same
|
||||
* type, media, and browsers, if needed to accommodate other items in between.
|
||||
*/
|
||||
public function group(array $css_assets) {
|
||||
$groups = array();
|
||||
// If a group can contain multiple items, we track the information that must
|
||||
// be the same for each item in the group, so that when we iterate the next
|
||||
// item, we can determine if it can be put into the current group, or if a
|
||||
// new group needs to be made for it.
|
||||
$current_group_keys = NULL;
|
||||
// When creating a new group, we pre-increment $i, so by initializing it to
|
||||
// -1, the first group will have index 0.
|
||||
$i = -1;
|
||||
foreach ($css_assets as $item) {
|
||||
// The browsers for which the CSS item needs to be loaded is part of the
|
||||
// information that determines when a new group is needed, but the order
|
||||
// of keys in the array doesn't matter, and we don't want a new group if
|
||||
// all that's different is that order.
|
||||
ksort($item['browsers']);
|
||||
|
||||
// If the item can be grouped with other items, set $group_keys to an
|
||||
// array of information that must be the same for all items in its group.
|
||||
// If the item can't be grouped with other items, set $group_keys to
|
||||
// FALSE. We put items into a group that can be aggregated together:
|
||||
// whether they will be aggregated is up to the _drupal_css_aggregate()
|
||||
// function or an
|
||||
// override of that function specified in hook_css_alter(), but regardless
|
||||
// of the details of that function, a group represents items that can be
|
||||
// aggregated. Since a group may be rendered with a single HTML tag, all
|
||||
// items in the group must share the same information that would need to
|
||||
// be part of that HTML tag.
|
||||
switch ($item['type']) {
|
||||
case 'file':
|
||||
// Group file items if their 'preprocess' flag is TRUE.
|
||||
// Help ensure maximum reuse of aggregate files by only grouping
|
||||
// together items that share the same 'group' value and 'every_page'
|
||||
// flag.
|
||||
$group_keys = $item['preprocess'] ? array($item['type'], $item['group'], $item['every_page'], $item['media'], $item['browsers']) : FALSE;
|
||||
break;
|
||||
|
||||
case 'inline':
|
||||
// Always group inline items.
|
||||
$group_keys = array($item['type'], $item['media'], $item['browsers']);
|
||||
break;
|
||||
|
||||
case 'external':
|
||||
// Do not group external items.
|
||||
$group_keys = FALSE;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the group keys don't match the most recent group we're working with,
|
||||
// then a new group must be made.
|
||||
if ($group_keys !== $current_group_keys) {
|
||||
$i++;
|
||||
// Initialize the new group with the same properties as the first item
|
||||
// being placed into it. The item's 'data', 'weight' and 'basename'
|
||||
// properties are unique to the item and should not be carried over to
|
||||
// the group.
|
||||
$groups[$i] = $item;
|
||||
unset($groups[$i]['data'], $groups[$i]['weight'], $groups[$i]['basename']);
|
||||
$groups[$i]['items'] = array();
|
||||
$current_group_keys = $group_keys ? $group_keys : NULL;
|
||||
}
|
||||
|
||||
// Add the item to the current group.
|
||||
$groups[$i]['items'][] = $item;
|
||||
}
|
||||
|
||||
return $groups;
|
||||
}
|
||||
|
||||
}
|
202
core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php
Normal file
202
core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php
Normal file
|
@ -0,0 +1,202 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\CssCollectionOptimizer.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
use Drupal\Core\State\StateInterface;
|
||||
|
||||
/**
|
||||
* Optimizes CSS assets.
|
||||
*/
|
||||
class CssCollectionOptimizer implements AssetCollectionOptimizerInterface {
|
||||
|
||||
/**
|
||||
* A CSS asset grouper.
|
||||
*
|
||||
* @var \Drupal\Core\Asset\CssCollectionGrouper
|
||||
*/
|
||||
protected $grouper;
|
||||
|
||||
/**
|
||||
* A CSS asset optimizer.
|
||||
*
|
||||
* @var \Drupal\Core\Asset\CssOptimizer
|
||||
*/
|
||||
protected $optimizer;
|
||||
|
||||
/**
|
||||
* An asset dumper.
|
||||
*
|
||||
* @var \Drupal\Core\Asset\AssetDumper
|
||||
*/
|
||||
protected $dumper;
|
||||
|
||||
/**
|
||||
* The state key/value store.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* Constructs a CssCollectionOptimizer.
|
||||
*
|
||||
* @param \Drupal\Core\Asset\AssetCollectionGrouperInterface
|
||||
* The grouper for CSS assets.
|
||||
* @param \Drupal\Core\Asset\AssetOptimizerInterface
|
||||
* The optimizer for a single CSS asset.
|
||||
* @param \Drupal\Core\Asset\AssetDumperInterface
|
||||
* The dumper for optimized CSS assets.
|
||||
* @param \Drupal\Core\State\StateInterface
|
||||
* The state key/value store.
|
||||
*/
|
||||
public function __construct(AssetCollectionGrouperInterface $grouper, AssetOptimizerInterface $optimizer, AssetDumperInterface $dumper, StateInterface $state) {
|
||||
$this->grouper = $grouper;
|
||||
$this->optimizer = $optimizer;
|
||||
$this->dumper = $dumper;
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* The cache file name is retrieved on a page load via a lookup variable that
|
||||
* contains an associative array. The array key is the hash of the file names
|
||||
* in $css while the value is the cache file name. The cache file is generated
|
||||
* in two cases. First, if there is no file name value for the key, which will
|
||||
* happen if a new file name has been added to $css or after the lookup
|
||||
* variable is emptied to force a rebuild of the cache. Second, the cache file
|
||||
* is generated if it is missing on disk. Old cache files are not deleted
|
||||
* immediately when the lookup variable is emptied, but are deleted after a
|
||||
* configurable period (@code system.performance.stale_file_threshold @endcode)
|
||||
* to ensure that files referenced by a cached page will still be available.
|
||||
*/
|
||||
public function optimize(array $css_assets) {
|
||||
// Group the assets.
|
||||
$css_groups = $this->grouper->group($css_assets);
|
||||
|
||||
// Now optimize (concatenate + minify) and dump each asset group, unless
|
||||
// that was already done, in which case it should appear in
|
||||
// drupal_css_cache_files.
|
||||
// Drupal contrib can override this default CSS aggregator to keep the same
|
||||
// grouping, optimizing and dumping, but change the strategy that is used to
|
||||
// determine when the aggregate should be rebuilt (e.g. mtime, HTTPS …).
|
||||
$map = $this->state->get('drupal_css_cache_files') ?: array();
|
||||
$css_assets = array();
|
||||
foreach ($css_groups as $order => $css_group) {
|
||||
// We have to return a single asset, not a group of assets. It is now up
|
||||
// to one of the pieces of code in the switch statement below to set the
|
||||
// 'data' property to the appropriate value.
|
||||
$css_assets[$order] = $css_group;
|
||||
unset($css_assets[$order]['items']);
|
||||
|
||||
switch ($css_group['type']) {
|
||||
case 'file':
|
||||
// No preprocessing, single CSS asset: just use the existing URI.
|
||||
if (!$css_group['preprocess']) {
|
||||
$uri = $css_group['items'][0]['data'];
|
||||
$css_assets[$order]['data'] = $uri;
|
||||
}
|
||||
// Preprocess (aggregate), unless the aggregate file already exists.
|
||||
else {
|
||||
$key = $this->generateHash($css_group);
|
||||
$uri = '';
|
||||
if (isset($map[$key])) {
|
||||
$uri = $map[$key];
|
||||
}
|
||||
if (empty($uri) || !file_exists($uri)) {
|
||||
// Optimize each asset within the group.
|
||||
$data = '';
|
||||
foreach ($css_group['items'] as $css_asset) {
|
||||
$data .= $this->optimizer->optimize($css_asset);
|
||||
}
|
||||
// Per the W3C specification at
|
||||
// http://www.w3.org/TR/REC-CSS2/cascade.html#at-import, @import
|
||||
// rules must proceed any other style, so we move those to the
|
||||
// top.
|
||||
$regexp = '/@import[^;]+;/i';
|
||||
preg_match_all($regexp, $data, $matches);
|
||||
$data = preg_replace($regexp, '', $data);
|
||||
$data = implode('', $matches[0]) . $data;
|
||||
// Dump the optimized CSS for this group into an aggregate file.
|
||||
$uri = $this->dumper->dump($data, 'css');
|
||||
// Set the URI for this group's aggregate file.
|
||||
$css_assets[$order]['data'] = $uri;
|
||||
// Persist the URI for this aggregate file.
|
||||
$map[$key] = $uri;
|
||||
$this->state->set('drupal_css_cache_files', $map);
|
||||
}
|
||||
else {
|
||||
// Use the persisted URI for the optimized CSS file.
|
||||
$css_assets[$order]['data'] = $uri;
|
||||
}
|
||||
$css_assets[$order]['preprocessed'] = TRUE;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'inline':
|
||||
// We don't do any caching for inline CSS assets.
|
||||
$data = '';
|
||||
foreach ($css_group['items'] as $css_asset) {
|
||||
$data .= $this->optimizer->optimize($css_asset);
|
||||
}
|
||||
unset($css_assets[$order]['data']['items']);
|
||||
$css_assets[$order]['data'] = $data;
|
||||
break;
|
||||
|
||||
case 'external':
|
||||
// We don't do any aggregation and hence also no caching for external
|
||||
// CSS assets.
|
||||
$uri = $css_group['items'][0]['data'];
|
||||
$css_assets[$order]['data'] = $uri;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $css_assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a hash for a given group of CSS assets.
|
||||
*
|
||||
* @param array $css_group
|
||||
* A group of CSS assets.
|
||||
*
|
||||
* @return string
|
||||
* A hash to uniquely identify the given group of CSS assets.
|
||||
*/
|
||||
protected function generateHash(array $css_group) {
|
||||
$css_data = array();
|
||||
foreach ($css_group['items'] as $css_file) {
|
||||
$css_data[] = $css_file['data'];
|
||||
}
|
||||
return hash('sha256', serialize($css_data));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAll() {
|
||||
return $this->state->get('drupal_css_cache_files');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteAll() {
|
||||
$this->state->delete('drupal_css_cache_files');
|
||||
|
||||
$delete_stale = function($uri) {
|
||||
// Default stale file threshold is 30 days.
|
||||
if (REQUEST_TIME - filemtime($uri) > \Drupal::config('system.performance')->get('stale_file_threshold')) {
|
||||
file_unmanaged_delete($uri);
|
||||
}
|
||||
};
|
||||
file_scan_directory('public://css', '/.*/', array('callback' => $delete_stale));
|
||||
}
|
||||
|
||||
}
|
223
core/lib/Drupal/Core/Asset/CssCollectionRenderer.php
Normal file
223
core/lib/Drupal/Core/Asset/CssCollectionRenderer.php
Normal file
|
@ -0,0 +1,223 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\CssCollectionRenderer.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\State\StateInterface;
|
||||
|
||||
/**
|
||||
* Renders CSS assets.
|
||||
*
|
||||
* For production websites, LINK tags are preferable to STYLE tags with @import
|
||||
* statements, because:
|
||||
* - They are the standard tag intended for linking to a resource.
|
||||
* - On Firefox 2 and perhaps other browsers, CSS files included with @import
|
||||
* statements don't get saved when saving the complete web page for offline
|
||||
* use: https://www.drupal.org/node/145218.
|
||||
* - On IE, if only LINK tags and no @import statements are used, all the CSS
|
||||
* files are downloaded in parallel, resulting in faster page load, but if
|
||||
* @import statements are used and span across multiple STYLE tags, all the
|
||||
* ones from one STYLE tag must be downloaded before downloading begins for
|
||||
* the next STYLE tag. Furthermore, IE7 does not support media declaration on
|
||||
* the @import statement, so multiple STYLE tags must be used when different
|
||||
* files are for different media types. Non-IE browsers always download in
|
||||
* parallel, so this is an IE-specific performance quirk:
|
||||
* http://www.stevesouders.com/blog/2009/04/09/dont-use-import/.
|
||||
*
|
||||
* However, IE has an annoying limit of 31 total CSS inclusion tags
|
||||
* (https://www.drupal.org/node/228818) and LINK tags are limited to one file
|
||||
* per tag, whereas STYLE tags can contain multiple @import statements allowing
|
||||
* multiple files to be loaded per tag. When CSS aggregation is disabled, a
|
||||
* Drupal site can easily have more than 31 CSS files that need to be loaded, so
|
||||
* using LINK tags exclusively would result in a site that would display
|
||||
* incorrectly in IE. Depending on different needs, different strategies can be
|
||||
* employed to decide when to use LINK tags and when to use STYLE tags.
|
||||
*
|
||||
* The strategy employed by this class is to use LINK tags for all aggregate
|
||||
* files and for all files that cannot be aggregated (e.g., if 'preprocess' is
|
||||
* set to FALSE or the type is 'external'), and to use STYLE tags for groups
|
||||
* of files that could be aggregated together but aren't (e.g., if the site-wide
|
||||
* aggregation setting is disabled). This results in all LINK tags when
|
||||
* aggregation is enabled, a guarantee that as many or only slightly more tags
|
||||
* are used with aggregation disabled than enabled (so that if the limit were to
|
||||
* be crossed with aggregation enabled, the site developer would also notice the
|
||||
* problem while aggregation is disabled), and an easy way for a developer to
|
||||
* view HTML source while aggregation is disabled and know what files will be
|
||||
* aggregated together when aggregation becomes enabled.
|
||||
*
|
||||
* This class evaluates the aggregation enabled/disabled condition on a group
|
||||
* by group basis by testing whether an aggregate file has been made for the
|
||||
* group rather than by testing the site-wide aggregation setting. This allows
|
||||
* this class to work correctly even if modules have implemented custom
|
||||
* logic for grouping and aggregating files.
|
||||
*/
|
||||
class CssCollectionRenderer implements AssetCollectionRendererInterface {
|
||||
|
||||
/**
|
||||
* The state key/value store.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* Constructs a CssCollectionRenderer.
|
||||
*
|
||||
* @param \Drupal\Core\State\StateInterface
|
||||
* The state key/value store.
|
||||
*/
|
||||
public function __construct(StateInterface $state) {
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render(array $css_assets) {
|
||||
$elements = array();
|
||||
|
||||
// A dummy query-string is added to filenames, to gain control over
|
||||
// browser-caching. The string changes on every update or full cache
|
||||
// flush, forcing browsers to load a new copy of the files, as the
|
||||
// URL changed.
|
||||
$query_string = $this->state->get('system.css_js_query_string') ?: '0';
|
||||
|
||||
// Defaults for LINK and STYLE elements.
|
||||
$link_element_defaults = array(
|
||||
'#type' => 'html_tag',
|
||||
'#tag' => 'link',
|
||||
'#attributes' => array(
|
||||
'rel' => 'stylesheet',
|
||||
),
|
||||
);
|
||||
$style_element_defaults = array(
|
||||
'#type' => 'html_tag',
|
||||
'#tag' => 'style',
|
||||
);
|
||||
|
||||
// For filthy IE hack.
|
||||
$current_ie_group_keys = NULL;
|
||||
$get_ie_group_key = function ($css_asset) {
|
||||
return array($css_asset['type'], $css_asset['preprocess'], $css_asset['group'], $css_asset['every_page'], $css_asset['media'], $css_asset['browsers']);
|
||||
};
|
||||
|
||||
// Loop through all CSS assets, by key, to allow for the special IE
|
||||
// workaround.
|
||||
$css_assets_keys = array_keys($css_assets);
|
||||
for ($i = 0; $i < count($css_assets_keys); $i++) {
|
||||
$css_asset = $css_assets[$css_assets_keys[$i]];
|
||||
switch ($css_asset['type']) {
|
||||
// For file items, there are three possibilities.
|
||||
// - There are up to 31 CSS assets on the page (some of which may be
|
||||
// aggregated). In this case, output a LINK tag for file CSS assets.
|
||||
// - There are more than 31 CSS assets on the page, yet we must stay
|
||||
// below IE<10's limit of 31 total CSS inclusion tags, we handle this
|
||||
// in two ways:
|
||||
// - file CSS assets that are not eligible for aggregation (their
|
||||
// 'preprocess' flag has been set to FALSE): in this case, output a
|
||||
// LINK tag.
|
||||
// - file CSS assets that can be aggregated (and possibly have been):
|
||||
// in this case, figure out which subsequent file CSS assets share
|
||||
// the same key properties ('group', 'every_page', 'media' and
|
||||
// 'browsers') and output this group into as few STYLE tags as
|
||||
// possible (a STYLE tag may contain only 31 @import statements).
|
||||
case 'file':
|
||||
// The dummy query string needs to be added to the URL to control
|
||||
// browser-caching.
|
||||
$query_string_separator = (strpos($css_asset['data'], '?') !== FALSE) ? '&' : '?';
|
||||
|
||||
// As long as the current page will not run into IE's limit for CSS
|
||||
// assets: output a LINK tag for a file CSS asset.
|
||||
if (count($css_assets) <= 31) {
|
||||
$element = $link_element_defaults;
|
||||
$element['#attributes']['href'] = file_create_url($css_asset['data']) . $query_string_separator . $query_string;
|
||||
$element['#attributes']['media'] = $css_asset['media'];
|
||||
$element['#browsers'] = $css_asset['browsers'];
|
||||
$elements[] = $element;
|
||||
}
|
||||
// The current page will run into IE's limits for CSS assets: work
|
||||
// around these limits by performing a light form of grouping.
|
||||
// Once Drupal only needs to support IE10 and later, we can drop this.
|
||||
else {
|
||||
// The file CSS asset is ineligible for aggregation: output it in a
|
||||
// LINK tag.
|
||||
if (!$css_asset['preprocess']) {
|
||||
$element = $link_element_defaults;
|
||||
$element['#attributes']['href'] = file_create_url($css_asset['data']) . $query_string_separator . $query_string;
|
||||
$element['#attributes']['media'] = $css_asset['media'];
|
||||
$element['#browsers'] = $css_asset['browsers'];
|
||||
$elements[] = $element;
|
||||
}
|
||||
// The file CSS asset can be aggregated, but hasn't been: combine
|
||||
// multiple items into as few STYLE tags as possible.
|
||||
else {
|
||||
$import = array();
|
||||
// Start with the current CSS asset, iterate over subsequent CSS
|
||||
// assets and find which ones have the same 'type', 'group',
|
||||
// 'every_page', 'preprocess', 'media' and 'browsers' properties.
|
||||
$j = $i;
|
||||
$next_css_asset = $css_asset;
|
||||
$current_ie_group_key = $get_ie_group_key($css_asset);
|
||||
do {
|
||||
// The dummy query string needs to be added to the URL to
|
||||
// control browser-caching. IE7 does not support a media type on
|
||||
// the @import statement, so we instead specify the media for
|
||||
// the group on the STYLE tag.
|
||||
$import[] = '@import url("' . SafeMarkup::checkPlain(file_create_url($next_css_asset['data']) . '?' . $query_string) . '");';
|
||||
// Move the outer for loop skip the next item, since we
|
||||
// processed it here.
|
||||
$i = $j;
|
||||
// Retrieve next CSS asset, unless there is none: then break.
|
||||
if ($j + 1 < count($css_assets_keys)) {
|
||||
$j++;
|
||||
$next_css_asset = $css_assets[$css_assets_keys[$j]];
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
} while ($get_ie_group_key($next_css_asset) == $current_ie_group_key);
|
||||
|
||||
// In addition to IE's limit of 31 total CSS inclusion tags, it
|
||||
// also has a limit of 31 @import statements per STYLE tag.
|
||||
while (!empty($import)) {
|
||||
$import_batch = array_slice($import, 0, 31);
|
||||
$import = array_slice($import, 31);
|
||||
$element = $style_element_defaults;
|
||||
// This simplifies the JavaScript regex, allowing each line
|
||||
// (separated by \n) to be treated as a completely different
|
||||
// string. This means that we can use ^ and $ on one line at a
|
||||
// time, and not worry about style tags since they'll never
|
||||
// match the regex.
|
||||
$element['#value'] = "\n" . implode("\n", $import_batch) . "\n";
|
||||
$element['#attributes']['media'] = $css_asset['media'];
|
||||
$element['#browsers'] = $css_asset['browsers'];
|
||||
$elements[] = $element;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// Output a LINK tag for an external CSS asset. The asset's 'data'
|
||||
// property contains the full URL.
|
||||
case 'external':
|
||||
$element = $link_element_defaults;
|
||||
$element['#attributes']['href'] = $css_asset['data'];
|
||||
$element['#attributes']['media'] = $css_asset['media'];
|
||||
$element['#browsers'] = $css_asset['browsers'];
|
||||
$elements[] = $element;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \Exception('Invalid CSS asset type.');
|
||||
}
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
}
|
272
core/lib/Drupal/Core/Asset/CssOptimizer.php
Normal file
272
core/lib/Drupal/Core/Asset/CssOptimizer.php
Normal file
|
@ -0,0 +1,272 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\CssOptimizer.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
use Drupal\Core\Asset\AssetOptimizerInterface;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
|
||||
/**
|
||||
* Optimizes a CSS asset.
|
||||
*/
|
||||
class CssOptimizer implements AssetOptimizerInterface {
|
||||
|
||||
/**
|
||||
* The base path used by rewriteFileURI().
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $rewriteFileURIBasePath;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function optimize(array $css_asset) {
|
||||
if (!in_array($css_asset['type'], array('file', 'inline'))) {
|
||||
throw new \Exception('Only file or inline CSS assets can be optimized.');
|
||||
}
|
||||
if ($css_asset['type'] === 'file' && !$css_asset['preprocess']) {
|
||||
throw new \Exception('Only file CSS assets with preprocessing enabled can be optimized.');
|
||||
}
|
||||
|
||||
if ($css_asset['type'] === 'file') {
|
||||
return $this->processFile($css_asset);
|
||||
}
|
||||
else {
|
||||
return $this->processCss($css_asset['data'], $css_asset['preprocess']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the contents of a CSS asset for cleanup.
|
||||
*
|
||||
* @param string $contents
|
||||
* The contents of the CSS asset.
|
||||
*
|
||||
* @return string
|
||||
* Contents of the CSS asset.
|
||||
*/
|
||||
public function clean($contents) {
|
||||
// Remove multiple charset declarations for standards compliance (and fixing
|
||||
// Safari problems).
|
||||
$contents = preg_replace('/^@charset\s+[\'"](\S*?)\b[\'"];/i', '', $contents);
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build aggregate CSS file.
|
||||
*/
|
||||
protected function processFile($css_asset) {
|
||||
$contents = $this->loadFile($css_asset['data'], TRUE);
|
||||
|
||||
$contents = $this->clean($contents);
|
||||
|
||||
// Get the parent directory of this file, relative to the Drupal root.
|
||||
$css_base_path = substr($css_asset['data'], 0, strrpos($css_asset['data'], '/'));
|
||||
// Store base path.
|
||||
$this->rewriteFileURIBasePath = $css_base_path . '/';
|
||||
|
||||
// Anchor all paths in the CSS with its base URL, ignoring external and absolute paths.
|
||||
return preg_replace_callback('/url\(\s*[\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\s*\)/i', array($this, 'rewriteFileURI'), $contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the stylesheet and resolves all @import commands.
|
||||
*
|
||||
* Loads a stylesheet and replaces @import commands with the contents of the
|
||||
* imported file. Use this instead of file_get_contents when processing
|
||||
* stylesheets.
|
||||
*
|
||||
* The returned contents are compressed removing white space and comments only
|
||||
* when CSS aggregation is enabled. This optimization will not apply for
|
||||
* color.module enabled themes with CSS aggregation turned off.
|
||||
*
|
||||
* Note: the only reason this method is public is so color.module can call it;
|
||||
* it is not on the AssetOptimizerInterface, so future refactorings can make
|
||||
* it protected.
|
||||
*
|
||||
* @param $file
|
||||
* Name of the stylesheet to be processed.
|
||||
* @param $optimize
|
||||
* Defines if CSS contents should be compressed or not.
|
||||
* @param $reset_basepath
|
||||
* Used internally to facilitate recursive resolution of @import commands.
|
||||
*
|
||||
* @return
|
||||
* Contents of the stylesheet, including any resolved @import commands.
|
||||
*/
|
||||
public function loadFile($file, $optimize = NULL, $reset_basepath = TRUE) {
|
||||
// These statics are not cache variables, so we don't use drupal_static().
|
||||
static $_optimize, $basepath;
|
||||
if ($reset_basepath) {
|
||||
$basepath = '';
|
||||
}
|
||||
// Store the value of $optimize for preg_replace_callback with nested
|
||||
// @import loops.
|
||||
if (isset($optimize)) {
|
||||
$_optimize = $optimize;
|
||||
}
|
||||
|
||||
// Stylesheets are relative one to each other. Start by adding a base path
|
||||
// prefix provided by the parent stylesheet (if necessary).
|
||||
if ($basepath && !file_uri_scheme($file)) {
|
||||
$file = $basepath . '/' . $file;
|
||||
}
|
||||
// Store the parent base path to restore it later.
|
||||
$parent_base_path = $basepath;
|
||||
// Set the current base path to process possible child imports.
|
||||
$basepath = dirname($file);
|
||||
|
||||
// Load the CSS stylesheet. We suppress errors because themes may specify
|
||||
// stylesheets in their .info.yml file that don't exist in the theme's path,
|
||||
// but are merely there to disable certain module CSS files.
|
||||
$content = '';
|
||||
if ($contents = @file_get_contents($file)) {
|
||||
// If a BOM is found, convert the file to UTF-8, then use substr() to
|
||||
// remove the BOM from the result.
|
||||
if ($encoding = (Unicode::encodingFromBOM($contents))) {
|
||||
$contents = Unicode::substr(Unicode::convertToUtf8($contents, $encoding), 1);
|
||||
}
|
||||
// If no BOM, check for fallback encoding. Per CSS spec the regex is very strict.
|
||||
elseif (preg_match('/^@charset "([^"]+)";/', $contents, $matches)) {
|
||||
if ($matches[1] !== 'utf-8' && $matches[1] !== 'UTF-8') {
|
||||
$contents = substr($contents, strlen($matches[0]));
|
||||
$contents = Unicode::convertToUtf8($contents, $matches[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Return the processed stylesheet.
|
||||
$content = $this->processCss($contents, $_optimize);
|
||||
}
|
||||
|
||||
// Restore the parent base path as the file and its children are processed.
|
||||
$basepath = $parent_base_path;
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads stylesheets recursively and returns contents with corrected paths.
|
||||
*
|
||||
* This function is used for recursive loading of stylesheets and
|
||||
* returns the stylesheet content with all url() paths corrected.
|
||||
*
|
||||
* @param array $matches
|
||||
* An array of matches by a preg_replace_callback() call that scans for
|
||||
* @import-ed CSS files, except for external CSS files.
|
||||
*
|
||||
* @return
|
||||
* The contents of the CSS file at $matches[1], with corrected paths.
|
||||
*
|
||||
* @see \Drupal\Core\Asset\AssetOptimizerInterface::loadFile()
|
||||
*/
|
||||
protected function loadNestedFile($matches) {
|
||||
$filename = $matches[1];
|
||||
// Load the imported stylesheet and replace @import commands in there as
|
||||
// well.
|
||||
$file = $this->loadFile($filename, NULL, FALSE);
|
||||
|
||||
// Determine the file's directory.
|
||||
$directory = dirname($filename);
|
||||
// If the file is in the current directory, make sure '.' doesn't appear in
|
||||
// the url() path.
|
||||
$directory = $directory == '.' ? '' : $directory .'/';
|
||||
|
||||
// Alter all internal url() paths. Leave external paths alone. We don't need
|
||||
// to normalize absolute paths here because that will be done later.
|
||||
return preg_replace('/url\(\s*([\'"]?)(?![a-z]+:|\/+)([^\'")]+)([\'"]?)\s*\)/i', 'url(\1' . $directory . '\2\3)', $file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the contents of a stylesheet for aggregation.
|
||||
*
|
||||
* @param $contents
|
||||
* The contents of the stylesheet.
|
||||
* @param $optimize
|
||||
* (optional) Boolean whether CSS contents should be minified. Defaults to
|
||||
* FALSE.
|
||||
*
|
||||
* @return
|
||||
* Contents of the stylesheet including the imported stylesheets.
|
||||
*/
|
||||
protected function processCss($contents, $optimize = FALSE) {
|
||||
// Remove unwanted CSS code that cause issues.
|
||||
$contents = $this->clean($contents);
|
||||
|
||||
if ($optimize) {
|
||||
// Perform some safe CSS optimizations.
|
||||
// Regexp to match comment blocks.
|
||||
$comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/';
|
||||
// Regexp to match double quoted strings.
|
||||
$double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
|
||||
// Regexp to match single quoted strings.
|
||||
$single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'";
|
||||
// Strip all comment blocks, but keep double/single quoted strings.
|
||||
$contents = preg_replace(
|
||||
"<($double_quot|$single_quot)|$comment>Ss",
|
||||
"$1",
|
||||
$contents
|
||||
);
|
||||
// Remove certain whitespace.
|
||||
// There are different conditions for removing leading and trailing
|
||||
// whitespace.
|
||||
// @see http://php.net/manual/regexp.reference.subpatterns.php
|
||||
$contents = preg_replace('<
|
||||
# Strip leading and trailing whitespace.
|
||||
\s*([@{};,])\s*
|
||||
# Strip only leading whitespace from:
|
||||
# - Closing parenthesis: Retain "@media (bar) and foo".
|
||||
| \s+([\)])
|
||||
# Strip only trailing whitespace from:
|
||||
# - Opening parenthesis: Retain "@media (bar) and foo".
|
||||
# - Colon: Retain :pseudo-selectors.
|
||||
| ([\(:])\s+
|
||||
>xS',
|
||||
// Only one of the three capturing groups will match, so its reference
|
||||
// will contain the wanted value and the references for the
|
||||
// two non-matching groups will be replaced with empty strings.
|
||||
'$1$2$3',
|
||||
$contents
|
||||
);
|
||||
// End the file with a new line.
|
||||
$contents = trim($contents);
|
||||
$contents .= "\n";
|
||||
}
|
||||
|
||||
// Replaces @import commands with the actual stylesheet content.
|
||||
// This happens recursively but omits external files.
|
||||
$contents = preg_replace_callback('/@import\s*(?:url\(\s*)?[\'"]?(?![a-z]+:)(?!\/\/)([^\'"\()]+)[\'"]?\s*\)?\s*;/', array($this, 'loadNestedFile'), $contents);
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefixes all paths within a CSS file for processFile().
|
||||
*
|
||||
* @param array $matches
|
||||
* An array of matches by a preg_replace_callback() call that scans for
|
||||
* url() references in CSS files, except for external or absolute ones.
|
||||
*
|
||||
* Note: the only reason this method is public is so color.module can call it;
|
||||
* it is not on the AssetOptimizerInterface, so future refactorings can make
|
||||
* it protected.
|
||||
*
|
||||
* @return string
|
||||
* The file path.
|
||||
*/
|
||||
public function rewriteFileURI($matches) {
|
||||
// Prefix with base and remove '../' segments where possible.
|
||||
$path = $this->rewriteFileURIBasePath . $matches[1];
|
||||
$last = '';
|
||||
while ($path != $last) {
|
||||
$last = $path;
|
||||
$path = preg_replace('`(^|/)(?!\.\./)([^/]+)/\.\./`', '$1', $path);
|
||||
}
|
||||
return 'url(' . file_create_url($path) . ')';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\Exception\IncompleteLibraryDefinitionException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset\Exception;
|
||||
|
||||
/**
|
||||
* Defines a custom exception if a library has no CSS/JS/JS setting specified.
|
||||
*/
|
||||
class IncompleteLibraryDefinitionException extends \RuntimeException {
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\Exception\InvalidLibraryFileException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset\Exception;
|
||||
|
||||
/**
|
||||
* Defines an exception if the library file could not be parsed.
|
||||
*/
|
||||
class InvalidLibraryFileException extends \RunTimeException {
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\Exception\LibraryDefinitionMissingLicenseException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset\Exception;
|
||||
|
||||
/**
|
||||
* Defines a custom exception if a library has a remote but no license.
|
||||
*/
|
||||
class LibraryDefinitionMissingLicenseException extends \RuntimeException {
|
||||
|
||||
}
|
81
core/lib/Drupal/Core/Asset/JsCollectionGrouper.php
Normal file
81
core/lib/Drupal/Core/Asset/JsCollectionGrouper.php
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\JsCollectionGrouper.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
use Drupal\Core\Asset\AssetCollectionGrouperInterface;
|
||||
|
||||
/**
|
||||
* Groups JavaScript assets.
|
||||
*/
|
||||
class JsCollectionGrouper implements AssetCollectionGrouperInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Puts multiple items into the same group if they are groupable and if they
|
||||
* are for the same browsers. Items of the 'file' type are groupable if their
|
||||
* 'preprocess' flag is TRUE. Items of the 'inline', 'settings', or 'external'
|
||||
* type are not groupable.
|
||||
*
|
||||
* Also ensures that the process of grouping items does not change their
|
||||
* relative order. This requirement may result in multiple groups for the same
|
||||
* type and browsers, if needed to accommodate other items in between.
|
||||
*/
|
||||
public function group(array $js_assets) {
|
||||
$groups = array();
|
||||
// If a group can contain multiple items, we track the information that must
|
||||
// be the same for each item in the group, so that when we iterate the next
|
||||
// item, we can determine if it can be put into the current group, or if a
|
||||
// new group needs to be made for it.
|
||||
$current_group_keys = NULL;
|
||||
$index = -1;
|
||||
foreach ($js_assets as $item) {
|
||||
// The browsers for which the JavaScript item needs to be loaded is part
|
||||
// of the information that determines when a new group is needed, but the
|
||||
// order of keys in the array doesn't matter, and we don't want a new
|
||||
// group if all that's different is that order.
|
||||
ksort($item['browsers']);
|
||||
|
||||
switch ($item['type']) {
|
||||
case 'file':
|
||||
// Group file items if their 'preprocess' flag is TRUE.
|
||||
// Help ensure maximum reuse of aggregate files by only grouping
|
||||
// together items that share the same 'group' value and 'every_page'
|
||||
// flag.
|
||||
$group_keys = $item['preprocess'] ? array($item['type'], $item['group'], $item['every_page'], $item['browsers']) : FALSE;
|
||||
break;
|
||||
|
||||
case 'external':
|
||||
case 'setting':
|
||||
case 'inline':
|
||||
// Do not group external, settings, and inline items.
|
||||
$group_keys = FALSE;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the group keys don't match the most recent group we're working with,
|
||||
// then a new group must be made.
|
||||
if ($group_keys !== $current_group_keys) {
|
||||
$index++;
|
||||
// Initialize the new group with the same properties as the first item
|
||||
// being placed into it. The item's 'data' and 'weight' properties are
|
||||
// unique to the item and should not be carried over to the group.
|
||||
$groups[$index] = $item;
|
||||
unset($groups[$index]['data'], $groups[$index]['weight']);
|
||||
$groups[$index]['items'] = array();
|
||||
$current_group_keys = $group_keys ? $group_keys : NULL;
|
||||
}
|
||||
|
||||
// Add the item to the current group.
|
||||
$groups[$index]['items'][] = $item;
|
||||
}
|
||||
|
||||
return $groups;
|
||||
}
|
||||
|
||||
}
|
197
core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php
Normal file
197
core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php
Normal file
|
@ -0,0 +1,197 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\JsCollectionOptimizer.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
use Drupal\Core\State\StateInterface;
|
||||
|
||||
|
||||
/**
|
||||
* Optimizes JavaScript assets.
|
||||
*/
|
||||
class JsCollectionOptimizer implements AssetCollectionOptimizerInterface {
|
||||
|
||||
/**
|
||||
* A JS asset grouper.
|
||||
*
|
||||
* @var \Drupal\Core\Asset\JsCollectionGrouper
|
||||
*/
|
||||
protected $grouper;
|
||||
|
||||
/**
|
||||
* A JS asset optimizer.
|
||||
*
|
||||
* @var \Drupal\Core\Asset\JsOptimizer
|
||||
*/
|
||||
protected $optimizer;
|
||||
|
||||
/**
|
||||
* An asset dumper.
|
||||
*
|
||||
* @var \Drupal\Core\Asset\AssetDumper
|
||||
*/
|
||||
protected $dumper;
|
||||
|
||||
/**
|
||||
* The state key/value store.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* Constructs a JsCollectionOptimizer.
|
||||
*
|
||||
* @param \Drupal\Core\Asset\AssetCollectionGrouperInterface
|
||||
* The grouper for JS assets.
|
||||
* @param \Drupal\Core\Asset\AssetOptimizerInterface
|
||||
* The optimizer for a single JS asset.
|
||||
* @param \Drupal\Core\Asset\AssetDumperInterface
|
||||
* The dumper for optimized JS assets.
|
||||
* @param \Drupal\Core\State\StateInterface
|
||||
* The state key/value store.
|
||||
*/
|
||||
public function __construct(AssetCollectionGrouperInterface $grouper, AssetOptimizerInterface $optimizer, AssetDumperInterface $dumper, StateInterface $state) {
|
||||
$this->grouper = $grouper;
|
||||
$this->optimizer = $optimizer;
|
||||
$this->dumper = $dumper;
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* The cache file name is retrieved on a page load via a lookup variable that
|
||||
* contains an associative array. The array key is the hash of the names in
|
||||
* $files while the value is the cache file name. The cache file is generated
|
||||
* in two cases. First, if there is no file name value for the key, which will
|
||||
* happen if a new file name has been added to $files or after the lookup
|
||||
* variable is emptied to force a rebuild of the cache. Second, the cache file
|
||||
* is generated if it is missing on disk. Old cache files are not deleted
|
||||
* immediately when the lookup variable is emptied, but are deleted after a
|
||||
* configurable period (@code system.performance.stale_file_threshold @endcode)
|
||||
* to ensure that files referenced by a cached page will still be available.
|
||||
*/
|
||||
public function optimize(array $js_assets) {
|
||||
// Group the assets.
|
||||
$js_groups = $this->grouper->group($js_assets);
|
||||
|
||||
// Now optimize (concatenate, not minify) and dump each asset group, unless
|
||||
// that was already done, in which case it should appear in
|
||||
// system.js_cache_files.
|
||||
// Drupal contrib can override this default JS aggregator to keep the same
|
||||
// grouping, optimizing and dumping, but change the strategy that is used to
|
||||
// determine when the aggregate should be rebuilt (e.g. mtime, HTTPS …).
|
||||
$map = $this->state->get('system.js_cache_files') ?: array();
|
||||
$js_assets = array();
|
||||
foreach ($js_groups as $order => $js_group) {
|
||||
// We have to return a single asset, not a group of assets. It is now up
|
||||
// to one of the pieces of code in the switch statement below to set the
|
||||
// 'data' property to the appropriate value.
|
||||
$js_assets[$order] = $js_group;
|
||||
unset($js_assets[$order]['items']);
|
||||
|
||||
switch ($js_group['type']) {
|
||||
case 'file':
|
||||
// No preprocessing, single JS asset: just use the existing URI.
|
||||
if (!$js_group['preprocess']) {
|
||||
$uri = $js_group['items'][0]['data'];
|
||||
$js_assets[$order]['data'] = $uri;
|
||||
}
|
||||
// Preprocess (aggregate), unless the aggregate file already exists.
|
||||
else {
|
||||
$key = $this->generateHash($js_group);
|
||||
$uri = '';
|
||||
if (isset($map[$key])) {
|
||||
$uri = $map[$key];
|
||||
}
|
||||
if (empty($uri) || !file_exists($uri)) {
|
||||
// Concatenate each asset within the group.
|
||||
$data = '';
|
||||
foreach ($js_group['items'] as $js_asset) {
|
||||
// Optimize this JS file, but only if it's not yet minified.
|
||||
if (isset($js_asset['minified']) && $js_asset['minified']) {
|
||||
$data .= file_get_contents($js_asset['data']);
|
||||
}
|
||||
else {
|
||||
$data .= $this->optimizer->optimize($js_asset);
|
||||
}
|
||||
// Append a ';' and a newline after each JS file to prevent them
|
||||
// from running together.
|
||||
$data .= ";\n";
|
||||
}
|
||||
// Remove unwanted JS code that cause issues.
|
||||
$data = $this->optimizer->clean($data);
|
||||
// Dump the optimized JS for this group into an aggregate file.
|
||||
$uri = $this->dumper->dump($data, 'js');
|
||||
// Set the URI for this group's aggregate file.
|
||||
$js_assets[$order]['data'] = $uri;
|
||||
// Persist the URI for this aggregate file.
|
||||
$map[$key] = $uri;
|
||||
$this->state->set('system.js_cache_files', $map);
|
||||
}
|
||||
else {
|
||||
// Use the persisted URI for the optimized JS file.
|
||||
$js_assets[$order]['data'] = $uri;
|
||||
}
|
||||
$js_assets[$order]['preprocessed'] = TRUE;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'external':
|
||||
case 'setting':
|
||||
case 'inline':
|
||||
// We don't do any aggregation and hence also no caching for external,
|
||||
// setting or inline JS assets.
|
||||
$uri = $js_group['items'][0]['data'];
|
||||
$js_assets[$order]['data'] = $uri;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $js_assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a hash for a given group of JavaScript assets.
|
||||
*
|
||||
* @param array $js_group
|
||||
* A group of JavaScript assets.
|
||||
*
|
||||
* @return string
|
||||
* A hash to uniquely identify the given group of JavaScript assets.
|
||||
*/
|
||||
protected function generateHash(array $js_group) {
|
||||
$js_data = array();
|
||||
foreach ($js_group['items'] as $js_file) {
|
||||
$js_data[] = $js_file['data'];
|
||||
}
|
||||
return hash('sha256', serialize($js_data));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAll() {
|
||||
return $this->state->get('system.js_cache_files');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteAll() {
|
||||
$this->state->delete('system.js_cache_files');
|
||||
$delete_stale = function($uri) {
|
||||
// Default stale file threshold is 30 days.
|
||||
if (REQUEST_TIME - filemtime($uri) > \Drupal::config('system.performance')->get('stale_file_threshold')) {
|
||||
file_unmanaged_delete($uri);
|
||||
}
|
||||
};
|
||||
file_scan_directory('public://js', '/.*/', array('callback' => $delete_stale));
|
||||
}
|
||||
|
||||
}
|
111
core/lib/Drupal/Core/Asset/JsCollectionRenderer.php
Normal file
111
core/lib/Drupal/Core/Asset/JsCollectionRenderer.php
Normal file
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\JsCollectionRenderer.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Core\State\StateInterface;
|
||||
|
||||
/**
|
||||
* Renders JavaScript assets.
|
||||
*/
|
||||
class JsCollectionRenderer implements AssetCollectionRendererInterface {
|
||||
|
||||
/**
|
||||
* The state key/value store.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* Constructs a JsCollectionRenderer.
|
||||
*
|
||||
* @param \Drupal\Core\State\StateInterface
|
||||
* The state key/value store.
|
||||
*/
|
||||
public function __construct(StateInterface $state) {
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* This class evaluates the aggregation enabled/disabled condition on a group
|
||||
* by group basis by testing whether an aggregate file has been made for the
|
||||
* group rather than by testing the site-wide aggregation setting. This allows
|
||||
* this class to work correctly even if modules have implemented custom
|
||||
* logic for grouping and aggregating files.
|
||||
*/
|
||||
public function render(array $js_assets) {
|
||||
$elements = array();
|
||||
|
||||
// A dummy query-string is added to filenames, to gain control over
|
||||
// browser-caching. The string changes on every update or full cache
|
||||
// flush, forcing browsers to load a new copy of the files, as the
|
||||
// URL changed. Files that should not be cached get REQUEST_TIME as
|
||||
// query-string instead, to enforce reload on every page request.
|
||||
$default_query_string = $this->state->get('system.css_js_query_string') ?: '0';
|
||||
|
||||
// For inline JavaScript to validate as XHTML, all JavaScript containing
|
||||
// XHTML needs to be wrapped in CDATA. To make that backwards compatible
|
||||
// with HTML 4, we need to comment out the CDATA-tag.
|
||||
$embed_prefix = "\n<!--//--><![CDATA[//><!--\n";
|
||||
$embed_suffix = "\n//--><!]]>\n";
|
||||
|
||||
// Defaults for each SCRIPT element.
|
||||
$element_defaults = array(
|
||||
'#type' => 'html_tag',
|
||||
'#tag' => 'script',
|
||||
'#value' => '',
|
||||
);
|
||||
|
||||
// Loop through all JS assets.
|
||||
foreach ($js_assets as $js_asset) {
|
||||
// Element properties that do not depend on JS asset type.
|
||||
$element = $element_defaults;
|
||||
$element['#browsers'] = $js_asset['browsers'];
|
||||
|
||||
// Element properties that depend on item type.
|
||||
switch ($js_asset['type']) {
|
||||
case 'setting':
|
||||
$element['#value_prefix'] = $embed_prefix;
|
||||
$element['#value'] = 'var drupalSettings = ' . Json::encode($js_asset['data']) . ";";
|
||||
$element['#value_suffix'] = $embed_suffix;
|
||||
break;
|
||||
|
||||
case 'file':
|
||||
$query_string = $js_asset['version'] == -1 ? $default_query_string : 'v=' . $js_asset['version'];
|
||||
$query_string_separator = (strpos($js_asset['data'], '?') !== FALSE) ? '&' : '?';
|
||||
$element['#attributes']['src'] = file_create_url($js_asset['data']);
|
||||
// Only add the cache-busting query string if this isn't an aggregate
|
||||
// file.
|
||||
if (!isset($js_asset['preprocessed'])) {
|
||||
$element['#attributes']['src'] .= $query_string_separator . ($js_asset['cache'] ? $query_string : REQUEST_TIME);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'external':
|
||||
$element['#attributes']['src'] = $js_asset['data'];
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \Exception('Invalid JS asset type.');
|
||||
}
|
||||
|
||||
// Attributes may only be set if this script is output independently.
|
||||
if (!empty($element['#attributes']['src']) && !empty($js_asset['attributes'])) {
|
||||
$element['#attributes'] += $js_asset['attributes'];
|
||||
}
|
||||
|
||||
$elements[] = $element;
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
}
|
60
core/lib/Drupal/Core/Asset/JsOptimizer.php
Normal file
60
core/lib/Drupal/Core/Asset/JsOptimizer.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\JsOptimizer.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
use Drupal\Core\Asset\AssetOptimizerInterface;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
|
||||
/**
|
||||
* Optimizes a JavaScript asset.
|
||||
*/
|
||||
class JsOptimizer implements AssetOptimizerInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function optimize(array $js_asset) {
|
||||
if ($js_asset['type'] !== 'file') {
|
||||
throw new \Exception('Only file JavaScript assets can be optimized.');
|
||||
}
|
||||
if ($js_asset['type'] === 'file' && !$js_asset['preprocess']) {
|
||||
throw new \Exception('Only file JavaScript assets with preprocessing enabled can be optimized.');
|
||||
}
|
||||
|
||||
// If a BOM is found, convert the file to UTF-8, then use substr() to
|
||||
// remove the BOM from the result.
|
||||
$data = file_get_contents($js_asset['data']);
|
||||
if ($encoding = (Unicode::encodingFromBOM($data))) {
|
||||
$data = Unicode::substr(Unicode::convertToUtf8($data, $encoding), 1);
|
||||
}
|
||||
// If no BOM is found, check for the charset attribute.
|
||||
elseif (isset($js_asset['attributes']['charset'])) {
|
||||
$data = Unicode::convertToUtf8($data, $js_asset['attributes']['charset']);
|
||||
}
|
||||
|
||||
// No-op optimizer: no optimizations are applied to JavaScript assets.
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the contents of a javascript asset for cleanup.
|
||||
*
|
||||
* @param string $contents
|
||||
* The contents of the javascript asset.
|
||||
*
|
||||
* @return string
|
||||
* Contents of the javascript asset.
|
||||
*/
|
||||
public function clean($contents) {
|
||||
// Remove JS source and source mapping urls or these may cause 404 errors.
|
||||
$contents = preg_replace('/\/\/(#|@)\s(sourceURL|sourceMappingURL)=\s*(\S*?)\s*$/m', '', $contents);
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
}
|
100
core/lib/Drupal/Core/Asset/LibraryDependencyResolver.php
Normal file
100
core/lib/Drupal/Core/Asset/LibraryDependencyResolver.php
Normal file
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\LibraryDependencyResolver.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
/**
|
||||
* Resolves the dependencies of asset (CSS/JavaScript) libraries.
|
||||
*/
|
||||
class LibraryDependencyResolver implements LibraryDependencyResolverInterface {
|
||||
|
||||
/**
|
||||
* The library discovery service.
|
||||
*
|
||||
* @var \Drupal\Core\Asset\LibraryDiscoveryInterface
|
||||
*/
|
||||
protected $libraryDiscovery;
|
||||
|
||||
/**
|
||||
* Constructs a new LibraryDependencyResolver instance.
|
||||
*
|
||||
* @param \Drupal\Core\Asset\LibraryDiscoveryInterface $library_discovery
|
||||
* The library discovery service.
|
||||
*/
|
||||
public function __construct(LibraryDiscoveryInterface $library_discovery) {
|
||||
$this->libraryDiscovery = $library_discovery;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLibrariesWithDependencies(array $libraries) {
|
||||
return $this->doGetDependencies($libraries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the given libraries with its dependencies.
|
||||
*
|
||||
* Helper method for ::getLibrariesWithDependencies().
|
||||
*
|
||||
* @param string[] $libraries_with_unresolved_dependencies
|
||||
* A list of libraries, with unresolved dependencies, in the order they
|
||||
* should be loaded.
|
||||
* @param string[] $final_libraries
|
||||
* The final list of libraries (the return value) that is being built
|
||||
* recursively.
|
||||
*
|
||||
* @return string[]
|
||||
* A list of libraries, in the order they should be loaded, including their
|
||||
* dependencies.
|
||||
*/
|
||||
protected function doGetDependencies(array $libraries_with_unresolved_dependencies, array $final_libraries = []) {
|
||||
foreach ($libraries_with_unresolved_dependencies as $library) {
|
||||
if (!in_array($library, $final_libraries)) {
|
||||
list($extension, $name) = explode('/', $library, 2);
|
||||
$definition = $this->libraryDiscovery->getLibraryByName($extension, $name);
|
||||
if (!empty($definition['dependencies'])) {
|
||||
$final_libraries = $this->doGetDependencies($definition['dependencies'], $final_libraries);
|
||||
}
|
||||
$final_libraries[] = $library;
|
||||
}
|
||||
}
|
||||
return $final_libraries;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMinimalRepresentativeSubset(array $libraries) {
|
||||
$minimal = [];
|
||||
|
||||
// Determine each library's dependencies.
|
||||
$with_deps = [];
|
||||
foreach ($libraries as $library) {
|
||||
$with_deps[$library] = $this->getLibrariesWithDependencies([$library]);
|
||||
}
|
||||
|
||||
foreach ($libraries as $library) {
|
||||
$exists = FALSE;
|
||||
foreach ($with_deps as $other_library => $dependencies) {
|
||||
if ($library == $other_library) {
|
||||
continue;
|
||||
}
|
||||
if (in_array($library, $dependencies)) {
|
||||
$exists = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$exists) {
|
||||
$minimal[] = $library;
|
||||
}
|
||||
}
|
||||
|
||||
return $minimal;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\LibraryDependencyResolverInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
/**
|
||||
* Resolves the dependencies of asset (CSS/JavaScript) libraries.
|
||||
*/
|
||||
interface LibraryDependencyResolverInterface {
|
||||
|
||||
/**
|
||||
* Gets the given libraries with their dependencies.
|
||||
*
|
||||
* Given ['core/a', 'core/b', 'core/c'], with core/a depending on core/c and
|
||||
* core/b on core/d, returns ['core/a', 'core/b', 'core/c', 'core/d'].
|
||||
*
|
||||
* @param string[] $libraries
|
||||
* A list of libraries, in the order they should be loaded.
|
||||
*
|
||||
* @return string[]
|
||||
* A list of libraries, in the order they should be loaded, including their
|
||||
* dependencies.
|
||||
*/
|
||||
public function getLibrariesWithDependencies(array $libraries);
|
||||
|
||||
/**
|
||||
* Gets the minimal representative subset of the given libraries.
|
||||
*
|
||||
* A minimal representative subset means that any library in the given set of
|
||||
* libraries that is a dependency of another library in the set, is removed.
|
||||
*
|
||||
* Hence a minimal representative subset is the most compact representation
|
||||
* possible of a set of libraries.
|
||||
*
|
||||
* (Each asset library has dependencies and can therefore be seen as a tree.
|
||||
* Hence the given list of libraries represent a forest. This function returns
|
||||
* all roots of trees that are not a subtree of another tree in the forest.)
|
||||
*
|
||||
* @param string[] $libraries
|
||||
* A set of libraries.
|
||||
*
|
||||
* @return string[]
|
||||
* A representative subset of the given set of libraries.
|
||||
*/
|
||||
public function getMinimalRepresentativeSubset(array $libraries);
|
||||
|
||||
}
|
92
core/lib/Drupal/Core/Asset/LibraryDiscovery.php
Normal file
92
core/lib/Drupal/Core/Asset/LibraryDiscovery.php
Normal file
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\LibraryDiscovery.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
use Drupal\Core\Cache\CacheCollectorInterface;
|
||||
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Theme\ThemeManagerInterface;
|
||||
|
||||
/**
|
||||
* Discovers available asset libraries in Drupal.
|
||||
*/
|
||||
class LibraryDiscovery implements LibraryDiscoveryInterface {
|
||||
|
||||
/**
|
||||
* The library discovery cache collector.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheCollectorInterface
|
||||
*/
|
||||
protected $collector;
|
||||
|
||||
/**
|
||||
* The cache tag invalidator.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface
|
||||
*/
|
||||
protected $cacheTagInvalidator;
|
||||
|
||||
/**
|
||||
* The final library definitions, statically cached.
|
||||
*
|
||||
* hook_library_info_alter() and hook_js_settings_alter() allows modules
|
||||
* and themes to dynamically alter a library definition (once per request).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $libraryDefinitions = [];
|
||||
|
||||
/**
|
||||
* Constructs a new LibraryDiscovery instance.
|
||||
*
|
||||
* @param \Drupal\Core\Cache\CacheCollectorInterface $library_discovery_collector
|
||||
* The library discovery cache collector.
|
||||
* @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cache_tag_invalidator
|
||||
* The cache tag invalidator.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
|
||||
* The theme manager.
|
||||
*/
|
||||
public function __construct(CacheCollectorInterface $library_discovery_collector, CacheTagsInvalidatorInterface $cache_tag_invalidator) {
|
||||
$this->collector = $library_discovery_collector;
|
||||
$this->cacheTagInvalidator = $cache_tag_invalidator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLibrariesByExtension($extension) {
|
||||
if (!isset($this->libraryDefinitions[$extension])) {
|
||||
$libraries = $this->collector->get($extension);
|
||||
$this->libraryDefinitions[$extension] = [];
|
||||
foreach ($libraries as $name => $definition) {
|
||||
$library_name = "$extension/$name";
|
||||
$this->libraryDefinitions[$extension][$name] = $definition;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->libraryDefinitions[$extension];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLibraryByName($extension, $name) {
|
||||
$extension = $this->getLibrariesByExtension($extension);
|
||||
return isset($extension[$name]) ? $extension[$name] : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clearCachedDefinitions() {
|
||||
$this->cacheTagInvalidator->invalidateTags(['library_info']);
|
||||
}
|
||||
|
||||
}
|
87
core/lib/Drupal/Core/Asset/LibraryDiscoveryCollector.php
Normal file
87
core/lib/Drupal/Core/Asset/LibraryDiscoveryCollector.php
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\LibraryDiscoveryCollector.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
use Drupal\Core\Cache\CacheCollector;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Lock\LockBackendInterface;
|
||||
use Drupal\Core\Theme\ThemeManagerInterface;
|
||||
|
||||
/**
|
||||
* A CacheCollector implementation for building library extension info.
|
||||
*/
|
||||
class LibraryDiscoveryCollector extends CacheCollector {
|
||||
|
||||
/**
|
||||
* The cache backend.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheBackendInterface
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* The lock backend.
|
||||
*
|
||||
* @var \Drupal\Core\Lock\LockBackendInterface
|
||||
*/
|
||||
protected $lock;
|
||||
|
||||
/**
|
||||
* The library discovery parser.
|
||||
*
|
||||
* @var \Drupal\Core\Asset\LibraryDiscoveryParser
|
||||
*/
|
||||
protected $discoveryParser;
|
||||
|
||||
/**
|
||||
* The theme manager.
|
||||
*
|
||||
* @var \Drupal\Core\Theme\ThemeManagerInterface
|
||||
*/
|
||||
protected $themeManager;
|
||||
|
||||
/**
|
||||
* Constructs a CacheCollector object.
|
||||
*
|
||||
* @param string $cid
|
||||
* The cid for the array being cached.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
|
||||
* The cache backend.
|
||||
* @param \Drupal\Core\Lock\LockBackendInterface $lock
|
||||
* The lock backend.
|
||||
* @param \Drupal\Core\Asset\LibraryDiscoveryParser $discovery_parser
|
||||
* The library discovery parser.
|
||||
*/
|
||||
public function __construct(CacheBackendInterface $cache, LockBackendInterface $lock, LibraryDiscoveryParser $discovery_parser, ThemeManagerInterface $theme_manager) {
|
||||
$this->themeManager = $theme_manager;
|
||||
parent::__construct(NULL, $cache, $lock, ['library_info']);
|
||||
|
||||
$this->discoveryParser = $discovery_parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getCid() {
|
||||
if (!isset($this->cid)) {
|
||||
$this->cid = 'library_info:' . $this->themeManager->getActiveTheme()->getName();
|
||||
}
|
||||
|
||||
return $this->cid;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function resolveCacheMiss($key) {
|
||||
$this->storage[$key] = $this->discoveryParser->buildByExtension($key);
|
||||
$this->persist($key);
|
||||
|
||||
return $this->storage[$key];
|
||||
}
|
||||
}
|
58
core/lib/Drupal/Core/Asset/LibraryDiscoveryInterface.php
Normal file
58
core/lib/Drupal/Core/Asset/LibraryDiscoveryInterface.php
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\LibraryDiscoveryInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
/**
|
||||
* Discovers information for asset (CSS/JavaScript) libraries.
|
||||
*
|
||||
* Library information is statically cached. Libraries are keyed by extension
|
||||
* for several reasons:
|
||||
* - Libraries are not unique. Multiple extensions might ship with the same
|
||||
* library in a different version or variant. This registry cannot (and does
|
||||
* not attempt to) prevent library conflicts.
|
||||
* - Extensions implementing and thereby depending on a library that is
|
||||
* registered by another extension can only rely on that extension's library.
|
||||
* - Two (or more) extensions can still register the same library and use it
|
||||
* without conflicts in case the libraries are loaded on certain pages only.
|
||||
*/
|
||||
interface LibraryDiscoveryInterface {
|
||||
|
||||
/**
|
||||
* Gets all libraries defined by an extension.
|
||||
*
|
||||
* @param string $extension
|
||||
* The name of the extension that registered a library.
|
||||
*
|
||||
* @return array
|
||||
* An associative array of libraries registered by $extension is returned
|
||||
* (which may be empty).
|
||||
*
|
||||
* @see self::getLibraryByName()
|
||||
*/
|
||||
public function getLibrariesByExtension($extension);
|
||||
|
||||
/**
|
||||
* Gets a single library defined by an extension by name.
|
||||
*
|
||||
* @param string $extension
|
||||
* The name of the extension that registered a library.
|
||||
* @param string $name
|
||||
* The name of a registered library to retrieve.
|
||||
*
|
||||
* @return array|FALSE
|
||||
* The definition of the requested library, if $name was passed and it
|
||||
* exists, otherwise FALSE.
|
||||
*/
|
||||
public function getLibraryByName($extension, $name);
|
||||
|
||||
/**
|
||||
* Clears static and persistent library definition caches.
|
||||
*/
|
||||
public function clearCachedDefinitions();
|
||||
|
||||
}
|
330
core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php
Normal file
330
core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php
Normal file
|
@ -0,0 +1,330 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Asset\LibraryDiscoveryParser.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Asset;
|
||||
|
||||
use Drupal\Core\Asset\Exception\IncompleteLibraryDefinitionException;
|
||||
use Drupal\Core\Asset\Exception\InvalidLibraryFileException;
|
||||
use Drupal\Core\Asset\Exception\LibraryDefinitionMissingLicenseException;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Theme\ThemeManagerInterface;
|
||||
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
|
||||
use Drupal\Component\Serialization\Yaml;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
|
||||
/**
|
||||
* Parses library files to get extension data.
|
||||
*/
|
||||
class LibraryDiscoveryParser {
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The theme manager.
|
||||
*
|
||||
* @var \Drupal\Core\Theme\ThemeManagerInterface
|
||||
*/
|
||||
protected $themeManager;
|
||||
|
||||
/**
|
||||
* The app root.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $root;
|
||||
|
||||
/**
|
||||
* Constructs a new LibraryDiscoveryParser instance.
|
||||
*
|
||||
* @param string $root
|
||||
* The app root.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
*/
|
||||
public function __construct($root, ModuleHandlerInterface $module_handler, ThemeManagerInterface $theme_manager) {
|
||||
$this->root = $root;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->themeManager = $theme_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses and builds up all the libraries information of an extension.
|
||||
*
|
||||
* @param string $extension
|
||||
* The name of the extension that registered a library.
|
||||
*
|
||||
* @return array
|
||||
* All library definitions of the passed extension.
|
||||
*
|
||||
* @throws \Drupal\Core\Asset\Exception\IncompleteLibraryDefinitionException
|
||||
* Thrown when a library has no js/css/setting.
|
||||
* @throws \UnexpectedValueException
|
||||
* Thrown when a js file defines a positive weight.
|
||||
*/
|
||||
public function buildByExtension($extension) {
|
||||
$libraries = array();
|
||||
|
||||
if ($extension === 'core') {
|
||||
$path = 'core';
|
||||
$extension_type = 'core';
|
||||
}
|
||||
else {
|
||||
if ($this->moduleHandler->moduleExists($extension)) {
|
||||
$extension_type = 'module';
|
||||
}
|
||||
else {
|
||||
$extension_type = 'theme';
|
||||
}
|
||||
$path = $this->drupalGetPath($extension_type, $extension);
|
||||
}
|
||||
|
||||
$libraries = $this->parseLibraryInfo($extension, $path);
|
||||
|
||||
foreach ($libraries as $id => &$library) {
|
||||
if (!isset($library['js']) && !isset($library['css']) && !isset($library['drupalSettings'])) {
|
||||
throw new IncompleteLibraryDefinitionException(sprintf("Incomplete library definition for definition '%s' in extension '%s'", $id, $extension));
|
||||
}
|
||||
$library += array('dependencies' => array(), 'js' => array(), 'css' => array());
|
||||
|
||||
if (isset($library['header']) && !is_bool($library['header'])) {
|
||||
throw new \LogicException(sprintf("The 'header' key in the library definition '%s' in extension '%s' is invalid: it must be a boolean.", $id, $extension));
|
||||
}
|
||||
|
||||
if (isset($library['version'])) {
|
||||
// @todo Retrieve version of a non-core extension.
|
||||
if ($library['version'] === 'VERSION') {
|
||||
$library['version'] = \Drupal::VERSION;
|
||||
}
|
||||
// Remove 'v' prefix from external library versions.
|
||||
elseif ($library['version'][0] === 'v') {
|
||||
$library['version'] = substr($library['version'], 1);
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a 3rd party library, the license info is required.
|
||||
if (isset($library['remote']) && !isset($library['license'])) {
|
||||
throw new LibraryDefinitionMissingLicenseException(sprintf("Missing license information in library definition for definition '%s' extension '%s': it has a remote, but no license.", $id, $extension));
|
||||
}
|
||||
|
||||
// Assign Drupal's license to libraries that don't have license info.
|
||||
if (!isset($library['license'])) {
|
||||
$library['license'] = array(
|
||||
'name' => 'GNU-GPL-2.0-or-later',
|
||||
'url' => 'https://www.drupal.org/licensing/faq',
|
||||
'gpl-compatible' => TRUE,
|
||||
);
|
||||
}
|
||||
|
||||
foreach (array('js', 'css') as $type) {
|
||||
// Prepare (flatten) the SMACSS-categorized definitions.
|
||||
// @todo After Asset(ic) changes, retain the definitions as-is and
|
||||
// properly resolve dependencies for all (css) libraries per category,
|
||||
// and only once prior to rendering out an HTML page.
|
||||
if ($type == 'css' && !empty($library[$type])) {
|
||||
foreach ($library[$type] as $category => $files) {
|
||||
foreach ($files as $source => $options) {
|
||||
if (!isset($options['weight'])) {
|
||||
$options['weight'] = 0;
|
||||
}
|
||||
// Apply the corresponding weight defined by CSS_* constants.
|
||||
$options['weight'] += constant('CSS_' . strtoupper($category));
|
||||
$library[$type][$source] = $options;
|
||||
}
|
||||
unset($library[$type][$category]);
|
||||
}
|
||||
}
|
||||
foreach ($library[$type] as $source => $options) {
|
||||
unset($library[$type][$source]);
|
||||
// Allow to omit the options hashmap in YAML declarations.
|
||||
if (!is_array($options)) {
|
||||
$options = array();
|
||||
}
|
||||
if ($type == 'js' && isset($options['weight']) && $options['weight'] > 0) {
|
||||
throw new \UnexpectedValueException("The $extension/$id library defines a positive weight for '$source'. Only negative weights are allowed (but should be avoided). Instead of a positive weight, specify accurate dependencies for this library.");
|
||||
}
|
||||
// Unconditionally apply default groups for the defined asset files.
|
||||
// The library system is a dependency management system. Each library
|
||||
// properly specifies its dependencies instead of relying on a custom
|
||||
// processing order.
|
||||
if ($type == 'js') {
|
||||
$options['group'] = JS_LIBRARY;
|
||||
}
|
||||
elseif ($type == 'css') {
|
||||
$options['group'] = $extension_type == 'theme' ? CSS_AGGREGATE_THEME : CSS_AGGREGATE_DEFAULT;
|
||||
}
|
||||
// By default, all library assets are files.
|
||||
if (!isset($options['type'])) {
|
||||
$options['type'] = 'file';
|
||||
}
|
||||
if ($options['type'] == 'external') {
|
||||
$options['data'] = $source;
|
||||
}
|
||||
// Determine the file asset URI.
|
||||
else {
|
||||
if ($source[0] === '/') {
|
||||
// An absolute path maps to DRUPAL_ROOT / base_path().
|
||||
if ($source[1] !== '/') {
|
||||
$options['data'] = substr($source, 1);
|
||||
}
|
||||
// A protocol-free URI (e.g., //cdn.com/example.js) is external.
|
||||
else {
|
||||
$options['type'] = 'external';
|
||||
$options['data'] = $source;
|
||||
}
|
||||
}
|
||||
// A stream wrapper URI (e.g., public://generated_js/example.js).
|
||||
elseif ($this->fileValidUri($source)) {
|
||||
$options['data'] = $source;
|
||||
}
|
||||
// By default, file paths are relative to the registering extension.
|
||||
else {
|
||||
$options['data'] = $path . '/' . $source;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($library['version'])) {
|
||||
// @todo Get the information from the extension.
|
||||
$options['version'] = -1;
|
||||
}
|
||||
else {
|
||||
$options['version'] = $library['version'];
|
||||
}
|
||||
|
||||
// Set the 'minified' flag on JS file assets, default to FALSE.
|
||||
if ($type == 'js' && $options['type'] == 'file') {
|
||||
$options['minified'] = isset($options['minified']) ? $options['minified'] : FALSE;
|
||||
}
|
||||
|
||||
$library[$type][] = $options;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $libraries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a given library file and allows modules and themes to alter it.
|
||||
*
|
||||
* This method sets the parsed information onto the library property.
|
||||
*
|
||||
* Library information is parsed from *.libraries.yml files; see
|
||||
* editor.library.yml for an example. Every library must have at least one js
|
||||
* or css entry. Each entry starts with a machine name and defines the
|
||||
* following elements:
|
||||
* - js: A list of JavaScript files to include. Each file is keyed by the file
|
||||
* path. An item can have several attributes (like HTML
|
||||
* attributes). For example:
|
||||
* @code
|
||||
* js:
|
||||
* path/js/file.js: { attributes: { defer: true } }
|
||||
* @endcode
|
||||
* If the file has no special attributes, just use an empty object:
|
||||
* @code
|
||||
* js:
|
||||
* path/js/file.js: {}
|
||||
* @endcode
|
||||
* The path of the file is relative to the module or theme directory, unless
|
||||
* it starts with a /, in which case it is relative to the Drupal root. If
|
||||
* the file path starts with //, it will be treated as a protocol-free,
|
||||
* external resource (e.g., //cdn.com/library.js). Full URLs
|
||||
* (e.g., http://cdn.com/library.js) as well as URLs that use a valid
|
||||
* stream wrapper (e.g., public://path/to/file.js) are also supported.
|
||||
* - css: A list of categories for which the library provides CSS files. The
|
||||
* available categories are:
|
||||
* - base
|
||||
* - layout
|
||||
* - component
|
||||
* - state
|
||||
* - theme
|
||||
* Each category is itself a key for a sub-list of CSS files to include:
|
||||
* @code
|
||||
* css:
|
||||
* component:
|
||||
* css/file.css: {}
|
||||
* @endcode
|
||||
* Just like with JavaScript files, each CSS file is the key of an object
|
||||
* that can define specific attributes. The format of the file path is the
|
||||
* same as for the JavaScript files.
|
||||
* - dependencies: A list of libraries this library depends on.
|
||||
* - version: The library version. The string "VERSION" can be used to mean
|
||||
* the current Drupal core version.
|
||||
* - header: By default, JavaScript files are included in the footer. If the
|
||||
* script must be included in the header (along with all its dependencies),
|
||||
* set this to true. Defaults to false.
|
||||
* - minified: If the file is already minified, set this to true to avoid
|
||||
* minifying it again. Defaults to false.
|
||||
* - remote: If the library is a third-party script, this provides the
|
||||
* repository URL for reference.
|
||||
* - license: If the remote property is set, the license information is
|
||||
* required. It has 3 properties:
|
||||
* - name: The human-readable name of the license.
|
||||
* - url: The URL of the license file/information for the version of the
|
||||
* library used.
|
||||
* - gpl-compatible: A Boolean for whether this library is GPL compatible.
|
||||
*
|
||||
* See https://www.drupal.org/node/2274843#define-library for more
|
||||
* information.
|
||||
*
|
||||
* @param string $extension
|
||||
* The name of the extension that registered a library.
|
||||
* @param string $path
|
||||
* The relative path to the extension.
|
||||
*
|
||||
* @return array
|
||||
* An array of parsed library data.
|
||||
*
|
||||
* @throws \Drupal\Core\Asset\Exception\InvalidLibraryFileException
|
||||
* Thrown when a parser exception got thrown.
|
||||
*/
|
||||
protected function parseLibraryInfo($extension, $path) {
|
||||
$libraries = [];
|
||||
|
||||
$library_file = $path . '/' . $extension . '.libraries.yml';
|
||||
if (file_exists($this->root . '/' . $library_file)) {
|
||||
try {
|
||||
$libraries = Yaml::decode(file_get_contents($this->root . '/' . $library_file));
|
||||
}
|
||||
catch (InvalidDataTypeException $e) {
|
||||
// Rethrow a more helpful exception to provide context.
|
||||
throw new InvalidLibraryFileException(sprintf('Invalid library definition in %s: %s', $library_file, $e->getMessage()), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
// Allow modules to add dynamic library definitions.
|
||||
$hook = 'library_info_build';
|
||||
if ($this->moduleHandler->implementsHook($extension, $hook)) {
|
||||
$libraries = NestedArray::mergeDeep($libraries, $this->moduleHandler->invoke($extension, $hook));
|
||||
}
|
||||
|
||||
// Allow modules to alter the module's registered libraries.
|
||||
$this->moduleHandler->alter('library_info', $libraries, $extension);
|
||||
$this->themeManager->alter('library_info', $libraries, $extension);
|
||||
|
||||
return $libraries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps drupal_get_path().
|
||||
*/
|
||||
protected function drupalGetPath($type, $name) {
|
||||
return drupal_get_path($type, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps file_valid_uri().
|
||||
*/
|
||||
protected function fileValidUri($source) {
|
||||
return file_valid_uri($source);
|
||||
}
|
||||
|
||||
}
|
248
core/lib/Drupal/Core/Authentication/AuthenticationManager.php
Normal file
248
core/lib/Drupal/Core/Authentication/AuthenticationManager.php
Normal file
|
@ -0,0 +1,248 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Authentication\AuthenticationManager.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Authentication;
|
||||
|
||||
use Drupal\Core\Routing\RouteMatch;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Manager for authentication.
|
||||
*
|
||||
* On each request, let all authentication providers try to authenticate the
|
||||
* user. The providers are iterated according to their priority and the first
|
||||
* provider detecting credentials for its method wins. No further provider will
|
||||
* get triggered.
|
||||
*
|
||||
* If no provider set an active user then the user is set to anonymous.
|
||||
*/
|
||||
class AuthenticationManager implements AuthenticationProviderInterface, AuthenticationProviderFilterInterface, AuthenticationProviderChallengeInterface, AuthenticationManagerInterface {
|
||||
|
||||
/**
|
||||
* Array of all registered authentication providers, keyed by ID.
|
||||
*
|
||||
* @var \Drupal\Core\Authentication\AuthenticationProviderInterface[]
|
||||
*/
|
||||
protected $providers;
|
||||
|
||||
/**
|
||||
* Array of all providers and their priority.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $providerOrders = array();
|
||||
|
||||
/**
|
||||
* Sorted list of registered providers.
|
||||
*
|
||||
* @var \Drupal\Core\Authentication\AuthenticationProviderInterface[]
|
||||
*/
|
||||
protected $sortedProviders;
|
||||
|
||||
/**
|
||||
* List of providers which implement the filter interface.
|
||||
*
|
||||
* @var \Drupal\Core\Authentication\AuthenticationProviderFilterInterface[]
|
||||
*/
|
||||
protected $filters;
|
||||
|
||||
/**
|
||||
* List of providers which implement the challenge interface.
|
||||
*
|
||||
* @var \Drupal\Core\Authentication\AuthenticationProviderChallengeInterface[]
|
||||
*/
|
||||
protected $challengers;
|
||||
|
||||
/**
|
||||
* List of providers which are allowed on routes with no _auth option.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $globalProviders;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addProvider(AuthenticationProviderInterface $provider, $provider_id, $priority = 0, $global = FALSE) {
|
||||
$this->providers[$provider_id] = $provider;
|
||||
$this->providerOrders[$priority][$provider_id] = $provider;
|
||||
// Force the builders to be re-sorted.
|
||||
$this->sortedProviders = NULL;
|
||||
|
||||
if ($provider instanceof AuthenticationProviderFilterInterface) {
|
||||
$this->filters[$provider_id] = $provider;
|
||||
}
|
||||
if ($provider instanceof AuthenticationProviderChallengeInterface) {
|
||||
$this->challengers[$provider_id] = $provider;
|
||||
}
|
||||
|
||||
if ($global) {
|
||||
$this->globalProviders[$provider_id] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applies(Request $request) {
|
||||
return (bool) $this->getProvider($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function authenticate(Request $request) {
|
||||
$provider_id = $this->getProvider($request);
|
||||
return $this->providers[$provider_id]->authenticate($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function appliesToRoutedRequest(Request $request, $authenticated) {
|
||||
$result = FALSE;
|
||||
|
||||
if ($authenticated) {
|
||||
$result = $this->applyFilter($request, $authenticated, $this->getProvider($request));
|
||||
}
|
||||
else {
|
||||
foreach ($this->getSortedProviders() as $provider_id => $provider) {
|
||||
if ($this->applyFilter($request, $authenticated, $provider_id)) {
|
||||
$result = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function challengeException(Request $request, \Exception $previous) {
|
||||
$provider_id = $this->getChallenger($request);
|
||||
if ($provider_id) {
|
||||
return $this->challengers[$provider_id]->challengeException($request, $previous);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id of the authentication provider for a request.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The incoming request.
|
||||
*
|
||||
* @return string|NULL
|
||||
* The id of the first authentication provider which applies to the request.
|
||||
* If no application detects appropriate credentials, then NULL is returned.
|
||||
*/
|
||||
protected function getProvider(Request $request) {
|
||||
foreach ($this->getSortedProviders() as $provider_id => $provider) {
|
||||
if ($provider->applies($request)) {
|
||||
return $provider_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id of the challenge provider for a request.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The incoming request.
|
||||
*
|
||||
* @return string|NULL
|
||||
* The id of the first authentication provider which applies to the request.
|
||||
* If no application detects appropriate credentials, then NULL is returned.
|
||||
*/
|
||||
protected function getChallenger(Request $request) {
|
||||
if (!empty($this->challengers)) {
|
||||
foreach ($this->getSortedProviders($request, FALSE) as $provider_id => $provider) {
|
||||
if (isset($this->challengers[$provider_id]) && !$provider->applies($request) && $this->applyFilter($request, FALSE, $provider_id)) {
|
||||
return $provider_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a provider is allowed on the given request.
|
||||
*
|
||||
* If no filter is registered for the given provider id, the default filter
|
||||
* is applied.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The incoming request.
|
||||
* @param bool $authenticated
|
||||
* Whether or not the request is authenticated.
|
||||
* @param string $provider_id
|
||||
* The id of the authentication provider to check access for.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if provider is allowed, FALSE otherwise.
|
||||
*/
|
||||
protected function applyFilter(Request $request, $authenticated, $provider_id) {
|
||||
if (isset($this->filters[$provider_id])) {
|
||||
$result = $this->filters[$provider_id]->appliesToRoutedRequest($request, $authenticated);
|
||||
}
|
||||
else {
|
||||
$result = $this->defaultFilter($request, $provider_id);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of the provider filter.
|
||||
*
|
||||
* Checks whether a provider is allowed as per the _auth option on a route. If
|
||||
* the option is not set or if the request did not match any route, only
|
||||
* providers from the global provider set are allowed.
|
||||
*
|
||||
* If no filter is registered for the given provider id, the default filter
|
||||
* is applied.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The incoming request.
|
||||
* @param string $provider_id
|
||||
* The id of the authentication provider to check access for.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if provider is allowed, FALSE otherwise.
|
||||
*/
|
||||
protected function defaultFilter(Request $request, $provider_id) {
|
||||
$route = RouteMatch::createFromRequest($request)->getRouteObject();
|
||||
$has_auth_option = isset($route) && $route->hasOption('_auth');
|
||||
|
||||
if ($has_auth_option) {
|
||||
return in_array($provider_id, $route->getOption('_auth'));
|
||||
}
|
||||
else {
|
||||
return isset($this->globalProviders[$provider_id]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sorted array of authentication providers.
|
||||
*
|
||||
* @return \Drupal\Core\Authentication\AuthenticationProviderInterface[]
|
||||
* An array of authentication provider objects.
|
||||
*/
|
||||
protected function getSortedProviders() {
|
||||
if (!isset($this->sortedProviders)) {
|
||||
// Sort the builders according to priority.
|
||||
krsort($this->providerOrders);
|
||||
// Merge nested providers from $this->providers into $this->sortedProviders.
|
||||
$this->sortedProviders = array();
|
||||
foreach ($this->providerOrders as $providers) {
|
||||
$this->sortedProviders = array_merge($this->sortedProviders, $providers);
|
||||
}
|
||||
}
|
||||
return $this->sortedProviders;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Authentication\AuthenticationManagerInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Authentication;
|
||||
|
||||
/**
|
||||
* Defines an interface for authentication managers.
|
||||
*/
|
||||
interface AuthenticationManagerInterface {
|
||||
|
||||
/**
|
||||
* Adds a provider to the array of registered providers.
|
||||
*
|
||||
* @param \Drupal\Core\Authentication\AuthenticationProviderInterface $provider
|
||||
* The provider object.
|
||||
* @param string $provider_id
|
||||
* Identifier of the provider.
|
||||
* @param int $priority
|
||||
* (optional) The provider's priority.
|
||||
* @param bool $global
|
||||
* (optional) TRUE if the provider is to be applied globally on all routes.
|
||||
* Defaults to FALSE.
|
||||
*/
|
||||
public function addProvider(AuthenticationProviderInterface $provider, $provider_id, $priority = 0, $global = FALSE);
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Authentication\AuthenticationProviderChallengeInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Authentication;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Generate a challenge when access is denied for unauthenticated users.
|
||||
*
|
||||
* On a 403 (access denied), if there are no credentials on the request, some
|
||||
* authentication methods (e.g. basic auth) require that a challenge is sent to
|
||||
* the client.
|
||||
*/
|
||||
interface AuthenticationProviderChallengeInterface {
|
||||
|
||||
/**
|
||||
* Constructs an exception which is used to generate the challenge.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\Request
|
||||
* The request.
|
||||
* @var \Exception $exception
|
||||
* The previous exception.
|
||||
*
|
||||
* @return \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface|NULL
|
||||
* An exception to be used in order to generate an authentication challenge.
|
||||
*/
|
||||
public function challengeException(Request $request, \Exception $previous);
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Authentication\AuthenticationProviderFilterInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Authentication;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Restrict authentication methods to a subset of the site.
|
||||
*
|
||||
* Some authentication methods should not be available throughout a whole site.
|
||||
* E.g., there are good reasons to restrict insecure methods like HTTP basic
|
||||
* auth or an URL token authentication method to API-only routes.
|
||||
*/
|
||||
interface AuthenticationProviderFilterInterface {
|
||||
|
||||
/**
|
||||
* Checks whether the authentication method is allowed on a given route.
|
||||
*
|
||||
* While authentication itself is run before routing, this method is called
|
||||
* after routing, hence RouteMatch is available and can be used to inspect
|
||||
* route options.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request.
|
||||
* @param bool $authenticated
|
||||
* Whether or not the request is authenticated.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if an authentication method is allowed on the request, otherwise
|
||||
* FALSE.
|
||||
*/
|
||||
public function appliesToRoutedRequest(Request $request, $authenticated);
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Authentication\AuthenticationProviderInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Authentication;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Interface for authentication providers.
|
||||
*/
|
||||
interface AuthenticationProviderInterface {
|
||||
|
||||
/**
|
||||
* Checks whether suitable authentication credentials are on the request.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request object.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if authentication credentials suitable for this provider are on the
|
||||
* request, FALSE otherwise.
|
||||
*/
|
||||
public function applies(Request $request);
|
||||
|
||||
/**
|
||||
* Authenticates the user.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request|NULL $request
|
||||
* The request object.
|
||||
*
|
||||
* @return \Drupal\Core\Session\AccountInterface|NULL
|
||||
* AccountInterface - in case of a successful authentication.
|
||||
* NULL - in case where authentication failed.
|
||||
*/
|
||||
public function authenticate(Request $request);
|
||||
|
||||
}
|
114
core/lib/Drupal/Core/Batch/BatchStorage.php
Normal file
114
core/lib/Drupal/Core/Batch/BatchStorage.php
Normal file
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Batch\BatchStorage.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Batch;
|
||||
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Drupal\Core\Access\CsrfTokenGenerator;
|
||||
|
||||
class BatchStorage implements BatchStorageInterface {
|
||||
|
||||
/**
|
||||
* The database connection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The session.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\Session\SessionInterface
|
||||
*/
|
||||
protected $session;
|
||||
|
||||
/**
|
||||
* The CSRF token generator.
|
||||
*
|
||||
* @var \Drupal\Core\Access\CsrfTokenGenerator
|
||||
*/
|
||||
protected $csrfToken;
|
||||
|
||||
/**
|
||||
* Constructs the database batch storage service.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $connection
|
||||
* The database connection.
|
||||
* @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session
|
||||
* The session.
|
||||
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
|
||||
* The CSRF token generator.
|
||||
*/
|
||||
public function __construct(Connection $connection, SessionInterface $session, CsrfTokenGenerator $csrf_token) {
|
||||
$this->connection = $connection;
|
||||
$this->session = $session;
|
||||
$this->csrfToken = $csrf_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load($id) {
|
||||
// Ensure that a session is started before using the CSRF token generator.
|
||||
$this->session->start();
|
||||
$batch = $this->connection->query("SELECT batch FROM {batch} WHERE bid = :bid AND token = :token", array(
|
||||
':bid' => $id,
|
||||
':token' => $this->csrfToken->get($id),
|
||||
))->fetchField();
|
||||
if ($batch) {
|
||||
return unserialize($batch);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete($id) {
|
||||
$this->connection->delete('batch')
|
||||
->condition('bid', $id)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function update(array $batch) {
|
||||
$this->connection->update('batch')
|
||||
->fields(array('batch' => serialize($batch)))
|
||||
->condition('bid', $batch['id'])
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function cleanup() {
|
||||
// Cleanup the batch table and the queue for failed batches.
|
||||
$this->connection->delete('batch')
|
||||
->condition('timestamp', REQUEST_TIME - 864000, '<')
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function create(array $batch) {
|
||||
// Ensure that a session is started before using the CSRF token generator.
|
||||
$this->session->start();
|
||||
$this->connection->insert('batch')
|
||||
->fields(array(
|
||||
'bid' => $batch['id'],
|
||||
'timestamp' => REQUEST_TIME,
|
||||
'token' => $this->csrfToken->get($batch['id']),
|
||||
'batch' => serialize($batch),
|
||||
))
|
||||
->execute();
|
||||
}
|
||||
|
||||
}
|
55
core/lib/Drupal/Core/Batch/BatchStorageInterface.php
Normal file
55
core/lib/Drupal/Core/Batch/BatchStorageInterface.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Batch\BatchStorageInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Batch;
|
||||
|
||||
/**
|
||||
* Defines a common interface for batch storage operations.
|
||||
*/
|
||||
interface BatchStorageInterface {
|
||||
|
||||
/**
|
||||
* Loads a batch.
|
||||
*
|
||||
* @param int $id
|
||||
* The ID of the batch to load.
|
||||
*
|
||||
* @return array
|
||||
* An array representing the batch, or FALSE if no batch was found.
|
||||
*/
|
||||
public function load($id);
|
||||
|
||||
/**
|
||||
* Creates and saves a batch.
|
||||
*
|
||||
* @param array $batch
|
||||
* The array representing the batch to create.
|
||||
*/
|
||||
public function create(array $batch);
|
||||
|
||||
/**
|
||||
* Updates a batch.
|
||||
*
|
||||
* @param array $batch
|
||||
* The array representing the batch to update.
|
||||
*/
|
||||
public function update(array $batch);
|
||||
|
||||
/**
|
||||
* Deletes a batch.
|
||||
*
|
||||
* @param int $id
|
||||
* The ID of the batch to delete.
|
||||
*/
|
||||
public function delete($id);
|
||||
|
||||
/**
|
||||
* Cleans up failed or old batches.
|
||||
*/
|
||||
public function cleanup();
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue