You are here

jsonapi.module in JSON:API 8

Same filename and directory in other branches
  1. 8.2 jsonapi.module

Module implementation file.

File

jsonapi.module
View source
<?php

/**
 * @file
 * Module implementation file.
 */
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Session\AccountInterface;

/**
 * Array key for denoting type-based filtering access.
 *
 * Array key for denoting access to filter among all entities of a given type,
 * regardless of whether they are published or enabled, and regardless of
 * their owner.
 *
 * @see hook_jsonapi_entity_filter_access()
 * @see hook_jsonapi_ENTITY_TYPE_filter_access()
 */
const JSONAPI_FILTER_AMONG_ALL = 'filter_among_all';

/**
 * Array key for denoting type-based published-only filtering access.
 *
 * Array key for denoting access to filter among all published entities of a
 * given type, regardless of their owner.
 *
 * This is used when an entity type has a "published" entity key and there's a
 * query condition for the value of that equaling 1.
 *
 * @see hook_jsonapi_entity_filter_access()
 * @see hook_jsonapi_ENTITY_TYPE_filter_access()
 */
const JSONAPI_FILTER_AMONG_PUBLISHED = 'filter_among_published';

/**
 * Array key for denoting type-based enabled-only filtering access.
 *
 * Array key for denoting access to filter among all enabled entities of a
 * given type, regardless of their owner.
 *
 * This is used when an entity type has a "status" entity key and there's a
 * query condition for the value of that equaling 1.
 *
 * For the User entity type, which does not have a "status" entity key, the
 * "status" field is used.
 *
 * @see hook_jsonapi_entity_filter_access()
 * @see hook_jsonapi_ENTITY_TYPE_filter_access()
 */
const JSONAPI_FILTER_AMONG_ENABLED = 'filter_among_enabled';

/**
 * Array key for denoting type-based owned-only filtering access.
 *
 * Array key for denoting access to filter among all entities of a given type,
 * regardless of whether they are published or enabled, so long as they are
 * owned by the user for whom access is being checked.
 *
 * When filtering among User entities, this is used when access is being
 * checked for an authenticated user and there's a query condition
 * limiting the result set to just that user's entity object.
 *
 * When filtering among entities of another type, this is used when all of the
 * following conditions are met:
 * - Access is being checked for an authenticated user.
 * - The entity type has an "owner" entity key.
 * - There's a filter/query condition for the value equal to the user's ID.
 *
 * @see hook_jsonapi_entity_filter_access()
 * @see hook_jsonapi_ENTITY_TYPE_filter_access()
 */
const JSONAPI_FILTER_AMONG_OWN = 'filter_among_own';

/**
 * Implements hook_help().
 */
function jsonapi_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.jsonapi':
      $output = '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('The JSON API module is a fully compliant implementation of the <a href=":spec">JSON API Specification</a>. By following shared conventions, you can increase productivity, take advantage of generalized tooling, and focus on what matters: your application. Clients built around JSON API are able to take advantage of its features such as efficiently caching responses, sometimes eliminating network requests entirely. For more information, see the <a href=":docs">online documentation for the JSON API module</a>.', [
        ':spec' => 'http://jsonapi.org',
        ':docs' => 'https://www.youtube.com/playlist?list=PLZOQ_ZMpYrZsyO-3IstImK1okrpfAjuMZ',
      ]) . '</p>';
      $output .= '<dl>';
      $output .= '<dt>' . t('General') . '</dt>';
      $output .= '<dd>' . t('JSON API is a particular implementation of REST that provides conventions for resource relationships, collections, filters, pagination, and sorting, in addition to error handling and full test coverage. These conventions help developers build clients faster and encourages reuse of code.') . '</dd>';
      $output .= '</dl>';
      return $output;
  }
  return NULL;
}

/**
 * Implements hook_entity_base_field_info().
 *
 * @todo This should probably live in core, but for now we will keep it as a
 * temporary solution. There are similar unresolved efforts already happening
 * there.
 *
 * @see https://www.drupal.org/node/2825487
 */
function jsonapi_entity_base_field_info(EntityTypeInterface $entity_type) {
  $fields = [];
  if ($entity_type
    ->id() == 'file') {
    $fields['url'] = BaseFieldDefinition::create('string')
      ->setLabel(t('Download URL'))
      ->setDescription(t('The download URL of the file.'))
      ->setComputed(TRUE)
      ->setCustomStorage(TRUE)
      ->setClass('\\Drupal\\jsonapi\\Field\\FileDownloadUrl')
      ->setDisplayOptions('view', [
      'label' => 'above',
      'weight' => -5,
      'region' => 'hidden',
    ])
      ->setDisplayConfigurable('view', TRUE);
  }
  return $fields;
}

/**
 * Implements hook_entity_base_field_info().
 */
function jsonapi_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
  $fields = [];
  if (floatval(\Drupal::VERSION) < 8.6 && $entity_type
    ->id() == 'taxonomy_term') {

    // Only terms in the same bundle can be a parent.
    $fields['parent'] = clone $base_field_definitions['parent'];
    $fields['parent']
      ->setSetting('handler_settings', [
      'target_bundles' => [
        $bundle,
      ],
    ]);
    return $fields;
  }
  return $fields;
}

/**
 * Implements hook_jsonapi_entity_filter_access().
 */
