/**
   * Test an AJAX flag link.
   */
  public function doUseAjaxFlag() {
    // Create and login as an authenticated user.
    $auth_user = $this->drupalCreateUser();
    $this->drupalLogin($auth_user);

    $node_url = $this->node->toUrl();

    // Navigate to the node page.
    $this->drupalGet($node_url);

    // Confirm the flag link exists.
    $this->assertLink($this->flag->getFlagShortText());

    // Click the flag link. This ensures that the non-JS fallback works we are
    // redirected to back to the page and the node is flagged.
    $this->clickLink($this->flag->getFlagShortText());
    $this->assertUrl($node_url);
    $this->assertLink($this->flag->getUnflagShortText());

    // Click the unflag link, repeat the check.
    $this->clickLink($this->flag->getUnflagShortText());
    $this->assertUrl($node_url);
    $this->assertLink($this->flag->getFlagShortText());

    // Now also test with an ajax request and that the correct response is
    // returned. Use the same logic as clickLink() to find the link.
    $urls = $this->xpath('//a[normalize-space()=:label]', array(':label' => $this->flag->getFlagShortText()));
    $url_target = $this->getAbsoluteUrl($urls[0]['href']);
    $ajax_response = $this->drupalGetAjax($url_target);

    // Assert that the replace selector is correct.
    $this->assertEqual($ajax_response[0]['selector'], '#' .$urls[0]['id']);

    // Request the returned URL to ensure that link is valid and has a valid
    // CSRF token.
    $xml_data = new \SimpleXMLElement($ajax_response[0]['data']);
    $this->assertEqual($this->flag->getUnflagShortText(), (string) $xml_data);

    $ajax_response = $this->drupalGetAjax($this->getAbsoluteUrl($xml_data['href']));
    // Assert that the replace selector is correct.
    $this->assertEqual($ajax_response[0]['selector'], '#' . $xml_data['id']);

    $xml_data_unflag = new \SimpleXMLElement($ajax_response[0]['data']);
    $this->assertEqual($this->flag->getFlagShortText(), (string) $xml_data_unflag);

  }
  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $this->flaggingService->reset($this->flag);
    drupal_set_message($this->t('Flag %label was reset.', [
      '%label' => $this->flag->label(),
    ]));

    $form_state->setRedirectUrl($this->getCancelUrl());
  }
  /**
   * {@inheritdoc}
   */
  public function reset(FlagInterface $flag, EntityInterface $entity = NULL) {
    $query = $this->entityQueryManager->get('flagging')
      ->condition('flag_id', $flag->id());

    if (!empty($entity)) {
      $query->condition('entity_id', $entity->id());
    }

    // Count the number of flaggings to delete.
    $count = $query->count()
      ->execute();

    $this->eventDispatcher->dispatch(FlagEvents::FLAG_RESET, new FlagResetEvent($flag, $count));

    $flaggings = $this->flagService->getFlaggings($flag, $entity);
    foreach ($flaggings as $flagging) {
      $flagging->delete();
    }

    return $count;
  }
  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    // Toggle the flag state.
    if ($this->flag->isEnabled()) {
      $this->flag->disable();
    }
    else {
      $this->flag->enable();
    }

    // Save The flag entity.
    $this->flag->save();

    // Redirect to the flag admin page.
    $form_state->setRedirect('entity.flag.collection');
  }
  /**
   * Assert editing an invalid flagging throws an exception.
   */
  public function doBadEditFlagField() {
    $flag_id = $this->flag->id();

    // Test a good flag ID param, but a bad flaggable ID param.
    $this->drupalGet('flag/details/edit/' . $flag_id . '/-9999');
    $this->assertResponse('404', 'Editing an invalid flagging path: good flag, bad entity.');

    // Test a bad flag ID param, but a good flaggable ID param.
    $this->drupalGet('flag/details/edit/jibberish/' . $this->nodeId);
    $this->assertResponse('404', 'Editing an invalid flagging path: bad flag, good entity');

    // Test editing a unflagged entity.
    $unlinked_node = $this->drupalCreateNode(['type' => $this->nodeType]);
    $this->drupalGet('flag/details/edit/' . $flag_id . '/' . $unlinked_node->id());
    $this->assertResponse('404', 'Editing an invalid flagging path: good flag, good entity, but not flagged');
  }
  /**
   * Delete the flag.
   */
  public function doFlagDelete() {
    // Flag node.
    $this->drupalGet('node/' . $this->nodeId);
    $this->assertLink($this->flagShortText);
    // Go to the delete form for the flag.
    $this->drupalGet('admin/structure/flags/manage/' . $this->flag->id() . '/delete');

    $this->assertText($this->t('Are you sure you want to delete the flag @this_label?', ['@this_label' => $this->label]));

    $this->drupalPostForm(NULL, [], $this->t('Delete'));

    // Check the flag has been deleted.
    $result = $this->flagService->getFlagById($this->flagId);

    $this->assertNull($result, 'The flag was deleted.');
    $this->drupalGet('node/' . $this->nodeId);
    $this->assertText($this->node->label());
    $this->assertNoLink($this->flagShortText);
  }
  /**
   * Tests flaggings and counts are deleted when its user is deleted.
   */
  public function testUserDeletion() {
    $auth_user = $this->createUser();

    // Create a flag.
    $user_flag = Flag::create([
      'id' => strtolower($this->randomMachineName()),
      'label' => $this->randomString(),
      'entity_type' => 'user',
      'flag_type' => 'entity:user',
      'link_type' => 'reload',
      'flagTypeConfig' => [],
      'linkTypeConfig' => [],
    ]);
    $user_flag->save();

    $article = Node::create([
      'type' => 'article',
      'title' => $this->randomMachineName(8),
    ]);
    $article->save();

    $this->flagService->flag($user_flag, $auth_user, $this->adminUser);
    $this->flagService->flag($this->flag, $article, $auth_user);

    $user_before_count = $this->flagCountService->getEntityFlagCounts($auth_user);
    $this->assertEqual($user_before_count[$user_flag->id()], 1, 'The user has been flagged.');

    $article_count_before = $this->flagCountService->getEntityFlagCounts($article);
    $this->assertEqual($article_count_before[$this->flag->id()], 1, 'The article has been flagged by the user.');

    $auth_user->delete();

    $flaggings_after = $this->flagService->getFlaggings($user_flag);
    $this->assertEmpty($flaggings_after, 'The user flaggings were removed when the user was deleted.');

    $flaggings_after = $this->flagService->getFlaggings($this->flag);
    $this->assert(empty($flaggings_after), 'The node flaggings were removed when the user was deleted');
  }
 /**
  * Checks access to the 'unflag' action.
  *
  * @param \Drupal\flag\FlagInterface $flag
  *   The flag entity.
  *
  * @return string
  *   A \Drupal\Core\Access\AccessInterface constant value.
  */
 public function access(FlagInterface $flag) {
   return AccessResult::allowedIf($flag->hasActionAccess('unflag'));
 }
  /**
   * Flag a node.
   */
  public function doFlagNode() {
    $node = $this->drupalCreateNode(['type' => $this->nodeType]);
    $node_id = $node->id();
    $flag_id = $this->flag->id();

    // Grant the flag permissions to the authenticated role, so that both
    // users have the same roles and share the render cache. ???? TODO
    $this->grantFlagPermissions($this->flag);

    // Create and login a new user.
    $user_1 = $this->drupalCreateUser();
    $this->drupalLogin($user_1);

    // Get the flag count before the flagging, querying the database directly.
    $flag_count_pre = db_query('SELECT count FROM {flag_counts}
      WHERE flag_id = :flag_id AND entity_type = :entity_type AND entity_id = :entity_id', [
      ':flag_id' => $flag_id,
      ':entity_type' => 'node',
      ':entity_id' => $node_id,
    ])->fetchField();

    // Attempt to load the reload link URL without the token.
    // We (probably) can't obtain the URL from the route rather than hardcoding
    // it, as that would probably give us the token too.
    $this->drupalGet("flag/flag/$flag_id/$node_id");
    $this->assertResponse(403, "Access to the flag reload link is denied when no token is supplied.");

    // Click the flag link.
    $this->drupalGet('node/' . $node_id);
    $this->clickLink($this->flag->getFlagShortText());

    // Check that the node is flagged.
    $this->drupalGet('node/' . $node_id);
    $this->assertLink($this->flag->getUnflagShortText());

    // Check the flag count was incremented.
    $flag_count_flagged = db_query('SELECT count FROM {flag_counts}
      WHERE flag_id = :flag_id AND entity_type = :entity_type AND entity_id = :entity_id', [
      ':flag_id' => $flag_id,
      ':entity_type' => 'node',
      ':entity_id' => $node_id,
    ])->fetchField();
    $this->assertEqual($flag_count_flagged, $flag_count_pre + 1, "The flag count was incremented.");

    // Attempt to load the reload link URL without the token.
    $this->drupalGet("flag/unflag/$flag_id/$node_id");
    $this->assertResponse(403, "Access to the unflag reload link is denied when no token is supplied.");

    // Unflag the node.
    $this->drupalGet('node/' . $node_id);
    $this->clickLink($this->flag->getUnflagShortText());

    // Check that the node is no longer flagged.
    $this->drupalGet('node/' . $node_id);
    $this->assertLink($this->flag->getFlagShortText());

    // Check the flag count was decremented.
    $flag_count_unflagged = db_query('SELECT count FROM {flag_counts}
      WHERE flag_id = :flag_id AND entity_type = :entity_type AND entity_id = :entity_id', [
      ':flag_id' => $flag_id,
      ':entity_type' => 'node',
      ':entity_id' => $node_id,
    ])->fetchField();
    $this->assertEqual($flag_count_unflagged, $flag_count_flagged - 1, "The flag count was decremented.");
  }
  /**
   * Create a node, flag it and unflag it.
   */
  public function doFlagUnflagNode() {
    $node = $this->drupalCreateNode(['type' => $this->nodeType]);
    $node_id = $node->id();
    $flag_id = $this->flag->id();

    // Grant the flag permissions to the authenticated role, so that both
    // users have the same roles and share the render cache.
    $this->grantFlagPermissions($this->flag);

    // Create and login a new user.
    $user_1 = $this->drupalCreateUser();
    $this->drupalLogin($user_1);

    // Get the flag count before the flagging, querying the database directly.
    $flag_count_pre = db_query('SELECT count FROM {flag_counts}
      WHERE flag_id = :flag_id AND entity_type = :entity_type AND entity_id = :entity_id', [
      ':flag_id' => $flag_id,
      ':entity_type' => 'node',
      ':entity_id' => $node_id,
    ])->fetchField();

    // Click the flag link.
    $this->drupalGet('node/' . $node_id);
    $this->clickLink($this->flag->getFlagShortText());

    // Check if we have the confirm form message displayed.
    $this->assertText($this->flagConfirmMessage);

    // Submit the confirm form.
    $this->drupalPostForm('flag/confirm/flag/' . $flag_id . '/' . $node_id, [], t('Flag'));
    $this->assertResponse(200);

    // Check that the node is flagged.
    $this->drupalGet('node/' . $node_id);
    $this->assertLink($this->flag->getUnflagShortText());

    // Check the flag count was incremented.
    $flag_count_flagged = db_query('SELECT count FROM {flag_counts}
      WHERE flag_id = :flag_id AND entity_type = :entity_type AND entity_id = :entity_id', [
      ':flag_id' => $flag_id,
      ':entity_type' => 'node',
      ':entity_id' => $node_id,
    ])->fetchField();
    $this->assertEqual($flag_count_flagged, $flag_count_pre + 1, "The flag count was incremented.");

    // Unflag the node.
    $this->clickLink($this->flag->getUnflagShortText());

    // Check if we have the confirm form message displayed.
    $this->assertText($this->unflagConfirmMessage);

    // Submit the confirm form.
    $this->drupalPostForm(NULL, [], t('Unflag'));
    $this->assertResponse(200);

    // Check that the node is no longer flagged.
    $this->drupalGet('node/' . $node_id);
    $this->assertLink($this->flag->getFlagShortText());

    // Check the flag count was decremented.
    $flag_count_unflagged = db_query('SELECT count FROM {flag_counts}
      WHERE flag_id = :flag_id AND entity_type = :entity_type AND entity_id = :entity_id', [
      ':flag_id' => $flag_id,
      ':entity_type' => 'node',
      ':entity_id' => $node_id,
    ])->fetchField();
    $this->assertEqual($flag_count_unflagged, $flag_count_flagged - 1, "The flag count was decremented.");
  }
 /**
  * Title callback when editing an existing flagging.
  *
  * @param \Drupal\flag\FlagInterface $flag
  *   The flag entity.
  * @param int $entity_id
  *   The entity ID to unflag.
  *
  * @return string
  *   The flag field entry form title.
  */
 public function editTitle(FlagInterface $flag, $entity_id) {
   $link_type = $flag->getLinkTypePlugin();
   return $link_type->getEditFlaggingTitle();
 }
  /**
   * Generates a render array of the applicable bundles for the flag..
   *
   * @param \Drupal\flag\FlagInterface $flag
   *   The flag entity.
   *
   * @return array
   *   A render array of the applicable bundles for the flag..
   */
  protected function getBundles(FlagInterface $flag) {
    $bundles = $flag->getBundles();

    if (empty($bundles)) {
      return [
        '#markup' => '<em>' . $this->t('All') . '</em>',
        '#allowed_tags' => ['em'],
      ];
    }

    return [
      '#markup' => implode(', ', $bundles),
    ];
  }
  /**
   * {@inheritdoc}
   */
  public function unflag(FlagInterface $flag, EntityInterface $entity, AccountInterface $account = NULL) {
    $bundles = $flag->getBundles();

    // Check the entity type corresponds to the flag type.
    if ($flag->getFlaggableEntityTypeId() != $entity->getEntityTypeId()) {
      throw new \LogicException('The flag does not apply to entities of this type.');
    }

    // Check the bundle is allowed by the flag.
    if (!empty($bundles) && !in_array($entity->bundle(), $bundles)) {
      throw new \LogicException('The flag does not apply to the bundle of the entity.');
    }

    $flagging = $this->getFlagging($flag, $entity, $account);

    // Check whether there is an existing flagging for the combination of flag,
    // entity, and user.
    if (!$flagging) {
      throw new \LogicException('The entity is not flagged by the user.');
    }

    $this->eventDispatcher->dispatch(FlagEvents::ENTITY_UNFLAGGED, new FlaggingEvent($flag, $entity));

    $flagging->delete();
  }
  /**
   * Flags a node using different user accounts and checks flag counts.
   */
  public function doTestFlagCounts() {
    /** \Drupal\Core\Database\Connection $connection */
    $connection = \Drupal::database();

    $node = $this->drupalCreateNode(['type' => $this->nodeType]);
    $node_id = $node->id();

    // Grant the flag permissions to the authenticated role, so that both
    // users have the same roles and share the render cache.
    $this->grantFlagPermissions($this->flag);

    // Create and login user 1.
    $user_1 = $this->drupalCreateUser();
    $this->drupalLogin($user_1);

    // Flag node (first count).
    $this->drupalGet('node/' . $node_id);
    $this->clickLink($this->flag->getFlagShortText());
    $this->assertResponse(200);
    $this->assertLink($this->flag->getUnflagShortText());

    // Check for 1 flag count.
    $count_flags_before = $connection->select('flag_counts')
      ->condition('flag_id', $this->flag->id())
      ->condition('entity_type', $node->getEntityTypeId())
      ->condition('entity_id', $node_id)
      ->countQuery()
      ->execute()
      ->fetchField();
    $this->assertTrue(1, $count_flags_before);

    // Logout user 1, create and login user 2.
    $user_2 = $this->drupalCreateUser();
    $this->drupalLogin($user_2);

    // Flag node (second count).
    $this->drupalGet('node/' . $node_id);
    $this->clickLink($this->flag->getFlagShortText());
    $this->assertResponse(200);
    $this->assertLink($this->flag->getUnflagShortText());

    // Check for 2 flag counts.
    $count_flags_after = $connection->select('flag_counts')
      ->condition('flag_id', $this->flag->id())
      ->condition('entity_type', $node->getEntityTypeId())
      ->condition('entity_id', $node_id)
      ->countQuery()
      ->execute()
      ->fetchField();
    $this->assertTrue(2, $count_flags_after);

    // Unflag the node again.
    $this->drupalGet('node/' . $node_id);
    $this->clickLink($this->flag->getUnflagShortText());
    $this->assertResponse(200);
    $this->assertLink($this->flag->getFlagShortText());

    // Check for 1 flag count.
    $count_flags_before = $connection->select('flag_counts')
      ->condition('flag_id', $this->flag->id())
      ->condition('entity_type', $node->getEntityTypeId())
      ->condition('entity_id', $node_id)
      ->countQuery()
      ->execute()
      ->fetchField();
    $this->assertEqual(1, $count_flags_before);

    // Delete  user 1.
    $user_1->delete();

    // Check for 0 flag counts, user deletion should lead to count decrement
    // or row deletion.
    $count_flags_before = $connection->select('flag_counts')
      ->condition('flag_id', $this->flag->id())
      ->condition('entity_type', $node->getEntityTypeId())
      ->condition('entity_id', $node_id)
      ->countQuery()
      ->execute()
      ->fetchField();

    $this->assertEqual(0, $count_flags_before);
  }
 /**
  * Resets loaded flag counts.
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The flagged entity.
  * @param \Drupal\flag\FlagInterface $flag
  *   The flag.
  */
 protected function resetLoadedCounts(EntityInterface $entity, FlagInterface $flag) {
   // @todo Consider updating them instead of just clearing it.
   unset($this->entityCounts[$entity->getEntityTypeId()][$entity->id()]);
   unset($this->flagCounts[$flag->id()]);
   unset($this->flagEntityCounts[$flag->id()]);
   unset($this->userFlagCounts[$flag->id()]);
 }
 /**
  * Helper for assertPseudofield() and assertNoPseudofield().
  *
  * It is not recommended to call this function directly.
  *
  * @param \Drupal\flag\FlagInterface $flag
  *   The flag to look for.
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The flaggable entity the flag is on.
  * @param string $message
  *   Message to display.
  * @param bool $exists
  *   TRUE if the flag link should exist, FALSE if it should not exist.
  */
 protected function assertPseudofieldHelper(FlagInterface $flag, EntityInterface $entity, $message, $exists) {
   $xpath = $this->xpath("//*[contains(@class, 'node__content')]/a[@id = :id]", [
     ':id' => 'flag-' . $flag->id() . '-id-' . $entity->id(),
   ]);
   $this->assert(count($xpath) == ($exists ? 1 : 0), $message);
 }
  /**
   * Grants flag and unflag permission to the given flag.
   *
   * @param \Drupal\flag\FlagInterface $flag
   *   The flag on which to grant permissions.
   * @param array|string $role_id
   *   (optional) The ID of the role to grant permissions. If omitted, the
   *   authenticated role is assumed.
   * @param bool $can_flag
   *   (optional) TRUE to grant the role flagging permission, FALSE to not grant
   *   flagging permission to the role. If omitted, TRUE is assumed.
   * @param bool $can_unflag
   *   Optional TRUE to grant the role unflagging permission, FALSE to not grant
   *   unflagging permission to the role. If omitted, TRUE is assumed.
   */
  protected function grantFlagPermissions(FlagInterface $flag,
                                      $role_id = RoleInterface::AUTHENTICATED_ID,
                                      $can_flag = TRUE, $can_unflag = TRUE) {

    // Grant the flag permissions to the authenticated role, so that both
    // users have the same roles and share the render cache.
    $role = Role::load($role_id);
    if ($can_flag) {
      $role->grantPermission('flag ' . $flag->id());
    }

    if ($can_unflag) {
      $role->grantPermission('unflag ' . $flag->id());
    }

    $role->save();
  }
  /**
   * {@inheritdoc}
   */
  public function getLink(FlagInterface $flag, EntityInterface $entity) {
    $action = $flag->isFlagged($entity) ? 'unflag' : 'flag';

    if ($flag->hasActionAccess($action)) {

      $link = $this->buildLink($action, $flag, $entity);

      // The actual render array must be in a nested key, due to a bug in
      // lazy builder handling that does not properly render top-level #type
      // elements.
      return ['link' => $link];
    }

    return [];
  }
  /**
   * Generates a response object after handing the un/flag request.
   *
   * Depending on the wrapper format of the request, it will either redirect
   * or return an ajax response.
   *
   * @param \Drupal\flag\FlagInterface $flag
   *   The flag entity.
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity object.
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request.
   *
   * @return \Drupal\Core\Ajax\AjaxResponse|\Symfony\Component\HttpFoundation\RedirectResponse
   *   The response object.
   */
  protected function generateResponse(FlagInterface $flag, EntityInterface $entity, Request $request) {
    if ($request->get(MainContentViewSubscriber::WRAPPER_FORMAT) == 'drupal_ajax') {
      // Create a new AJAX response.
      $response = new AjaxResponse();

      // Get the link type plugin.
      $link_type = $flag->getLinkTypePlugin();

      // Generate the link render array and get the link CSS ID.
      $link = $link_type->getLink($flag, $entity);
      $link_id = '#' . $link['link']['#attributes']['id'];

      // Create a new JQuery Replace command to update the link display.
      $replace = new ReplaceCommand($link_id, $this->renderer->renderPlain($link));
      $response->addCommand($replace);

      return $response;
    }
    else {
      // Redirect back to the entity. A passed in destination query parameter
      // will automatically override this.
      $url_info = $entity->toUrl();
      return $this->redirect($url_info->getRouteName(), $url_info->getRouteParameters());
    }

  }