<?php

namespace Drupal\Tests\entity\Kernel\QueryAccess;

use Drupal\entity_module_test\Entity\EnhancedEntityWithOwner;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\views\Tests\ViewResultAssertionTrait;
use Drupal\views\Views;

/**
 * Test uncacheable query access filtering for EntityQuery and Views.
 *
 * @group entity
 *
 * @see \Drupal\entity\QueryAccess\UncacheableQueryAccessHandler
 * @see \Drupal\entity\QueryAccess\EntityQueryAlter
 * @see \Drupal\entity\QueryAccess\ViewsQueryAlter
 */
class UncacheableQueryAccessTest extends EntityKernelTestBase {

  use ViewResultAssertionTrait;

  /**
   * The test entities.
   *
   * @var \Drupal\Core\Entity\ContentEntityInterface[]
   */
  protected $entities;

  /**
   * The entity_test_enhanced storage.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface
   */
  protected $storage;

  /**
   * {@inheritdoc}
   */
  public static $modules = [
    'entity',
    'entity_module_test',
    'user',
    'views',
    'system',
  ];

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

    $this->installEntitySchema('entity_test_enhanced_with_owner');
    $this->installConfig(['entity_module_test']);

    // Create uid: 1 here so that it's skipped in test cases.
    $admin_user = $this->createUser();

    $first_entity = EnhancedEntityWithOwner::create([
      'type' => 'first',
      'name' => 'First',
      'status' => 1,
    ]);
    $first_entity->save();

    $first_entity->set('name', 'First!');
    $first_entity->set('status', 0);
    $first_entity->setNewRevision(TRUE);
    $first_entity->save();

    $second_entity = EnhancedEntityWithOwner::create([
      'type' => 'first',
      'name' => 'Second',
      'status' => 0,
    ]);
    $second_entity->save();

    $second_entity->set('name', 'Second!');
    $second_entity->set('status', 1);
    $second_entity->setNewRevision(TRUE);
    $second_entity->save();

    $third_entity = EnhancedEntityWithOwner::create([
      'type' => 'second',
      'name' => 'Third',
      'status' => 1,
    ]);
    $third_entity->save();

    $third_entity->set('name', 'Third!');
    $third_entity->setNewRevision(TRUE);
    $third_entity->save();

