Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176

This commit is contained in:
Pantheon Automation 2015-08-17 17:00:26 -07:00 committed by Greg Anderson
commit 9921556621
13277 changed files with 1459781 additions and 0 deletions

View file

@ -0,0 +1,13 @@
# Schema for the views plugins of the History module.
views.field.history_user_timestamp:
type: views.field.node
label: 'History user'
mapping:
comments:
type: boolean
label: 'Check for new comments as well'
views.filter.history_user_timestamp:
type: views_filter
label: 'History user'

View file

@ -0,0 +1,8 @@
name: History
type: module
description: 'Records which user has read which content.'
package: Core
version: VERSION
core: 8.x
dependencies:
- node

View file

@ -0,0 +1,41 @@
<?php
/**
* @file
* Installation functions for History module.
*/
/**
* Implements hook_schema().
*/
function history_schema() {
$schema['history'] = array(
'description' => 'A record of which {users} have read which {node}s.',
'fields' => array(
'uid' => array(
'description' => 'The {users}.uid that read the {node} nid.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'nid' => array(
'description' => 'The {node}.nid that was read.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'timestamp' => array(
'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'),
),
);
return $schema;
}

View file

@ -0,0 +1,16 @@
api:
version: VERSION
js:
js/history.js: {}
dependencies:
- core/jquery
- core/drupalSettings
- core/drupal
- core/drupal.ajax
mark-as-read:
version: VERSION
js:
js/mark-as-read.js: {}
dependencies:
- history/api

View file

@ -0,0 +1,194 @@
<?php
/**
* @file
* Records which users have read which content.
*
* @todo
* - Generic helper for _forum_user_last_visit() + history_read().
* - Generic helper for node_mark().
*/
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Entities changed before this time are always shown as read.
*
* Entities changed within this time may be marked as new, updated, or read,
* depending on their state for the current user. Defaults to 30 days ago.
*/
define('HISTORY_READ_LIMIT', REQUEST_TIME - 30 * 24 * 60 * 60);
/**
* Implements hook_help().
*/
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>';
return $output;
}
}
/**
* Retrieves the timestamp for the current user's last view of a specified node.
*
* @param int $nid
* A node ID.
*
* @return int
* If a node has been previously viewed by the user, the timestamp in seconds
* of when the last view occurred; otherwise, zero.
*/
function history_read($nid) {
$history = history_read_multiple(array($nid));
return $history[$nid];
}
/**
* Retrieves the last viewed timestamp for each of the passed node IDs.
*
* @param array $nids
* An array of node IDs.
*
* @return array
* Array of timestamps keyed by node ID. If a node has been previously viewed
* by the user, the timestamp in seconds of when the last view occurred;
* otherwise, zero.
*/
function history_read_multiple($nids) {
$history = &drupal_static(__FUNCTION__, array());
$return = array();
$nodes_to_read = array();
foreach ($nids as $nid) {
if (isset($history[$nid])) {
$return[$nid] = $history[$nid];
}
else {
// Initialize value if current user has not viewed the node.
$nodes_to_read[$nid] = 0;
}
}
if (empty($nodes_to_read)) {
return $return;
}
$result = db_query('SELECT nid, timestamp FROM {history} WHERE uid = :uid AND nid IN ( :nids[] )', array(
':uid' => \Drupal::currentUser()->id(),
':nids[]' => array_keys($nodes_to_read),
));
foreach ($result as $row) {
$nodes_to_read[$row->nid] = (int) $row->timestamp;
}
$history += $nodes_to_read;
return $return + $nodes_to_read;
}
/**
* Updates 'last viewed' timestamp of the specified entity for the current user.
*
* @param $nid
* The node ID that has been read.
* @param $account
* (optional) The user account to update the history for. Defaults to the
* current user.
*/
function history_write($nid, $account = NULL) {
if (!isset($account)) {
$account = \Drupal::currentUser();
}
if ($account->isAuthenticated()) {
db_merge('history')
->keys(array(
'uid' => $account->id(),
'nid' => $nid,
))
->fields(array('timestamp' => REQUEST_TIME))
->execute();
// Update static cache.
$history = &drupal_static('history_read_multiple', array());
$history[$nid] = REQUEST_TIME;
}
}
/**
* Implements hook_cron().
*/
function history_cron() {
db_delete('history')
->condition('timestamp', HISTORY_READ_LIMIT, '<')
->execute();
}
/**
* Implements hook_ENTITY_TYPE_view_alter() for node entities.
*/
function history_node_view_alter(array &$build, EntityInterface $node, EntityViewDisplayInterface $display) {
// Update the history table, stating that this user viewed this node.
if ($display->getOriginalMode() === 'full') {
$build['#cache']['contexts'][] = 'user.roles:authenticated';
if (\Drupal::currentUser()->isAuthenticated()) {
// When the window's "load" event is triggered, mark the node as read.
// This still allows for Drupal behaviors (which are triggered on the
// "DOMContentReady" event) to add "new" and "updated" indicators.
$build['#attached']['library'][] = 'history/mark-as-read';
$build['#attached']['drupalSettings']['history']['nodesToMarkAsRead'][$node->id()] = TRUE;
}
}
}
/**
* Implements hook_ENTITY_TYPE_delete() for node entities.
*/
function history_node_delete(EntityInterface $node) {
db_delete('history')
->condition('nid', $node->id())
->execute();
}
/**
* Implements hook_user_cancel().
*/
function history_user_cancel($edit, $account, $method) {
switch ($method) {
case 'user_cancel_reassign':
db_delete('history')
->condition('uid', $account->id())
->execute();
break;
}
}
/**
* Implements hook_ENTITY_TYPE_delete() for user entities.
*/
function history_user_delete($account) {
db_delete('history')
->condition('uid', $account->id())
->execute();
}
/**
* #lazy_builder callback; attaches the last read timestamp for a node.
*
* @param int $node_id
* The node ID for which to attach the last read timestamp.
*
* @return array $element
* A renderable array containing the last read timestamp.
*/
function history_attach_timestamp($node_id) {
$element = [];
$element['#attached']['drupalSettings']['history']['lastReadTimestamps'][$node_id] = (int) history_read($node_id);
return $element;
}

