Skip to content
......@@ -19,8 +19,3 @@ services:
class: Drupal\system\PathProcessor\PathProcessorFiles
tags:
- { name: path_processor_inbound, priority: 200 }
theme.negotiator.system.batch:
class: Drupal\system\Theme\BatchNegotiator
arguments: ['@batch.storage']
tags:
- { name: theme_negotiator, priority: 1000 }
......@@ -12,6 +12,19 @@
use Drupal\Core\Ajax\CloseDialogCommand;
use Drupal\Core\Ajax\HtmlCommand;
/**
* Implements hook_menu().
*/
function ajax_test_menu() {
$items['ajax-test/order'] = array(
'title' => 'AJAX commands order',
'route_name' => 'ajax_test.order',
'theme callback' => 'ajax_base_page_theme',
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Implements hook_system_theme_info().
*/
......
......@@ -37,8 +37,6 @@ ajax_test.order:
path: '/ajax-test/order'
defaults:
_controller: '\Drupal\ajax_test\Controller\AjaxTestController::order'
options:
_theme: ajax_base_page
requirements:
_access: 'TRUE'
......
<?php
/**
* @file
* Contains \Drupal\menu_test\EventSubscriber\ActiveTrailSubscriber.
*/
namespace Drupal\menu_test\EventSubscriber;
use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Tracks the active trail.
*/
class ActiveTrailSubscriber implements EventSubscriberInterface {
/**
* The active trail before redirect.
*
* @var array
*/
protected $trail = array();
/**
* The state service.
*
* @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
*/
protected $state;
/**
* Constructs a new ActiveTrailSubscriber.
*
* @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $state
* The state service.
*/
public function __construct(KeyValueStoreInterface $state) {
$this->state = $state;
}
/**
* Tracks the active trail.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The event to process.
*/
public function onKernelRequest(GetResponseEvent $event) {
// When requested by one of the MenuTrailTestCase tests, record the initial
// active trail during Drupal's bootstrap (before the user is redirected to
// a custom 403 or 404 page).
if (!$this->trail && $this->state->get('menu_test.record_active_trail') ?: FALSE) {
$this->trail = menu_get_active_trail();
$this->state->set('menu_test.active_trail_initial', $this->trail);
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = array('onKernelRequest');
return $events;
}
}
<?php
/**
* @file
* Contains \Drupal\menu_test\Theme\TestThemeNegotiator.
*/
namespace Drupal\menu_test\Theme;
use Drupal\Core\Theme\ThemeNegotiatorInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Tests the theme negotiation functionality.
*
* Retrieves the theme key of the theme to use for the current request based on
* the theme name provided in the URL.
*/
class TestThemeNegotiator implements ThemeNegotiatorInterface {
/**
* {@inheritdoc}
*/
public function determineActiveTheme(Request $request) {
$argument = $request->attributes->get('inherited');
// Test using the variable administrative theme.
if ($argument == 'use-admin-theme') {
return \Drupal::config('system.theme')->get('admin');
}
// Test using a theme that exists, but may or may not be enabled.
elseif ($argument == 'use-stark-theme') {
return 'stark';
}
// Test using a theme that does not exist.
elseif ($argument == 'use-fake-theme') {
return 'fake_theme';
}
// For any other value of the URL argument, do not return anything. This
// allows us to test that returning nothing from a theme negotiation
// causes the page to correctly fall back on using the main site theme.
}
}
......@@ -64,12 +64,18 @@ function menu_test_menu() {
'route_name' => 'menu_test.hierarchy_parent_child2',
);
// Theme callback tests.
$items['menu-test/theme-callback/%'] = array(
'title' => 'Page that displays different themes',
'route_name' => 'menu_test.theme_callback',
'theme callback' => 'menu_test_theme_callback',
'theme arguments' => array(2),
);
$items['menu-test/theme-callback/%/inheritance'] = array(
'title' => 'Page that tests theme negotiation inheritance.',
'title' => 'Page that tests theme callback inheritance.',
'route_name' => 'menu_test.theme_callback_inheritance',
);
$items['menu-test/no-theme-callback'] = array(
'title' => 'Page that displays different themes without using a theme negotiation.',
'title' => 'Page that displays different themes without using a theme callback.',
'route_name' => 'menu_test.no_theme_callback',
);
// Path containing "exotic" characters.
......@@ -433,7 +439,7 @@ function menu_test_custom_403_404_callback() {
}
/**
* Page callback: Tests the theme negotiation functionality.
* Page callback: Tests the theme callback functionality.
*
* @param bool $inherited
* (optional) TRUE when the requested page is intended to inherit
......@@ -451,15 +457,66 @@ function menu_test_theme_page_callback($inherited = FALSE) {
global $theme_key;
// Initialize the theme system so that $theme_key will be populated.
drupal_theme_initialize();
// Now we check what the theme negotiator service returns.
$active_theme = \Drupal::service('theme.negotiator')->getActiveTheme('getActiveTheme');
$output = "Active theme: $active_theme. Actual theme: $theme_key.";
// Now check both the requested custom theme and the actual theme being used.
$custom_theme = menu_get_custom_theme();
$requested_theme = empty($custom_theme) ? 'NONE' : $custom_theme;
$output = "Custom theme: $requested_theme. Actual theme: $theme_key.";
if ($inherited) {
$output .= ' Theme negotiation inheritance is being tested.';
$output .= ' Theme callback inheritance is being tested.';
}
return $output;
}
/**
* Theme callback: Tests the theme callback functionality.
*
* Retrieves the theme key of the theme to use for the current request based on
* the theme name provided in the URL.
*
* @param string $argument
* The argument passed in from the URL.
*
* @return string
* The name of the custom theme to request for the current page.
*
* @see menu_test_menu().
*/
function menu_test_theme_callback($argument) {
// Test using the variable administrative theme.
if ($argument == 'use-admin-theme') {
return \Drupal::config('system.theme')->get('admin');
}
// Test using a theme that exists, but may or may not be enabled.
elseif ($argument == 'use-stark-theme') {
return 'stark';
}
// Test using a theme that does not exist.
elseif ($argument == 'use-fake-theme') {
return 'fake_theme';
}
// For any other value of the URL argument, do not return anything. This
// allows us to test that returning nothing from a theme callback function
// causes the page to correctly fall back on using the main site theme.
}
/**
* Implements hook_custom_theme().
*
* If an appropriate variable has been set in the database, request the theme
* that is stored there. Otherwise, do not attempt to dynamically set the theme.
*/
function menu_test_custom_theme() {
// When requested by one of the MenuTrailTestCase tests, record the initial
// active trail during Drupal's bootstrap (before the user is redirected to a
// custom 403 or 404 page). See menu_test_custom_403_404_callback().
if (\Drupal::state()->get('menu_test.record_active_trail') ?: FALSE) {
\Drupal::state()->set('menu_test.active_trail_initial', menu_get_active_trail());
}
if ($theme = \Drupal::state()->get('menu_test.hook_custom_theme_name') ?: FALSE) {
return $theme;
}
}
/**
* Sets a static variable for the testMenuName() test.
*
......
......@@ -490,7 +490,7 @@ menu_test.theme_callback:
menu_test.no_theme_callback:
path: '/menu-test/no-theme-callback'
defaults:
_title: 'Page that displays different themes without using a theme negotiation.'
_title: 'Page that displays different themes without using a theme callback.'
_content: '\Drupal\menu_test\Controller\MenuTestController::themePage'
inherited: false
requirements:
......@@ -511,7 +511,7 @@ menu_test.exotic_path:
menu_test.theme_callback_inheritance:
path: '/menu-test/theme-callback/{inherited}/inheritance'
defaults:
_title: 'Page that tests theme negotiation inheritance.'
_title: 'Page that tests theme callback inheritance.'
_content: '\Drupal\menu_test\Controller\MenuTestController::themePage'
requirements:
_permission: 'access content'
......
......@@ -3,14 +3,3 @@ services:
class: Drupal\menu_test\EventSubscriber\MaintenanceModeSubscriber
tags:
- { name: event_subscriber }
menu_test.active_trail_subscriber:
class: Drupal\menu_test\EventSubscriber\ActiveTrailSubscriber
arguments: ['@state']
tags:
- { name: event_subscriber }
theme.negotiator.test_theme:
class: Drupal\menu_test\Theme\TestThemeNegotiator
tags:
- { name: theme_negotiator }
<?php
/**
* @file
* Contains \Drupal\theme_test\Theme\CustomThemeNegotiator.
*/
namespace Drupal\theme_test\Theme;
use Drupal\Core\Theme\ThemeNegotiatorInterface;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
/**
* Just forces the 'test_theme' theme.
*/
class CustomThemeNegotiator implements ThemeNegotiatorInterface {
/**
* {@inheritdoc}
*/
public function determineActiveTheme(Request $request) {
if (($route_object = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)) && $route_object instanceof Route && $route_object->hasOption('_custom_theme')) {
return $route_object->getOption('_custom_theme');
}
}
}
<?php
/**
* @file
* Contains \Drupal\theme_test\Theme\HighPriorityThemeNegotiator.
*/
namespace Drupal\theme_test\Theme;
use Drupal\Core\Theme\ThemeNegotiatorInterface;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Implements a test theme negotiator which was configured with a high priority.
*/
class HighPriorityThemeNegotiator implements ThemeNegotiatorInterface {
/**
* {@inheritdoc}
*/
public function determineActiveTheme(Request $request) {
if (($route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME)) && $route_name == 'theme_test.priority') {
return 'stark';
}
}
}
......@@ -80,6 +80,13 @@ function theme_test_menu() {
return $items;
}
/**
* Custom theme callback.
*/
function _theme_custom_theme() {
return 'test_theme';
}
/**
* Implements hook_preprocess_HOOK() for HTML document templates.
*/
......
theme_test.function_template_override:
path: '/theme-test/function-template-overridden'
options:
_custom_theme: 'test_theme'
defaults:
_content: '\Drupal\theme_test\ThemeTestController::functionTemplateOverridden'
requirements:
......@@ -23,18 +21,6 @@ theme_test.template_test:
theme_test.suggestion:
path: '/theme-test/suggestion'
options:
_custom_theme: 'test_theme'
defaults:
_content: '\Drupal\theme_test\ThemeTestController::testSuggestion'
_title: 'Suggestion'
requirements:
_access: 'TRUE'
theme_test.priority:
path: '/theme-test/priority'
options:
_custom_theme: 'test_theme'
defaults:
_content: '\Drupal\theme_test\ThemeTestController::testSuggestion'
_title: 'Suggestion'
......@@ -43,8 +29,6 @@ theme_test.priority:
theme_test.alter:
path: '/theme-test/alter'
options:
_custom_theme: 'test_theme'
defaults:
_content: '\Drupal\theme_test\ThemeTestController::testAlter'
_title: 'Suggestion'
......
......@@ -3,13 +3,3 @@ services:
class: Drupal\theme_test\EventSubscriber\ThemeTestSubscriber
tags:
- { name: event_subscriber }
theme.negotiator.test_custom_theme:
class: Drupal\theme_test\Theme\CustomThemeNegotiator
tags:
- { name: theme_negotiator }
theme.negotiator.high_priority:
class: Drupal\theme_test\Theme\HighPriorityThemeNegotiator
tags:
- { name: theme_negotiator, priority: 1000 }
......@@ -25,11 +25,11 @@ function setUp() {
// Make sure we are using distinct default and administrative themes for
// the duration of these tests.
theme_enable(array('bartik', 'seven'));
\Drupal::config('system.theme')
->set('default', 'bartik')
->set('admin', 'seven')
->save();
theme_enable(array('seven'));
\Drupal::config('system.theme')->set('admin', 'seven')->save();
// Create and log in as a user who has permission to add and edit taxonomy
// terms and view the administrative theme.
......
......@@ -24,11 +24,23 @@ function taxonomy_term_page(Term $term) {
foreach ($term->uriRelationships() as $rel) {
$uri = $term->uri($rel);
// Set the term path as the canonical URL to prevent duplicate content.
drupal_add_html_head_link(array('rel' => $rel, 'href' => url($uri['path'], $uri['options'])), TRUE);
$build['#attached']['drupal_add_html_head_link'][] = array(
array(
'rel' => $rel,
'href' => url($uri['path'], $uri['options']),
),
TRUE,
);
if ($rel == 'canonical') {
// Set the non-aliased canonical path as a default shortlink.
drupal_add_html_head_link(array('rel' => 'shortlink', 'href' => url($uri['path'], array_merge($uri['options'], array('alias' => TRUE)))), TRUE);
$build['#attached']['drupal_add_html_head_link'][] = array(
array(
'rel' => 'shortlink',
'href' => url($uri['path'], array_merge($uri['options'], array('alias' => TRUE))),
),
TRUE,
);
}
}
......
......@@ -49,7 +49,6 @@ function setUp() {
// Configure the theme system.
$this->installConfig(array('system', 'field'));
$this->installSchema('entity_test', 'entity_test');
$this->installSchema('user', 'users');
// @todo Add helper methods for all of the following.
......
<?php
/**
* @file
* Contains \Drupal\user\Theme\AdminNegotiator.
*/
namespace Drupal\user\Theme;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Entity\EntityManager;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Theme\ThemeNegotiatorInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Sets the active theme on admin pages.
*/
class AdminNegotiator implements ThemeNegotiatorInterface {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $user;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactory
*/
protected $configFactory;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManager
*/
protected $entityManager;
/**
* Creates a new AdminNegotiator instance.
*
* @param \Drupal\Core\Session\AccountInterface $user
* The current user.
* @param \Drupal\Core\Config\ConfigFactory $config_factory
* The config factory.
* @param \Drupal\Core\Entity\EntityManager $entity_manager
* The entity manager.
*/
public function __construct(AccountInterface $user, ConfigFactory $config_factory, EntityManager $entity_manager) {
$this->user = $user;
$this->configFactory = $config_factory;
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public function determineActiveTheme(Request $request) {
$path = $request->attributes->get('_system_path');
// Don't break if the user_role entity is not available in order to decouple
// system and user module.
if ($this->entityManager->hasController('user_role', 'storage') && $this->user->hasPermission('view the administration theme') && path_is_admin($path)) {
return $this->configFactory->get('system.theme')->get('admin');
}
}
}
<?php
/**
* @file
* Contains \Drupal\user\Theme\UserNegotiator.
*/
namespace Drupal\user\Theme;
use Drupal\Core\Entity\EntityManager;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Theme\ActiveTheme;
use Drupal\Core\Theme\ThemeNegotiatorInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Defines the theme negotiator service for theme configured per user.
*/
class UserNegotiator implements ThemeNegotiatorInterface {
/**
* The user storage controller.
*
* @var \Drupal\user\UserStorageControllerInterface
*/
protected $userStorageController;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs a UserNegotiator object.
*
* @param \Drupal\Core\Entity\EntityManager $entity_manager
* The entity manager
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
*/
public function __construct(EntityManager $entity_manager, AccountInterface $current_user) {
$this->userStorageController = $entity_manager->getStorageController('user');
$this->currentUser = $current_user;
}
/**
* {@inheritdoc}
*/
public function determineActiveTheme(Request $request) {
if ($user = $this->userStorageController->load($this->currentUser->id())) {;
// Only select the user selected theme if it is available in the
// list of themes that can be accessed.
if (!empty($user->theme) && drupal_theme_access($user->theme)) {
return $user->theme;
}
}
}
}
......@@ -25,13 +25,3 @@ services:
class: Drupal\user\EventSubscriber\MaintenanceModeSubscriber
tags:
- { name: event_subscriber }
theme.negotiator.user:
class: Drupal\user\Theme\UserNegotiator
arguments: ['@plugin.manager.entity', '@current_user']
tags:
- { name: theme_negotiator, priority: -50 }
theme.negotiator.admin_theme:
class: Drupal\user\Theme\AdminNegotiator
arguments: ['@current_user', '@config.factory', '@entity.manager']
tags:
- { name: theme_negotiator, priority: -40 }
......@@ -57,13 +57,13 @@ public function attachTo($display_id, $path, $title) {
'#url' => $url,
'#title' => $title,
);
$this->view->feed_icon .= drupal_render($feed_icon);
drupal_add_html_head_link(array(
$feed_icon['#attached']['drupal_add_html_head_link'][][] = array(
'rel' => 'alternate',
'type' => 'application/rss+xml',
'title' => $title,
'href' => $url
));
'href' => $url,
);
$this->view->feed_icon .= drupal_render($feed_icon);
}
}
......