Commit f71774a2 authored by martin frances's avatar martin frances Committed by Tess
Browse files

Issue #2834455 by martin107, socketwench, joachim: Fixed regression:...

Issue #2834455 by martin107, socketwench, joachim: Fixed regression: Flag/unflag messages don't show for reload and confirm link types
parent dd511975
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -42,6 +42,17 @@ flag.action_link_flag:
  path: '/flag/flag/{flag}/{entity_id}'
  defaults:
    _controller: '\Drupal\flag\Controller\ActionLinkController::flag'
  methods: [POST]
  requirements:
    _flag_access: 'entity:flag{flag}'
    _format: 'html'
    _csrf_token: 'TRUE'

flag.action_link_flag_nojs:
  path: '/flag/flag/{flag}/{entity_id}'
  defaults:
    _controller: '\Drupal\flag\Controller\ActionLinkNoJsController::flag'
  methods: [GET]
  requirements:
    _flag_access: 'entity:flag{flag}'
    _format: 'html'
@@ -51,6 +62,17 @@ flag.action_link_unflag:
  path: '/flag/unflag/{flag}/{entity_id}'
  defaults:
    _controller: '\Drupal\flag\Controller\ActionLinkController::unflag'
  methods: [POST]
  requirements:
    _unflag_access: 'entity:flag{flag}'
    _format: 'html'
    _csrf_token: 'TRUE'

flag.action_link_unflag_nojs:
  path: '/flag/unflag/{flag}/{entity_id}'
  defaults:
    _controller: '\Drupal\flag\Controller\ActionLinkNoJsController::unflag'
  methods: [GET]
  requirements:
    _unflag_access: 'entity:flag{flag}'
    _format: 'html'
+27 −58
Original line number Diff line number Diff line
@@ -6,28 +6,19 @@ use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Url;
use Drupal\flag\Ajax\ActionLinkFlashCommand;
use Drupal\flag\FlagInterface;
use Drupal\flag\FlagServiceInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Component\Utility\Html;

/**
 * Controller responses to flag and unflag action links.
 *
 * If the action_link is a normal link then after an update the response to a
 *  valid request is a redirect to the entity with drupal update message.
 *
 * For an ajax_action_link the response is a set of AJAX commands to update the
 * link in the page. If the user agent has javascript disabled then the
 * behaviour reverts to that of a normal link.
 * The response is a set of AJAX commands to update the
 * link in the page.
 */

