Unverified Commit 78ddb1a7 authored by Alex Pott's avatar Alex Pott
Browse files

Issue #2325899 by casey, dawehner, lendude, alexpott, webflo, geertvd,...

Issue #2325899 by casey, dawehner, lendude, alexpott, webflo, geertvd, hauruck, mpdonadio, twistor, owenbush, howards, cilefen, jofitz, joachim, rensingh99, jungle, sokru, pdenooijer, anmolgoyal74, ragnarkurm, skylord, anairamzap, larowlan, solide-echt, ludo.r, xjm, joelpittet: UI fatal caused by views argument handlers no longer being able to provide their own default argument handling
parent 2d67d387
Loading
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\node\Plugin\views\argument_default;

use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\node\NodeInterface;
use Drupal\views\Attribute\ViewsArgumentDefault;

/**
 * Provides the 'changed' time of the current node as default argument value.
 */
#[ViewsArgumentDefault(
  id: 'node_changed',
  title: new TranslatableMarkup("Current node 'changed' time"),
)]
class NodeChanged extends NodeDateArgumentDefaultPluginBase {

  /**
   * {@inheritdoc}
   */
  protected function getNodeDateValue(NodeInterface $node): int {
    return $node->getChangedTime();
  }

}
+25 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\node\Plugin\views\argument_default;

use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\node\NodeInterface;
use Drupal\views\Attribute\ViewsArgumentDefault;

/**
 * Provides the created time of the current node as default argument value.
 */
#[ViewsArgumentDefault(
  id: 'node_created',
  title: new TranslatableMarkup("Current node 'created' time"),
)]
class NodeCreated extends NodeDateArgumentDefaultPluginBase {

  /**
   * {@inheritdoc}
   */
  protected function getNodeDateValue(NodeInterface $node): int {
    return $node->getCreatedTime();
  }

}
+91 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\node\Plugin\views\argument_default;

use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\node\NodeInterface;
use Drupal\views\Plugin\views\argument\Date;
use Drupal\views\Plugin\views\argument_default\ArgumentDefaultPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Provides a base class for node date values.
 */
abstract class NodeDateArgumentDefaultPluginBase extends ArgumentDefaultPluginBase implements CacheableDependencyInterface {

  /**
   * Constructs a new NodeDateArgumentDefaultPluginBase instance.
   *
   * @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\Core\Routing\RouteMatchInterface $routeMatch
   *   The route match.
   * @param \Drupal\Core\Datetime\DateFormatterInterface $dateFormatter
   *   The date formatter service.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, protected RouteMatchInterface $routeMatch, protected DateFormatterInterface $dateFormatter) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('current_route_match'),
      $container->get('date.formatter')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getArgument(): string|bool {
    // Return a time value from the current node if available.
    if (($node = $this->routeMatch->getParameter('node')) && $node instanceof NodeInterface) {

      // The Date argument handlers provide their own format strings, otherwise
      // use a default.
      $format = $this->argument instanceof Date ? $this->argument->getArgFormat() : 'Y-m-d';

      return $this->dateFormatter->format($this->getNodeDateValue($node), 'custom', $format);
    }
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheContexts() {
    return ['url'];
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheMaxAge() {
    return Cache::PERMANENT;
  }

  /**
   * Gets a timestamp value from the passed node.
   *
   * @param \Drupal\node\NodeInterface $node
   *   The node to get the timestamp value from.
   *
   * @return int
   *   A timestamp value from a node field.
   */
  abstract protected function getNodeDateValue(NodeInterface $node): int;

}
+167 −0
Original line number Diff line number Diff line
langcode: und
status: true
dependencies:
  module:
    - node
