You are here

NodeTypeAccessService.php in Nodetype access 8

File

src/NodeTypeAccessService.php
View source
<?php

namespace Drupal\nodetype_access;

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\node\NodeInterface;
class NodeTypeAccessService {

  /**
   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
   */
  protected $entityTypeBundleInfo;

  /**
   * NodetypeAccessService constructor.
   *
   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entityTypeBundleInfo
   */
  public function __construct(EntityTypeBundleInfoInterface $entityTypeBundleInfo) {
    $this->entityTypeBundleInfo = $entityTypeBundleInfo;
  }

  /**
   * Get permission ID for bundle
   */
  protected function makeViewAnyPermissionId($bundleId) {
    return "view any {$bundleId} content";
  }

  /**
   * Permissions callback.
   *
   * Defines a 'view any $type content' permission.
   */
  public function permissions() {
    $permissions = array();
    foreach ($this->entityTypeBundleInfo
      ->getBundleInfo('node') as $bundleId => $info) {
      $permissions[$this
        ->makeViewAnyPermissionId($bundleId)] = [
        'title' => t('View published %bundle_label content', array(
          '%bundle_label' => $info['label'],
        )),
      ];
    }
    return $permissions;
  }
  protected function permittedBundleIds(AccountInterface $account) {
    $bundleIds = [];
    foreach ($this->entityTypeBundleInfo
      ->getAllBundleInfo()['node'] as $bundleId => $info) {
      if ($account
        ->hasPermission($this
        ->makeViewAnyPermissionId($bundleId))) {
        $bundleIds[$bundleId] = $bundleId;
      }
    }
    return $bundleIds;
  }

  /**
   * Implements hook_node_access().
   *
   * Why does this use forbiddenIf()? The main controller uses allowedIf() so
   * if we want an AND conjunction we must use the stronger forbiddenIf().
   * (Which also means: If we want to OR this result with something else like
   * OG access, we can not use independent node access hooks, but somehow must
   * consolidate both results first.)
   *
   * @see \Drupal\Core\Entity\EntityAccessControlHandler::access
   * @see \Drupal\Core\Entity\EntityAccessControlHandler::checkAccess
   * @see \Drupal\node\NodeAccessControlHandler::access
   * @see \Drupal\node\NodeAccessControlHandler::checkAccess
   * @see \Drupal\node\NodeGrantDatabaseStorage::access
   *
   * @param \Drupal\node\NodeInterface $node
   * @param string $op
   * @param \Drupal\Core\Session\AccountInterface $account
   *
   * @return \Drupal\Core\Access\AccessResultInterface
   */
  public function hookNodeAccess(NodeInterface $node, $op, AccountInterface $account) {
    if ($op === 'view') {
      $bundleIsPermitted = in_array($node
        ->bundle(), $this
        ->permittedBundleIds($account));
      return AccessResult::forbiddenIf(!$bundleIsPermitted)
        ->cachePerPermissions();
    }
  }
  public function hookQueryNodeAccessAlter(AlterableInterface $query) {
    $account = $query
      ->getMetaData('account') ?? \Drupal::currentUser();
    $op = $query
      ->getMetaData('op') ?? 'view';
    if ($query instanceof SelectInterface && $op === 'view') {
      $nodeTableAlias = $this
        ->extractBaseTableAlias($query);

      // Bail out if the base table is missing.
      if (!$nodeTableAlias) {
        throw new \Exception('Query tagged for node access but there is no node table, specify the base_table using meta data.');
      }
      $permittedBundleIds = $this
        ->permittedBundleIds($account);
      if ($permittedBundleIds) {
        $query
          ->condition("{$nodeTableAlias}.type", $permittedBundleIds, 'IN');
      }
      else {
        $query
          ->alwaysFalse();
      }
    }
  }

  /**
   * Extract base table alias.
   *
   * @param \Drupal\Core\Database\Query\AlterableInterface $query
   *
   * @return string|null
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Exception
   */
  private function extractBaseTableAlias(AlterableInterface $query) {

    // Copied from @see \node_query_node_access_alter
    $tables = $query
      ->getTables();
    $base_table = $query
      ->getMetaData('base_table');

    // If the base table is not given, default to one of the node base tables.
    if (!$base_table) {

      /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
      $table_mapping = \Drupal::entityTypeManager()
        ->getStorage('node')
        ->getTableMapping();
      $node_base_tables = $table_mapping
        ->getTableNames();
      foreach ($tables as $table_info) {
        if (!$table_info instanceof SelectInterface) {
          $table = $table_info['table'];

          // Ensure that 'node' and 'node_field_data' are always preferred over
          // 'node_revision' and 'node_field_revision'.
          if ($table == 'node' || $table == 'node_field_data') {
            $base_table = $table;
            break;
          }

          // If one of the node base tables are in the query, add it to the list
          // of possible base tables to join against.
          if (in_array($table, $node_base_tables)) {
            $base_table = $table;
          }
        }
      }
    }
    if (isset($base_table)) {

      // Copied from @see \Drupal\node\NodeGrantDatabaseStorage::alterQuery
      foreach ($tables as $table_alias => $tableinfo) {
        $table = $tableinfo['table'];
        if (!$table instanceof SelectInterface && $table == $base_table) {
          return $table_alias;
        }
      }
    }
  }

}

Classes