function jsonapi_jsonapi_entity_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {

  // All core entity types and most or all contrib entity types allow users
  // with the entity type's administrative permission to view all of the
  // entities, so enable similarly permissive filtering to those users as well.
  // A contrib module may override this decision by returning
  // AccessResult::forbidden() from its implementation of this hook.
  if ($admin_permission = $entity_type
    ->getAdminPermission()) {
    return [
      JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, $admin_permission),
    ];
  }
}

/**
 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'aggregator_feed'.
 */
function jsonapi_jsonapi_aggregator_feed_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {

  // @see \Drupal\aggregator\FeedAccessControlHandler::checkAccess()
  return [
    JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'access news feeds'),
  ];
}

/**
 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'block_content'.
 */
function jsonapi_jsonapi_block_content_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {

  // @see \Drupal\block_content\BlockContentAccessControlHandler::checkAccess()
  // \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for
  // (isReusable()), so this does not have to.
  return [
    JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowed(),
  ];
}

/**
 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'comment'.
 */
function jsonapi_jsonapi_comment_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {

  // @see \Drupal\comment\CommentAccessControlHandler::checkAccess()
  // \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for
  // (access to the commented entity), so this does not have to.
  return [
    JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer comments'),
    JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'access comments'),
  ];
}

/**
 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'entity_test'.
 */
function jsonapi_jsonapi_entity_test_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {

  // @see \Drupal\entity_test\EntityTestAccessControlHandler::checkAccess()
  return [
    JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'view test entity'),
  ];
}

/**
 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'file'.
 */
function jsonapi_jsonapi_file_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {

  // @see \Drupal\file\FileAccessControlHandler::checkAccess()
  // \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for
  // (public OR owner), so this does not have to.
  return [
    JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'access content'),
  ];
}

/**
 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'media'.
 */
function jsonapi_jsonapi_media_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {

  // @see \Drupal\media\MediaAccessControlHandler::checkAccess()
  return [
    JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'view media'),
  ];
}

/**
 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'node'.
 */
function jsonapi_jsonapi_node_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {

  // @see \Drupal\node\NodeAccessControlHandler::access()
  if ($account
    ->hasPermission('bypass node access')) {
    return [
      JSONAPI_FILTER_AMONG_ALL => AccessResult::allowed()
        ->cachePerPermissions(),
    ];
  }
  if (!$account
    ->hasPermission('access content')) {
    $forbidden = AccessResult::forbidden("The 'access content' permission is required.")
      ->cachePerPermissions();
    return [
      JSONAPI_FILTER_AMONG_ALL => $forbidden,
      JSONAPI_FILTER_AMONG_OWN => $forbidden,
      JSONAPI_FILTER_AMONG_PUBLISHED => $forbidden,
      // For legacy reasons, the Node entity type has a "status" key, so forbid
      // this subset as well, even though it has no semantic meaning.
      JSONAPI_FILTER_AMONG_ENABLED => $forbidden,
    ];
  }
  return [
    // @see \Drupal\node\NodeAccessControlHandler::checkAccess()
    JSONAPI_FILTER_AMONG_OWN => AccessResult::allowedIfHasPermission($account, 'view own unpublished content'),
    // @see \Drupal\node\NodeGrantDatabaseStorage::access()
    // Note that:
    // - This is just for the default grant. Other node access conditions are
    //   added via the 'node_access' query tag.
    // - Permissions were checked earlier in this function, so we must vary the
    //   cache by them.
    JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowed()
      ->cachePerPermissions(),
  ];
}

/**
 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'shortcut'.
 */
function jsonapi_jsonapi_shortcut_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {

  // @see \Drupal\shortcut\ShortcutAccessControlHandler::checkAccess()
  // \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for
  // (shortcut_set = shortcut_current_displayed_set()), so this does not have
  // to.
  return [
    JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer shortcuts')
      ->orIf(AccessResult::allowedIfHasPermissions($account, [
      'access shortcuts',
      'customize shortcut links',
    ])),
  ];
}

/**
 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'taxonomy_term'.
 */
function jsonapi_jsonapi_taxonomy_term_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {

  // @see \Drupal\taxonomy\TermAccessControlHandler::checkAccess()
  return [
    JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer taxonomy'),
    JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'access content'),
  ];
}

/**
 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'user'.
 */
function jsonapi_jsonapi_user_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {

  // @see \Drupal\user\UserAccessControlHandler::checkAccess()
  // \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for
  // (!isAnonymous()), so this does not have to.
  return [
    JSONAPI_FILTER_AMONG_OWN => AccessResult::allowed(),
    JSONAPI_FILTER_AMONG_ENABLED => AccessResult::allowedIfHasPermission($account, 'access user profiles'),
  ];
}

/**
 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'workspace'.
 */
function jsonapi_jsonapi_workspace_filter_access(EntityTypeInterface $entity_type, $published, $owner, AccountInterface $account) {

  // @see \Drupal\workspaces\WorkspaceAccessControlHandler::checkAccess()
  // \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for
  // (isDefaultWorkspace()), so this does not have to.
  return [
    JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'view any workspace'),
    JSONAPI_FILTER_AMONG_OWN => AccessResult::allowedIfHasPermission($account, 'view own workspace'),
  ];
}

Functions

Constants

Namesort descending Description
JSONAPI_FILTER_AMONG_ALL Array key for denoting type-based filtering access.
JSONAPI_FILTER_AMONG_ENABLED Array key for denoting type-based enabled-only filtering access.
JSONAPI_FILTER_AMONG_OWN Array key for denoting type-based owned-only filtering access.
JSONAPI_FILTER_AMONG_PUBLISHED Array key for denoting type-based published-only filtering access.