View file

@ -0,0 +1,13 @@
history.get_last_node_view:
path: '/history/get_node_read_timestamps'
defaults:
_controller: '\Drupal\history\Controller\HistoryController::getNodeReadTimestamps'
requirements:
_permission: 'access content'
history.read_node:
path: '/history/{node}/read'
defaults:
_controller: '\Drupal\history\Controller\HistoryController::readNode'
requirements:
_entity_access: 'node.view'

View file

@ -0,0 +1,45 @@
<?php
/**
* @file
* Provide views data for history.module.
*/
/**
* Implements hook_views_data().
*/
function history_views_data() {
// History table
// We're actually defining a specific instance of the table, so let's
// alias it so that we can later add the real table for other purposes if we
// need it.
$data['history']['table']['group'] = t('Content');
// Explain how this table joins to others.
$data['history']['table']['join'] = array(
// Directly links to node table.
'node_field_data' => array(
'table' => 'history',
'left_field' => 'nid',
'field' => 'nid',
'extra' => array(
array('field' => 'uid', 'value' => '***CURRENT_USER***', 'numeric' => TRUE),
),
),
);
$data['history']['timestamp'] = array(
'title' => t('Has new content'),
'field' => array(
'id' => 'history_user_timestamp',
'help' => t('Show a marker if the content is new or updated.'),
),
'filter' => array(
'help' => t('Show only content that is new or updated.'),
'id' => 'history_user_timestamp',
),
);
return $data;
}

View file

