Update to Drupal 8.1.9. For more information, see https://www.drupal.org/project/drupal/releases/8.1.9

This commit is contained in:
Pantheon Automation 2016-09-07 13:26:21 -07:00 committed by Greg Anderson
parent f9f23cdf38
commit 09b113657a
125 changed files with 2307 additions and 385 deletions

View file

@ -1213,7 +1213,7 @@ function _node_access_rebuild_batch_operation(&$context) {
// Initiate multistep processing.
$context['sandbox']['progress'] = 0;
$context['sandbox']['current_node'] = 0;
$context['sandbox']['max'] = \Drupal::entityQuery('node')->count()->execute();
$context['sandbox']['max'] = \Drupal::entityQuery('node')->accessCheck(FALSE)->count()->execute();
}
// Process the next 20 nodes.

View file

@ -127,7 +127,7 @@ class NodeController extends ControllerBase implements ContainerInjectionInterfa
public function revisionShow($node_revision) {
$node = $this->entityManager()->getStorage('node')->loadRevision($node_revision);
$node = $this->entityManager()->getTranslationFromContext($node);
$node_view_controller = new NodeViewController($this->entityManager, $this->renderer);
$node_view_controller = new NodeViewController($this->entityManager, $this->renderer, $this->currentUser());
$page = $node_view_controller->view($node);
unset($page['nodes'][$node->id()]['#cache']);
return $page;

View file

@ -4,12 +4,50 @@ namespace Drupal\node\Controller;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Controller\EntityViewController;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a controller to render a single node.
*/
class NodeViewController extends EntityViewController {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Creates an NodeViewController object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer service.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user. For backwards compatibility this is optional, however
* this will be removed before Drupal 9.0.0.
*/
public function __construct(EntityManagerInterface $entity_manager, RendererInterface $renderer, AccountInterface $current_user = NULL) {
parent::__construct($entity_manager, $renderer);
$this->currentUser = $current_user ?: \Drupal::currentUser();
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager'),
$container->get('renderer'),
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
@ -17,27 +55,44 @@ class NodeViewController extends EntityViewController {
$build = parent::view($node, $view_mode, $langcode);
foreach ($node->uriRelationships() as $rel) {
// Set the node path as the canonical URL to prevent duplicate content.
$build['#attached']['html_head_link'][] = array(
array(
'rel' => $rel,
'href' => $node->url($rel),
),
TRUE,
);
$url = $node->toUrl($rel);
// Add link relationships if the user is authenticated or if the anonymous
// user has access. Access checking must be done for anonymous users to
// avoid traffic to inaccessible pages from web crawlers. For
// authenticated users, showing the links in HTML head does not impact
// user experience or security, since the routes are access checked when
// visited and only visible via view source. This prevents doing
// potentially expensive and hard to cache access checks on every request.
// This means that the page will vary by user.permissions. We also rely on
// the access checking fallback to ensure the correct cacheability
// metadata if we have to check access.
if ($this->currentUser->isAuthenticated() || $url->access($this->currentUser)) {
// Set the node path as the canonical URL to prevent duplicate content.
$build['#attached']['html_head_link'][] = array(
array(
'rel' => $rel,
'href' => $url->toString(),
),
TRUE,
);
}
if ($rel == 'canonical') {
// Set the non-aliased canonical path as a default shortlink.
$build['#attached']['html_head_link'][] = array(
array(
'rel' => 'shortlink',
'href' => $node->url($rel, array('alias' => TRUE)),
'href' => $url->setOption('alias', TRUE)->toString(),
),
TRUE,
);
}
}
// Given this varies by $this->currentUser->isAuthenticated(), add a cache
// context based on the anonymous role.
$build['#cache']['contexts'][] = 'user.roles:anonymous';
return $build;
}

View file

@ -3,11 +3,12 @@
namespace Drupal\node\Plugin\migrate;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\migrate\Plugin\MigrationDeriverTrait;
use Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@ -33,7 +34,7 @@ class D6NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
/**
* The CCK plugin manager.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
* @var \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface
*/
protected $cckPluginManager;
@ -49,12 +50,12 @@ class D6NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
*
* @param string $base_plugin_id
* The base plugin ID for the plugin ID.
* @param \Drupal\Component\Plugin\PluginManagerInterface $cck_manager
* @param \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface $cck_manager
* The CCK plugin manager.
* @param bool $translations
* Whether or not to include translations.
*/
public function __construct($base_plugin_id, PluginManagerInterface $cck_manager, $translations) {
public function __construct($base_plugin_id, MigrateCckFieldPluginManagerInterface $cck_manager, $translations) {
$this->basePluginId = $base_plugin_id;
$this->cckPluginManager = $cck_manager;
$this->includeTranslations = $translations;
@ -128,14 +129,15 @@ class D6NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
if (isset($fields[$node_type])) {
foreach ($fields[$node_type] as $field_name => $info) {
$field_type = $info['type'];
if ($this->cckPluginManager->hasDefinition($info['type'])) {
try {
$plugin_id = $this->cckPluginManager->getPluginIdFromFieldType($field_type, ['core' => 6], $migration);
if (!isset($this->cckPluginCache[$field_type])) {
$this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($field_type, ['core' => 6], $migration);
$this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($plugin_id, ['core' => 6], $migration);
}
$this->cckPluginCache[$field_type]
->processCckFieldValues($migration, $field_name, $info);
}
else {
catch (PluginNotFoundException $ex) {
$migration->setProcessOfProperty($field_name, $field_name);
}
}

View file

@ -3,11 +3,12 @@
namespace Drupal\node\Plugin\migrate;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\migrate\Plugin\MigrationDeriverTrait;
use Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@ -33,7 +34,7 @@ class D7NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
/**
* The CCK plugin manager.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
* @var \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface
*/
protected $cckPluginManager;
@ -42,10 +43,10 @@ class D7NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
*
* @param string $base_plugin_id
* The base plugin ID for the plugin ID.
* @param \Drupal\Component\Plugin\PluginManagerInterface $cck_manager
* @param \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface $cck_manager
* The CCK plugin manager.
*/
public function __construct($base_plugin_id, PluginManagerInterface $cck_manager) {
public function __construct($base_plugin_id, MigrateCckFieldPluginManagerInterface $cck_manager) {
$this->basePluginId = $base_plugin_id;
$this->cckPluginManager = $cck_manager;
}
@ -98,14 +99,15 @@ class D7NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
if (isset($fields[$node_type])) {
foreach ($fields[$node_type] as $field_name => $info) {
$field_type = $info['type'];
if ($this->cckPluginManager->hasDefinition($field_type)) {
try {
$plugin_id = $this->cckPluginManager->getPluginIdFromFieldType($field_type, ['core' => 7], $migration);
if (!isset($this->cckPluginCache[$field_type])) {
$this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($field_type, ['core' => 7], $migration);
$this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($plugin_id, ['core' => 7], $migration);
}
$this->cckPluginCache[$field_type]
->processCckFieldValues($migration, $field_name, $info);
}
else {
catch (PluginNotFoundException $ex) {
$migration->setProcessOfProperty($field_name, $field_name);
}
}

View file

@ -2,6 +2,8 @@
namespace Drupal\node\Tests;
use Drupal\node\Entity\NodeType;
/**
* Ensures that node access rebuild functions work correctly even
* when other modules implements hook_node_grants().
@ -11,20 +13,27 @@ namespace Drupal\node\Tests;
class NodeAccessRebuildNodeGrantsTest extends NodeTestBase {
/**
* A user to test the rebuild nodes feature.
* A user to create nodes that only it has access to.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
/**
* A user to test the rebuild nodes feature which can't access the nodes.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$admin_user = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports', 'bypass node access'));
$this->drupalLogin($admin_user);
$this->adminUser = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports'));
$this->drupalLogin($this->adminUser);
$this->webUser = $this->drupalCreateUser();
}
@ -34,25 +43,54 @@ class NodeAccessRebuildNodeGrantsTest extends NodeTestBase {
*/
public function testNodeAccessRebuildNodeGrants() {
\Drupal::service('module_installer')->install(['node_access_test']);
\Drupal::state()->set('node_access_test.private', TRUE);
node_access_test_add_field(NodeType::load('page'));
$this->resetAll();
$node = $this->drupalCreateNode(array(
'uid' => $this->webUser->id(),
));
// Create 30 nodes so that _node_access_rebuild_batch_operation() has to run
// more than once.
for ($i = 0; $i < 30; $i++) {
$nodes[] = $this->drupalCreateNode(array(
'uid' => $this->webUser->id(),
'private' => [['value' => 1]]
));
}
/** @var \Drupal\node\NodeGrantDatabaseStorageInterface $grant_storage */
$grant_storage = \Drupal::service('node.grant_storage');
// Default realm access and node records are present.
$this->assertTrue(\Drupal::service('node.grant_storage')->access($node, 'view', $this->webUser), 'The expected node access records are present');
foreach ($nodes as $node) {
$this->assertTrue($node->private->value);
$this->assertTrue($grant_storage->access($node, 'view', $this->webUser)->isAllowed(), 'Prior to rebuilding node access the grant storage returns allowed for the node author.');
$this->assertTrue($grant_storage->access($node, 'view', $this->adminUser)->isAllowed(), 'Prior to rebuilding node access the grant storage returns allowed for the admin user.');
}
$this->assertEqual(1, \Drupal::service('node.grant_storage')->checkAll($this->webUser), 'There is an all realm access record');
$this->assertTrue(\Drupal::state()->get('node.node_access_needs_rebuild'), 'Node access permissions need to be rebuilt');
// Rebuild permissions.
$this->drupalGet('admin/reports/status/rebuild');
$this->drupalGet('admin/reports/status');
$this->clickLink(t('Rebuild permissions'));
$this->drupalPostForm(NULL, array(), t('Rebuild permissions'));
$this->assertText(t('The content access permissions have been rebuilt.'));
// Test if the rebuild has been successful.
// Test if the rebuild by user that cannot bypass node access and does not
// have access to the nodes has been successful.
$this->assertFalse($this->adminUser->hasPermission('bypass node access'));
$this->assertNull(\Drupal::state()->get('node.node_access_needs_rebuild'), 'Node access permissions have been rebuilt');
$this->assertTrue(\Drupal::service('node.grant_storage')->access($node, 'view', $this->webUser), 'The expected node access records are present');
foreach ($nodes as $node) {
$this->assertTrue($grant_storage->access($node, 'view', $this->webUser)->isAllowed(), 'After rebuilding node access the grant storage returns allowed for the node author.');
$this->assertFalse($grant_storage->access($node, 'view', $this->adminUser)->isForbidden(), 'After rebuilding node access the grant storage returns forbidden for the admin user.');
}
$this->assertFalse(\Drupal::service('node.grant_storage')->checkAll($this->webUser), 'There is no all realm access record');
// Test an anonymous node access rebuild from code.
$this->drupalLogout();
node_access_rebuild();
foreach ($nodes as $node) {
$this->assertTrue($grant_storage->access($node, 'view', $this->webUser)->isAllowed(), 'After rebuilding node access the grant storage returns allowed for the node author.');
$this->assertFalse($grant_storage->access($node, 'view', $this->adminUser)->isForbidden(), 'After rebuilding node access the grant storage returns forbidden for the admin user.');
}
$this->assertFalse(\Drupal::service('node.grant_storage')->checkAll($this->webUser), 'There is no all realm access record');
}

View file

@ -1,36 +0,0 @@
<?php
namespace Drupal\node\Tests;
/**
* Ensures that node access rebuild functions work correctly.
*
* @group node
*/
class NodeAccessRebuildTest extends NodeTestBase {
/**
* A normal authenticated user.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
protected function setUp() {
parent::setUp();
$web_user = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports'));
$this->drupalLogin($web_user);
$this->webUser = $web_user;
}
/**
* Tests rebuilding the node access permissions table.
*/
function testNodeAccessRebuild() {
$this->drupalGet('admin/reports/status');
$this->clickLink(t('Rebuild permissions'));
$this->drupalPostForm(NULL, array(), t('Rebuild permissions'));
$this->assertText(t('Content permissions have been rebuilt.'));
}
}

View file

@ -38,6 +38,16 @@ class NodeCacheTagsTest extends EntityWithUriCacheTagsTestBase {
return $node;
}
/**
* {@inheritdoc}
*/
protected function getDefaultCacheContexts() {
$defaults = parent::getDefaultCacheContexts();
// @see \Drupal\node\Controller\NodeViewController::view()
$defaults[] = 'user.roles:anonymous';
return $defaults;
}
/**
* {@inheritdoc}
*/

View file

@ -18,14 +18,49 @@ class NodeViewTest extends NodeTestBase {
$this->drupalGet($node->urlInfo());
$result = $this->xpath('//link[@rel = "canonical"]');
$this->assertEqual($result[0]['href'], $node->url());
// Link relations are checked for access for anonymous users.
$result = $this->xpath('//link[@rel = "version-history"]');
$this->assertFalse($result, 'Version history not present for anonymous users without access.');
$result = $this->xpath('//link[@rel = "edit-form"]');
$this->assertFalse($result, 'Edit form not present for anonymous users without access.');
$this->drupalLogin($this->createUser(['access content']));
$this->drupalGet($node->urlInfo());
$result = $this->xpath('//link[@rel = "canonical"]');
$this->assertEqual($result[0]['href'], $node->url());
// Link relations are present regardless of access for authenticated users.
$result = $this->xpath('//link[@rel = "version-history"]');
$this->assertEqual($result[0]['href'], $node->url('version-history'));
$result = $this->xpath('//link[@rel = "edit-form"]');
$this->assertEqual($result[0]['href'], $node->url('edit-form'));
// Give anonymous users access to edit the node. Do this through the UI to
// ensure caches are handled properly.
$this->drupalLogin($this->rootUser);
$edit = [
'anonymous[edit own ' . $node->bundle() . ' content]' => TRUE
];
$this->drupalPostForm('admin/people/permissions', $edit, 'Save permissions');
$this->drupalLogout();
// Anonymous user's should now see the edit-form link but not the
// version-history link.
$this->drupalGet($node->urlInfo());
$result = $this->xpath('//link[@rel = "canonical"]');
$this->assertEqual($result[0]['href'], $node->url());
$result = $this->xpath('//link[@rel = "version-history"]');
$this->assertFalse($result, 'Version history not present for anonymous users without access.');
$result = $this->xpath('//link[@rel = "edit-form"]');
$this->assertEqual($result[0]['href'], $node->url('edit-form'));
}
/**

View file

@ -47,7 +47,7 @@ class MigrateNodeTest extends MigrateDrupal7TestBase {
'd7_taxonomy_vocabulary',
'd7_field',
'd7_field_instance',
'd7_node:test_content_type',
'd7_node',
'd7_node:article',
]);
}