id: test_argument_node_date
label: test_argument_node_date
module: views
description: ''
tag: default
base_table: node_field_data
base_field: nid
display:
  default:
    display_options:
      access:
        type: none
      arguments:
        'null':
          default_action: default
          default_argument_type: date
          field: 'null'
          id: 'null'
          must_not_be: false
          table: views
          plugin_id: 'null'
      cache:
        type: none
      exposed_form:
        type: basic
      fields:
        title:
          alter:
            alter_text: false
            ellipsis: true
            html: false
            make_link: false
            strip_tags: false
            trim: false
            word_boundary: true
          empty_zero: false
          field: title
          hide_empty: false
          id: title
          link_to_node: false
          table: node_field_data
          plugin_id: node
          entity_type: node
          entity_field: title
      pager:
        options:
          id: 0
          items_per_page: 10
          offset: 0
        type: full
      style:
        type: default
      row:
        type: fields
    display_plugin: default
    display_title: Default
    id: default
    position: 0
  block_1:
    display_plugin: block
    id: block_1
    display_title: Block
    position: 1
    display_options:
      display_extenders: {  }
      arguments:
        created:
          id: created
          table: node_field_data
          field: created
          relationship: none
          group_type: group
          admin_label: ''
          default_action: default
          exception:
            value: all
            title_enable: false
            title: All
          title_enable: false
          title: ''
          default_argument_type: node_created
          default_argument_options: {  }
          summary_options:
            base_path: ''
            count: true
            items_per_page: 25
            override: false
          summary:
            sort_order: asc
            number_of_records: 0
            format: default_summary
          specify_validation: false
          validate:
            type: none
            fail: 'not found'
          validate_options: {  }
          entity_type: node
          entity_field: created
          plugin_id: date
      defaults:
        arguments: false
    cache_metadata:
      max-age: -1
      contexts:
        - 'languages:language_content'
        - 'languages:language_interface'
        - url
        - 'user.node_grants:view'
        - user.permissions
      tags: {  }
  block_2:
    display_plugin: block
    id: block_2
    display_title: 'Block 2'
    position: 2
    display_options:
      display_extenders: {  }
      arguments:
        changed:
          id: changed
          table: node_field_data
          field: changed
          relationship: none
          group_type: group
          admin_label: ''
          default_action: default
          exception:
            value: all
            title_enable: false
            title: All
          title_enable: false
          title: ''
          default_argument_type: node_changed
          default_argument_options: {  }
          summary_options:
            base_path: ''
            count: true
            items_per_page: 25
            override: false
          summary:
            sort_order: asc
            number_of_records: 0
            format: default_summary
          specify_validation: false
          validate:
            type: none
            fail: 'not found'
          validate_options: {  }
          entity_type: node
          entity_field: changed
          plugin_id: date
      defaults:
        arguments: false
    cache_metadata:
      max-age: -1
      contexts:
        - 'languages:language_content'
        - 'languages:language_interface'
        - url
        - 'user.node_grants:view'
        - user.permissions
      tags: {  }
+193 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Tests\node\Functional\Views;

use Drupal\views\Entity\View;
use Drupal\views\Views;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;

/**
 * Tests pluggable argument_default for views.
 */
#[Group('views')]
#[RunTestsInSeparateProcesses]
class DateArgumentDefaultTest extends NodeTestBase {

  /**
   * {@inheritdoc}
   */
  protected $defaultTheme = 'stark';

  /**
   * Views used by this test.
   *
   * @var array
   */
  public static $testViews = ['test_argument_node_date'];

  /**
   * Modules to enable.
   *
   * @var array
   */
  protected static $modules = ['block', 'node', 'node_test_views'];

  /**
   * The current node.
   *
   * @var \Drupal\node\NodeInterface
   */
  protected $currentNode;

  /**
   * Node with the same create time as the current node.
   *
   * @var \Drupal\node\NodeInterface
   */
  protected $sameTimeNode;

  /**
   * Node with a different create time then the current node.
   *
   * @var \Drupal\node\NodeInterface
   */
  protected $otherTimeNode;

  /**
   * The node representing the page.
   *
   * @var \Drupal\node\NodeInterface
   */
  protected $fixedTimeNode;

  /**
   * The node representing the page.
   *
   * @var \Drupal\node\NodeInterface
   */
  protected $sameMonthNode;

  /**
   * {@inheritdoc}
   */
  public function setUp($import_test_views = TRUE, $modules = ['node_test_views']): void {
    parent::setUp($import_test_views, $modules);

    $this->drupalCreateContentType(['type' => 'page']);
    $this->currentNode = $this->drupalCreateNode(['type' => 'page']);
    $this->sameTimeNode = $this->drupalCreateNode(['type' => 'page']);
    $this->otherTimeNode = $this->drupalCreateNode([
      'type' => 'page',
      'created' => strtotime('-5 days'),
      'changed' => strtotime('-5 days'),
    ]);
    $this->fixedTimeNode = $this->drupalCreateNode([
      'type' => 'page',
      'created' => strtotime('1975-05-18'),
      'changed' => strtotime('1975-05-18'),
    ]);
    $this->sameMonthNode = $this->drupalCreateNode([
      'type' => 'page',
      'created' => strtotime('1975-05-13'),
      'changed' => strtotime('1975-05-13'),
    ]);
  }