@ -0,0 +1,130 @@
/**
* @file
* JavaScript API for the History module, with client-side caching.
*
* May only be loaded for authenticated users, with the History module enabled.
*/
(function ($, Drupal, drupalSettings, storage) {
"use strict";
var currentUserID = parseInt(drupalSettings.user.uid, 10);
// Any comment that is older than 30 days is automatically considered read,
// so for these we don't need to perform a request at all!
var thirtyDaysAgo = Math.round(new Date().getTime() / 1000) - 30 * 24 * 60 * 60;
// Use the data embedded in the page, if available.
var embeddedLastReadTimestamps = false;
if (drupalSettings.history && drupalSettings.history.lastReadTimestamps) {
embeddedLastReadTimestamps = drupalSettings.history.lastReadTimestamps;
}
/**
* @namespace
*/
Drupal.history = {
/**
* Fetch "last read" timestamps for the given nodes.
*
* @param {Array} nodeIDs
* An array of node IDs.
* @param {function} callback
* A callback that is called after the requested timestamps were fetched.
*/
fetchTimestamps: function (nodeIDs, callback) {
// Use the data embedded in the page, if available.
if (embeddedLastReadTimestamps) {
callback();
return;
}
$.ajax({
url: Drupal.url('history/get_node_read_timestamps'),
type: 'POST',
data: {'node_ids[]': nodeIDs},
dataType: 'json',
success: function (results) {
for (var nodeID in results) {
if (results.hasOwnProperty(nodeID)) {
storage.setItem('Drupal.history.' + currentUserID + '.' + nodeID, results[nodeID]);
}
}
callback();
}
});
},
/**
* Get the last read timestamp for the given node.
*
* @param {number|string} nodeID
* A node ID.
*
* @return {number}
* A UNIX timestamp.
*/
getLastRead: function (nodeID) {
// Use the data embedded in the page, if available.
if (embeddedLastReadTimestamps && embeddedLastReadTimestamps[nodeID]) {
return parseInt(embeddedLastReadTimestamps[nodeID], 10);
}
return parseInt(storage.getItem('Drupal.history.' + currentUserID + '.' + nodeID) || 0, 10);
},
/**
* Marks a node as read, store the last read timestamp in client-side storage.
*
* @param {number|string} nodeID
* A node ID.
*/
markAsRead: function (nodeID) {
$.ajax({
url: Drupal.url('history/' + nodeID + '/read'),
type: 'POST',
dataType: 'json',
success: function (timestamp) {
// If the data is embedded in the page, don't store on the client side.
if (embeddedLastReadTimestamps && embeddedLastReadTimestamps[nodeID]) {
return;
}
storage.setItem('Drupal.history.' + currentUserID + '.' + nodeID, timestamp);
}
});
},
/**
* Determines whether a server check is necessary.
*
* Any content that is >30 days old never gets a "new" or "updated" indicator.
* Any content that was published before the oldest known reading also never
* gets a "new" or "updated" indicator, because it must've been read already.
*
* @param {number|string} nodeID
* A node ID.
* @param {number} contentTimestamp
* The time at which some content (e.g. a comment) was published.
*
* @return {bool}
* Whether a server check is necessary for the given node and its timestamp.
*/
needsServerCheck: function (nodeID, contentTimestamp) {
// First check if the content is older than 30 days, then we can bail early.
if (contentTimestamp < thirtyDaysAgo) {
return false;
}
// Use the data embedded in the page, if available.
if (embeddedLastReadTimestamps && embeddedLastReadTimestamps[nodeID]) {
return contentTimestamp > parseInt(embeddedLastReadTimestamps[nodeID], 10);
}
var minLastReadTimestamp = parseInt(storage.getItem('Drupal.history.' + currentUserID + '.' + nodeID) || 0, 10);
return contentTimestamp > minLastReadTimestamp;
}
};
})(jQuery, Drupal, drupalSettings, window.localStorage);

View file

@ -0,0 +1,23 @@
/**
* @file
* Marks the nodes listed in drupalSettings.history.nodesToMarkAsRead as read.
*
* Uses the History module JavaScript API.
*
* @see Drupal.history
*/
(function (window, Drupal, drupalSettings) {
"use strict";
// When the window's "load" event is triggered, mark all enumerated nodes as
// read. This still allows for Drupal behaviors (which are triggered on the
// "DOMContentReady" event) to add "new" and "updated" indicators.
window.addEventListener('load', function () {
if (drupalSettings.history && drupalSettings.history.nodesToMarkAsRead) {
Object.keys(drupalSettings.history.nodesToMarkAsRead).forEach(Drupal.history.markAsRead);
}
});
})(window, Drupal, drupalSettings);

View file

