annotate core/modules/jsonapi/jsonapi.module @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@18 1 <?php
Chris@18 2
Chris@18 3 /**
Chris@18 4 * @file
Chris@18 5 * Module implementation file.
Chris@18 6 */
Chris@18 7
Chris@18 8 use Drupal\Core\Access\AccessResult;
Chris@18 9 use Drupal\Core\Routing\RouteMatchInterface;
Chris@18 10 use Drupal\Core\Entity\EntityInterface;
Chris@18 11 use Drupal\Core\Entity\EntityTypeInterface;
Chris@18 12 use Drupal\Core\Session\AccountInterface;
Chris@18 13 use Drupal\jsonapi\Routing\Routes as JsonApiRoutes;
Chris@18 14
Chris@18 15 /**
Chris@18 16 * Array key for denoting type-based filtering access.
Chris@18 17 *
Chris@18 18 * Array key for denoting access to filter among all entities of a given type,
Chris@18 19 * regardless of whether they are published or enabled, and regardless of
Chris@18 20 * their owner.
Chris@18 21 *
Chris@18 22 * @see hook_jsonapi_entity_filter_access()
Chris@18 23 * @see hook_jsonapi_ENTITY_TYPE_filter_access()
Chris@18 24 */
Chris@18 25 const JSONAPI_FILTER_AMONG_ALL = 'filter_among_all';
Chris@18 26
Chris@18 27 /**
Chris@18 28 * Array key for denoting type-based published-only filtering access.
Chris@18 29 *
Chris@18 30 * Array key for denoting access to filter among all published entities of a
Chris@18 31 * given type, regardless of their owner.
Chris@18 32 *
Chris@18 33 * This is used when an entity type has a "published" entity key and there's a
Chris@18 34 * query condition for the value of that equaling 1.
Chris@18 35 *
Chris@18 36 * @see hook_jsonapi_entity_filter_access()
Chris@18 37 * @see hook_jsonapi_ENTITY_TYPE_filter_access()
Chris@18 38 */
Chris@18 39 const JSONAPI_FILTER_AMONG_PUBLISHED = 'filter_among_published';
Chris@18 40
Chris@18 41 /**
Chris@18 42 * Array key for denoting type-based enabled-only filtering access.
Chris@18 43 *
Chris@18 44 * Array key for denoting access to filter among all enabled entities of a
Chris@18 45 * given type, regardless of their owner.
Chris@18 46 *
Chris@18 47 * This is used when an entity type has a "status" entity key and there's a
Chris@18 48 * query condition for the value of that equaling 1.
Chris@18 49 *
Chris@18 50 * For the User entity type, which does not have a "status" entity key, the
Chris@18 51 * "status" field is used.
Chris@18 52 *
Chris@18 53 * @see hook_jsonapi_entity_filter_access()
Chris@18 54 * @see hook_jsonapi_ENTITY_TYPE_filter_access()
Chris@18 55 */
Chris@18 56 const JSONAPI_FILTER_AMONG_ENABLED = 'filter_among_enabled';
Chris@18 57
Chris@18 58 /**
Chris@18 59 * Array key for denoting type-based owned-only filtering access.
Chris@18 60 *
Chris@18 61 * Array key for denoting access to filter among all entities of a given type,
Chris@18 62 * regardless of whether they are published or enabled, so long as they are
Chris@18 63 * owned by the user for whom access is being checked.
Chris@18 64 *
Chris@18 65 * When filtering among User entities, this is used when access is being
Chris@18 66 * checked for an authenticated user and there's a query condition
Chris@18 67 * limiting the result set to just that user's entity object.
Chris@18 68 *
Chris@18 69 * When filtering among entities of another type, this is used when all of the
Chris@18 70 * following conditions are met:
Chris@18 71 * - Access is being checked for an authenticated user.
Chris@18 72 * - The entity type has an "owner" entity key.
Chris@18 73 * - There's a filter/query condition for the value equal to the user's ID.
Chris@18 74 *
Chris@18 75 * @see hook_jsonapi_entity_filter_access()
Chris@18 76 * @see hook_jsonapi_ENTITY_TYPE_filter_access()
Chris@18 77 */
Chris@18 78 const JSONAPI_FILTER_AMONG_OWN = 'filter_among_own';
Chris@18 79
Chris@18 80 /**
Chris@18 81 * Implements hook_help().
Chris@18 82 */
Chris@18 83 function jsonapi_help($route_name, RouteMatchInterface $route_match) {
Chris@18 84 switch ($route_name) {
Chris@18 85 case 'help.page.jsonapi':
Chris@18 86 $output = '<h3>' . t('About') . '</h3>';
Chris@18 87 $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>.', [
Chris@18 88 ':spec' => 'https://jsonapi.org',
Chris@18 89 ':docs' => 'https://www.drupal.org/docs/8/modules/json-api',
Chris@18 90 ]) . '</p>';
Chris@18 91 $output .= '<dl>';
Chris@18 92 $output .= '<dt>' . t('General') . '</dt>';
Chris@18 93 $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>';
Chris@18 94 $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.', [
Chris@18 95 ':jsonapi-docs' => 'https://www.drupal.org/docs/8/modules/json-api',
Chris@18 96 ':rest-docs' => 'https://www.drupal.org/docs/8/core/modules/rest',
Chris@18 97 ':comparison' => 'https://www.drupal.org/docs/8/modules/jsonapi/jsonapi-vs-cores-rest-module',
Chris@18 98 ]) . '</dd>';
Chris@18 99 $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.', [
Chris@18 100 ':jsonapi-docs' => 'https://www.drupal.org/docs/8/modules/jsonapi/translations',
Chris@18 101 ]) . '</dd>';
Chris@18 102 $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.', [
Chris@18 103 ':jsonapi-docs' => 'https://www.drupal.org/docs/8/modules/jsonapi/revisions',
Chris@18 104 ]) . '</dd>';
Chris@18 105 $output .= '</dl>';
Chris@18 106
Chris@18 107 return $output;
Chris@18 108 }
Chris@18 109 return NULL;
Chris@18 110 }
Chris@18 111
Chris@18 112 /**
Chris@18 113 * Implements hook_modules_installed().
Chris@18 114 */
Chris@18 115 function jsonapi_modules_installed($modules) {
Chris@18 116 $potential_conflicts = [
Chris@18 117 'content_translation',
Chris@18 118 'config_translation',
Chris@18 119 'language',
Chris@18 120 ];
Chris@18 121 if (!empty(array_intersect($modules, $potential_conflicts))) {
Chris@18 122 \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.', [
Chris@18 123 ':jsonapi-docs' => 'https://www.drupal.org/docs/8/modules/jsonapi/translations',
Chris@18 124 ]));
Chris@18 125 }
Chris@18 126 }
Chris@18 127
Chris@18 128 /**
Chris@18 129 * Implements hook_entity_bundle_create().
Chris@18 130 */
Chris@18 131 function jsonapi_entity_bundle_create() {
Chris@18 132 JsonApiRoutes::rebuild();
Chris@18 133 }
Chris@18 134
Chris@18 135 /**
Chris@18 136 * Implements hook_entity_bundle_delete().
Chris@18 137 */
Chris@18 138 function jsonapi_entity_bundle_delete() {
Chris@18 139 JsonApiRoutes::rebuild();
Chris@18 140 }
Chris@18 141
Chris@18 142 /**
Chris@18 143 * Implements hook_entity_create().
Chris@18 144 */
Chris@18 145 function jsonapi_entity_create(EntityInterface $entity) {
Chris@18 146 if (in_array($entity->getEntityTypeId(), ['field_storage_config', 'field_config'])) {
Chris@18 147 // @todo: only do this when relationship fields are updated, not just any field.
Chris@18 148 JsonApiRoutes::rebuild();
Chris@18 149 }
Chris@18 150 }
Chris@18 151
Chris@18 152 /**
Chris@18 153 * Implements hook_entity_delete().
Chris@18 154 */
Chris@18 155 function jsonapi_entity_delete(EntityInterface $entity) {
Chris@18 156 if (in_array($entity->getEntityTypeId(), ['field_storage_config', 'field_config'])) {
Chris@18 157 // @todo: only do this when relationship fields are updated, not just any field.
Chris@18 158 JsonApiRoutes::rebuild();
Chris@18 159 }
Chris@18 160 }
Chris@18 161
Chris@18 162 /**
Chris@18 163 * Implements hook_jsonapi_entity_filter_access().
Chris@18 164 */
Chris@18 165 function jsonapi_jsonapi_entity_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {
Chris@18 166 // All core entity types and most or all contrib entity types allow users
Chris@18 167 // with the entity type's administrative permission to view all of the
Chris@18 168 // entities, so enable similarly permissive filtering to those users as well.
Chris@18 169 // A contrib module may override this decision by returning
Chris@18 170 // AccessResult::forbidden() from its implementation of this hook.
Chris@18 171 if ($admin_permission = $entity_type->getAdminPermission()) {
Chris@18 172 return ([
Chris@18 173 JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, $admin_permission),
Chris@18 174 ]);
Chris@18 175 }
Chris@18 176 }
Chris@18 177
Chris@18 178 /**
Chris@18 179 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'aggregator_feed'.
Chris@18 180 */
Chris@18 181 function jsonapi_jsonapi_aggregator_feed_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {
Chris@18 182 // @see \Drupal\aggregator\FeedAccessControlHandler::checkAccess()
Chris@18 183 return ([
Chris@18 184 JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'access news feeds'),
Chris@18 185 ]);
Chris@18 186 }
Chris@18 187
Chris@18 188 /**
Chris@18 189 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'block_content'.
Chris@18 190 */
Chris@18 191 function jsonapi_jsonapi_block_content_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {
Chris@18 192 // @see \Drupal\block_content\BlockContentAccessControlHandler::checkAccess()
Chris@18 193 // \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for
Chris@18 194 // (isReusable()), so this does not have to.
Chris@18 195 return ([
Chris@18 196 JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowed(),
Chris@18 197 ]);
Chris@18 198 }
Chris@18 199
Chris@18 200 /**
Chris@18 201 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'comment'.
Chris@18 202 */
Chris@18 203 function jsonapi_jsonapi_comment_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {
Chris@18 204 // @see \Drupal\comment\CommentAccessControlHandler::checkAccess()
Chris@18 205 // \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for
Chris@18 206 // (access to the commented entity), so this does not have to.
Chris@18 207 return ([
Chris@18 208 JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer comments'),
Chris@18 209 JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'access comments'),
Chris@18 210 ]);
Chris@18 211 }
Chris@18 212
Chris@18 213 /**
Chris@18 214 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'entity_test'.
Chris@18 215 */
Chris@18 216 function jsonapi_jsonapi_entity_test_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {
Chris@18 217 // @see \Drupal\entity_test\EntityTestAccessControlHandler::checkAccess()
Chris@18 218 return ([
Chris@18 219 JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'view test entity'),
Chris@18 220 ]);
Chris@18 221 }
Chris@18 222
Chris@18 223 /**
Chris@18 224 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'file'.
Chris@18 225 */
Chris@18 226 function jsonapi_jsonapi_file_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {
Chris@18 227 // @see \Drupal\file\FileAccessControlHandler::checkAccess()
Chris@18 228 // \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for
Chris@18 229 // (public OR owner), so this does not have to.
Chris@18 230 return ([
Chris@18 231 JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'access content'),
Chris@18 232 ]);
Chris@18 233 }
Chris@18 234
Chris@18 235 /**
Chris@18 236 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'media'.
Chris@18 237 */
Chris@18 238 function jsonapi_jsonapi_media_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {
Chris@18 239 // @see \Drupal\media\MediaAccessControlHandler::checkAccess()
Chris@18 240 return ([
Chris@18 241 JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'view media'),
Chris@18 242 ]);
Chris@18 243 }
Chris@18 244
Chris@18 245 /**
Chris@18 246 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'node'.
Chris@18 247 */
Chris@18 248 function jsonapi_jsonapi_node_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {
Chris@18 249 // @see \Drupal\node\NodeAccessControlHandler::access()
Chris@18 250 if ($account->hasPermission('bypass node access')) {
Chris@18 251 return ([
Chris@18 252 JSONAPI_FILTER_AMONG_ALL => AccessResult::allowed()->cachePerPermissions(),
Chris@18 253 ]);
Chris@18 254 }
Chris@18 255 if (!$account->hasPermission('access content')) {
Chris@18 256 $forbidden = AccessResult::forbidden("The 'access content' permission is required.")->cachePerPermissions();
Chris@18 257 return ([
Chris@18 258 JSONAPI_FILTER_AMONG_ALL => $forbidden,
Chris@18 259 JSONAPI_FILTER_AMONG_OWN => $forbidden,
Chris@18 260 JSONAPI_FILTER_AMONG_PUBLISHED => $forbidden,
Chris@18 261 // For legacy reasons, the Node entity type has a "status" key, so forbid
Chris@18 262 // this subset as well, even though it has no semantic meaning.
Chris@18 263 JSONAPI_FILTER_AMONG_ENABLED => $forbidden,
Chris@18 264 ]);
Chris@18 265 }
Chris@18 266
Chris@18 267 return ([
Chris@18 268 // @see \Drupal\node\NodeAccessControlHandler::checkAccess()
Chris@18 269 JSONAPI_FILTER_AMONG_OWN => AccessResult::allowedIfHasPermission($account, 'view own unpublished content'),
Chris@18 270
Chris@18 271 // @see \Drupal\node\NodeGrantDatabaseStorage::access()
Chris@18 272 // Note that:
Chris@18 273 // - This is just for the default grant. Other node access conditions are
Chris@18 274 // added via the 'node_access' query tag.
Chris@18 275 // - Permissions were checked earlier in this function, so we must vary the
Chris@18 276 // cache by them.
Chris@18 277 JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowed()->cachePerPermissions(),
Chris@18 278 ]);
Chris@18 279 }
Chris@18 280
Chris@18 281 /**
Chris@18 282 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'shortcut'.
Chris@18 283 */
Chris@18 284 function jsonapi_jsonapi_shortcut_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {
Chris@18 285 // @see \Drupal\shortcut\ShortcutAccessControlHandler::checkAccess()
Chris@18 286 // \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for
Chris@18 287 // (shortcut_set = shortcut_current_displayed_set()), so this does not have
Chris@18 288 // to.
Chris@18 289 return ([
Chris@18 290 JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer shortcuts')
Chris@18 291 ->orIf(AccessResult::allowedIfHasPermissions($account, ['access shortcuts', 'customize shortcut links'])),
Chris@18 292 ]);
Chris@18 293 }
Chris@18 294
Chris@18 295 /**
Chris@18 296 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'taxonomy_term'.
Chris@18 297 */
Chris@18 298 function jsonapi_jsonapi_taxonomy_term_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {
Chris@18 299 // @see \Drupal\taxonomy\TermAccessControlHandler::checkAccess()
Chris@18 300 return ([
Chris@18 301 JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'administer taxonomy'),
Chris@18 302 JSONAPI_FILTER_AMONG_PUBLISHED => AccessResult::allowedIfHasPermission($account, 'access content'),
Chris@18 303 ]);
Chris@18 304 }
Chris@18 305
Chris@18 306 /**
Chris@18 307 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'user'.
Chris@18 308 */
Chris@18 309 function jsonapi_jsonapi_user_filter_access(EntityTypeInterface $entity_type, AccountInterface $account) {
Chris@18 310 // @see \Drupal\user\UserAccessControlHandler::checkAccess()
Chris@18 311 // \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for
Chris@18 312 // (!isAnonymous()), so this does not have to.
Chris@18 313 return ([
Chris@18 314 JSONAPI_FILTER_AMONG_OWN => AccessResult::allowed(),
Chris@18 315 JSONAPI_FILTER_AMONG_ENABLED => AccessResult::allowedIfHasPermission($account, 'access user profiles'),
Chris@18 316 ]);
Chris@18 317 }
Chris@18 318
Chris@18 319 /**
Chris@18 320 * Implements hook_jsonapi_ENTITY_TYPE_filter_access() for 'workspace'.
Chris@18 321 */
Chris@18 322 function jsonapi_jsonapi_workspace_filter_access(EntityTypeInterface $entity_type, $published, $owner, AccountInterface $account) {
Chris@18 323 // @see \Drupal\workspaces\WorkspaceAccessControlHandler::checkAccess()
Chris@18 324 // \Drupal\jsonapi\Access\TemporaryQueryGuard adds the condition for
Chris@18 325 // (isDefaultWorkspace()), so this does not have to.
Chris@18 326 return ([
Chris@18 327 JSONAPI_FILTER_AMONG_ALL => AccessResult::allowedIfHasPermission($account, 'view any workspace'),
Chris@18 328 JSONAPI_FILTER_AMONG_OWN => AccessResult::allowedIfHasPermission($account, 'view own workspace'),
Chris@18 329 ]);
Chris@18 330 }