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 }
|