@ -0,0 +1,62 @@
<?php
/**
* @file
* Contains \Drupal\history\Controller\HistoryController.
*/
namespace Drupal\history\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Drupal\Core\Controller\ControllerBase;
use Drupal\node\NodeInterface;
/**
* Returns responses for History module routes.
*/
class HistoryController extends ControllerBase {
/**
* Returns a set of nodes' last read timestamps.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request of the page.
*
* @return Symfony\Component\HttpFoundation\JsonResponse
* The JSON response.
*/
public function getNodeReadTimestamps(Request $request) {
if ($this->currentUser()->isAnonymous()) {
throw new AccessDeniedHttpException();
}
$nids = $request->request->get('node_ids');
if (!isset($nids)) {
throw new NotFoundHttpException();
}
return new JsonResponse(history_read_multiple($nids));
}
/**
* Marks a node as read by the current user right now.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request of the page.
* @param \Drupal\node\NodeInterface $node
* The node whose "last read" timestamp should be updated.
*/
public function readNode(Request $request, NodeInterface $node) {
if ($this->currentUser()->isAnonymous()) {
throw new AccessDeniedHttpException();
}
// Update the history table, stating that this user viewed this node.
history_write($node->id());
return new JsonResponse((int)history_read($node->id()));
}
}

View file

@ -0,0 +1,108 @@
<?php
/**
* @file
* Contains \Drupal\history\Plugin\views\field\HistoryUserTimestamp.
*/
namespace Drupal\history\Plugin\views\field;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\node\Plugin\views\field\Node;
/**
* Field handler to display the marker for new content.
*
* The handler is named history_user, because of compatibility reasons, the
* table is history.
*
* @ingroup views_field_handlers
*
* @ViewsField("history_user_timestamp")
*/
class HistoryUserTimestamp extends Node {
/**
* {@inheritdoc}
*/
public function usesGroupBy() {
return FALSE;
}
/**
* Overrides \Drupal\node\Plugin\views\field\Node::init().
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
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');
if (\Drupal::moduleHandler()->moduleExists('comment') && !empty($this->options['comments'])) {
$this->additional_fields['last_comment'] = array('table' => 'comment_entity_statistics', 'field' => 'last_comment_timestamp');
}
}
}
protected function defineOptions() {
$options = parent::defineOptions();
$options['comments'] = array('default' => FALSE);
return $options;
}
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
if (\Drupal::moduleHandler()->moduleExists('comment')) {
$form['comments'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Check for new comments as well'),
'#default_value' => !empty($this->options['comments']),
);
}
}
public function query() {
// Only add ourselves to the query if logged in.
if (\Drupal::currentUser()->isAnonymous()) {
return;
}
parent::query();
}
/**
* {@inheritdoc}
*/
public function render(ResultRow $values) {
// Let's default to 'read' state.
// This code shadows node_mark, but it reads from the db directly and
// we already have that info.
$mark = MARK_READ;
if (\Drupal::currentUser()->isAuthenticated()) {
$last_read = $this->getValue($values);
$changed = $this->getValue($values, 'changed');
$last_comment = \Drupal::moduleHandler()->moduleExists('comment') && !empty($this->options['comments']) ? $this->getValue($values, 'last_comment') : 0;
if (!$last_read && $changed > HISTORY_READ_LIMIT) {
$mark = MARK_NEW;
}
elseif ($changed > $last_read && $changed > HISTORY_READ_LIMIT) {
$mark = MARK_UPDATED;
}
elseif ($last_comment > $last_read && $last_comment > HISTORY_READ_LIMIT) {
$mark = MARK_UPDATED;
}
$build = array(
'#theme' => 'mark',
'#status' => $mark,
);
return $this->renderLink(drupal_render($build), $values);
}
}
}

View file

