Commit c2b28643 authored by alexpott's avatar alexpott

Issue #2745953 by dawehner, tim.plunkett, Wim Leers: AjaxBasePageNegotiator...

Issue #2745953 by dawehner, tim.plunkett, Wim Leers: AjaxBasePageNegotiator should not require _theme: ajax_base_page to be specified
parent 729f65aa
......@@ -21,8 +21,6 @@
* 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 {
......@@ -67,28 +65,25 @@ public function __construct(CsrfTokenGenerator $token_generator, ConfigFactoryIn
* {@inheritdoc}
*/
public function applies(RouteMatchInterface $route_match) {
// Check whether the route was configured to use the base page theme.
return ($route = $route_match->getRouteObject())
&& $route->hasOption('_theme')
&& $route->getOption('_theme') == 'ajax_base_page';
$ajax_page_state = $this->requestStack->getCurrentRequest()->request->get('ajax_page_state');
return !empty($ajax_page_state['theme']) && isset($ajax_page_state['theme_token']);
}
/**
* {@inheritdoc}
*/
public function determineActiveTheme(RouteMatchInterface $route_match) {
if (($ajax_page_state = $this->requestStack->getCurrentRequest()->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'];
$ajax_page_state = $this->requestStack->getCurrentRequest()->request->get('ajax_page_state');
$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;
}
// 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;
}
}
......
......@@ -2,7 +2,5 @@ contextual.render:
path: '/contextual/render'
defaults:
_controller: '\Drupal\contextual\ContextualController::render'
options:
_theme: ajax_base_page
requirements:
_permission: 'access contextual links'
......@@ -2,8 +2,6 @@ editor.filter_xss:
path: '/editor/filter_xss/{filter_format}'
defaults:
_controller: '\Drupal\editor\EditorController::filterXss'
options:
_theme: ajax_base_page
requirements:
_entity_access: 'filter_format.use'
......@@ -12,7 +10,6 @@ editor.field_untransformed_text:
defaults:
_controller: '\Drupal\editor\EditorController::getUntransformedText'
options:
_theme: ajax_base_page
parameters:
entity:
type: entity:{entity_type}
......@@ -25,8 +22,6 @@ editor.image_dialog:
defaults:
_form: '\Drupal\editor\Form\EditorImageDialog'
_title: 'Upload image'
options:
_theme: ajax_base_page
requirements:
_entity_access: 'filter_format.use'
......@@ -35,7 +30,5 @@ editor.link_dialog:
defaults:
_form: '\Drupal\editor\Form\EditorLinkDialog'
_title: 'Add link'
options:
_theme: ajax_base_page
requirements:
_entity_access: 'filter_format.use'
......@@ -2,8 +2,6 @@ quickedit.metadata:
path: '/quickedit/metadata'
defaults:
_controller: '\Drupal\quickedit\QuickEditController::metadata'
options:
_theme: ajax_base_page
requirements:
_permission: 'access in-place editing'
......@@ -19,7 +17,6 @@ quickedit.field_form:
defaults:
_controller: '\Drupal\quickedit\QuickEditController::fieldForm'
options:
_theme: ajax_base_page
parameters:
entity:
type: entity:{entity_type}
......
......@@ -35,12 +35,17 @@ ajax_test.render:
requirements:
_access: 'TRUE'
ajax_test.admin.theme:
path: '/admin/ajax-test/theme'
defaults:
_controller: '\Drupal\ajax_test\Controller\AjaxTestController::theme'
requirements:
_access: 'TRUE'
ajax_test.order:
path: '/ajax-test/order'
defaults:
_controller: '\Drupal\ajax_test\Controller\AjaxTestController::order'
options:
_theme: ajax_base_page
requirements:
_access: 'TRUE'
......
......@@ -61,6 +61,15 @@ public function render() {
];
}
/**
* Returns the used theme.
*/
public function theme() {
return [
'#markup' => 'Current theme: ' . \Drupal::theme()->getActiveTheme()->getName(),
];
}
/**
* Returns an AjaxResponse; settings command set last.
*
......@@ -184,6 +193,17 @@ public function dialog() {
))
),
),
'link8' => [
'title' => 'Link 8 (ajax)',
'url' => Url::fromRoute('ajax_test.admin.theme'),
'attributes' => [
'class' => ['use-ajax'],
'data-dialog-type' => 'modal',
'data-dialog-options' => json_encode([
'width' => 400,
]),
],
],
),
);
......
......@@ -2,7 +2,5 @@ toolbar.subtrees:
path: '/toolbar/subtrees/{hash}'
defaults:
_controller: '\Drupal\toolbar\Controller\ToolbarController::subtreesAjax'
options:
_theme: ajax_base_page
requirements:
_custom_access: '\Drupal\toolbar\Controller\ToolbarController::checkSubTreeAccess'
......@@ -2,8 +2,6 @@ views.ajax:
path: '/views/ajax'
defaults:
_controller: '\Drupal\views\Controller\ViewAjaxController::ajaxView'
options:
_theme: ajax_base_page
requirements:
_access: 'TRUE'
......
<?php
namespace Drupal\FunctionalJavascriptTests\Ajax;
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
/**
* Tests that AJAX responses use the current theme.
*
* @group Ajax
*/
class AjaxThemeTest extends JavascriptTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['ajax_test'];
public function testAjaxWithAdminRoute() {
\Drupal::service('theme_installer')->install(['stable', 'seven']);
$theme_config = \Drupal::configFactory()->getEditable('system.theme');
$theme_config->set('admin', 'seven');
$theme_config->set('default', 'stable');
$theme_config->save();
$account = $this->drupalCreateUser(['view the administration theme']);
$this->drupalLogin($account);
// First visit the site directly via the URL. This should render it in the
// admin theme.
$this->drupalGet('admin/ajax-test/theme');
$assert = $this->assertSession();
$assert->pageTextContains('Current theme: seven');
// Now click the modal, which should also use the admin theme.
$this->drupalGet('ajax-test/dialog');
$assert->pageTextNotContains('Current theme: stable');
$this->clickLink('Link 8 (ajax)');
$this->waitForAjaxToFinish();
$assert->pageTextContains('Current theme: stable');
$assert->pageTextNotContains('Current theme: seven');
}
/**
* Waits for jQuery to become active and animations to complete.
*/
protected function waitForAjaxToFinish() {
$condition = "(0 === jQuery.active && 0 === jQuery(':animated').length)";
$this->assertJsCondition($condition, 10000);
}
}
<?php
namespace Drupal\Tests\Core\Theme;
use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Theme\AjaxBasePageNegotiator;
use Drupal\Tests\UnitTestCase;
use Prophecy\Argument;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* @coversDefaultClass \Drupal\Core\Theme\AjaxBasePageNegotiator
* @group Theme
*/
class AjaxBasePageNegotiatorTest extends UnitTestCase {
/**
* @var \Drupal\Core\Theme\AjaxBasePageNegotiator
*
* The AJAX base page negotiator.
*/
protected $negotiator;
/**
* @var \Drupal\Core\Access\CsrfTokenGenerator|\Prophecy\Prophecy\ProphecyInterface
*
* The CSRF token generator.
*/
protected $tokenGenerator;
/**
* @var \Symfony\Component\HttpFoundation\RequestStack
*
* The request stack.
*/
protected $requestStack;
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->tokenGenerator = $this->prophesize(CsrfTokenGenerator::class);
$config_factory = $this->getConfigFactoryStub(['system.theme' => ['default' => 'bartik']]);
$this->requestStack = new RequestStack();
$this->negotiator = new AjaxBasePageNegotiator($this->tokenGenerator->reveal(), $config_factory, $this->requestStack);
}
/**
* @covers ::applies
* @dataProvider providerTestApplies
*/
public function testApplies($request_data, $expected) {
$request = new Request([], $request_data);
$route_match = RouteMatch::createFromRequest($request);
$this->requestStack->push($request);
$result = $this->negotiator->applies($route_match);
$this->assertSame($expected, $result);
}
public function providerTestApplies() {
$data = [];
$data['empty'] = [[], FALSE];
$data['no_theme'] = [['ajax_page_state' => ['theme' => '', 'theme_token' => '']], FALSE];
$data['valid_theme_empty_theme_token'] = [['ajax_page_state' => ['theme' => 'seven', 'theme_token' => '']], TRUE];
$data['valid_theme_valid_theme_token'] = [['ajax_page_state' => ['theme' => 'seven', 'theme_token' => 'valid_theme_token']], TRUE];
return $data;
}
/**
* @covers ::determineActiveTheme
*/
public function testDetermineActiveThemeValidToken() {
$theme = 'seven';
$theme_token = 'valid_theme_token';
$request = new Request([], ['ajax_page_state' => ['theme' => $theme, 'theme_token' => $theme_token]]);
$this->requestStack->push($request);
$route_match = RouteMatch::createFromRequest($request);
$this->tokenGenerator->validate($theme_token, $theme)->willReturn(TRUE);
$result = $this->negotiator->determineActiveTheme($route_match);
$this->assertSame($theme, $result);
}
/**
* @covers ::determineActiveTheme
*/
public function testDetermineActiveThemeInvalidToken() {
$theme = 'seven';
$theme_token = 'invalid_theme_token';
$request = new Request([], ['ajax_page_state' => ['theme' => $theme, 'theme_token' => $theme_token]]);
$this->requestStack->push($request);
$route_match = RouteMatch::createFromRequest($request);
$this->tokenGenerator->validate($theme_token, $theme)->willReturn(FALSE);
$result = $this->negotiator->determineActiveTheme($route_match);
$this->assertSame(NULL, $result);
}
/**
* @covers ::determineActiveTheme
*/
public function testDetermineActiveThemeDefaultTheme() {
$theme = 'bartik';
// When the theme is the system default, an empty string is provided as the
// theme token. See system_js_settings_alter().
$theme_token = '';
$request = new Request([], ['ajax_page_state' => ['theme' => $theme, 'theme_token' => $theme_token]]);
$this->requestStack->push($request);
$route_match = RouteMatch::createFromRequest($request);
$this->tokenGenerator->validate(Argument::cetera())->shouldNotBeCalled();
$result = $this->negotiator->determineActiveTheme($route_match);
$this->assertSame($theme, $result);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment