Skip to content
Commits on Source (14)
......@@ -165,21 +165,6 @@ services:
calls:
- [addSubscriber, ['@http_client_simpletest_subscriber']]
- [setUserAgent, ['Drupal (+http://drupal.org/)']]
theme.negotiator:
class: Drupal\Core\Theme\ThemeNegotiator
arguments: ['@access_check.theme']
calls:
- [setRequest, ['@request']]
theme.negotiator.default:
class: Drupal\Core\Theme\DefaultNegotiator
arguments: ['@config.factory']
tags:
- { name: theme_negotiator, priority: -100 }
theme.negotiator.ajax_base_page:
class: Drupal\Core\Theme\AjaxBasePageNegotiator
arguments: ['@csrf_token', '@config.factory']
tags:
- { name: theme_negotiator, priority: 1000 }
container.namespaces:
class: ArrayObject
arguments: [ '%container.namespaces%' ]
......
......@@ -302,6 +302,42 @@ function ajax_render($commands = array()) {
return drupal_json_encode($commands);
}
/**
* Theme callback: Returns the correct theme for an Ajax request.
*
* Many different pages can invoke an Ajax request to system/ajax or another
* generic Ajax path. It is almost always desired for an Ajax response to be
* rendered using the same theme as the base page, because most themes are built
* with the assumption that they control the entire page, so if the CSS for two
* themes are both loaded for a given page, they may conflict with each other.
* For example, Bartik is Drupal's default theme, and Seven is Drupal's default
* administration theme. Depending on whether the "Use the administration theme
* when editing or creating content" checkbox is checked, the node edit form may
* be displayed in either theme, but the Ajax response to the Field module's
* "Add another item" button should be rendered using the same theme as the rest
* of the page. Therefore, system_menu() sets the 'theme callback' for
* 'system/ajax' to this function, and it is recommended that modules
* implementing other generic Ajax paths do the same.
*
* @see system_menu()
* @see file_menu()
*/
function ajax_base_page_theme() {
if (!empty($_POST['ajax_page_state']['theme']) && !empty($_POST['ajax_page_state']['theme_token'])) {
$theme = $_POST['ajax_page_state']['theme'];
$token = $_POST['ajax_page_state']['theme_token'];
// Prevent a request forgery from giving a person access to a theme they
// shouldn't be otherwise allowed to see. However, since everyone is allowed
// to see the default theme, token validation isn't required for that, and
// bypassing it allows most use-cases to work even when accessed from the
// page cache.
if ($theme === \Drupal::config('system.theme')->get('default') || drupal_valid_token($token, $theme)) {
return $theme;
}
}
}
/**
* Converts the return value of a page callback into an Ajax commands array.
*
......
......@@ -399,14 +399,15 @@ function drupal_add_feed($url = NULL, $title = '') {
if (isset($url)) {
$stored_feed_links[$url] = theme('feed_icon', array('url' => $url, 'title' => $title));
drupal_add_html_head_link(array(
$build['#attached']['drupal_add_html_head_link'][][] = array(
'rel' => 'alternate',
'type' => 'application/rss+xml',
'title' => $title,
// Force the URL to be absolute, for consistency with other <link> tags
// output by Drupal.
'href' => url($url, array('absolute' => TRUE)),
));
);
drupal_render($build);
}
return $stored_feed_links;
}
......@@ -2330,7 +2331,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
global $theme_key;
// Provide the page with information about the theme that's used, so that
// a later AJAX request can be rendered using the same theme.
// @see \Drupal\Core\Theme\AjaxBasePageNegotiator
// @see ajax_base_page_theme()
$setting['ajaxPageState']['theme'] = $theme_key;
// Checks that the DB is available before filling theme_token.
if (!defined('MAINTENANCE_MODE')) {
......@@ -3147,6 +3148,7 @@ function _drupal_bootstrap_full($skip = FALSE) {
// Let all modules take action before the menu system handles the request.
// We do not want this while running update.php.
if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
menu_set_custom_theme();
drupal_theme_initialize();
}
}
......
......@@ -463,8 +463,8 @@ function menu_set_item($path, $router_item) {
* menu_router table. The value corresponding to the key 'map' holds the
* loaded objects. The value corresponding to the key 'access' is TRUE if the
* current user can access this page. The values corresponding to the keys
* 'title', 'page_arguments', and 'access_arguments', will be filled in based
* on the database values and the objects loaded.
* 'title', 'page_arguments', 'access_arguments', and 'theme_arguments' will
* be filled in based on the database values and the objects loaded.
*/
function menu_get_item($path = NULL, $router_item = NULL) {
$router_items = &drupal_static(__FUNCTION__);
......@@ -501,6 +501,7 @@ function menu_get_item($path = NULL, $router_item = NULL) {
if ($router_item['access']) {
$router_item['map'] = $map;
$router_item['page_arguments'] = array_merge(menu_unserialize($router_item['page_arguments'], $map), array_slice($map, $router_item['number_parts']));
$router_item['theme_arguments'] = array_merge(menu_unserialize($router_item['theme_arguments'], $map), array_slice($map, $router_item['number_parts']));
}
}
$router_items[$path] = $router_item;
......@@ -1794,6 +1795,51 @@ function drupal_help_arg($arg = array()) {
return $arg + array('', '', '', '', '', '', '', '', '', '', '', '');
}
/**
* Gets the custom theme for the current page, if there is one.
*
* @param $initialize
* This parameter should only be used internally; it is set to TRUE in order
* to force the custom theme to be initialized for the current page request.
*
* @return
* The machine-readable name of the custom theme, if there is one.
*
* @see menu_set_custom_theme()
*/
function menu_get_custom_theme($initialize = FALSE) {
$custom_theme = &drupal_static(__FUNCTION__);
// Skip this if the site is offline or being installed or updated, since the
// menu system may not be correctly initialized then.
if ($initialize && !_menu_site_is_offline(TRUE) && (!defined('MAINTENANCE_MODE') || (MAINTENANCE_MODE != 'update' && MAINTENANCE_MODE != 'install'))) {
// First allow modules to dynamically set a custom theme for the current
// page. Since we can only have one, the last module to return a valid
// theme takes precedence.
$custom_themes = array_filter(\Drupal::moduleHandler()->invokeAll('custom_theme'), 'drupal_theme_access');
if (!empty($custom_themes)) {
$custom_theme = array_pop($custom_themes);
}
// If there is a theme callback function for the current page, execute it.
// If this returns a valid theme, it will override any theme that was set
// by a hook_custom_theme() implementation above.
$router_item = menu_get_item();
if (!empty($router_item['access']) && !empty($router_item['theme_callback'])) {
$theme_name = call_user_func_array($router_item['theme_callback'], $router_item['theme_arguments']);
if (drupal_theme_access($theme_name)) {
$custom_theme = $theme_name;
}
}
}
return $custom_theme;
}
/**
* Sets a custom theme for the current page, if there is one.
*/
function menu_set_custom_theme() {
menu_get_custom_theme(TRUE);
}
/**
* Returns an array containing the names of system-defined (default) menus.
*/
......@@ -3020,6 +3066,13 @@ function _menu_router_build($callbacks, $save = FALSE) {
}
}
}
// Same for theme callbacks.
if (!isset($item['theme callback']) && isset($parent['theme callback'])) {
$item['theme callback'] = $parent['theme callback'];
if (!isset($item['theme arguments']) && isset($parent['theme arguments'])) {
$item['theme arguments'] = $parent['theme arguments'];
}
}
// Same for load arguments: if a loader doesn't have any explict
// arguments, try to find arguments in the parent.
if (!isset($item['load arguments'])) {
......@@ -3056,6 +3109,8 @@ function _menu_router_build($callbacks, $save = FALSE) {
'page callback' => '',
'title arguments' => array(),
'title callback' => 't',
'theme arguments' => array(),
'theme callback' => '',
'description' => '',
'description arguments' => array(),
'description callback' => 't',
......@@ -3120,6 +3175,8 @@ function _menu_router_save($menu, $masks) {
'title',
'title_callback',
'title_arguments',
'theme_callback',
'theme_arguments',
'type',
'description',
'description_callback',
......@@ -3150,6 +3207,8 @@ function _menu_router_save($menu, $masks) {
'title' => $item['title'],
'title_callback' => $item['title callback'],
'title_arguments' => ($item['title arguments'] ? serialize($item['title arguments']) : ''),
'theme_callback' => $item['theme callback'],
'theme_arguments' => serialize($item['theme arguments']),
'type' => $item['type'],
'description' => $item['description'],
'description_callback' => $item['description callback'],
......
......@@ -92,14 +92,16 @@ function drupal_theme_initialize() {
}
drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
$themes = list_themes();
// @todo Let the theme.negotiator listen to the kernel request event.
// Determine the active theme for the theme negotiator service. This includes
// the default theme as well as really specific ones like the ajax base theme.
$request = \Drupal::request();
$theme = \Drupal::service('theme.negotiator')->determineActiveTheme($request) ?: 'stark';
// Only select the user selected theme if it is available in the
// list of themes that can be accessed.
$theme = !empty($user->theme) && drupal_theme_access($user->theme) ? $user->theme : \Drupal::config('system.theme')->get('default');
// Allow modules to override the theme. Validation has already been performed
// inside menu_get_custom_theme(), so we do not need to check it again here.
$custom_theme = menu_get_custom_theme();
$theme = !empty($custom_theme) ? $custom_theme : $theme;
// Store the identifier for retrieving theme settings with.
$theme_key = $theme;
......@@ -112,6 +114,9 @@ function drupal_theme_initialize() {
$base_theme[] = $themes[$ancestor];
}
_drupal_theme_initialize($themes[$theme], array_reverse($base_theme));
// Themes can have alter functions, so reset the drupal_alter() cache.
drupal_static_reset('drupal_alter');
}
/**
......@@ -363,17 +368,11 @@ function list_themes($refresh = FALSE) {
$list = array();
// Extract from the database only when it is available.
// Also check that the site is not in the middle of an install or update.
if (!defined('MAINTENANCE_MODE')) {
try {
$themes = system_list('theme');
}
catch (Exception $e) {
// If the database is not available, rebuild the theme data.
$themes = _system_rebuild_theme_data();
}
try {
$themes = system_list('theme');
}
else {
// Scan the installation when the database should not be read.
catch (Exception $e) {
// If the database is not available, rebuild the theme data.
$themes = _system_rebuild_theme_data();
}
......@@ -1274,8 +1273,14 @@ function template_preprocess_status_messages(&$variables) {
* is used as its CSS class. Each link should be itself an array, with the
* following elements:
* - title: The link text.
* - href: The link URL. If omitted, the 'title' is shown as a plain text
* item in the links list.
* - route_name: (optional) The name of the route to link to. If omitted
* (and if 'href' is omitted as well), the 'title' is shown as
* a plain text item in the links list.
* - route_parameters: (optional) An array of route parameters for the link.
* - href: (optional) The link URL. It is preferred to use 'route_name' and
* 'route parameters' for internal links. Use 'href' for links to external
* URLs. If omitted (and if 'route_name' is omitted as well), the 'title'
* is shown as a plain text item in the links list.
* - html: (optional) Whether or not 'title' is HTML. If set, the title
* will not be passed through
* \Drupal\Component\Utility\String::checkPlain().
......@@ -2221,7 +2226,12 @@ function template_preprocess_html(&$variables) {
if (theme_get_setting('features.favicon')) {
$favicon = theme_get_setting('favicon.url');
$type = theme_get_setting('favicon.mimetype');
drupal_add_html_head_link(array('rel' => 'shortcut icon', 'href' => Url::stripDangerousProtocols($favicon), 'type' => $type));
$build['#attached']['drupal_add_html_head_link'][][] = array(
'rel' => 'shortcut icon',
'href' => Url::stripDangerousProtocols($favicon),
'type' => $type,
);
drupal_render($build);
}
$site_config = \Drupal::config('system.site');
......@@ -2504,7 +2514,12 @@ function template_preprocess_maintenance_page(&$variables) {
if (theme_get_setting('features.favicon')) {
$favicon = theme_get_setting('favicon.url');
$type = theme_get_setting('favicon.mimetype');
drupal_add_html_head_link(array('rel' => 'shortcut icon', 'href' => Url::stripDangerousProtocols($favicon), 'type' => $type));
$build['#attached']['drupal_add_html_head_link'][][] = array(
'rel' => 'shortcut icon',
'href' => Url::stripDangerousProtocols($favicon),
'type' => $type,
);
drupal_render($build);
}
// Get all region content set with drupal_add_region_content().
......
......@@ -9,9 +9,6 @@
/**
* Defines an object which stores multiple plugin instances to lazy load them.
*
* The \ArrayAccess implementation is only for backwards compatibility, it is
* deprecated and should not be used by new code.
*/
abstract class PluginBag implements \Iterator, \Countable {
......@@ -30,7 +27,7 @@ abstract class PluginBag implements \Iterator, \Countable {
protected $instanceIDs = array();
/**
* Initializes a plugin and stores the result in $this->pluginInstances.
* Initializes and stores a plugin.
*
* @param string $instance_id
* The ID of the plugin instance to initialize.
......@@ -85,7 +82,7 @@ public function set($instance_id, $value) {
/**
* Removes an initialized plugin.
*
* The plugin can still be used, it will be reinitialized.
* The plugin can still be used; it will be reinitialized.
*
* @param string $instance_id
* The ID of the plugin instance to remove.
......@@ -95,7 +92,7 @@ public function remove($instance_id) {
}
/**
* Adds an instance ID to the array of available instance IDs.
* Adds an instance ID to the available instance IDs.
*
* @param string $id
* The ID of the plugin instance to add.
......@@ -117,7 +114,7 @@ public function getInstanceIds() {
}
/**
* Sets the instance IDs property.
* Sets all instance IDs.
*
* @param array $instance_ids
* An associative array of instance IDs.
......@@ -138,28 +135,28 @@ public function removeInstanceId($instance_id) {
}
/**
* Implements \Iterator::current().
* {@inheritdoc}
*/
public function current() {
return $this->get($this->key());
}
/**
* Implements \Iterator::next().
* {@inheritdoc}
*/
public function next() {
next($this->instanceIDs);
}
/**
* Implements \Iterator::key().
* {@inheritdoc}
*/
public function key() {
return key($this->instanceIDs);
}
/**
* Implements \Iterator::valid().
* {@inheritdoc}
*/
public function valid() {
$key = key($this->instanceIDs);
......@@ -167,14 +164,14 @@ public function valid() {
}
/**
* Implements \Iterator::rewind().
* {@inheritdoc}
*/
public function rewind() {
reset($this->instanceIDs);
}
/**
* Implements \Countable::count().
* {@inheritdoc}
*/
public function count() {
return count($this->instanceIDs);
......
......@@ -204,14 +204,11 @@ public function listAll($prefix = '') {
throw new StorageException($this->directory . '/ not found.');
}
$extension = '.' . static::getFileExtension();
$files = new \GlobIterator($this->directory . '/' . $prefix . '*' . $extension);
$names = array();
foreach ($files as $file) {
$names[] = $file->getBasename($extension);
}
return $names;
$files = glob($this->directory . '/' . $prefix . '*' . $extension);
$clean_name = function ($value) use ($extension) {
return basename($value, $extension);
};
return array_map($clean_name, $files);
}
/**
......
......@@ -134,9 +134,10 @@ public function getComponentNames($type, array $list) {
foreach ($list as $name) {
$directory = $this->getComponentFolder($type, $name);
if (file_exists($directory)) {
$files = new \GlobIterator($directory . '/*' . $extension);
foreach ($files as $file) {
$folders[$file->getBasename($extension)] = $directory;
$files = glob($directory . '/*' . $extension);
foreach ($files as $filename) {
$name = basename($filename, $extension);
$folders[$name] = $directory;
}
}
}
......
......@@ -23,7 +23,6 @@
use Drupal\Core\DependencyInjection\Compiler\RegisterBreadcrumbBuilderPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterAuthenticationPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterTwigExtensionsPass;
use Drupal\Core\Theme\ThemeNegotiatorPass;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Definition;
......@@ -72,9 +71,6 @@ public function register(ContainerBuilder $container) {
// Add the compiler pass that will process the tagged breadcrumb builder
// services.
$container->addCompilerPass(new RegisterBreadcrumbBuilderPass());
// Add the compiler pass that will process the tagged theme negotiator
// service.
$container->addCompilerPass(new ThemeNegotiatorPass());
// Add the compiler pass that lets service providers modify existing
// service definitions.
$container->addCompilerPass(new ModifyServiceDefinitionsPass());
......
......@@ -225,16 +225,11 @@ class EntityType extends Plugin {
public $bundle_keys;
/**
* The base router path for the entity type's field administration page.
*
* If the entity type has a bundle, include {bundle} in the path.
*
* For example, the node entity type specifies
* "admin/structure/types/manage/{bundle}" as its base field admin path.
* The name of the entity type which provides bundles.
*
* @var string (optional)
*/
public $route_base_path;
public $bundle_entity_type = 'bundle';
/**
* Link templates using the URI template syntax.
......
......@@ -301,9 +301,9 @@ public function getAdminPath($entity_type, $bundle) {
$admin_path = '';
$entity_info = $this->getDefinition($entity_type);
// Check for an entity type's admin base path.
if (isset($entity_info['route_base_path'])) {
// Replace any dynamic 'bundle' portion of the path with the actual bundle.
$admin_path = str_replace('{bundle}', $bundle, $entity_info['route_base_path']);
if (isset($entity_info['links']['admin-form'])) {
$route_parameters[$entity_info['bundle_entity_type']] = $bundle;
$admin_path = \Drupal::urlGenerator()->getPathFromRoute($entity_info['links']['admin-form'], $route_parameters);
}
return $admin_path;
......@@ -313,10 +313,11 @@ public function getAdminPath($entity_type, $bundle) {
* {@inheritdoc}
*/
public function getAdminRouteInfo($entity_type, $bundle) {
$entity_info = $this->getDefinition($entity_type);
return array(
'route_name' => "field_ui.overview_$entity_type",
'route_parameters' => array(
'bundle' => $bundle,
$entity_info['bundle_entity_type'] => $bundle,
)
);
}
......
......@@ -164,6 +164,11 @@ public function getViewBuilder($entity_type);
*
* @return string
* The administration path for an entity type bundle, if it exists.
*
* @deprecated since version 8.0
* System paths should not be used - use route names and parameters.
*
* @see self::getAdminRouteInfo()
*/
public function getAdminPath($entity_type, $bundle);
......
......@@ -27,6 +27,9 @@ class LegacyRequestSubscriber implements EventSubscriberInterface {
*/
public function onKernelRequestLegacy(GetResponseEvent $event) {
if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) {
menu_set_custom_theme();
drupal_theme_initialize();
// Tell Drupal it is now fully bootstrapped (for the benefit of code that
// calls drupal_get_bootstrap_phase()), but without having
// _drupal_bootstrap_full() do anything, since we've already done the
......@@ -36,16 +39,6 @@ public function onKernelRequestLegacy(GetResponseEvent $event) {
}
}
/**
* Initializes the theme system after the routing system.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The Event to process.
*/
public function onKernelRequestLegacyAfterRouting(GetResponseEvent $event) {
drupal_theme_initialize();
}
/**
* Registers the methods in this class that should be listeners.
*
......@@ -54,8 +47,6 @@ public function onKernelRequestLegacyAfterRouting(GetResponseEvent $event) {
*/
static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = array('onKernelRequestLegacy', 90);
// Initialize the theme system after the routing system.
$events[KernelEvents::REQUEST][] = array('onKernelRequestLegacyAfterRouting', 30);
return $events;
}
......
<?php
/**
* @file
* Contains \Drupal\Core\Theme\AjaxBasePageNegotiator.
*/
namespace Drupal\Core\Theme;
use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Core\Config\ConfigFactory;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Defines a theme negotiator that deals with the active theme on ajax requests.
*
* Many different pages can invoke an Ajax request to system/ajax or another
* generic Ajax path. It is almost always desired for an Ajax response to be
* rendered using the same theme as the base page, because most themes are built
* with the assumption that they control the entire page, so if the CSS for two
* themes are both loaded for a given page, they may conflict with each other.
* For example, Bartik is Drupal's default theme, and Seven is Drupal's default
* administration theme. Depending on whether the "Use the administration theme
* when editing or creating content" checkbox is checked, the node edit form may
* be displayed in either theme, but the Ajax response to the Field module's
* "Add another item" button should be rendered using the same theme as the rest
* of the page.
*
* Therefore specify '_theme: ajax_base_page' as part of the router options.
*/
class AjaxBasePageNegotiator implements ThemeNegotiatorInterface {
/**
* The CSRF token generator.
*
* @var \Drupal\Core\Access\CsrfTokenGenerator
*/
protected $csrfGenerator;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactory
*/
protected $configFactory;
/**
* Constructs a new AjaxBasePageNegotiator.
*
* @param \Drupal\Core\Access\CsrfTokenGenerator $token_generator
* The CSRF token generator.
* @param \Drupal\Core\Config\ConfigFactory $config_factory
* The config factory.
*/
public function __construct(CsrfTokenGenerator $token_generator, ConfigFactory $config_factory) {
$this->csrfGenerator = $token_generator;
$this->configFactory = $config_factory;
}
/**
* {@inheritdoc}
*/
public function determineActiveTheme(Request $request) {
// Check whether the route was configured to use the base page theme.
if (!(($route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)) && $route->hasOption('_theme') && $route->getOption('_theme') == 'ajax_base_page')) {
return NULL;
}
if (($ajax_page_state = $request->request->get('ajax_page_state')) && !empty($ajax_page_state['theme']) && !empty($ajax_page_state['theme_token'])) {
$theme = $ajax_page_state['theme'];
$token = $ajax_page_state['theme_token'];
// Prevent a request forgery from giving a person access to a theme they
// shouldn't be otherwise allowed to see. However, since everyone is allowed
// to see the default theme, token validation isn't required for that, and
// bypassing it allows most use-cases to work even when accessed from the
// page cache.
if ($theme === $this->configFactory->get('system.theme')->get('default') || $this->csrfGenerator->validate($token, $theme)) {
return $theme;
}
}
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Theme\DefaultNegotiator.
*/
namespace Drupal\Core\Theme;
use Drupal\Core\Config\ConfigFactory;
use Symfony\Component\HttpFoundation\Request;
/**
* Determines the default theme of the site.
*/
class DefaultNegotiator implements ThemeNegotiatorInterface {
/**
* The system theme config object.
*
* @var \Drupal\Core\Config\Config
*/
protected $config;
/**
* Constructs a DefaultNegotiator object.
*
* @param \Drupal\Core\Config\ConfigFactory $config_factory
* The config factory.
*/
public function __construct(ConfigFactory $config_factory) {
$this->config = $config_factory->get('system.theme');
}
/**
* {@inheritdoc}
*/
public function determineActiveTheme(Request $request) {
return $this->config->get('default');
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Theme\ThemeNegotiator.
*/
namespace Drupal\Core\Theme;
use Symfony\Component\HttpFoundation\Request;
/**
* Provides a class which determines the active theme of the page.
*
* It therefore uses ThemeNegotiatorInterface objects which are passed in
* using the 'theme_negotiator' tag.
*
* @see \Drupal\Core\Theme\ThemeNegotiatorPass
* @see \Drupal\Core\Theme\ThemeNegotiatorInterface
*/
class ThemeNegotiator implements ThemeNegotiatorInterface {
/**
* Holds arrays of theme negotiators, keyed by priority.
*
* @var array
*/
protected $negotiators = array();
/**
* Holds the array of theme negotiators sorted by priority.
*
* Set to NULL if the array needs to be re-calculated.
*
* @var array|NULL
*/
protected $sortedNegotiators;
/**
* The current request.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $request;
/**
* The access checker for themes.
*
* @var \Drupal\Core\Theme\ThemeAccessCheck
*/
protected $themeAccess;
/**
* Constructs a new ThemeNegotiator.
*
* @param \Drupal\Core\Theme\ThemeAccessCheck $theme_access
* The access checker for themes.
*/
public function __construct(ThemeAccessCheck $theme_access) {
$this->themeAccess = $theme_access;
}
/**
* Sets the request object to use.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
*/
public function setRequest(Request $request) {
$this->request = $request;
}
/**
* Adds a active theme negotiation service.
*
* @param \Drupal\Core\Theme\ThemeNegotiatorInterface $negotiator
* The theme negotiator to add.
* @param int $priority
* Priority of the breadcrumb builder.
*/
public function addNegotiator(ThemeNegotiatorInterface $negotiator, $priority) {
$this->negotiators[$priority][] = $negotiator;
// Force the negotiators to be re-sorted.
$this->sortedNegotiators = NULL;
}
/**
* Returns the sorted array of theme negotiators.
*
* @return array|\Drupal\Core\Theme\ThemeNegotiatorInterface[]
* An array of breadcrumb builder objects.
*/
protected function getSortedNegotiators() {
if (!isset($this->sortedNegotiators)) {
// Sort the negotiators according to priority.
krsort($this->negotiators);
// Merge nested negotiators from $this->negotiators into
// $this->sortedNegotiators.
$this->sortedNegotiators = array();
foreach ($this->negotiators as $builders) {
$this->sortedNegotiators = array_merge($this->sortedNegotiators, $builders);
}
}
return $this->sortedNegotiators;
}
/**
* Get the current active theme.
*
* @return string
* The current active string.
*/
public function getActiveTheme() {
if (!$this->request->attributes->has('_theme_active')) {
$this->determineActiveTheme($this->request);
}
return $this->request->attributes->get('_theme_active');
}
/**
* {@inheritdoc}
*/
public function determineActiveTheme(Request $request) {
foreach ($this->getSortedNegotiators() as $negotiator) {
$theme = $negotiator->determineActiveTheme($request);
if ($theme !== NULL && $this->themeAccess->checkAccess($theme)) {
$request->attributes->set('_theme_active', $theme);
return $request->attributes->get('_theme_active');
}
}
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Theme\ThemeNegotiatorInterface.
*/
namespace Drupal\Core\Theme;
use Symfony\Component\HttpFoundation\Request;
/**
* Defines an interface for classes which determine the active theme.
*
* To set the active theme, create a new service tagged with 'theme_negotiator'
* (see user.services.yml for an example). The only method this service needs
* to implement is determineActiveTheme. Return the name of the theme, or NULL
* if other negotiators like the configured default one should kick in instead.
*
* If you are setting a theme which is closely tied to the functionality of a
* particular page or set of pages (such that the page might not function
* correctly if a different theme is used), make sure to set the priority on
* the service to a high number so that it is not accidentally overridden by
* other theme negotiators. By convention, a priority of "1000" is used in
* these cases; see \Drupal\Core\Theme\AjaxBasePageNegotiator and
* core.services.yml for an example.
*/
interface ThemeNegotiatorInterface {
/**
* Determine the active theme for the request.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The active request of the site.
*
* @return string|null
* Returns the active theme name, else return NULL.
*/
public function determineActiveTheme(Request $request);
}
<?php
/**
* @file
* Contains \Drupal\Core\Theme\ThemeNegotiatorPass.
*/
namespace Drupal\Core\Theme;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Adds services to the theme negotiator service.
*
* @see \Drupal\Core\Theme\ThemeNegotiator
* @see \Drupal\Core\Theme\ThemeNegotiatorInterfa
*/
class ThemeNegotiatorPass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
if (!$container->hasDefinition('theme.negotiator')) {
return;
}
$manager = $container->getDefinition('theme.negotiator');
foreach ($container->findTaggedServiceIds('theme_negotiator') as $id => $attributes) {
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$manager->addMethodCall('addNegotiator', array(new Reference($id), $priority));
}
}
}
......@@ -382,7 +382,7 @@ Drupal.ajax.prototype.beforeSerialize = function (element, options) {
// Allow Drupal to return new JavaScript and CSS files to load without
// returning the ones already loaded.
// @see \Drupal\Core\Theme\AjaxBasePageNegotiator
// @see ajax_base_page_theme()
// @see drupal_get_css()
// @see drupal_get_js()
var pageState = drupalSettings.ajaxPageState;
......
......@@ -2,9 +2,7 @@
/**
* @file
* Provides views data and handlers for action.module.
*
* @ingroup views_module_handlers
* Provides views data for action.module.
*/
/**
......