@ -0,0 +1,110 @@
<?php
/**
* @file
* Contains \Drupal\history\Plugin\views\filter\HistoryUserTimestamp.
*/
namespace Drupal\history\Plugin\views\filter;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\filter\FilterPluginBase;
/**
* Filter for new content.
*
* The handler is named history_user, because of compatibility reasons, the
* table is history.
*
* @ingroup views_filter_handlers
*
* @ViewsFilter("history_user_timestamp")
*/
class HistoryUserTimestamp extends FilterPluginBase {
// Don't display empty space where the operator would be.
var $no_operator = TRUE;
/**
* {@inheritdoc}
*/
public function usesGroupBy() {
return FALSE;
}
public function buildExposeForm(&$form, FormStateInterface $form_state) {
parent::buildExposeForm($form, $form_state);
// @todo There are better ways of excluding required and multiple (object flags)
unset($form['expose']['required']);
unset($form['expose']['multiple']);
unset($form['expose']['remember']);
}
protected function valueForm(&$form, FormStateInterface $form_state) {
// Only present a checkbox for the exposed filter itself. There's no way
// to tell the difference between not checked and the default value, so
// specifying the default value via the views UI is meaningless.
if ($form_state->get('exposed')) {
if (isset($this->options['expose']['label'])) {
$label = $this->options['expose']['label'];
}
else {
$label = $this->t('Has new content');
}
$form['value'] = array(
'#type' => 'checkbox',
'#title' => $label,
'#default_value' => $this->value,
);
}
}
public function query() {
// This can only work if we're authenticated in.
if (!\Drupal::currentUser()->isAuthenticated()) {
return;
}
// Don't filter if we're exposed and the checkbox isn't selected.
if ((!empty($this->options['exposed'])) && empty($this->value)) {
return;
}
// Hey, Drupal kills old history, so nodes that haven't been updated
// since HISTORY_READ_LIMIT are bzzzzzzzt outta here!
$limit = REQUEST_TIME - HISTORY_READ_LIMIT;
$this->ensureMyTable();
$field = "$this->tableAlias.$this->realField";
$node = $this->query->ensureTable('node_field_data', $this->relationship);
$clause = '';
$clause2 = '';
if (\Drupal::moduleHandler()->moduleExists('comment')) {
$ces = $this->query->ensureTable('comment_entity_statistics', $this->relationship);
$clause = ("OR $ces.last_comment_timestamp > (***CURRENT_TIME*** - $limit)");
$clause2 = "OR $field < $ces.last_comment_timestamp";
}
// NULL means a history record doesn't exist. That's clearly new content.
// Unless it's very very old content. Everything in the query is already
// type safe cause none of it is coming from outside here.
$this->query->addWhereExpression($this->options['group'], "($field IS NULL AND ($node.changed > (***CURRENT_TIME*** - $limit) $clause)) OR $field < $node.changed $clause2");
}
public function adminSummary() {
if (!empty($this->options['exposed'])) {
return $this->t('exposed');
}
}
/**
* {@inheritdoc}
*/
public function isCacheable() {
// This filter depends on the current time and therefore is never cacheable.
return FALSE;
}
}

View file

@ -0,0 +1,152 @@
<?php
/**
* @file
* Contains \Drupal\history\Tests\HistoryTest.
*/
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

@ -0,0 +1,84 @@
<?php
/**
* @file
* Contains \Drupal\history\Tests\Views\HistoryTimestampTest.
*/
namespace Drupal\history\Tests\Views;
use Drupal\views\Views;
use Drupal\views\Tests\ViewTestBase;
/**
* Tests the history timestamp handlers.
*
* @group history
* @see \Drupal\history\Plugin\views\field\HistoryTimestamp.
* @see \Drupal\history\Plugin\views\filter\HistoryTimestamp.
*/
class HistoryTimestampTest extends ViewTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('history', 'node');
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_history');
/**
* Tests the handlers.
*/
public function testHandlers() {
$nodes = array();
$nodes[] = $this->drupalCreateNode();
$nodes[] = $this->drupalCreateNode();
$account = $this->drupalCreateUser();
$this->drupalLogin($account);
\Drupal::currentUser()->setAccount($account);
db_insert('history')
->fields(array(
'uid' => $account->id(),
'nid' => $nodes[0]->id(),
'timestamp' => REQUEST_TIME - 100,
))->execute();
db_insert('history')
->fields(array(
'uid' => $account->id(),
'nid' => $nodes[1]->id(),
'timestamp' => REQUEST_TIME + 100,
))->execute();
$column_map = array(
'nid' => 'nid',
);
// Test the history field.
$view = Views::getView('test_history');
$view->setDisplay('page_1');
$this->executeView($view);
$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'));
$this->assertEqual(count($result), 1, 'Just one node is marked as new');
// Test the history filter.
$view = Views::getView('test_history');
$view->setDisplay('page_2');
$this->executeView($view);
$this->assertEqual(count($view->result), 1);
$this->assertIdenticalResultset($view, array(array('nid' => $nodes[0]->id())), $column_map);
}
}