clean install

This commit is contained in:
2024-12-10 15:08:16 +01:00
commit e14eb2d8fd
31193 changed files with 3555714 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
<?php
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();
}
if (!$request->request->has('node_ids')) {
throw new NotFoundHttpException();
}
$nids = $request->request->all('node_ids');
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,27 @@
<?php
namespace Drupal\history;
use Drupal\Core\Render\Element\RenderCallbackInterface;
/**
* Render callback object.
*/
class HistoryRenderCallback implements RenderCallbackInterface {
/**
* #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
* A renderable array containing the last read timestamp.
*/
public static function lazyBuilder($node_id) {
$element = [];
$element['#attached']['drupalSettings']['history']['lastReadTimestamps'][$node_id] = (int) history_read($node_id);
return $element;
}
}

View File

@@ -0,0 +1,112 @@
<?php
namespace Drupal\history\Plugin\views\field;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Attribute\ViewsField;
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;
}
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, ?array &$options = NULL) {
parent::init($view, $display, $options);
if (\Drupal::currentUser()->isAuthenticated()) {
$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'] = ['table' => 'comment_entity_statistics', 'field' => 'last_comment_timestamp'];
}
}
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['comments'] = ['default' => FALSE];
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
if (\Drupal::moduleHandler()->moduleExists('comment')) {
$form['comments'] = [
'#type' => 'checkbox',
'#title' => $this->t('Check for new comments as well'),
'#default_value' => !empty($this->options['comments']),
];
}
}
/**
* {@inheritdoc}
*/
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 = [
'#theme' => 'mark',
'#status' => $mark,
];
return $this->renderLink(\Drupal::service('renderer')->render($build), $values);
}
}
}

View File

@@ -0,0 +1,147 @@
<?php
namespace Drupal\history\Plugin\views\filter;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Cache\UncacheableDependencyTrait;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Attribute\ViewsFilter;
use Drupal\views\Plugin\views\filter\FilterPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* 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 {
use UncacheableDependencyTrait;
/**
* {@inheritdoc}
*/
// phpcs:ignore Drupal.NamingConventions.ValidVariableName.LowerCamelName
public $no_operator = TRUE;
/**
* Constructs a HistoryUserTimestamp object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Component\Datetime\TimeInterface|null $time
* The time service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, protected ?TimeInterface $time = NULL) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
if (!$time) {
@trigger_error('Calling ' . __METHOD__ . ' without the $time argument is deprecated in drupal:10.3.0 and it will be required in drupal:11.0.0. See https://www.drupal.org/node/3395991', E_USER_DEPRECATED);
$this->time = \Drupal::service('datetime.time');
}
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('datetime.time'),
);
}
/**
* {@inheritdoc}
*/
public function usesGroupBy() {
return FALSE;
}
/**
* {@inheritdoc}
*/
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']);
}
/**
* {@inheritdoc}
*/
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'] = [
'#type' => 'checkbox',
'#title' => $label,
'#default_value' => $this->value,
];
}
}
/**
* {@inheritdoc}
*/
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 outta here!
$limit = $this->time->getRequestTime() - HISTORY_READ_LIMIT;
$this->ensureMyTable();
$field = "$this->tableAlias.$this->realField";
$node = $this->query->ensureTable('node_field_data', $this->relationship);
$clause = '';
$clause2 = '';
if ($alias = $this->query->ensureTable('comment_entity_statistics', $this->relationship)) {
$clause = "OR $alias.last_comment_timestamp > (***CURRENT_TIME*** - $limit)";
$clause2 = "OR $field < $alias.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");
}
/**
* {@inheritdoc}
*/
public function adminSummary() {
if (!empty($this->options['exposed'])) {
return $this->t('exposed');
}
}
}