jsonapi.module

Same filename in other branches
  1. 8.9.x core/modules/jsonapi/jsonapi.module
  2. 10 core/modules/jsonapi/jsonapi.module
  3. 11.x core/modules/jsonapi/jsonapi.module

Module implementation file.

File

core/modules/jsonapi/jsonapi.module

View source
<?php


/**
 * @file
 * Module implementation file.
 */
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\jsonapi\Routing\Routes as JsonApiRoutes;

/**
 * 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 features like efficient response caching, which can sometimes eliminate network requests entirely. For more information, see the <a href=":docs">online documentation for the JSON:API module</a>.', [
                ':spec' => 'https://jsonapi.org',
                ':docs' => 'https://www.drupal.org/docs/8/modules/json-api',
            ]) . '</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. These conventions help developers build clients faster and encourages reuse of code.') . '</dd>';
            $output .= '<dd>' . t('The <a href=":jsonapi-docs">JSON:API</a> and <a href=":rest-docs">RESTful Web Services</a> modules serve similar purposes. <a href=":comparison">Read the comparison of the RESTFul Web Services and JSON:API modules</a> to determine the best choice for your site.', [
                ':jsonapi-docs' => 'https://www.drupal.org/docs/8/modules/json-api',
                ':rest-docs' => 'https://www.drupal.org/docs/8/core/modules/rest',
                ':comparison' => 'https://www.drupal.org/docs/8/modules/jsonapi/jsonapi-vs-cores-rest-module',
            ]) . '</dd>';
            $output .= '<dd>' . t('Some multilingual features currently do not work well with JSON:API. See the <a href=":jsonapi-docs">JSON:API multilingual support documentation</a> for more information on the current status of multilingual support.', [
                ':jsonapi-docs' => 'https://www.drupal.org/docs/8/modules/jsonapi/translations',
            ]) . '</dd>';
            $output .= '<dd>' . t('Revision support is currently read-only and only for the "Content" and "Media" entity types in JSON:API. See the <a href=":jsonapi-docs">JSON:API revision support documentation</a> for more information on the current status of revision support.', [
                ':jsonapi-docs' => 'https://www.drupal.org/docs/8/modules/jsonapi/revisions',
            ]) . '</dd>';
            $output .= '</dl>';
            return $output;
    }
    return NULL;
}

/**
 * Implements hook_modules_installed().
 */
function jsonapi_modules_installed($modules) {
    $potential_conflicts = [
        'content_translation',
        'config_translation',
        'language',
    ];
    if (!empty(array_intersect($modules, $potential_conflicts))) {
        \Drupal::messenger()->addWarning(t('Some multilingual features currently do not work well with JSON:API. See the <a href=":jsonapi-docs">JSON:API multilingual support documentation</a> for more information on the current status of multilingual support.', [
            ':jsonapi-docs' => 'https://www.drupal.org/docs/8/modules/jsonapi/translations',
        ]));
    }
}

/**
 * Implements hook_entity_bundle_create().
 */
function jsonapi_entity_bundle_create() {
    JsonApiRoutes::rebuild();
}

/**
 * Implements hook_entity_bundle_delete().
 */
function jsonapi_entity_bundle_delete() {
    JsonApiRoutes::rebuild();
}

/**
 * Implements hook_entity_create().
 */
function jsonapi_entity_create(EntityInterface $entity) {
    if (in_array($entity->getEntityTypeId(), [
        'field_storage_config',
        'field_config',
    ])) {
        // @todo: only do this when relationship fields are updated, not just any field.
        JsonApiRoutes::rebuild();
    }
}

/**
 * Implements hook_entity_delete().
 */
function jsonapi_entity_delete(EntityInterface $entity) {
    if (in_array($entity->getEntityTypeId(), [
        'field_storage_config',
        'field_config',
    ])) {
        // @todo: only do this when relationship fields are updated, not just any field.
        JsonApiRoutes::rebuild();
    }
}

/**
 * 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 '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()
    return [
        JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'view any workspace'),
        JSONAPI_FILTER_AMONG_OWN => AccessResult::allowedIfHasPermission($account, 'view own workspace'),
    ];
}

Functions

Title Deprecated Summary
jsonapi_entity_bundle_create Implements hook_entity_bundle_create().
jsonapi_entity_bundle_delete Implements hook_entity_bundle_delete().
jsonapi_entity_create Implements hook_entity_create().
jsonapi_entity_delete Implements hook_entity_delete().
jsonapi_help Implements hook_help().
jsonapi_jsonapi_block_content_filter_access Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'block_content'.
jsonapi_jsonapi_comment_filter_access Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'comment'.
jsonapi_jsonapi_entity_filter_access Implements hook_jsonapi_entity_filter_access().
jsonapi_jsonapi_entity_test_filter_access Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'entity_test'.
jsonapi_jsonapi_file_filter_access Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'file'.
jsonapi_jsonapi_media_filter_access Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'media'.
jsonapi_jsonapi_node_filter_access Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'node'.
jsonapi_jsonapi_shortcut_filter_access Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'shortcut'.
jsonapi_jsonapi_taxonomy_term_filter_access Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'taxonomy_term'.
jsonapi_jsonapi_user_filter_access Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'user'.
jsonapi_jsonapi_workspace_filter_access Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'workspace'.
jsonapi_modules_installed Implements hook_modules_installed().

Constants

Title Deprecated Summary
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.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.