<?php

namespace Drupal\Tests\metatag\Functional;

use Drupal\metatag\MetatagManager;
use Drupal\metatag\Entity\MetatagDefaults;
use Drupal\Tests\BrowserTestBase;
use Drupal\Core\StringTranslation\StringTranslationTrait;

/**
 * Tests the Metatag administration.
 *
 * @group metatag
 */
class MetatagAdminTest extends BrowserTestBase {

  use MetatagHelperTrait;
  use StringTranslationTrait;

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    // Core modules.
    // @see testAvailableConfigEntities
    'block',
    'block_content',
    'comment',
    'contact',
    'field_ui',
    'menu_link_content',
    'menu_ui',
    'node',
    'shortcut',
    'taxonomy',

    // Core test modules.
    'entity_test',
    'test_page_test',

    // Contrib modules.
    'token',

    // This module.
    'metatag',
  ];

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

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

    // Use the test page as the front page.
    $this->config('system.site')->set('page.front', '/test-page')->save();

    // Create Basic page and Article node types.
    if ($this->profile != 'standard') {
      $this->drupalCreateContentType([
        'type' => 'page',
        'name' => 'Basic page',
        'display_submitted' => FALSE,
      ]);
      $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
    }
  }

  /**
   * Tests the interface to manage metatag defaults.
   */
  public function testDefaults() {
    // Save the default title to test the Revert operation at the end.
    $metatag_defaults = \Drupal::config('metatag.metatag_defaults.global');
    $default_title = $metatag_defaults->get('tags')['title'];

    // Initiate session with a user who can manage metatags.
    $permissions = ['administer site configuration', 'administer meta tags'];
    $account = $this->drupalCreateUser($permissions);
    $this->drupalLogin($account);

    // Check that the user can see the list of metatag defaults.
    $this->drupalGet('admin/config/search/metatag');
    $session = $this->assertSession();
    $session->statusCodeEquals(200);

    // Check that the Global defaults were created.
    $session->linkByHrefExists('admin/config/search/metatag/global');

    // Check that Global and entity defaults can't be deleted.
    $session->linkByHrefNotExists('admin/config/search/metatag/global/delete');
    $session->linkByHrefNotExists('admin/config/search/metatag/node/delete');

    // Check that the module defaults were injected into the Global config
    // entity.
    $this->drupalGet('admin/config/search/metatag/global');
    $session->statusCodeEquals(200);
    $this->assertFieldById('edit-title', $metatag_defaults->get('title'), $this->t('Metatag defaults were injected into the Global configuration entity.'));
    // Update the Global defaults and test them.
    $this->drupalGet('admin/config/search/metatag/global');
    $session->statusCodeEquals(200);
    $values = [
      'title' => 'Test title',
      'description' => 'Test description',
    ];
    $this->drupalPostForm(NULL, $values, 'Save');
    $session->pageTextContains('Saved the Global Metatag defaults.');
    $this->drupalGet('hit-a-404');
    $session->statusCodeEquals(404);
    foreach ($values as $metatag => $value) {
      $session->responseContains($value, $this->t('Updated metatag @tag was found in the HEAD section of the page.', ['@tag' => $metatag]));
    }

    // Check that tokens are processed.
    $this->drupalGet('admin/config/search/metatag/global');
    $session->statusCodeEquals(200);
    $values = [
      'title' => '[site:name] | Test title',
      'description' => '[site:name] | Test description',
    ];
    $this->drupalPostForm(NULL, $values, 'Save');
    $session->pageTextContains('Saved the Global Metatag defaults.');
    drupal_flush_all_caches();
    $this->drupalGet('hit-a-404');
    $session->statusCodeEquals(404);
    foreach ($values as $metatag => $value) {
      $processed_value = \Drupal::token()->replace($value);
      $session->responseContains($processed_value, $this->t('Processed token for metatag @tag was found in the HEAD section of the page.', ['@tag' => $metatag]));
    }

    // Test the Robots plugin.
    $this->drupalGet('admin/config/search/metatag/global');
    $session->statusCodeEquals(200);
    $robots_values = ['index', 'follow', 'noydir'];
    $values = [];
    foreach ($robots_values as $value) {
      $values['robots[' . $value . ']'] = TRUE;
    }
    $this->drupalPostForm(NULL, $values, 'Save');
    $session->pageTextContains('Saved the Global Metatag defaults.');
    drupal_flush_all_caches();

    // Trigger a 404 request.
    $this->drupalGet('hit-a-404');
    $session->statusCodeEquals(404);
    $robots_value = implode(', ', $robots_values);
    $session->responseContains($robots_value, $this->t('Robots metatag has the expected values.'));

    // Test reverting global configuration to its defaults.
    $this->drupalGet('admin/config/search/metatag/global/revert');
    $session->statusCodeEquals(200);
    $this->drupalPostForm(NULL, [], 'Revert');
    $session->pageTextContains('Reverted Global defaults.');
    $session->pageTextContains($default_title);

    $this->drupalLogout();
  }

  /**
   * Confirm the available entity types show on the add-default page.
   */
  public function testAvailableConfigEntities() {
    // Initiate session with a user who can manage metatags.
    $permissions = [
      'administer site configuration',
      'administer meta tags',
    ];
    $account = $this->drupalCreateUser($permissions);
    $this->drupalLogin($account);

    // Load the default-add page.
    $this->drupalGet('admin/config/search/metatag/add');
    $session = $this->assertSession();
    $session->statusCodeEquals(200);

    // Confirm the 'type' field exists.
    $session->fieldExists('id');

    // Compile a list of entities from the list.
    $options = $this->cssSelect('select[name="id"] option');
    $types = [];
    foreach ($options as $option) {
      $types[$option->getAttribute('value')] = $option->getAttribute('value');
    }

    // Check through the values that are in the 'select' list, make sure that
    // unwanted items are not present.
    $this->assertArrayNotHasKey('block_content', $types, 'Custom block entities are not supported.');
    $this->assertArrayNotHasKey('comment', $types, 'Comment entities are not supported.');
    $this->assertArrayNotHasKey('menu_link_content', $types, 'Menu link entities are not supported.');
    $this->assertArrayNotHasKey('shortcut', $types, 'Shortcut entities are not supported.');
    $this->assertArrayHasKey('node__page', $types, 'Nodes are supported.');
    $this->assertArrayHasKey('user__user', $types, 'Users are supported.');
    $this->assertArrayHasKey('entity_test', $types, 'Test entities are supported.');
  }

  /**
   * Tests special pages.
   */
  public function testSpecialPages() {
    // Initiate session with a user who can manage metatags.
    $permissions = ['administer site configuration', 'administer meta tags'];
    $account = $this->drupalCreateUser($permissions);
    $this->drupalLogin($account);

    // Adjust the front page and test it.
    $this->drupalGet('admin/config/search/metatag/front');
    $session = $this->assertSession();
    $session->statusCodeEquals(200);
    $values = [
      'description' => 'Front page description',
    ];
    $this->drupalPostForm(NULL, $values, 'Save');
    $session->pageTextContains('Saved the Front page Metatag defaults.');
    $this->drupalGet('<front>');
    $session->statusCodeEquals(200);
    $session->responseContains($values['description'], $this->t('Front page defaults are used at the front page.'));

    // Adjust the 403 page and test it.
    $this->drupalGet('admin/config/search/metatag/403');
    $session->statusCodeEquals(200);
    $values = [
      'description' => '403 page description.',
    ];
    $this->drupalPostForm(NULL, $values, 'Save');
    $session->pageTextContains('Saved the 403 access denied Metatag defaults.');
    $this->drupalLogout();
    $this->drupalGet('admin/config/search/metatag');
    $session->statusCodeEquals(403);
    $session->responseContains($values['description'], $this->t('403 page defaults are used at 403 pages.'));

    // Adjust the 404 page and test it.
    $this->drupalLogin($account);
    $this->drupalGet('admin/config/search/metatag/404');
    $session->statusCodeEquals(200);
    $values = [
      'description' => '404 page description.',
    ];
    $this->drupalPostForm(NULL, $values, 'Save');
    $session->pageTextContains('Saved the 404 page not found Metatag defaults.');
    $this->drupalGet('foo');
    $session->statusCodeEquals(404);
    $session->responseContains($values['description'], $this->t('404 page defaults are used at 404 pages.'));
    $this->drupalLogout();
  }

  /**
   * Tests entity and bundle overrides.
   */
  public function testOverrides() {
    // Initiate session with a user who can manage metatags.
    $permissions = [
      'administer site configuration',
      'administer meta tags',
      'access content',
      'create article content',
      'administer nodes',
      'create article content',
      'create page content',
    ];
    $account = $this->drupalCreateUser($permissions);
    $this->drupalLogin($account);

    // Update the Metatag Node defaults.
    $this->drupalGet('admin/config/search/metatag/node');
    $session = $this->assertSession();
    $session->statusCodeEquals(200);
    $values = [
      'title' => 'Test title for a node.',
      'description' => 'Test description for a node.',
    ];
    $this->drupalPostForm(NULL, $values, 'Save');
    $session->pageTextContains('Saved the Content Metatag defaults.');

    // Create a test node.
    $node = $this->drupalCreateNode([
      'title' => $this->t('Hello, world!'),
      'type' => 'article',
    ]);

    // Check that the new values are found in the response.
    $this->drupalGet('node/' . $node->id());
    $session->statusCodeEquals(200);
    foreach ($values as $metatag => $value) {
      $session->responseContains($value, $this->t('Node metatag @tag overrides Global defaults.', ['@tag' => $metatag]));
    }

    // Check that when the node defaults don't define a metatag, the Global one
    // is used.
    // First unset node defaults.
    $this->drupalGet('admin/config/search/metatag/node');
    $session->statusCodeEquals(200);
    $values = [
      'title' => '',
      'description' => '',
    ];
    $this->drupalPostForm(NULL, $values, 'Save');
    $session->pageTextContains('Saved the Content Metatag defaults.');

    // Then, set global ones.
    $this->drupalGet('admin/config/search/metatag/global');
    $session->statusCodeEquals(200);
    $values = [
      'title' => 'Global title',
      'description' => 'Global description',
    ];
    $this->drupalPostForm(NULL, $values, 'Save');
    $session->pageTextContains('Saved the Global Metatag defaults.');

    // Next, test that global defaults are rendered since node ones are empty.
    // We are creating a new node as doing a get on the previous one would
    // return cached results.
    // @todo BookTest.php resets the cache of a single node, which is way more
    // performant than creating a node for every set of assertions.
    // @see BookTest::testDelete()
    $node = $this->drupalCreateNode([
      'title' => $this->t('Hello, world!'),
      'type' => 'article',
    ]);
    $this->drupalGet('node/' . $node->id());
    $session->statusCodeEquals(200);
    foreach ($values as $metatag => $value) {
      $session->responseContains($value, $this->t('Found global @tag tag as Node does not set it.', ['@tag' => $metatag]));
    }

    // Now create article overrides and then test them.
    $this->drupalGet('admin/config/search/metatag/add');
    $session->statusCodeEquals(200);
    $values = [
      'id' => 'node__article',
      'title' => 'Article title override',
      'description' => 'Article description override',
    ];
    $this->drupalPostForm(NULL, $values, 'Save');
    $session->pageTextContains(strip_tags('Created the Content: Article Metatag defaults.'));

    // Confirm the fields load properly on the node/add/article page.
    $node = $this->drupalCreateNode([
      'title' => $this->t('Hello, world!'),
      'type' => 'article',
    ]);
    $this->drupalGet('node/' . $node->id());
    $session->statusCodeEquals(200);
    unset($values['id']);
    foreach ($values as $metatag => $value) {
      $session->responseContains($value, $this->t('Found bundle override for tag @tag.', ['@tag' => $metatag]));
    }

    // Test deleting the article defaults.
    $this->drupalGet('admin/config/search/metatag/node__article/delete');
    $session->statusCodeEquals(200);
    $this->drupalPostForm(NULL, [], 'Delete');
    $session->pageTextContains('Deleted Content: Article defaults.');
  }

  /**
   * Test that the entity default values load on the entity form.
   *
   * And that they can then be overridden correctly.
   */
  public function testEntityDefaultInheritence() {
    // Initiate session with a user who can manage meta tags and content type
    // fields.
    $permissions = [
      'administer site configuration',
      'administer meta tags',
      'access content',
      'administer node fields',
      'create article content',
      'administer nodes',
      'create article content',
      'create page content',
    ];
    $account = $this->drupalCreateUser($permissions);
    $this->drupalLogin($account);

    // Add a Metatag field to the Article content type.
    $this->drupalGet('admin/structure/types/manage/article/fields/add-field');
    $session = $this->assertSession();
    $session->statusCodeEquals(200);
    $edit = [
      'new_storage_type' => 'metatag',
      'label' => 'Meta tags',
      'field_name' => 'meta_tags',
    ];
    $this->drupalPostForm(NULL, $edit, $this->t('Save and continue'));
    $this->drupalPostForm(NULL, [], $this->t('Save field settings'));
    $session->pageTextContains(strip_tags('Updated field Meta tags field settings.'));
    $this->drupalPostForm(NULL, [], $this->t('Save settings'));
    $session->pageTextContains(strip_tags('Saved Meta tags configuration.'));

    // Try creating an article, confirm the fields are present. This should be
    // the node default values that are shown.
    $this->drupalGet('node/add/article');
    $session->statusCodeEquals(200);
    $session->fieldValueEquals('field_meta_tags[0][basic][title]', '[node:title] | [site:name]');
    $session->fieldValueEquals('field_meta_tags[0][basic][description]', '[node:summary]');

    // Customize the Article content type defaults.
    $this->drupalGet('admin/config/search/metatag/add');
    $session->statusCodeEquals(200);
    $values = [
      'id' => 'node__article',
      'title' => 'Article title override',
      'description' => 'Article description override',
    ];
    $this->drupalPostForm(NULL, $values, 'Save');
    $session->pageTextContains(strip_tags('Created the Content: Article Metatag defaults.'));

    // Try creating an article, this time with the overridden defaults.
    $this->drupalGet('node/add/article');
    $session->statusCodeEquals(200);
    $session->fieldValueEquals('field_meta_tags[0][basic][title]', 'Article title override');
    $session->fieldValueEquals('field_meta_tags[0][basic][description]', 'Article description override');
  }

  /**
   * Test that protected Metatag defaults cannot be deleted.
   */
  public function testDefaultProtected() {
    // Initiate session with a user who can manage metatags.
    $permissions = ['administer site configuration', 'administer meta tags'];
    $account = $this->drupalCreateUser($permissions);
    $this->drupalLogin($account);
    $session = $this->assertSession();

    // Add default metatag for Articles.
    $edit = [
      'id' => 'node__article',
    ];
    $this->drupalPostForm('/admin/config/search/metatag/add', $edit, 'Save');

    // Check that protected defaults contains "Revert" link instead of "Delete".
    foreach (MetatagManager::protectedDefaults() as $protected) {
      $session->linkByHrefExists('/admin/config/search/metatag/' . $protected);
      $session->linkByHrefExists('/admin/config/search/metatag/' . $protected . '/revert');
      $session->linkByHrefNotExists('/admin/config/search/metatag/' . $protected . '/delete');
    }

    // Confirm that non protected defaults can be deleted.
    $session->linkByHrefExists('/admin/config/search/metatag/node__article');
    $session->linkByHrefNotExists('/admin/config/search/metatag/node__article/revert');
    $session->linkByHrefExists('/admin/config/search/metatag/node__article/delete');

    // Visit each protected default page to confirm "Delete" button is hidden.
    foreach (MetatagManager::protectedDefaults() as $protected) {
      $this->drupalGet('/admin/config/search/metatag/' . $protected);
      $session->linkNotExists('Delete');
    }

    // Confirm that non protected defaults can be deleted.
    $this->drupalGet('/admin/config/search/metatag/node__article');
    $session->linkExists('Delete');
  }

  /**
   * Test that metatag list page pager works as expected.
   */
  public function testListPager() {
    $this->loginUser1();
    $session = $this->assertSession();

    $this->drupalGet('admin/config/search/metatag');
    $session->statusCodeEquals(200);
    $session->linkByHrefExists('/admin/config/search/metatag/global');
    $session->linkByHrefExists('/admin/config/search/metatag/front');
    $session->linkByHrefExists('/admin/config/search/metatag/403');
    $session->linkByHrefExists('/admin/config/search/metatag/404');
    $session->linkByHrefExists('/admin/config/search/metatag/node');
    $session->linkByHrefExists('/admin/config/search/metatag/taxonomy_term');
    $session->linkByHrefExists('/admin/config/search/metatag/user');

    // Create 50 vocabularies and generate metatag defaults for all of them.
    for ($i = 0; $i < 50; $i++) {
      $vocabulary = $this->createVocabulary();
      MetatagDefaults::create([
        'id' => 'taxonomy_term__' . $vocabulary->id(),
        'label' => 'Taxonomy term: ' . $vocabulary->label(),
      ])->save();
    }

    // Reload the page.
    $this->drupalGet('admin/config/search/metatag');
    $session->linkByHrefExists('/admin/config/search/metatag/global');
    $session->linkByHrefExists('/admin/config/search/metatag/front');
    $session->linkByHrefExists('/admin/config/search/metatag/403');
    $session->linkByHrefExists('/admin/config/search/metatag/404');
    $session->linkByHrefExists('/admin/config/search/metatag/node');
    $session->linkByHrefExists('/admin/config/search/metatag/taxonomy_term');
    // User entity not visible because it has been pushed to the next page.
    $session->linkByHrefNotExists('/admin/config/search/metatag/user');
    $this->clickLink('Next');

    // Go to next page and confirm that parents are loaded and user us present.
    $session->linkByHrefExists('/admin/config/search/metatag/global');
    $session->linkByHrefExists('/admin/config/search/metatag/taxonomy_term');
    // Main links not visible in the 2nd page.
    $session->linkByHrefNotExists('/admin/config/search/metatag/front');
    $session->linkByHrefNotExists('/admin/config/search/metatag/403');
    $session->linkByHrefNotExists('/admin/config/search/metatag/404');
    $session->linkByHrefNotExists('/admin/config/search/metatag/node');
    // User is present because was pushed to page 2.
    $session->linkByHrefExists('/admin/config/search/metatag/user');
  }

}