    $this->entities = [$first_entity, $second_entity, $third_entity];
    $this->storage = \Drupal::entityTypeManager()->getStorage('entity_test_enhanced_with_owner');
  }

  /**
   * Tests EntityQuery filtering.
   */
  public function testEntityQuery() {
    // Admin permission, full access.
    $admin_user = $this->createUser([], ['administer entity_test_enhanced_with_owner']);
    \Drupal::currentUser()->setAccount($admin_user);

    $result = $this->storage->getQuery()->sort('id')->execute();
    $this->assertEquals([
      $this->entities[0]->id(),
      $this->entities[1]->id(),
      $this->entities[2]->id(),
    ], array_values($result));

    // No view permissions, no access.
    $user = $this->createUser([], ['access content']);
    \Drupal::currentUser()->setAccount($user);

    $result = $this->storage->getQuery()->execute();
    $this->assertEmpty($result);

    // View own (published-only).
    $user = $this->createUser([], ['view own entity_test_enhanced_with_owner']);
    \Drupal::currentUser()->setAccount($user);

    $this->entities[0]->set('user_id', $user->id());
    $this->entities[0]->save();
    $this->entities[1]->set('user_id', $user->id());
    $this->entities[1]->save();

    $result = $this->storage->getQuery()->sort('id')->execute();
    $this->assertEquals([
      $this->entities[1]->id(),
    ], array_values($result));

    // View any (published-only).
    $user = $this->createUser([], ['view any entity_test_enhanced_with_owner']);
    \Drupal::currentUser()->setAccount($user);

    $result = $this->storage->getQuery()->sort('id')->execute();
    $this->assertEquals([
      $this->entities[1]->id(),
      $this->entities[2]->id(),
    ], array_values($result));

    // View own unpublished.
    $user = $this->createUser([], ['view own unpublished entity_test_enhanced_with_owner']);
    \Drupal::currentUser()->setAccount($user);

    $this->entities[0]->set('user_id', $user->id());
    $this->entities[0]->save();
    $this->entities[1]->set('user_id', $user->id());
    $this->entities[1]->save();

    $result = $this->storage->getQuery()->sort('id')->execute();
    $this->assertEquals([
      $this->entities[0]->id(),
    ], array_values($result));

    // View own unpublished + view any (published-only).
    $user = $this->createUser([], [
      'view own unpublished entity_test_enhanced_with_owner',
      'view any entity_test_enhanced_with_owner',
    ]);
    \Drupal::currentUser()->setAccount($user);

    $this->entities[0]->set('user_id', $user->id());
    $this->entities[0]->save();

    $result = $this->storage->getQuery()->sort('id')->execute();
    $this->assertEquals([
      $this->entities[0]->id(),
      $this->entities[1]->id(),
      $this->entities[2]->id(),
    ], array_values($result));

    // View own $first_bundle + View any $second_bundle.
    $user = $this->createUser([], [
      'view own first entity_test_enhanced_with_owner',
      'view any second entity_test_enhanced_with_owner',
    ]);
    \Drupal::currentUser()->setAccount($user);

    $this->entities[1]->set('user_id', $user->id());
    $this->entities[1]->save();

    $result = $this->storage->getQuery()->sort('id')->execute();
    $this->assertEquals([
      $this->entities[1]->id(),
      $this->entities[2]->id(),
    ], array_values($result));
  }

  /**
   * Tests EntityQuery filtering when all revisions are queried.
   */
  public function testEntityQueryWithRevisions() {
    // Admin permission, full access.
    $admin_user = $this->createUser([], ['administer entity_test_enhanced_with_owner']);
    \Drupal::currentUser()->setAccount($admin_user);

    $result = $this->storage->getQuery()->allRevisions()->sort('id')->execute();
    $this->assertEquals([
      '1' => $this->entities[0]->id(),
      '2' => $this->entities[0]->id(),
      '3' => $this->entities[1]->id(),
      '4' => $this->entities[1]->id(),
      '5' => $this->entities[2]->id(),
      '6' => $this->entities[2]->id(),
    ], $result);

    // No view permissions, no access.
    $user = $this->createUser([], ['access content']);
    \Drupal::currentUser()->setAccount($user);

    $result = $this->storage->getQuery()->allRevisions()->execute();
    $this->assertEmpty($result);

    // View own (published-only).
    $user = $this->createUser([], ['view own entity_test_enhanced_with_owner']);
    \Drupal::currentUser()->setAccount($user);

    // The user_id field is not revisionable, which means that updating it
    // will modify both revisions for each entity.
    $this->entities[0]->set('user_id', $user->id());
    $this->entities[0]->save();
    $this->entities[1]->set('user_id', $user->id());
    $this->entities[1]->save();

    $result = $this->storage->getQuery()->allRevisions()->sort('id')->execute();
    $this->assertEquals([
      '1' => $this->entities[0]->id(),
      '4' => $this->entities[1]->id(),
    ], $result);

    // View any (published-only).
    $user = $this->createUser([], ['view any entity_test_enhanced_with_owner']);
    \Drupal::currentUser()->setAccount($user);

    $result = $this->storage->getQuery()->allRevisions()->sort('id')->execute();
    $this->assertEquals([
      '1' => $this->entities[0]->id(),
      '4' => $this->entities[1]->id(),
      '5' => $this->entities[2]->id(),
      '6' => $this->entities[2]->id(),
    ], $result);

    // View own unpublished.
    $user = $this->createUser([], ['view own unpublished entity_test_enhanced_with_owner']);
    \Drupal::currentUser()->setAccount($user);

    $this->entities[0]->set('user_id', $user->id());
    $this->entities[0]->save();
    $this->entities[1]->set('user_id', $user->id());
    $this->entities[1]->save();

    $result = $this->storage->getQuery()->allRevisions()->sort('id')->execute();
    $this->assertEquals([
      '2' => $this->entities[0]->id(),
      '3' => $this->entities[1]->id(),
    ], $result);

    // View own unpublished + view any (published-only).
    $user = $this->createUser([], [
      'view own unpublished entity_test_enhanced_with_owner',
      'view any entity_test_enhanced_with_owner',
    ]);
    \Drupal::currentUser()->setAccount($user);

    $this->entities[0]->set('user_id', $user->id());
    $this->entities[0]->save();

    $result = $this->storage->getQuery()->allRevisions()->sort('id')->execute();
    $this->assertEquals([
      '1' => $this->entities[0]->id(),
      '2' => $this->entities[0]->id(),
      '4' => $this->entities[1]->id(),
      '5' => $this->entities[2]->id(),
      '6' => $this->entities[2]->id(),
    ], $result);

    // View own $first_bundle + View any $second_bundle.
    $user = $this->createUser([], [
      'view own first entity_test_enhanced_with_owner',
      'view any second entity_test_enhanced_with_owner',
    ]);
    \Drupal::currentUser()->setAccount($user);

    $this->entities[1]->set('user_id', $user->id());
    $this->entities[1]->save();

    $result = $this->storage->getQuery()->allRevisions()->sort('id')->execute();
    $this->assertEquals([
      '4' => $this->entities[1]->id(),
      '5' => $this->entities[2]->id(),
      '6' => $this->entities[2]->id(),
    ], $result);
  }

  /**
   * Tests Views filtering.
   */
  public function testViews() {
    // Admin permission, full access.
    $admin_user = $this->createUser([], ['administer entity_test_enhanced_with_owner']);
    \Drupal::currentUser()->setAccount($admin_user);

    $view = Views::getView('entity_test_enhanced_with_owner');
    $view->execute();
    $this->assertIdenticalResultset($view, [
      ['id' => $this->entities[0]->id()],
      ['id' => $this->entities[1]->id()],
      ['id' => $this->entities[2]->id()],
    ], ['id' => 'id']);

    // No view permissions, no access.
    $user = $this->createUser([], ['access content']);
    \Drupal::currentUser()->setAccount($user);

    $view = Views::getView('entity_test_enhanced_with_owner');
    $view->execute();
    $this->assertIdenticalResultset($view, []);

    // View own (published-only).
    $user = $this->createUser([], ['view own entity_test_enhanced_with_owner']);
    \Drupal::currentUser()->setAccount($user);

    $this->entities[0]->set('user_id', $user->id());
    $this->entities[0]->save();
    $this->entities[1]->set('user_id', $user->id());
    $this->entities[1]->save();

    $view = Views::getView('entity_test_enhanced_with_owner');
    $view->execute();
    $this->assertIdenticalResultset($view, [
      ['id' => $this->entities[1]->id()],
    ], ['id' => 'id']);

    // View any (published-only).
    $user = $this->createUser([], ['view any entity_test_enhanced_with_owner']);
    \Drupal::currentUser()->setAccount($user);

    $view = Views::getView('entity_test_enhanced_with_owner');
    $view->execute();
    $this->assertIdenticalResultset($view, [
      ['id' => $this->entities[1]->id()],
      ['id' => $this->entities[2]->id()],
    ], ['id' => 'id']);

    // View own unpublished.
    $user = $this->createUser([], ['view own unpublished entity_test_enhanced_with_owner']);
    \Drupal::currentUser()->setAccount($user);

    $this->entities[0]->set('user_id', $user->id());
    $this->entities[0]->save();
    $this->entities[1]->set('user_id', $user->id());
    $this->entities[1]->save();

    $view = Views::getView('entity_test_enhanced_with_owner');
    $view->execute();
    $this->assertIdenticalResultset($view, [
      ['id' => $this->entities[0]->id()],
    ], ['id' => 'id']);

    // View own unpublished + view any (published-only).
    $user = $this->createUser([], [
      'view own unpublished entity_test_enhanced_with_owner',
      'view any entity_test_enhanced_with_owner',
    ]);
    \Drupal::currentUser()->setAccount($user);

    $this->entities[0]->set('user_id', $user->id());
    $this->entities[0]->save();

    $view = Views::getView('entity_test_enhanced_with_owner');
    $view->execute();
    $this->assertIdenticalResultset($view, [
      ['id' => $this->entities[0]->id()],
      ['id' => $this->entities[1]->id()],
      ['id' => $this->entities[2]->id()],
    ], ['id' => 'id']);

    // View own $first_bundle + View any $second_bundle.
    $user = $this->createUser([], [
      'view own first entity_test_enhanced_with_owner',
      'view any second entity_test_enhanced_with_owner',
    ]);
    \Drupal::currentUser()->setAccount($user);

    $this->entities[1]->set('user_id', $user->id());
    $this->entities[1]->save();

    $view = Views::getView('entity_test_enhanced_with_owner');
    $view->execute();
    $this->assertIdenticalResultset($view, [
      ['id' => $this->entities[1]->id()],
      ['id' => $this->entities[2]->id()],
    ], ['id' => 'id']);
  }

  /**
   * Tests Views filtering when all revisions are queried.
   */
  public function testViewsWithRevisions() {
    // Admin permission, full access.
    $admin_user = $this->createUser([], ['administer entity_test_enhanced_with_owner']);
    \Drupal::currentUser()->setAccount($admin_user);

    $view = Views::getView('entity_test_enhanced_with_owner_revisions');
    $view->execute();
    $this->assertIdenticalResultset($view, [
      ['vid' => '1', 'id' => $this->entities[0]->id()],
      ['vid' => '2', 'id' => $this->entities[0]->id()],
      ['vid' => '3', 'id' => $this->entities[1]->id()],
      ['vid' => '4', 'id' => $this->entities[1]->id()],
      ['vid' => '5', 'id' => $this->entities[2]->id()],
      ['vid' => '6', 'id' => $this->entities[2]->id()],
    ], ['vid' => 'vid']);

    // No view permissions, no access.
    $user = $this->createUser([], ['access content']);
    \Drupal::currentUser()->setAccount($user);

    $view = Views::getView('entity_test_enhanced_with_owner_revisions');
    $view->execute();
    $this->assertIdenticalResultset($view, []);

    // View own (published-only).
    $user = $this->createUser([], ['view own entity_test_enhanced_with_owner']);
    \Drupal::currentUser()->setAccount($user);

    $this->entities[0]->set('user_id', $user->id());
    $this->entities[0]->save();
    $this->entities[1]->set('user_id', $user->id());
    $this->entities[1]->save();

    $view = Views::getView('entity_test_enhanced_with_owner_revisions');
    $view->execute();
    $this->assertIdenticalResultset($view, [
      ['vid' => '1', 'id' => $this->entities[0]->id()],
      ['vid' => '4', 'id' => $this->entities[1]->id()],
    ], ['vid' => 'vid']);

    // View any (published-only).
    $user = $this->createUser([], ['view any entity_test_enhanced_with_owner']);
    \Drupal::currentUser()->setAccount($user);

    $view = Views::getView('entity_test_enhanced_with_owner_revisions');
    $view->execute();
    $this->assertIdenticalResultset($view, [
      ['vid' => '1', 'id' => $this->entities[0]->id()],
      ['vid' => '4', 'id' => $this->entities[1]->id()],
      ['vid' => '5', 'id' => $this->entities[2]->id()],
      ['vid' => '6', 'id' => $this->entities[2]->id()],
    ], ['vid' => 'vid']);

    // View own unpublished.
    $user = $this->createUser([], ['view own unpublished entity_test_enhanced_with_owner']);
    \Drupal::currentUser()->setAccount($user);

    $this->entities[0]->set('user_id', $user->id());
    $this->entities[0]->save();
    $this->entities[1]->set('user_id', $user->id());
    $this->entities[1]->save();

    $view = Views::getView('entity_test_enhanced_with_owner_revisions');
    $view->execute();
    $this->assertIdenticalResultset($view, [
      ['vid' => '2', 'id' => $this->entities[0]->id()],
      ['vid' => '3', 'id' => $this->entities[1]->id()],
    ], ['vid' => 'vid']);

    // View own unpublished + view any (published-only).
    $user = $this->createUser([], [
      'view own unpublished entity_test_enhanced_with_owner',
      'view any entity_test_enhanced_with_owner',
    ]);
    \Drupal::currentUser()->setAccount($user);

    $this->entities[0]->set('user_id', $user->id());
    $this->entities[0]->save();

    $view = Views::getView('entity_test_enhanced_with_owner_revisions');
    $view->execute();
    $this->assertIdenticalResultset($view, [
      ['vid' => '1', 'id' => $this->entities[0]->id()],
      ['vid' => '2', 'id' => $this->entities[0]->id()],
      ['vid' => '4', 'id' => $this->entities[1]->id()],
      ['vid' => '5', 'id' => $this->entities[2]->id()],
      ['vid' => '6', 'id' => $this->entities[2]->id()],
    ], ['vid' => 'vid']);

    // View own $first_bundle + View any $second_bundle.
    $user = $this->createUser([], [
      'view own first entity_test_enhanced_with_owner',
      'view any second entity_test_enhanced_with_owner',
    ]);
    \Drupal::currentUser()->setAccount($user);

    $this->entities[1]->set('user_id', $user->id());
    $this->entities[1]->save();

    $view = Views::getView('entity_test_enhanced_with_owner_revisions');
    $view->execute();
    $this->assertIdenticalResultset($view, [
      ['vid' => '4', 'id' => $this->entities[1]->id()],
      ['vid' => '5', 'id' => $this->entities[2]->id()],
      ['vid' => '6', 'id' => $this->entities[2]->id()],
    ], ['vid' => 'vid']);
  }

}