class ActionLinkController implements ContainerInjectionInterface {
  /**
   * The flag service.
@@ -46,8 +37,10 @@ class ActionLinkController implements ContainerInjectionInterface {
  /**
   * Constructor.
   *
   * @param FlagServiceInterface $flag
   * @param \Drupal\flag\FlagServiceInterface $flag
   *   The flag service.
   * @param \Drupal\Core\Render\RendererInterface $renderer
   *   The renderer service.
   */
  public function __construct(FlagServiceInterface $flag, RendererInterface $renderer) {
    $this->flagService = $flag;
@@ -71,15 +64,13 @@ class ActionLinkController implements ContainerInjectionInterface {
   *   The flag entity.
   * @param int $entity_id
   *   The flaggable entity ID.
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request.
   *
   * @return \Drupal\Core\Ajax\AjaxResponse|\Symfony\Component\HttpFoundation\RedirectResponse|null
   * @return \Drupal\Core\Ajax\AjaxResponse|null
   *   The response object, only if successful.
   *
   * @see \Drupal\flag\Plugin\Reload
   */
  public function flag(FlagInterface $flag, $entity_id, Request $request) {
  public function flag(FlagInterface $flag, $entity_id) {
    /* @var \Drupal\Core\Entity\EntityInterface $entity */
    $entity = $this->flagService->getFlaggableById($flag, $entity_id);

@@ -91,7 +82,7 @@ class ActionLinkController implements ContainerInjectionInterface {
      // link for the existing state of the flag.
    }

    return $this->generateResponse($flag, $entity, $request, $flag->getMessage('flag'));
    return $this->generateResponse($flag, $entity, $flag->getMessage('flag'));
  }

  /**
@@ -101,15 +92,13 @@ class ActionLinkController implements ContainerInjectionInterface {
   *   The flag entity.
   * @param int $entity_id
   *   The flaggable entity ID.
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request.
   *
   * @return \Drupal\Core\Ajax\AjaxResponse|\Symfony\Component\HttpFoundation\RedirectResponse|null
   * @return \Drupal\Core\Ajax\AjaxResponse|null
   *   The response object, only if successful.
   *
   * @see \Drupal\flag\Plugin\Reload
   */
  public function unflag(FlagInterface $flag, $entity_id, Request $request) {
  public function unflag(FlagInterface $flag, $entity_id) {
    /* @var \Drupal\Core\Entity\EntityInterface $entity */
    $entity = $this->flagService->getFlaggableById($flag, $entity_id);

@@ -121,7 +110,7 @@ class ActionLinkController implements ContainerInjectionInterface {
      // link for the existing state of the flag.
    }

    return $this->generateResponse($flag, $entity, $request, $flag->getMessage('unflag'));
    return $this->generateResponse($flag, $entity, $flag->getMessage('unflag'));
  }

  /**
@@ -134,11 +123,10 @@ class ActionLinkController implements ContainerInjectionInterface {
   * @param string $message
   *   (optional) The message to flash.
   *
   * @return \Drupal\Core\Ajax\AjaxResponse|\Symfony\Component\HttpFoundation\RedirectResponse
   * @return \Drupal\Core\Ajax\AjaxResponse
   *   The response object.
   */
  protected function generateResponse(FlagInterface $flag, EntityInterface $entity, Request $request, $message = NULL) {
    if ($request->get(MainContentViewSubscriber::WRAPPER_FORMAT) == 'drupal_ajax') {
  private function generateResponse(FlagInterface $flag, EntityInterface $entity, $message) {
    // Create a new AJAX response.
    $response = new AjaxResponse();

@@ -155,28 +143,9 @@ class ActionLinkController implements ContainerInjectionInterface {
    $replace = new ReplaceCommand($selector, $this->renderer->renderPlain($link));
    $response->addCommand($replace);

      if ($message) {
    // Push a message pulsing command onto the stack.
    $pulse = new ActionLinkFlashCommand($selector, $message);
    $response->addCommand($pulse);
     }
    }
    elseif ($entity->hasLinkTemplate('canonical')) {
      // Redirect back to the entity. A passed in destination query parameter
      // will automatically override this.
      $url_info = $entity->toUrl();

      $options['absolute'] = TRUE;
      $url = Url::fromRoute($url_info->getRouteName(), $url_info->getRouteParameters(), $options);
      $response = new RedirectResponse($url->toString());

    }
    else {
      // For entities that don't have a canonical URL (like paragraphs),
      // redirect to the front page.
      $front = Url::fromUri('internal:/');
      $response = new RedirectResponse($front);
    }

    return $response;
  }
+150 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\flag\Controller;

use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Url;
use Drupal\flag\FlagInterface;
use Drupal\flag\FlagServiceInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;

/**
 * Returns nojs responses to flag and unflag action links.
 *
 * "nojs" is when the user agent has javascript disabled the
 * behaviour reverts to that of a normal link.
 *
 * After an update the response to a valid request is a redirect to the entity
 * with drupal update message.
 */
class ActionLinkNoJsController implements ContainerInjectionInterface {
  /**
   * The flag service.
   *
   * @var \Drupal\flag\FlagServiceInterface
   */
  protected $flagService;

  /**
   * The messenger service.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  protected $messenger;

  /**
   * Constructor.
   *
   * @param \Drupal\flag\FlagServiceInterface $flag
   *   The flag service.
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
   *   The messenger service.
   */
  public function __construct(FlagServiceInterface $flag, MessengerInterface $messenger) {
    $this->flagService = $flag;
    $this->messenger = $messenger;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('flag'),
      $container->get('messenger')
    );
  }

  /**
   * Performs a flagging when called via a route.
   *
   * @param \Drupal\flag\FlagInterface $flag
   *   The flag entity.
   * @param int $entity_id
   *   The flaggable entity ID.
   *
   * @return \Symfony\Component\HttpFoundation\RedirectResponse|null
   *   The response object, only if successful.
   *
   * @see \Drupal\flag\Plugin\Reload
   */
  public function flag(FlagInterface $flag, $entity_id) {
    /* @var \Drupal\Core\Entity\EntityInterface $entity */
    $entity = $this->flagService->getFlaggableById($flag, $entity_id);

    try {
      $this->flagService->flag($flag, $entity);
    }
    catch (\LogicException $e) {
      // Fail silently so we return to the entity, which will show an updated
      // link for the existing state of the flag.
    }

    return $this->generateResponse($entity, $flag->getMessage('flag'));
  }

  /**
   * Performs a unflagging when called via a route.
   *
   * @param \Drupal\flag\FlagInterface $flag
   *   The flag entity.
   * @param int $entity_id
   *   The flaggable entity ID.
   *
   * @return \Symfony\Component\HttpFoundation\RedirectResponse|null
   *   The response object, only if successful.
   *
   * @see \Drupal\flag\Plugin\Reload
   */
  public function unflag(FlagInterface $flag, $entity_id) {
    /* @var \Drupal\Core\Entity\EntityInterface $entity */
    $entity = $this->flagService->getFlaggableById($flag, $entity_id);

    try {
      $this->flagService->unflag($flag, $entity);
    }
    catch (\LogicException $e) {
      // Fail silently so we return to the entity, which will show an updated
      // link for the existing state of the flag.
    }

    return $this->generateResponse($entity, $flag->getMessage('unflag'));
  }

  /**
   * Generates a response after the flag has been updated.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity object.
   * @param string $message
   *   The message to display.
   *
   * @return \Symfony\Component\HttpFoundation\RedirectResponse
   *   The response object.
   */
  private function generateResponse(EntityInterface $entity, $message) {
    $this->messenger->addMessage($message);

    if ($entity->hasLinkTemplate('canonical')) {
      // Redirect back to the entity. A passed in destination query parameter
      // will automatically override this.
      $url_info = $entity->toUrl();

      $options['absolute'] = TRUE;
      $url = Url::fromRoute($url_info->getRouteName(), $url_info->getRouteParameters(), $options);
      $response = new RedirectResponse($url->toString());
    }
    else {
      // For entities that don't have a canonical URL (like paragraphs),
      // redirect to the front page.
      $front = Url::fromUri('internal:/');
      $response = new RedirectResponse($front);
    }

    return $response;
  }

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

namespace Drupal\Tests\flag\Functional;

use Drupal\flag\Tests\FlagCreateTrait;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\flag\Traits\FlagPermissionsTrait;
use Drupal\Core\Url;

/**
 * Test the NoJS responses to clicking on  AjaxLinks.
 *
 * @see ActionLinkNoJsController
 *
 * @group flag
 */
class AjaxLinkNoJsTest extends BrowserTestBase {

  use FlagCreateTrait;
  use FlagPermissionsTrait;

  /**
   * {@inheritdoc}
   */
  public static $modules = ['flag', 'node', 'user'];

  /**
   * Flag to test with.
   *
   * @var \Drupal\flag\FlagInterface
   */
  protected $flag;

  /**
   * The flag service.
   *
   * @var \Drupal\flag\FlagServiceInterface
   */
  protected $flagService;

  /**
   * Test node.
   *
   * @var \Drupal\node\NodeInterface
   */
  protected $node;

  /**
   * Admin user.
   *
   * @var \Drupal\user\UserInterface
   */
  protected $admin;

  /**
   * Normal user.
   *
   * @var \Drupal\user\UserInterface
   */
  protected $webUser;

  /**
   * {@inheritdoc}
   */
  protected function setUp() {
    parent::setUp();

    // A article to test with.
    $this->createContentType(['type' => 'article']);

    $this->admin = $this->createUser();

    $this->node = $this->createNode([
      'type' => 'article',
      'uid' => $this->admin->id(),
    ]);

    // A test flag.
    $this->flag = $this->createFlag('node', ['article'], 'ajax_link');
    $this->flagService = $this->container->get('flag');

    $this->webUser = $this->createUser([
      'access content',
    ]);

    $this->grantFlagPermissions($this->flag);
    $this->drupalLogin($this->webUser);

  }

  /**
   * Test nojs response to AJAX links.
   *
   * The response is a redirect accompanied by a message appearing at the top
   * of the page.
   *
   * Click on flag and then unflag links verifying that the link cycles as
   * expected and flag message functions.
   */
  public function testNoJsMessage() {
    // Get Page.
    $this->drupalGet(Url::fromRoute('entity.node.canonical', ['node' => $this->node->id()]));
    $session = $this->getSession();

    // Verify initially flag link is on the page.
    $page = $session->getPage();
    $flag_link = $page->findLink($this->flag->getShortText('flag'));
    $this->assertNotNull($flag_link, 'flag link exists.');

    // Since this test is BrowserTestBase, and not JavascriptTestBase, this
    // simulates a noJS interaction.
    $flag_link->click();

    // Verify flags message appears.
    $flag_message = $this->flag->getMessage('flag');
    $this->assertSession()->pageTextContains($flag_message);

    // Verify new link.
    $unflag_link = $session->getPage()->findLink($this->flag->getShortText('unflag'));
    $this->assertNotNull($unflag_link, 'unflag link exists.');

    // Simulate a noJs ActionLink (unflag).
    $unflag_link->click();

    // Verfy unflag message appears.
    $unflag_message = $this->flag->getMessage('unflag');
    $this->assertSession()->pageTextContains($unflag_message);

    // Verify the cycle completes and flag returns.
    $flag_link2 = $session->getPage()->findLink($this->flag->getShortText('flag'));
    $this->assertNotNull($flag_link2, 'flag cycle return to start.');

  }

}
+2 −0
Original line number Diff line number Diff line
@@ -13,6 +13,8 @@ use Drupal\Tests\flag\Traits\FlagPermissionsTrait;
 * When a user clicks on an AJAX link a salvo of AJAX commands is issued in
 * response which update the DOM with a new link and a short lived message.
 *
 * @see ActionLinkController
 *
 * @group flag
 */
class AjaxLinkTest extends JavascriptTestBase {