Skip to content
Commits on Source (40)
......@@ -77,15 +77,23 @@ Drupal 8.0, xxxx-xx-xx (development version)
* All Gettext files are now imported in chunks, better for low resource
environments.
* Improved content language support:
* Freely orderable language selector in forms.
* Made it possible to assign language to taxonomy terms, vocabularies
and files.
* Node type specific default language assignment is much more versatile.
* Made it possible to assign language to taxonomy terms, vocabularies,
menu items, and files.
* Added a field translation based content translation module that applies
to all content entities.
* Removed the old node-copy based content translation module.
* Introduced language defaults configuration for each entity type and
subtype.
* Added entity language variance support to search module.
* Search indexing and query preprocessors now get language information.
* Unified content translation permission granularity with content editing
permissions.
* Better language based configuration
* Made the language selector freely orderable in entity forms.
* Better configuration language support
* Added language selectors to most configuration options (views, menus,
etc.)
* Added a configuration translation user interface that works with any
configuration with translatable values (blocks, views, fields, etc).
* Added language options to block visibility.
* Much improved language APIs for developers:
* Added simple APIs and hooks to save/delete/update languages.
......@@ -97,10 +105,13 @@ Drupal 8.0, xxxx-xx-xx (development version)
* Made it possible for users to have a preferred language separate from
their user entity language.
* The text formatter from t() is now available as format_string().
* Added support for interface translation contexts in Drupal.t() and
Drupal.formatPlural() in JavaScript.
* Added support for interface translation contexts in Drupal.t(),
Drupal.formatPlural() as well as routing, tabs, actions, and contextual
links.
* Removed textgroups support from interface translation in favor of
native configuration language support.
* Added configuration schema system to support generating translation
forms for any configuration.
* Reworked Gettext PO support to use pluggable read/write handlers.
* Added language select form element in the Form API.
- Added E-mail field type to core.
......
......@@ -237,9 +237,17 @@ Comment module
Configuration module
- ?
Configuration Translation module
- Gábor Hojtsy 'Gábor Hojtsy' http://drupal.org/user/4166
- Tobias Stöckler 'tstoeckler' https://drupal.org/user/107158
- Vijayachandran Mani 'vijaycs85' https://drupal.org/user/93488
Contact module
- ?
Content Translation module
- Francesco Placella 'plach' http://drupal.org/user/183211
Contextual module
- Daniel F. Kudwien 'sun' http://drupal.org/user/54136
......@@ -294,12 +302,16 @@ Help module
Image module
- Claudiu Cristea 'claudiu.cristea' https://drupal.org/user/56348
Link module
- ?
Interface Translation (locale) module
- Gábor Hojtsy 'Gábor Hojtsy' http://drupal.org/user/4166
Locale module
Language module
- Francesco Placella 'plach' http://drupal.org/user/183211
- Gábor Hojtsy 'Gábor Hojtsy' http://drupal.org/user/4166
Link module
- ?
Menu module
- ?
......@@ -382,9 +394,6 @@ Tour module
Tracker module
- David Strauss 'David Strauss' http://drupal.org/user/93254
Translation module
- Francesco Placella 'plach' http://drupal.org/user/183211
Update module
- Derek Wright 'dww' http://drupal.org/user/46549
......
......@@ -196,17 +196,12 @@ services:
plugin.manager.menu.local_task:
class: Drupal\Core\Menu\LocalTaskManager
arguments: ['@controller_resolver', '@request', '@router.route_provider', '@module_handler', '@cache.cache', '@language_manager', '@access_manager', '@current_user']
scope: request
plugin.manager.menu.contextual_link:
class: Drupal\Core\Menu\ContextualLinkManager
arguments: ['@controller_resolver', '@module_handler', '@cache.cache', '@language_manager', '@access_manager', '@current_user']
request:
class: Symfony\Component\HttpFoundation\Request
# @TODO the synthetic setting must be uncommented whenever drupal_session_initialize()
# is run after there is a request and the following two lines should be removed.
factory_class: Symfony\Component\HttpFoundation\Request
factory_method: createFromGlobals
#synthetic: true
synthetic: true
event_dispatcher:
class: Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher
arguments: ['@service_container']
......@@ -350,6 +345,10 @@ services:
tags:
- { name: event_subscriber }
arguments: ['@settings']
ajax_response_subscriber:
class: Drupal\Core\EventSubscriber\AjaxResponseSubscriber
tags:
- { name: event_subscriber }
route_enhancer.authentication:
class: Drupal\Core\Routing\Enhancer\AuthenticationEnhancer
calls:
......@@ -407,7 +406,7 @@ services:
class: Drupal\Core\Access\CsrfTokenGenerator
arguments: ['@private_key']
calls:
- [setRequest, ['@?request']]
- [setCurrentUser, ['@?current_user']]
access_manager:
class: Drupal\Core\Access\AccessManager
arguments: ['@router.route_provider', '@url_generator', '@paramconverter_manager']
......@@ -421,7 +420,6 @@ services:
- [setCurrentUser, ['@?current_user']]
tags:
- { name: event_subscriber }
scope: request
access_route_subscriber:
class: Drupal\Core\EventSubscriber\AccessRouteSubscriber
tags:
......@@ -641,6 +639,11 @@ services:
class: Zend\Feed\Writer\Extension\Threading\Renderer\Entry
feed.writer.wellformedwebrendererentry:
class: Zend\Feed\Writer\Extension\WellFormedWeb\Renderer\Entry
theme.registry:
class: Drupal\Core\Theme\Registry
arguments: ['@cache.cache', '@lock', '@module_handler']
tags:
- { name: needs_destruction }
authentication:
class: Drupal\Core\Authentication\AuthenticationManager
authentication.cookie:
......
......@@ -1374,6 +1374,9 @@ function format_string($string, array $args = array()) {
* @see \Drupal\Component\Utility\String::checkPlain()
* @see drupal_validate_utf8()
* @ingroup sanitization
*
* @deprecated as of Drupal 8.0. Use
* Drupal\Component\Utility\String::checkPlain() directly instead.
*/
function check_plain($text) {
return String::checkPlain($text);
......@@ -1549,7 +1552,7 @@ function watchdog($type, $message, array $variables = NULL, $severity = WATCHDOG
$log_entry['referer'] = $request->headers->get('Referer', '');
$log_entry['ip'] = $request->getClientIP();
}
catch (\InvalidArgumentException $e) {
catch (DependencyInjectionRuntimeException $e) {
// We are not in a request context.
}
......@@ -1715,9 +1718,18 @@ function drupal_set_title($title = NULL, $output = Title::CHECK_PLAIN) {
* The user session object.
*/
function drupal_anonymous_user() {
try {
$request = \Drupal::request();
$hostname = $request->getClientIP();
}
catch (DependencyInjectionRuntimeException $e) {
// We are not in a request context.
$hostname = '';
}
$values = array(
'uid' => 0,
'hostname' => \Drupal::request()->getClientIP(),
'hostname' => $hostname,
'roles' => array(DRUPAL_ANONYMOUS_RID),
);
return new UserSession($values);
......@@ -1858,10 +1870,13 @@ function drupal_handle_request($test_only = FALSE) {
// @todo Remove this once everything in the bootstrap has been
// converted to services in the DIC.
$kernel->boot();
drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE);
// Create a request object from the HttpFoundation.
$request = Request::createFromGlobals();
\Drupal::getContainer()->set('request', $request);
drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE);
$response = $kernel->handle($request)->prepare($request)->send();
$kernel->terminate($request, $response);
......@@ -1991,6 +2006,8 @@ function _drupal_bootstrap_kernel() {
if (!\Drupal::getContainer()) {
$kernel = new DrupalKernel('prod', drupal_classloader());
$kernel->boot();
$request = Request::createFromGlobals();
\Drupal::getContainer()->set('request', $request);
}
}
......
......@@ -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;
}
......@@ -3797,7 +3798,19 @@ function drupal_render_page($page) {
* - If this element has #prefix and/or #suffix defined, they are concatenated
* to #children.
* - If this element has #cache defined, the rendered output of this element
* is saved to drupal_render()'s internal cache.
* is saved to drupal_render()'s internal cache. This includes the changes
* made by #post_render.
* - If this element (or any of its children) has an array of
* #post_render_cache functions defined, they are called sequentially to
* replace placeholders in the final #markup and extend #attached.
* Placeholders must contain a unique token, to guarantee that e.g. samples
* of placeholders are not replaced also. For this, a special element named
* 'render_cache_placeholder' is provided.
* Note that these callbacks run always: when hitting the render cache, when
* missing, or when render caching is not used at all. This is done to allow
* any Drupal module to customize other render arrays without breaking the
* render cache if it is enabled, and to not require it to use other logic
* when render caching is disabled.
* - #printed is set to TRUE for this element to ensure that it is only
* rendered once.
* - The final value of #children for this element is returned as the rendered
......@@ -3805,6 +3818,8 @@ function drupal_render_page($page) {
*
* @param array $elements
* The structured array describing the data to be rendered.
* @param bool $is_recursive_call
* Whether this is a recursive call or not, for internal use.
*
* @return string
* The rendered HTML.
......@@ -3814,7 +3829,7 @@ function drupal_render_page($page) {
* @see drupal_process_states()
* @see drupal_process_attached()
*/
function drupal_render(&$elements) {
function drupal_render(&$elements, $is_recursive_call = FALSE) {
// Early-return nothing if user does not have access.
if (empty($elements) || (isset($elements['#access']) && !$elements['#access'])) {
return '';
......@@ -3825,11 +3840,19 @@ function drupal_render(&$elements) {
return '';
}
// Try to fetch the element's markup from cache and return.
// Try to fetch the prerendered element from cache, run any #post_render_cache
// callbacks and return the final markup.
if (isset($elements['#cache'])) {
$cached_output = drupal_render_cache_get($elements);
if ($cached_output !== FALSE) {
return $cached_output;
$cached_element = drupal_render_cache_get($elements);
if ($cached_element !== FALSE) {
$elements = $cached_element;
// Only when we're not in a recursive drupal_render() call,
// #post_render_cache callbacks must be executed, to prevent breaking the
// render cache in case of nested elements with #cache set.
if (!$is_recursive_call) {
_drupal_render_process_post_render_cache($elements);
}
return $elements['#markup'];
}
}
......@@ -3888,7 +3911,7 @@ function drupal_render(&$elements) {
// process as drupal_render_children() but is inlined for speed.
if ((!$theme_is_implemented || isset($elements['#render_children'])) && empty($elements['#children'])) {
foreach ($children as $key) {
$elements['#children'] .= drupal_render($elements[$key]);
$elements['#children'] .= drupal_render($elements[$key], TRUE);
}
}
......@@ -3948,17 +3971,40 @@ function drupal_render(&$elements) {
}
}
// We store the resulting output in $elements['#markup'], to be consistent
// with how render cached output gets stored. This ensures that
// #post_render_cache callbacks get the same data to work with, no matter if
// #cache is disabled, #cache is enabled, there is a cache hit or miss.
$prefix = isset($elements['#prefix']) ? $elements['#prefix'] : '';
$suffix = isset($elements['#suffix']) ? $elements['#suffix'] : '';
$output = $prefix . $elements['#children'] . $suffix;
$elements['#markup'] = $prefix . $elements['#children'] . $suffix;
// Cache the processed element if #cache is set.
if (isset($elements['#cache'])) {
drupal_render_cache_set($output, $elements);
// Collect all #post_render_cache callbacks associated with this element.
$post_render_cache = drupal_render_collect_post_render_cache($elements);
if ($post_render_cache) {
$elements['#post_render_cache'] = $post_render_cache;
}
drupal_render_cache_set($elements['#markup'], $elements);
}
// Only when we're not in a recursive drupal_render() call,
// #post_render_cache callbacks must be executed, to prevent breaking the
// render cache in case of nested elements with #cache set.
//
// By running them here, we ensure that:
// - they run when #cache is disabled,
// - they run when #cache is enabled and there is a cache miss.
// Only the case of a cache hit when #cache is enabled, is not handled here,
// that is handled earlier in drupal_render().
if (!$is_recursive_call) {
_drupal_render_process_post_render_cache($elements);
}
$elements['#printed'] = TRUE;
return $output;
return $elements['#markup'];
}
/**
......@@ -4074,32 +4120,33 @@ function show(&$element) {
}
/**
* Gets the rendered output of a renderable element from the cache.
* Gets the cached, prerendered element of a renderable element from the cache.
*
* @param $elements
* @param array $elements
* A renderable array.
*
* @return
* A markup string containing the rendered content of the element, or FALSE
* if no cached copy of the element is available.
* @return array
* A renderable array, with the original element and all its children pre-
* rendered, or FALSE if no cached copy of the element is available.
*
* @see drupal_render()
* @see drupal_render_cache_set()
*/
function drupal_render_cache_get($elements) {
function drupal_render_cache_get(array $elements) {
if (!\Drupal::request()->isMethodSafe() || !$cid = drupal_render_cid_create($elements)) {
return FALSE;
}
$bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'cache';
if (!empty($cid) && $cache = cache($bin)->get($cid)) {
$cached_element = $cache->data;
// Add additional libraries, JavaScript, CSS and other data attached
// to this element.
if (isset($cache->data['#attached'])) {
drupal_process_attached($cache->data);
if (isset($cached_element['#attached'])) {
drupal_process_attached($cached_element);
}
// Return the rendered output.
return $cache->data['#markup'];
// Return the cached element.
return $cached_element;
}
return FALSE;
}
......@@ -4112,12 +4159,12 @@ function drupal_render_cache_get($elements) {
*
* @param $markup
* The rendered output string of $elements.
* @param $elements
* @param array $elements
* A renderable array.
*
* @see drupal_render_cache_get()
*/
function drupal_render_cache_set(&$markup, $elements) {
function drupal_render_cache_set(&$markup, array $elements) {
// Create the cache ID for the element.
if (!\Drupal::request()->isMethodSafe() || !$cid = drupal_render_cid_create($elements)) {
return FALSE;
......@@ -4129,18 +4176,194 @@ function drupal_render_cache_set(&$markup, $elements) {
// $data['#real-value']) and return an include command instead. When the
// ESI command is executed by the content accelerator, the real value can
// be retrieved and used.
$data['#markup'] = &$markup;
$data['#markup'] = $markup;
// Persist attached data associated with this element.
$attached = drupal_render_collect_attached($elements, TRUE);
if ($attached) {
$data['#attached'] = $attached;
}
// Persist #post_render_cache callbacks associated with this element.
if (isset($elements['#post_render_cache'])) {
$data['#post_render_cache'] = $elements['#post_render_cache'];
}
$bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'cache';
$expire = isset($elements['#cache']['expire']) ? $elements['#cache']['expire'] : CacheBackendInterface::CACHE_PERMANENT;
$tags = drupal_render_collect_cache_tags($elements);
cache($bin)->set($cid, $data, $expire, $tags);
}
/**
* Generates a render cache placeholder.
*
* This is used by drupal_pre_render_render_cache_placeholder() to generate
* placeholders, but should also be called by #post_render_cache callbacks that
* want to replace the placeholder with the final markup.
*
* @param callable $callback
* The #post_render_cache callback that will replace the placeholder with its
* eventual markup.
* @param array $context
* An array providing context for the #post_render_cache callback.
* @param string $token
* A unique token to uniquely identify the placeholder.
*
* @see drupal_render_cache_get()
*/
function drupal_render_cache_generate_placeholder($callback, array $context, $token) {
// Serialize the context into a HTML attribute; unserializing is unnecessary.
$context_attribute = '';
foreach ($context as $key => $value) {
$context_attribute .= $key . ':' . $value . ';';
}
return '<drupal:render-cache-placeholder callback="' . $callback . '" context="' . $context_attribute . '" token="'. $token . '" />';;
}
/**
* Pre-render callback: Renders a render cache placeholder into #markup.
*
* @param $elements
* A structured array whose keys form the arguments to l():
* - #callback: The #post_render_cache callback that will replace the
* placeholder with its eventual markup.
* - #context: An array providing context for the #post_render_cache callback.
*
* @return
* The passed-in element containing a render cache placeholder in '#markup'
* and a callback with context, keyed by a generated unique token in
* '#post_render_cache'.
*
* @see drupal_render_cache_generate_placeholder()
*/
function drupal_pre_render_render_cache_placeholder($element) {
$callback = $element['#callback'];
if (!is_callable($callback)) {
throw new Exception(t('#callback must be a callable function.'));
}
$context = $element['#context'];
if (!is_array($context)) {
throw new Exception(t('#context must be an array.'));
}
$token = \Drupal\Component\Utility\Crypt::randomStringHashed(55);
// Generate placeholder markup and store #post_render_cache callback.
$element['#markup'] = drupal_render_cache_generate_placeholder($callback, $context, $token);
$element['#post_render_cache'][$callback][$token] = $context;
return $element;
}
/**
* Processes #post_render_cache callbacks.
*
* #post_render_cache callbacks may modify:
* - #markup: to replace placeholders
* - #attached: to add libraries or JavaScript settings
*
* Note that in either of these cases, #post_render_cache callbacks are
* implicitly idempotent: a placeholder that has been replaced can't be replaced
* again, and duplicate attachments are ignored.
*
* @param array &$elements
* The structured array describing the data being rendered.
*
* @see drupal_render()
* @see drupal_render_collect_post_render_cache
*/
function _drupal_render_process_post_render_cache(array &$elements) {
if (isset($elements['#post_render_cache'])) {
// Call all #post_render_cache callbacks, while passing the provided context
// and if keyed by a number, no token is passed, otherwise, the token string
// is passed to the callback as well. This token is used to uniquely
// identify the placeholder in the markup.
$modified_elements = $elements;
foreach ($elements['#post_render_cache'] as $callback => $options) {
foreach ($elements['#post_render_cache'][$callback] as $token => $context) {
// The advanced option, when setting #post_render_cache directly.
if (is_numeric($token)) {
$modified_elements = call_user_func_array($callback, array($modified_elements, $context));
}
// The simple option, when using the standard placeholders, and hence
// also when using #type => render_cache_placeholder.
else {
// Call #post_render_cache callback to generate the element that will
// fill in the placeholder.
$generated_element = call_user_func_array($callback, array($context));
// Update #attached based on the generated element.
if (isset($generated_element['#attached'])) {
if (!isset($modified_elements['#attached'])) {
$modified_elements['#attached'] = array();
}
$modified_elements['#attached'] = drupal_merge_attached($modified_elements['#attached'], drupal_render_collect_attached($generated_element, TRUE));
}
// Replace the placeholder with the rendered markup of the generated
// element.
$placeholder = drupal_render_cache_generate_placeholder($callback, $context, $token);
$modified_elements['#markup'] = str_replace($placeholder, drupal_render($generated_element), $modified_elements['#markup']);
}
}
}
// Only retain changes to the #markup and #attached properties, as would be
// the case when the render cache was actually being used.
$elements['#markup'] = $modified_elements['#markup'];
if (isset($modified_elements['#attached'])) {
$elements['#attached'] = $modified_elements['#attached'];
}
// Make sure that any attachments added in #post_render_cache callbacks are
// also executed.
if (isset($elements['#attached'])) {
drupal_process_attached($elements);
}
}
}
/**
* Collects #post_render_cache for an element and its children into a single
* array.
*
* When caching elements, it is necessary to collect all #post_render_cache
* callbacks into a single array, from both the element itself and all child
* elements. This allows drupal_render() to execute all of them when the element
* is retrieved from the render cache.
*
* @param array $elements
* The element to collect #post_render_cache from.
* @param array $callbacks
* Internal use only. The #post_render_callbacks array so far.
* @param bool $is_root_element
* Internal use only. Whether the element being processed is the root or not.
*
* @return
* The #post_render_cache array for this element and its descendants.
*
* @see drupal_render()
* @see _drupal_render_process_post_render_cache()
*/
function drupal_render_collect_post_render_cache(array $elements, array $callbacks = array(), $is_root_element = TRUE) {
// Collect all #post_render_cache for this element.
if (isset($elements['#post_render_cache'])) {
$callbacks = NestedArray::mergeDeep($callbacks, $elements['#post_render_cache']);
}
// Child elements that have #cache set will already have collected all their
// children's #post_render_cache callbacks, so no need to traverse further.
if (!$is_root_element && isset($elements['#cache'])) {
return $callbacks;
}
else if ($children = element_children($elements)) {
foreach ($children as $child) {
$callbacks = drupal_render_collect_post_render_cache($elements[$child], $callbacks, FALSE);
}
}
return $callbacks;
}
/**
* Collects #attached for an element and its children into a single array.
*
......
......@@ -410,6 +410,13 @@ function install_begin_request(&$install_state) {
// implementation here.
$container->register('lock', 'Drupal\Core\Lock\NullLockBackend');
$container
->register('theme.registry', 'Drupal\Core\Theme\Registry')
->addArgument(new Reference('cache.cache'))
->addArgument(new Reference('lock'))
->addArgument(new Reference('module_handler'))
->addTag('needs_destruction');
// Register a module handler for managing enabled modules.
$container
->register('module_handler', 'Drupal\Core\Extension\ModuleHandler');
......
......@@ -625,10 +625,19 @@ function drupal_install_system() {
// Create tables.
drupal_install_schema('system');
if (!drupal_container()->has('kernel')) {
// Immediately boot a kernel to have real services ready.
if (!\Drupal::getContainer()->has('kernel')) {
// Immediately boot a kernel to have real services ready. If there's already
// an initialized request object in the pre-kernel container, persist it in
// the post-kernel container.
if (\Drupal::getContainer()->initialized('request')) {
$request = \Drupal::request();
}
$kernel = new DrupalKernel('install', drupal_classloader(), FALSE);
$kernel->boot();
if (isset($request)) {
\Drupal::getContainer()->set('request', $request);
}
}
$system_path = drupal_get_path('module', 'system');
......
......@@ -10,15 +10,14 @@
use Drupal\Component\Utility\String;
use Drupal\Component\Utility\Url;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\Config;
use Drupal\Core\Language\Language;
use Drupal\Core\Extension\ExtensionNameLengthException;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Template\RenderWrapper;
use Drupal\Core\Utility\ThemeRegistry;
use Drupal\Core\Theme\ThemeSettings;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\MapArray;
/**
* @defgroup content_flags Content markers
......@@ -143,10 +142,8 @@ function drupal_theme_initialize() {
* the same information as the $theme object. It should be in
* 'oldest first' order, meaning the top level of the chain will
* be first.
* @param $registry_callback
* The callback to invoke to set the theme registry.
*/
function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callback = '_theme_load_registry') {
function _drupal_theme_initialize($theme, $base_theme = array()) {
global $theme_info, $base_theme_info, $theme_engine, $theme_path;
$theme_info = $theme;
$base_theme_info = $base_theme;
......@@ -275,10 +272,6 @@ function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callb
// Always include Twig as the default theme engine.
include_once DRUPAL_ROOT . '/core/themes/engines/twig/twig.engine';
if (isset($registry_callback)) {
_theme_registry_callback($registry_callback, array($theme, $base_theme, $theme_engine));
}
}
/**
......@@ -299,97 +292,15 @@ function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callb
* Drupal\Core\Utility\ThemeRegistry class.
*/
function theme_get_registry($complete = TRUE) {
// Use the advanced drupal_static() pattern, since this is called very often.
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
$drupal_static_fast['registry'] = &drupal_static('theme_get_registry');
}
$theme_registry = &$drupal_static_fast['registry'];
// Initialize the theme, if this is called early in the bootstrap, or after
// static variables have been reset.
if (!is_array($theme_registry)) {
drupal_theme_initialize();
$theme_registry = array();
}
$key = (int) $complete;
if (!isset($theme_registry[$key])) {
list($callback, $arguments) = _theme_registry_callback();
if (!$complete) {
$arguments[] = FALSE;
}
$theme_registry[$key] = call_user_func_array($callback, $arguments);
}
return $theme_registry[$key];
}
/**
* Sets the callback that will be used by theme_get_registry().
*
* @param $callback
* The name of the callback function.
* @param $arguments
* The arguments to pass to the function.
*/
function _theme_registry_callback($callback = NULL, array $arguments = array()) {
static $stored;
if (isset($callback)) {
$stored = array($callback, $arguments);
}
return $stored;
}
/**
* Gets the theme_registry cache; if it doesn't exist, builds it.
*
* @param $theme
* The loaded $theme object as returned by list_themes().
* @param $base_theme
* An array of loaded $theme objects representing the ancestor themes in
* oldest first order.
* @param $theme_engine
* The name of the theme engine.
* @param $complete
* Whether to load the complete theme registry or an instance of the
* Drupal\Core\Utility\ThemeRegistry class.
*
* @return
* The theme registry array, or an instance of the
* Drupal\Core\Utility\ThemeRegistry class.
*/
function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL, $complete = TRUE) {
$theme_registry = \Drupal::service('theme.registry');
if ($complete) {
// Check the theme registry cache; if it exists, use it.
$cached = cache()->get("theme_registry:$theme->name");
if (isset($cached->data)) {
$registry = $cached->data;
}
else {
// If not, build one and cache it.
$registry = _theme_build_registry($theme, $base_theme, $theme_engine);
// Only persist this registry if all modules are loaded. This assures a
// complete set of theme hooks.
if (\Drupal::moduleHandler()->isLoaded()) {
_theme_save_registry($theme, $registry);
}
}
return $registry;
return $theme_registry->get();
}
else {
return new ThemeRegistry('theme_registry:runtime:' . $theme->name, 'cache', array('theme_registry' => TRUE), \Drupal::moduleHandler()->isLoaded());
return $theme_registry->getRuntime();
}
}
/**
* Writes the theme_registry cache into the database.
*/
function _theme_save_registry($theme, $registry) {
cache()->set("theme_registry:$theme->name", $registry, CacheBackendInterface::CACHE_PERMANENT, array('theme_registry' => TRUE));
}
/**
* Forces the system to rebuild the theme registry.
*
......@@ -397,256 +308,7 @@ function _theme_save_registry($theme, $registry) {
* a dynamic system needs to add more theme hooks.
*/
function drupal_theme_rebuild() {
drupal_static_reset('theme_get_registry');
cache()->invalidateTags(array('theme_registry' => TRUE));
}
/**
* Process a single implementation of hook_theme().
*
* @param $cache
* The theme registry that will eventually be cached; It is an associative
* array keyed by theme hooks, whose values are associative arrays describing
* the hook:
* - 'type': The passed-in $type.
* - 'theme path': The passed-in $path.
* - 'function': The name of the function generating output for this theme
* hook. Either defined explicitly in hook_theme() or, if neither
* 'function' nor 'template' is defined, then the default theme function
* name is used. The default theme function name is the theme hook prefixed
* by either 'theme_' for modules or '$name_' for everything else. If
* 'function' is defined, 'template' is not used.
* - 'template': The filename of the template generating output for this
* theme hook. The template is in the directory defined by the 'path' key of
* hook_theme() or defaults to "$path/templates".
* - 'variables': The variables for this theme hook as defined in
* hook_theme(). If there is more than one implementation and 'variables'
* is not specified in a later one, then the previous definition is kept.
* - 'render element': The renderable element for this theme hook as defined
* in hook_theme(). If there is more than one implementation and
* 'render element' is not specified in a later one, then the previous
* definition is kept.
* - 'preprocess functions': See theme() for detailed documentation.
* @param $name
* The name of the module, theme engine, base theme engine, theme or base
* theme implementing hook_theme().
* @param $type
* One of 'module', 'theme_engine', 'base_theme_engine', 'theme', or
* 'base_theme'. Unlike regular hooks that can only be implemented by modules,
* each of these can implement hook_theme(). _theme_process_registry() is
* called in aforementioned order and new entries override older ones. For
* example, if a theme hook is both defined by a module and a theme, then the
* definition in the theme will be used.
* @param $theme
* The loaded $theme object as returned from list_themes().
* @param $path
* The directory where $name is. For example, modules/system or
* themes/bartik.
*
* @see theme()
* @see hook_theme()
* @see list_themes()
*/
function _theme_process_registry(&$cache, $name, $type, $theme, $path) {
$result = array();
$hook_defaults = array(
'variables' => TRUE,
'render element' => TRUE,
'pattern' => TRUE,
'base hook' => TRUE,
);
$module_list = array_keys(\Drupal::moduleHandler()->getModuleList());
// Invoke the hook_theme() implementation, preprocess what is returned, and
// merge it into $cache.
$function = $name . '_theme';
if (function_exists($function)) {
$result = $function($cache, $type, $theme, $path);
foreach ($result as $hook => $info) {
// When a theme or engine overrides a module's theme function
// $result[$hook] will only contain key/value pairs for information being
// overridden. Pull the rest of the information from what was defined by
// an earlier hook.
// Fill in the type and path of the module, theme, or engine that
// implements this theme function.
$result[$hook]['type'] = $type;
$result[$hook]['theme path'] = $path;
// If function and file are omitted, default to standard naming
// conventions.
if (!isset($info['template']) && !isset($info['function'])) {
$result[$hook]['function'] = ($type == 'module' ? 'theme_' : $name . '_') . $hook;
}
if (isset($cache[$hook]['includes'])) {
$result[$hook]['includes'] = $cache[$hook]['includes'];
}
// If the theme implementation defines a file, then also use the path
// that it defined. Otherwise use the default path. This allows
// system.module to declare theme functions on behalf of core .include
// files.
if (isset($info['file'])) {
$include_file = isset($info['path']) ? $info['path'] : $path;
$include_file .= '/' . $info['file'];
include_once DRUPAL_ROOT . '/' . $include_file;
$result[$hook]['includes'][] = $include_file;
}
// If the default keys are not set, use the default values registered
// by the module.
if (isset($cache[$hook])) {
$result[$hook] += array_intersect_key($cache[$hook], $hook_defaults);
}
// The following apply only to theming hooks implemented as templates.
if (isset($info['template'])) {
// Prepend the current theming path when none is set.
if (!isset($info['path'])) {
$result[$hook]['template'] = $path . '/templates/' . $info['template'];
}
}
// Preprocess variables for all theming hooks, whether the hook is
// implemented as a template or as a function. Ensure they are arrays.
if (!isset($info['preprocess functions']) || !is_array($info['preprocess functions'])) {
$info['preprocess functions'] = array();
$prefixes = array();
if ($type == 'module') {
// Default variable preprocessor prefix.
$prefixes[] = 'template';
// Add all modules so they can intervene with their own variable
// preprocessors. This allows them to provide variable preprocessors
// even if they are not the owner of the current hook.
$prefixes = array_merge($prefixes, $module_list);
}
elseif ($type == 'theme_engine' || $type == 'base_theme_engine') {
// Theme engines get an extra set that come before the normally
// named variable preprocessors.
$prefixes[] = $name . '_engine';
// The theme engine registers on behalf of the theme using the
// theme's name.
$prefixes[] = $theme;
}
else {
// This applies when the theme manually registers their own variable
// preprocessors.
$prefixes[] = $name;
}
foreach ($prefixes as $prefix) {
// Only use non-hook-specific variable preprocessors for theming
// hooks implemented as templates. See theme().
if (isset($info['template']) && function_exists($prefix . '_preprocess')) {
$info['preprocess functions'][] = $prefix . '_preprocess';
}
if (function_exists($prefix . '_preprocess_' . $hook)) {
$info['preprocess functions'][] = $prefix . '_preprocess_' . $hook;
}
}
}
// Check for the override flag and prevent the cached variable
// preprocessors from being used. This allows themes or theme engines
// to remove variable preprocessors set earlier in the registry build.
if (!empty($info['override preprocess functions'])) {
// Flag not needed inside the registry.
unset($result[$hook]['override preprocess functions']);
}
elseif (isset($cache[$hook]['preprocess functions']) && is_array($cache[$hook]['preprocess functions'])) {
$info['preprocess functions'] = array_merge($cache[$hook]['preprocess functions'], $info['preprocess functions']);
}
$result[$hook]['preprocess functions'] = $info['preprocess functions'];
}
// Merge the newly created theme hooks into the existing cache.
$cache = $result + $cache;
}
// Let themes have variable preprocessors even if they didn't register a
// template.
if ($type == 'theme' || $type == 'base_theme') {
foreach ($cache as $hook => $info) {
// Check only if not registered by the theme or engine.
if (empty($result[$hook])) {
if (!isset($info['preprocess functions'])) {
$cache[$hook]['preprocess functions'] = array();
}
// Only use non-hook-specific variable preprocessors for theme hooks
// implemented as templates. See theme().
if (isset($info['template']) && function_exists($name . '_preprocess')) {
$cache[$hook]['preprocess functions'][] = $name . '_preprocess';
}
if (function_exists($name . '_preprocess_' . $hook)) {
$cache[$hook]['preprocess functions'][] = $name . '_preprocess_' . $hook;
$cache[$hook]['theme path'] = $path;
}
// Ensure uniqueness.
$cache[$hook]['preprocess functions'] = array_unique($cache[$hook]['preprocess functions']);
}
}
}
}
/**
* Builds the theme registry cache.
*
* @param $theme
* The loaded $theme object as returned by list_themes().
* @param $base_theme
* An array of loaded $theme objects representing the ancestor themes in
* oldest first order.
* @param $theme_engine
* The name of the theme engine.
*/
function _theme_build_registry($theme, $base_theme, $theme_engine) {
$cache = array();
// First, preprocess the theme hooks advertised by modules. This will
// serve as the basic registry. Since the list of enabled modules is the same
// regardless of the theme used, this is cached in its own entry to save
// building it for every theme.
if ($cached = cache()->get('theme_registry:build:modules')) {
$cache = $cached->data;
}
else {
foreach (\Drupal::moduleHandler()->getImplementations('theme') as $module) {
_theme_process_registry($cache, $module, 'module', $module, drupal_get_path('module', $module));
}
// Only cache this registry if all modules are loaded.
if (\Drupal::moduleHandler()->isLoaded()) {
cache()->set("theme_registry:build:modules", $cache, CacheBackendInterface::CACHE_PERMANENT, array('theme_registry' => TRUE));
}
}
// Process each base theme.
foreach ($base_theme as $base) {
// If the base theme uses a theme engine, process its hooks.
$base_path = dirname($base->filename);
if ($theme_engine) {
_theme_process_registry($cache, $theme_engine, 'base_theme_engine', $base->name, $base_path);
}
_theme_process_registry($cache, $base->name, 'base_theme', $base->name, $base_path);
}
// And then the same thing, but for the theme.
if ($theme_engine) {
_theme_process_registry($cache, $theme_engine, 'theme_engine', $theme->name, dirname($theme->filename));
}
// Finally, hooks provided by the theme itself.
_theme_process_registry($cache, $theme->name, 'theme', $theme->name, dirname($theme->filename));
// Let modules alter the registry.
drupal_alter('theme_registry', $cache);
// Optimize the registry to not have empty arrays for functions.
foreach ($cache as $hook => $info) {
if (empty($info['preprocess functions'])) {
unset($cache[$hook]['preprocess functions']);
}
}
return $cache;
\Drupal::service('theme.registry')->reset();
}
/**
......@@ -706,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();
}
......@@ -912,18 +568,19 @@ function theme($hook, $variables = array()) {
static $default_attributes;
// If called before all modules are loaded, we do not necessarily have a full
// theme registry to work with, and therefore cannot process the theme
// request properly. See also _theme_load_registry().
// request properly. See also \Drupal\Core\Theme\Registry::get().
if (!\Drupal::moduleHandler()->isLoaded() && !defined('MAINTENANCE_MODE')) {
throw new Exception(t('theme() may not be called until all modules are loaded.'));
}
$hooks = theme_get_registry(FALSE);
/** @var \Drupal\Core\Utility\ThemeRegistry $theme_registry */
$theme_registry = \Drupal::service('theme.registry')->getRuntime();
// If an array of hook candidates were passed, use the first one that has an
// implementation.
if (is_array($hook)) {
foreach ($hook as $candidate) {
if (isset($hooks[$candidate])) {
if ($theme_registry->has($candidate)) {
break;
}
}
......@@ -935,16 +592,16 @@ function theme($hook, $variables = array()) {
// If there's no implementation, check for more generic fallbacks. If there's
// still no implementation, log an error and return an empty string.
if (!isset($hooks[$hook])) {
if (!$theme_registry->has($hook)) {
// Iteratively strip everything after the last '__' delimiter, until an
// implementation is found.
while ($pos = strrpos($hook, '__')) {
$hook = substr($hook, 0, $pos);
if (isset($hooks[$hook])) {
if ($theme_registry->has($hook)) {
break;
}
}
if (!isset($hooks[$hook])) {
if (!$theme_registry->has($hook)) {
// Only log a message when not trying theme suggestions ($hook being an
// array).
if (!isset($candidate)) {
......@@ -957,7 +614,7 @@ function theme($hook, $variables = array()) {
}
}
$info = $hooks[$hook];
$info = $theme_registry->get($hook);
global $theme_path;
$temp = $theme_path;
// point path_to_theme() to the currently used theme path:
......@@ -1030,8 +687,8 @@ function theme($hook, $variables = array()) {
// 'node__article' as a suggestion via hook_theme_suggestions_HOOK_alter(),
// enabling a theme to have an alternate template file for article nodes.
foreach (array_reverse($suggestions) as $suggestion) {
if (isset($hooks[$suggestion])) {
$info = $hooks[$suggestion];
if ($theme_registry->has($suggestion)) {
$info = $theme_registry->get($suggestion);
break;
}
}
......@@ -1039,7 +696,7 @@ function theme($hook, $variables = array()) {
// Invoke the variable preprocessors, if any.
if (isset($info['base hook'])) {
$base_hook = $info['base hook'];
$base_hook_info = $hooks[$base_hook];
$base_hook_info = $theme_registry->get($base_hook);
// Include files required by the base hook, since its variable preprocessors
// might reside there.
if (!empty($base_hook_info['includes'])) {
......@@ -1616,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().
......@@ -1645,8 +1308,6 @@ function template_preprocess_status_messages(&$variables) {
* http://www.w3.org/TR/WCAG-TECHS/H42.html for more information.
*/
function theme_links($variables) {
$language_url = language(Language::TYPE_URL);
$links = $variables['links'];
$attributes = $variables['attributes'];
$heading = $variables['heading'];
......@@ -1678,9 +1339,18 @@ function theme_links($variables) {
$num_links = count($links);
$i = 0;
$active = \Drupal::linkGenerator()->getActive();
$language_url = \Drupal::languageManager()->getLanguage(Language::TYPE_URL);
foreach ($links as $key => $link) {
$i++;
$link += array(
'href' => NULL,
'route_name' => NULL,
'route_parameters' => NULL,
);
$class = array();
// Use the array key as class name.
$class[] = drupal_html_class($key);
......@@ -1693,44 +1363,46 @@ function theme_links($variables) {
$class[] = 'last';
}
$link += array(
'href' => NULL,
$link_element = array(
'#type' => 'link',
'#title' => $link['title'],
'#options' => array_diff_key($link, MapArray::copyValuesToKeys(array('title', 'href', 'route_name', 'route_parameters'))),
'#href' => $link['href'],
'#route_name' => $link['route_name'],
'#route_parameters' => $link['route_parameters'],
);
// Handle links.
if (isset($link['href']) || isset($link['route_name'])) {
// @todo Reconcile Views usage of 'ajax' as a boolean with the rest of
// core's usage of it as an array.
if (isset($link['ajax']) && is_array($link['ajax'])) {
$link_element['#ajax'] = $link['ajax'];
}
// Handle links and ensure that the active class is added on the LIs.
if (isset($link['route_name'])) {
$variables = array(
'options' => array(),
);
if (!empty($link['language'])) {
$variables['options']['language'] = $link['language'];
}
if (($link['route_name'] == $active['route_name'])
// The language of an active link is equal to the current language.
&& (empty($variables['options']['language']) || ($variables['options']['language']->id == $active['language']))
&& ($link['route_parameters'] == $active['parameters'])) {
$class[] = 'active';
}
$item = drupal_render($link_element);
}
elseif (isset($link['href'])) {
$is_current_path = ($link['href'] == current_path() || ($link['href'] == '<front>' && drupal_is_front_page()));
$is_current_language = (empty($link['language']) || $link['language']->id == $language_url->id);
if ($is_current_path && $is_current_language) {
$class[] = 'active';
}
// @todo Reconcile Views usage of 'ajax' as a boolean with the rest of
// core's usage of it as an array.
if (isset($link['ajax']) && is_array($link['ajax'])) {
// To attach Ajax behavior, render a link element, rather than just
// call l().
$link_element = array(
'#type' => 'link',
'#title' => $link['title'],
'#href' => $link['href'],
'#ajax' => $link['ajax'],
'#options' => array_diff_key($link, drupal_map_assoc(array('title', 'href', 'ajax'))),
);
$item = drupal_render($link_element);
}
else {
// @todo theme_links() should *really* use the same parameters as l(),
// and just take an array of '#type' => 'link' elements, see
// https://drupal.org/node/2102777.
// Pass in $link as $options, as they share the same keys.
if (isset($link['href'])) {
$item = l($link['title'], $link['href'], $link);
}
else {
$link += array('route_parameters' => array());
$item = \Drupal::l($link['title'], $link['route_name'], $link['route_parameters'], $link);
}
}
$item = drupal_render($link_element);
}
// Handle title-only text items.
else {
......@@ -2554,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');
......@@ -2837,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().
......
......@@ -91,6 +91,10 @@ function _drupal_maintenance_theme() {
$ancestor = $themes[$ancestor]->base_theme;
}
_drupal_theme_initialize($themes[$theme], array_reverse($base_theme), '_theme_load_offline_registry');
_drupal_theme_initialize($themes[$theme], array_reverse($base_theme));
// Prime the theme registry.
// @todo Remove global theme variables.
Drupal::service('theme.registry');
// These CSS files are normally added by system_page_build(), except
// system.maintenance.css. When the database is inactive, it's not called so
......@@ -98,13 +102,6 @@ function _drupal_maintenance_theme() {
drupal_add_library('system', 'normalize');
}
/**
* Builds the registry when the site needs to bypass any database calls.
*/
function _theme_load_offline_registry($theme, $base_theme = NULL, $theme_engine = NULL) {
return _theme_build_registry($theme, $base_theme, $theme_engine);
}
/**
* Returns HTML for a list of maintenance tasks to perform.
*
......
......@@ -458,6 +458,10 @@ function update_prepare_d8_bootstrap() {
new Settings($settings);
$kernel = new DrupalKernel('update', drupal_classloader(), FALSE);
$kernel->boot();
// Clear the D7 caches, to ensure that for example the theme_registry does not
// take part in the upgrade process.
Drupal::cache('cache')->deleteAll();
}
/**
......
......@@ -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);
......
......@@ -9,7 +9,7 @@
use Drupal\Component\Utility\Crypt;
use Drupal\Core\PrivateKey;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Session\AccountInterface;
/**
* Generates and validates CSRF tokens.
......@@ -26,11 +26,11 @@ class CsrfTokenGenerator {
protected $privateKey;
/**
* The current request object.
* The current user.
*
* @var \Symfony\Component\HttpFoundation\Request
* @var \Drupal\Core\Session\AccountInterface
*/
protected $request;
protected $currentUser;
/**
* Constructs the token generator.
......@@ -43,13 +43,13 @@ public function __construct(PrivateKey $private_key) {
}
/**
* Sets the $request property.
* Sets the current user.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The HttpRequest object representing the current request.
* @param \Drupal\Core\Session\AccountInterface|null $current_user
* The current user service.
*/
public function setRequest(Request $request) {
$this->request = $request;
public function setCurrentUser(AccountInterface $current_user = NULL) {
$this->currentUser = $current_user;
}
/**
......@@ -84,9 +84,7 @@ public function get($value = '') {
* is TRUE, the return value will always be TRUE for anonymous users.
*/
public function validate($token, $value = '', $skip_anonymous = FALSE) {
$user = $this->request->attributes->get('_account');
return ($skip_anonymous && $user->isAnonymous()) || ($token == $this->get($value));
return ($skip_anonymous && $this->currentUser->isAnonymous()) || ($token == $this->get($value));
}
}
......@@ -9,6 +9,7 @@
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* JSON response object for AJAX requests.
......@@ -46,17 +47,25 @@ public function addCommand($command, $prepend = FALSE) {
}
/**
* Sets the response's data to be the array of AJAX commands.
*
* @param Request $request
* A request object.
* {@inheritdoc}
*
* @return Response
* The current response.
* Sets the response's data to be the array of AJAX commands.
*/
public function prepare(Request $request) {
$this->setData($this->ajaxRender($request));
return parent::prepare($request);
$this->prepareResponse($request);
return $this;
}
/**
* Sets the rendered AJAX right before the response is prepared.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
*/
public function prepareResponse(Request $request) {
if ($this->data == '{}') {
$this->setData($this->ajaxRender($request));
}
}
/**
......
......@@ -110,7 +110,6 @@ public function authenticate(Request $request) {
// Save the authenticated account and the provider that supplied it
// for later access.
$request->attributes->set('_account', $account);
$request->attributes->set('_authentication_provider', $this->triggeredProviderId);
// The global $user object is included for backward compatibility only and
......
......@@ -369,10 +369,17 @@ protected function getKernelParameters() {
protected function initializeContainer() {
$this->containerNeedsDumping = FALSE;
$persist = $this->getServicesToPersist();
// If we are rebuilding the kernel and we are in a request scope, store
// request info so we can add them back after the rebuild.
if (isset($this->container) && $this->container->hasScope('request')) {
$request = $this->container->get('request');
// The request service requires custom persisting logic, since it is also
// potentially scoped. During Drupal installation, there is a request
// service without a request scope.
$request_scope = FALSE;
if (isset($this->container)) {
if ($this->container->isScopeActive('request')) {
$request_scope = TRUE;
}
if ($this->container->initialized('request')) {
$request = $this->container->get('request');
}
}
$this->container = NULL;
$class = $this->getClassName();
......@@ -445,8 +452,10 @@ protected function initializeContainer() {
// Set the class loader which was registered as a synthetic service.
$this->container->set('class_loader', $this->classLoader);
// If we have a request set it back to the new container.
if (isset($request)) {
if ($request_scope) {
$this->container->enterScope('request');
}
if (isset($request)) {
$this->container->set('request', $request);
}
\Drupal::setContainer($this->container);
......
......@@ -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.
......@@ -267,9 +262,7 @@ class EntityType extends Plugin {
*
* @var array
*/
public $links = array(
'canonical' => '/entity/{entityType}/{id}',
);
public $links = array();
/**
* Specifies whether a module exposing permissions for the current entity type
......
......@@ -904,9 +904,18 @@ public function __clone() {
// Avoid deep-cloning when we are initializing a translation object, since
// it will represent the same entity, only with a different active language.
if (!$this->translationInitialize) {
foreach ($this->fields as $name => $properties) {
foreach ($properties as $langcode => $property) {
$this->fields[$name][$langcode] = clone $property;
$definitions = $this->getPropertyDefinitions();
foreach ($this->fields as $name => $values) {
$this->fields[$name] = array();
// Untranslatable fields may have multiple references for the same field
// object keyed by language. To avoid creating different field objects
// we retain just the original value, as references will be recreated
// later as needed.
if (empty($definitions[$name]['translatable']) && count($values) > 1) {
$values = array_intersect_key($values, array(Language::LANGCODE_DEFAULT => TRUE));
}
foreach ($values as $langcode => $items) {
$this->fields[$name][$langcode] = clone $items;
$this->fields[$name][$langcode]->setContext($name, $this);
}
}
......
......@@ -9,6 +9,7 @@
use Drupal\Core\Language\Language;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
/**
* Defines a base entity class.
......@@ -36,6 +37,13 @@ abstract class Entity implements EntityInterface {
*/
protected $enforceIsNew;
/**
* The route provider service.
*
* @var \Drupal\Core\Routing\RouteProviderInterface
*/
protected $routeProvider;
/**
* Constructs an Entity object.
*
......@@ -144,10 +152,18 @@ public function uri($rel = 'canonical') {
// The links array might contain URI templates set in annotations.
$link_templates = isset($entity_info['links']) ? $entity_info['links'] : array();
$template = NULL;
if (isset($link_templates[$rel])) {
try {
$template = $this->routeProvider()->getRouteByName($link_templates[$rel])->getPath();
}
catch (RouteNotFoundException $e) {
// Fall back to a non-template-based URI.
}
}
if ($template) {
// If there is a template for the given relationship type, do the
// placeholder replacement and use that as the path.
$template = $link_templates[$rel];
$replacements = $this->uriPlaceholderReplacements();
$uri['path'] = str_replace(array_keys($replacements), array_values($replacements), $template);
......@@ -369,4 +385,17 @@ public function changed() {
}
}
/**
* Wraps the route provider service.
*
* @return \Drupal\Core\Routing\RouteProviderInterface
* The route provider.
*/
protected function routeProvider() {
if (!$this->routeProvider) {
$this->routeProvider = \Drupal::service('router.route_provider');
}
return $this->routeProvider;
}
}
......@@ -176,6 +176,10 @@ public function form(array $form, array &$form_state) {
* @see \Drupal\Core\Entity\EntityFormController::form()
*/
public function processForm($element, $form_state, $form) {
// If the form is cached, process callbacks may not have a valid reference
// to the entity object, hence we must restore it.
$this->entity = $form_state['controller']->getEntity();
// Assign the weights configured in the form display.
foreach ($this->getFormDisplay($form_state)->getComponents() as $name => $options) {
if (isset($element[$name])) {
......
......@@ -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,
)
);
}
......