  /**
   * Test the 'Current node created time' default argument handler.
   *
   * @see \Drupal\node\Plugin\views\argument_default\NodeCreated
   */
  public function testArgumentDefaultNodeCreated(): void {
    $this->drupalPlaceBlock('views_block:test_argument_node_date-block_1', ['label' => 'test_argument_node_date-block_1:1']);
    $assert = $this->assertSession();

    // Assert that only nodes with the same creation time as the current node
    // are shown in the block.
    $this->drupalGet($this->currentNode->toUrl());
    $assert->pageTextContains($this->currentNode->getTitle());
    $assert->pageTextContains($this->sameTimeNode->getTitle());
    $assert->pageTextNotContains($this->otherTimeNode->getTitle());

    // Update the View to use the Y-m format argument.
    $view = View::load('test_argument_node_date');
    $display = &$view->getDisplay('block_1');
    $display['display_options']['arguments']['created']['plugin_id'] = 'date_year_month';
    $display['display_options']['arguments']['created']['field'] = 'created_year_month';
    $display['display_options']['arguments']['created']['id'] = 'created_year_month';
    $view->save();

    // Test that the nodes with a create date in the same month are shown.
    $this->drupalGet($this->fixedTimeNode->toUrl());
    $assert->pageTextContains($this->fixedTimeNode->getTitle());
    $assert->pageTextContains($this->sameMonthNode->getTitle());
    $assert->pageTextNotContains($this->currentNode->getTitle());

    // Update the View to use the date format argument for non-date field.
    $display['display_options']['arguments']['created']['field'] = 'title';
    $view->save();

    // Test that the nodes with a title in the same create date are shown.
    $nodeTitleFixed = $this->drupalCreateNode(['type' => 'page', 'title' => '1975-05-18']);
    $this->drupalGet($this->fixedTimeNode->toUrl());
    $assert->pageTextContains($nodeTitleFixed->getTitle());
    $assert->pageTextNotContains($this->sameMonthNode->getTitle());

    // Test the getDefaultArgument() outside of node page.
    $view = Views::getView('test_argument_node_date');
    $view->setDisplay('block_1');
    $view->initHandlers();
    $this->assertFalse($view->argument['created']->getDefaultArgument());
  }

  /**
   * Test the 'Current node changed time' default argument handler.
   *
   * @see \Drupal\node\Plugin\views\argument_default\NodeChanged
   */
  public function testArgumentDefaultNodeChanged(): void {
    $this->drupalPlaceBlock('views_block:test_argument_node_date-block_2', ['label' => 'test_argument_node_date-block_2:2']);
    $assert = $this->assertSession();

    // Assert that only nodes with the same changed time as the current node
    // are shown in the block.
    $this->drupalGet($this->currentNode->toUrl());
    $assert->pageTextContains($this->currentNode->getTitle());
    $assert->pageTextContains($this->sameTimeNode->getTitle());
    $assert->pageTextNotContains($this->otherTimeNode->getTitle());

    // Update the View to use the Y-m format argument.
    $view = View::load('test_argument_node_date');
    $display = &$view->getDisplay('block_2');
    $display['display_options']['arguments']['changed']['plugin_id'] = 'date_year_month';
    $display['display_options']['arguments']['changed']['field'] = 'changed_year_month';
    $display['display_options']['arguments']['changed']['id'] = 'changed_year_month';
    $view->save();

    // Test that the nodes with a changed date in the same month are shown.
    $this->drupalGet($this->fixedTimeNode->toUrl());
    $assert->pageTextContains($this->fixedTimeNode->getTitle());
    $assert->pageTextContains($this->sameMonthNode->getTitle());
    $assert->pageTextNotContains($this->currentNode->getTitle());

    // Update the View to use the date format argument for non-date field.
    $display['display_options']['arguments']['changed']['field'] = 'title';
    $view->save();

    // Test that the nodes with a title in the same changed date are shown.
    $nodeTitleFixed = $this->drupalCreateNode(['type' => 'page', 'title' => '1975-05-18']);
    $this->drupalGet($this->fixedTimeNode->toUrl());
    $assert->pageTextContains($nodeTitleFixed->getTitle());
    $assert->pageTextNotContains($this->sameMonthNode->getTitle());

    // Test the getDefaultArgument() outside of node page.
    $view = Views::getView('test_argument_node_date');
    $view->setDisplay('block_2');
    $view->initHandlers();
    $this->assertFalse($view->argument['changed']->getDefaultArgument());
  }

}
Loading