Update core 8.3.0

This commit is contained in:
Rob Davies 2017-04-13 15:53:35 +01:00
parent da7a7918f8
commit cd7a898e66
6144 changed files with 132297 additions and 87747 deletions

View file

@ -11,44 +11,38 @@ use Drupal\Core\Database\Database;
* Implements hook_schema().
*/
function history_schema() {
$schema['history'] = array(
$schema['history'] = [
'description' => 'A record of which {users} have read which {node}s.',
'fields' => array(
'uid' => array(
'fields' => [
'uid' => [
'description' => 'The {users}.uid that read the {node} nid.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'nid' => array(
],
'nid' => [
'description' => 'The {node}.nid that was read.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'timestamp' => array(
],
'timestamp' => [
'description' => 'The Unix timestamp at which the read occurred.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('uid', 'nid'),
'indexes' => array(
'nid' => array('nid'),
),
);
],
],
'primary key' => ['uid', 'nid'],
'indexes' => [
'nid' => ['nid'],
],
];
return $schema;
}
/**
* @defgroup updates-8.0.x-to-8.1.x Updates from 8.0.x to 8.1.x
* @{
* Update functions from 8.0.x to 8.1.x.
*/
/**
* Change {history}.nid to an unsigned int in order to match {node}.nid.
*/
@ -56,45 +50,41 @@ function history_update_8101() {
$schema = Database::getConnection()->schema();
$schema->dropPrimaryKey('history');
$schema->dropIndex('history', 'nid');
$schema->changeField('history', 'nid', 'nid', array(
$schema->changeField('history', 'nid', 'nid', [
'description' => 'The {node}.nid that was read.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
));
$schema->addPrimaryKey('history', array('uid', 'nid'));
$spec = array(
]);
$schema->addPrimaryKey('history', ['uid', 'nid']);
$spec = [
'description' => 'A record of which {users} have read which {node}s.',
'fields' => array(
'uid' => array(
'fields' => [
'uid' => [
'description' => 'The {users}.uid that read the {node} nid.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'nid' => array(
],
'nid' => [
'description' => 'The {node}.nid that was read.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
'timestamp' => array(
],
'timestamp' => [
'description' => 'The Unix timestamp at which the read occurred.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('uid', 'nid'),
'indexes' => array(
'nid' => array('nid'),
),
);
$schema->addIndex('history', 'nid', array('nid'), $spec);
],
],
'primary key' => ['uid', 'nid'],
'indexes' => [
'nid' => ['nid'],
],
];
$schema->addIndex('history', 'nid', ['nid'], $spec);
}
/**
* @} End of "defgroup updates-8.0.x-to-8.1.x".
*/

View file

@ -28,7 +28,7 @@ function history_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.history':
$output = '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The History module keeps track of which content a user has read. It marks content as <em>new</em> or <em>updated</em> depending on the last time the user viewed it. History records that are older than one month are removed during cron, which means that content older than one month is always considered <em>read</em>. The History module does not have a user interface but it provides a filter to <a href=":views-help">Views</a> to show new or updated content. For more information, see the <a href=":url">online documentation for the History module</a>.', array(':views-help' => (\Drupal::moduleHandler()->moduleExists('views')) ? \Drupal::url('help.page', array ('name' => 'views')) : '#', ':url' => 'https://www.drupal.org/documentation/modules/history')) . '</p>';
$output .= '<p>' . t('The History module keeps track of which content a user has read. It marks content as <em>new</em> or <em>updated</em> depending on the last time the user viewed it. History records that are older than one month are removed during cron, which means that content older than one month is always considered <em>read</em>. The History module does not have a user interface but it provides a filter to <a href=":views-help">Views</a> to show new or updated content. For more information, see the <a href=":url">online documentation for the History module</a>.', [':views-help' => (\Drupal::moduleHandler()->moduleExists('views')) ? \Drupal::url('help.page', ['name' => 'views']) : '#', ':url' => 'https://www.drupal.org/documentation/modules/history']) . '</p>';
return $output;
}
}
@ -44,7 +44,7 @@ function history_help($route_name, RouteMatchInterface $route_match) {
* of when the last view occurred; otherwise, zero.
*/
function history_read($nid) {
$history = history_read_multiple(array($nid));
$history = history_read_multiple([$nid]);
return $history[$nid];
}
@ -60,11 +60,11 @@ function history_read($nid) {
* otherwise, zero.
*/
function history_read_multiple($nids) {
$history = &drupal_static(__FUNCTION__, array());
$history = &drupal_static(__FUNCTION__, []);
$return = array();
$return = [];
$nodes_to_read = array();
$nodes_to_read = [];
foreach ($nids as $nid) {
if (isset($history[$nid])) {
$return[$nid] = $history[$nid];
@ -79,10 +79,10 @@ function history_read_multiple($nids) {
return $return;
}
$result = db_query('SELECT nid, timestamp FROM {history} WHERE uid = :uid AND nid IN ( :nids[] )', array(
$result = db_query('SELECT nid, timestamp FROM {history} WHERE uid = :uid AND nid IN ( :nids[] )', [
':uid' => \Drupal::currentUser()->id(),
':nids[]' => array_keys($nodes_to_read),
));
]);
foreach ($result as $row) {
$nodes_to_read[$row->nid] = (int) $row->timestamp;
}
@ -108,14 +108,14 @@ function history_write($nid, $account = NULL) {
if ($account->isAuthenticated()) {
db_merge('history')
->keys(array(
->keys([
'uid' => $account->id(),
'nid' => $nid,
))
->fields(array('timestamp' => REQUEST_TIME))
])
->fields(['timestamp' => REQUEST_TIME])
->execute();
// Update static cache.
$history = &drupal_static('history_read_multiple', array());
$history = &drupal_static('history_read_multiple', []);
$history[$nid] = REQUEST_TIME;
}
}
@ -184,7 +184,7 @@ function history_user_delete($account) {
* @param int $node_id
* The node ID for which to attach the last read timestamp.
*
* @return array $element
* @return array
* A renderable array containing the last read timestamp.
*/
function history_attach_timestamp($node_id) {

View file

@ -17,29 +17,29 @@ function history_views_data() {
$data['history']['table']['group'] = t('Content');
// Explain how this table joins to others.
$data['history']['table']['join'] = array(
$data['history']['table']['join'] = [
// Directly links to node table.
'node_field_data' => array(
'node_field_data' => [
'table' => 'history',
'left_field' => 'nid',
'field' => 'nid',
'extra' => array(
array('field' => 'uid', 'value' => '***CURRENT_USER***', 'numeric' => TRUE),
),
),
);
'extra' => [
['field' => 'uid', 'value' => '***CURRENT_USER***', 'numeric' => TRUE],
],
],
];
$data['history']['timestamp'] = array(
$data['history']['timestamp'] = [
'title' => t('Has new content'),
'field' => array(
'field' => [
'id' => 'history_user_timestamp',
'help' => t('Show a marker if the content is new or updated.'),
),
'filter' => array(
],
'filter' => [
'help' => t('Show only content that is new or updated.'),
'id' => 'history_user_timestamp',
),
);
],
];
return $data;
}

View file

@ -34,10 +34,10 @@ class HistoryUserTimestamp extends Node {
parent::init($view, $display, $options);
if (\Drupal::currentUser()->isAuthenticated()) {
$this->additional_fields['created'] = array('table' => 'node_field_data', 'field' => 'created');
$this->additional_fields['changed'] = array('table' => 'node_field_data', 'field' => 'changed');
$this->additional_fields['created'] = ['table' => 'node_field_data', 'field' => 'created'];
$this->additional_fields['changed'] = ['table' => 'node_field_data', 'field' => 'changed'];
if (\Drupal::moduleHandler()->moduleExists('comment') && !empty($this->options['comments'])) {
$this->additional_fields['last_comment'] = array('table' => 'comment_entity_statistics', 'field' => 'last_comment_timestamp');
$this->additional_fields['last_comment'] = ['table' => 'comment_entity_statistics', 'field' => 'last_comment_timestamp'];
}
}
}
@ -45,7 +45,7 @@ class HistoryUserTimestamp extends Node {
protected function defineOptions() {
$options = parent::defineOptions();
$options['comments'] = array('default' => FALSE);
$options['comments'] = ['default' => FALSE];
return $options;
}
@ -53,11 +53,11 @@ class HistoryUserTimestamp extends Node {
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
if (\Drupal::moduleHandler()->moduleExists('comment')) {
$form['comments'] = array(
$form['comments'] = [
'#type' => 'checkbox',
'#title' => $this->t('Check for new comments as well'),
'#default_value' => !empty($this->options['comments']),
);
];
}
}
@ -92,10 +92,10 @@ class HistoryUserTimestamp extends Node {
elseif ($last_comment > $last_read && $last_comment > HISTORY_READ_LIMIT) {
$mark = MARK_UPDATED;
}
$build = array(
$build = [
'#theme' => 'mark',
'#status' => $mark,
);
];
return $this->renderLink(drupal_render($build), $values);
}
}

View file

@ -49,11 +49,11 @@ class HistoryUserTimestamp extends FilterPluginBase {
else {
$label = $this->t('Has new content');
}
$form['value'] = array(
$form['value'] = [
'#type' => 'checkbox',
'#title' => $label,
'#default_value' => $this->value,
);
];
}
}

View file

@ -1,148 +0,0 @@
<?php
namespace Drupal\history\Tests;
use Drupal\Component\Serialization\Json;
use Drupal\simpletest\WebTestBase;
/**
* Tests the History endpoints.
*
* @group history
*/
class HistoryTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node', 'history');
/**
* The main user for testing.
*
* @var object
*/
protected $user;
/**
* A page node for which to check content statistics.
*
* @var object
*/
protected $testNode;
protected function setUp() {
parent::setUp();
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
$this->user = $this->drupalCreateUser(array('create page content', 'access content'));
$this->drupalLogin($this->user);
$this->testNode = $this->drupalCreateNode(array('type' => 'page', 'uid' => $this->user->id()));
}
/**
* Get node read timestamps from the server for the current user.
*
* @param array $node_ids
* An array of node IDs.
*
* @return string
* The response body.
*/
protected function getNodeReadTimestamps(array $node_ids) {
// Build POST values.
$post = array();
for ($i = 0; $i < count($node_ids); $i++) {
$post['node_ids[' . $i . ']'] = $node_ids[$i];
}
// Serialize POST values.
foreach ($post as $key => $value) {
// Encode according to application/x-www-form-urlencoded
// Both names and values needs to be urlencoded, according to
// http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
$post[$key] = urlencode($key) . '=' . urlencode($value);
}
$post = implode('&', $post);
// Perform HTTP request.
return $this->curlExec(array(
CURLOPT_URL => \Drupal::url('history.get_last_node_view', array(), array('absolute' => TRUE)),
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => $post,
CURLOPT_HTTPHEADER => array(
'Accept: application/json',
'Content-Type: application/x-www-form-urlencoded',
),
));
}
/**
* Mark a node as read for the current user.
*
* @param int $node_id
* A node ID.
*
* @return string
* The response body.
*/
protected function markNodeAsRead($node_id) {
return $this->curlExec(array(
CURLOPT_URL => \Drupal::url('history.read_node', array('node' => $node_id), array('absolute' => TRUE)),
CURLOPT_HTTPHEADER => array(
'Accept: application/json',
),
));
}
/**
* Verifies that the history endpoints work.
*/
function testHistory() {
$nid = $this->testNode->id();
// Retrieve "last read" timestamp for test node, for the current user.
$response = $this->getNodeReadTimestamps(array($nid));
$this->assertResponse(200);
$json = Json::decode($response);
$this->assertIdentical(array(1 => 0), $json, 'The node has not yet been read.');
// View the node.
$this->drupalGet('node/' . $nid);
$this->assertCacheContext('user.roles:authenticated');
// JavaScript present to record the node read.
$settings = $this->getDrupalSettings();
$libraries = explode(',', $settings['ajaxPageState']['libraries']);
$this->assertTrue(in_array('history/mark-as-read', $libraries), 'history/mark-as-read library is present.');
$this->assertEqual([$nid => TRUE], $settings['history']['nodesToMarkAsRead'], 'drupalSettings to mark node as read are present.');
// Simulate JavaScript: perform HTTP request to mark node as read.
$response = $this->markNodeAsRead($nid);
$this->assertResponse(200);
$timestamp = Json::decode($response);
$this->assertTrue(is_numeric($timestamp), 'Node has been marked as read. Timestamp received.');
// Retrieve "last read" timestamp for test node, for the current user.
$response = $this->getNodeReadTimestamps(array($nid));
$this->assertResponse(200);
$json = Json::decode($response);
$this->assertIdentical(array(1 => $timestamp), $json, 'The node has been read.');
// Failing to specify node IDs for the first endpoint should return a 404.
$this->getNodeReadTimestamps(array());
$this->assertResponse(404);
// Accessing either endpoint as the anonymous user should return a 403.
$this->drupalLogout();
$this->getNodeReadTimestamps(array($nid));
$this->assertResponse(403);
$this->getNodeReadTimestamps(array());
$this->assertResponse(403);
$this->markNodeAsRead($nid);
$this->assertResponse(403);
}
}

View file

@ -19,20 +19,20 @@ class HistoryTimestampTest extends ViewTestBase {
*
* @var array
*/
public static $modules = array('history', 'node');
public static $modules = ['history', 'node'];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_history');
public static $testViews = ['test_history'];
/**
* Tests the handlers.
*/
public function testHandlers() {
$nodes = array();
$nodes = [];
$nodes[] = $this->drupalCreateNode();
$nodes[] = $this->drupalCreateNode();
@ -41,23 +41,23 @@ class HistoryTimestampTest extends ViewTestBase {
\Drupal::currentUser()->setAccount($account);
db_insert('history')
->fields(array(
->fields([
'uid' => $account->id(),
'nid' => $nodes[0]->id(),
'timestamp' => REQUEST_TIME - 100,
))->execute();
])->execute();
db_insert('history')
->fields(array(
->fields([
'uid' => $account->id(),
'nid' => $nodes[1]->id(),
'timestamp' => REQUEST_TIME + 100,
))->execute();
])->execute();
$column_map = array(
$column_map = [
'nid' => 'nid',
);
];
// Test the history field.
$view = Views::getView('test_history');
@ -66,7 +66,7 @@ class HistoryTimestampTest extends ViewTestBase {
$this->assertEqual(count($view->result), 2);
$output = $view->preview();
$this->setRawContent(\Drupal::service('renderer')->renderRoot($output));
$result = $this->xpath('//span[@class=:class]', array(':class' => 'marker'));
$result = $this->xpath('//span[@class=:class]', [':class' => 'marker']);
$this->assertEqual(count($result), 1, 'Just one node is marked as new');
// Test the history filter.
@ -74,7 +74,7 @@ class HistoryTimestampTest extends ViewTestBase {
$view->setDisplay('page_2');
$this->executeView($view);
$this->assertEqual(count($view->result), 1);
$this->assertIdenticalResultset($view, array(array('nid' => $nodes[0]->id())), $column_map);
$this->assertIdenticalResultset($view, [['nid' => $nodes[0]->id()]], $column_map);
// Install Comment module and make sure that content types without comment
// field will not break the view.

View file

@ -0,0 +1,174 @@
<?php
namespace Drupal\Tests\history\Functional;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Url;
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
use Drupal\Tests\BrowserTestBase;
use GuzzleHttp\Cookie\CookieJar;
/**
* Tests the History endpoints.
*
* @group history
*/
class HistoryTest extends BrowserTestBase {
use AssertPageCacheContextsAndTagsTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node', 'history'];
/**
* The main user for testing.
*
* @var object
*/
protected $user;
/**
* A page node for which to check content statistics.
*
* @var object
*/
protected $testNode;
/**
* The cookie jar holding the testing session cookies for Guzzle requests.
*
* @var \GuzzleHttp\Client;
*/
protected $client;
/**
* The Guzzle HTTP client.
*
* @var \GuzzleHttp\Cookie\CookieJar;
*/
protected $cookies;
protected function setUp() {
parent::setUp();
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
$this->user = $this->drupalCreateUser(['create page content', 'access content']);
$this->drupalLogin($this->user);
$this->testNode = $this->drupalCreateNode(['type' => 'page', 'uid' => $this->user->id()]);
$this->client = $this->getHttpClient();
}
/**
* Get node read timestamps from the server for the current user.
*
* @param array $node_ids
* An array of node IDs.
*
* @return \Psr\Http\Message\ResponseInterface
* The response object.
*/
protected function getNodeReadTimestamps(array $node_ids) {
// Perform HTTP request.
$url = Url::fromRoute('history.get_last_node_view')
->setAbsolute()
->toString();
return $this->client->post($url, [
'body' => http_build_query(['node_ids' => $node_ids]),
'cookies' => $this->cookies,
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/x-www-form-urlencoded',
],
'http_errors' => FALSE,
]);
}
/**
* Mark a node as read for the current user.
*
* @param int $node_id
* A node ID.
*
* @return \Psr\Http\Message\ResponseInterface
* The response body.
*/
protected function markNodeAsRead($node_id) {
$url = Url::fromRoute('history.read_node', ['node' => $node_id], ['absolute' => TRUE])->toString();
return $this->client->post($url, [
'cookies' => $this->cookies,
'headers' => [
'Accept' => 'application/json',
],
'http_errors' => FALSE,
]);
}
/**
* Verifies that the history endpoints work.
*/
public function testHistory() {
$nid = $this->testNode->id();
// Retrieve "last read" timestamp for test node, for the current user.
$response = $this->getNodeReadTimestamps([$nid]);
$this->assertEquals(200, $response->getStatusCode());
$json = Json::decode($response->getBody());
$this->assertIdentical([1 => 0], $json, 'The node has not yet been read.');
// View the node.
$this->drupalGet('node/' . $nid);
$this->assertCacheContext('user.roles:authenticated');
// JavaScript present to record the node read.
$settings = $this->getDrupalSettings();
$libraries = explode(',', $settings['ajaxPageState']['libraries']);
$this->assertTrue(in_array('history/mark-as-read', $libraries), 'history/mark-as-read library is present.');
$this->assertEqual([$nid => TRUE], $settings['history']['nodesToMarkAsRead'], 'drupalSettings to mark node as read are present.');
// Simulate JavaScript: perform HTTP request to mark node as read.
$response = $this->markNodeAsRead($nid);
$this->assertEquals(200, $response->getStatusCode());
$timestamp = Json::decode($response->getBody());
$this->assertTrue(is_numeric($timestamp), 'Node has been marked as read. Timestamp received.');
// Retrieve "last read" timestamp for test node, for the current user.
$response = $this->getNodeReadTimestamps([$nid]);
$this->assertEquals(200, $response->getStatusCode());
$json = Json::decode($response->getBody());
$this->assertIdentical([1 => $timestamp], $json, 'The node has been read.');
// Failing to specify node IDs for the first endpoint should return a 404.
$response = $this->getNodeReadTimestamps([]);
$this->assertEquals(404, $response->getStatusCode());
// Accessing either endpoint as the anonymous user should return a 403.
$this->drupalLogout();
$response = $this->getNodeReadTimestamps([$nid]);
$this->assertEquals(403, $response->getStatusCode());
$response = $this->getNodeReadTimestamps([]);
$this->assertEquals(403, $response->getStatusCode());
$response = $this->markNodeAsRead($nid);
$this->assertEquals(403, $response->getStatusCode());
}
/**
* Obtain the HTTP client and set the cookies.
*
* @return \GuzzleHttp\Client
* The client with BrowserTestBase configuration.
*/
protected function getHttpClient() {
// Similar code is also employed to test CSRF tokens.
// @see \Drupal\Tests\system\Functional\CsrfRequestHeaderTest::testRouteAccess()
$domain = parse_url($this->getUrl(), PHP_URL_HOST);
$session_id = $this->getSession()->getCookie($this->getSessionName());
$this->cookies = CookieJar::fromArray([$this->getSessionName() => $session_id], $domain);
return $this->getSession()->getDriver()->getClient()->getClient();
}
}