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,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);
}
}