PluginProbe ʕ •ᴥ•ʔ
Pods – Custom Content Types and Fields / 3.3.4
Pods – Custom Content Types and Fields v3.3.4
trunk 1.14.8 2.7.31.3 2.8.23.3 2.9.19.3 3.0.10.3 3.1.4.1 3.2.0 3.2.1 3.2.1.1 3.2.2 3.2.4 3.2.5 3.2.6 3.2.7 3.2.7.1 3.2.8 3.2.8.1 3.2.8.2 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 3.3.9
pods / includes / access.php
pods / includes Last commit date
compatibility 6 years ago access.php 9 months ago classes.php 2 years ago compatibility.php 2 years ago data.php 11 months ago forms.php 4 years ago general.php 11 months ago media.php 4 years ago
access.php
2381 lines
1 <?php
2 /**
3 * @package Pods\Global\Functions\Access
4 */
5
6 use Pods\Whatsit\Pod;
7
8 /**
9 * Normalize Pod information with a Pods object or object info.
10 *
11 * @since 3.1.0
12 *
13 * @param array $args {
14 * The arguments to use.
15 *
16 * @type string|null $object_type The object type.
17 * @type string|null $object_name The object name.
18 * @type int|string|null $item_id The item ID.
19 * @type Pods|null $pods The Pods object.
20 * @type Pod|null $pod The Pod object.
21 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
22 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
23 * }
24 *
25 * @return array {
26 * The arguments to use.
27 *
28 * @type string|null $object_type The object type (if set).
29 * @type string|null $object_name The object name (if set).
30 * @type int|string|null $item_id The item ID (if set).
31 * @type Pods|null $pods The Pods object (if built or provided).
32 * @type Pod|null $pod The Pod object (if built or provided).
33 * }
34 */
35 function pods_info_from_args( array $args ): array {
36 $info = [
37 'object_type' => null,
38 'object_name' => null,
39 'item_id' => null,
40 'pods' => null,
41 'pod' => null,
42 ];
43
44 $build_pods = false;
45 $build_pod = false;
46
47 if ( isset( $args['build_pods'] ) ) {
48 $build_pods = $args['build_pods'];
49
50 unset( $args['build_pods'] );
51 }
52
53 if ( isset( $args['build_pod'] ) ) {
54 $build_pod = $args['build_pod'];
55
56 unset( $args['build_pod'] );
57 }
58
59 // Merge in the args with the defaults.
60 $info = array_merge( $info, $args );
61
62 $object_type_set = null !== $info['object_type'];
63 $object_name_set = null !== $info['object_name'];
64
65 // Maybe auto-set the object name from the type if we can.
66 if (
67 $object_type_set
68 && ! $object_name_set
69 && in_array( $info['object_type'], [ 'comment', 'media', 'user' ], true )
70 ) {
71 $info['object_name'] = $info['object_type'];
72
73 $object_name_set = true;
74 }
75
76 // Normalize the Pods info to null if it's not valid.
77 if (
78 $info['pods'] instanceof Pods
79 && ! $info['pods']->is_valid()
80 ) {
81 $info['pods'] = null;
82 }
83
84 // Maybe build the Pods object from the info.
85 if (
86 $build_pods
87 && $object_name_set
88 && ! $info['pods'] instanceof Pods
89 ) {
90 $pods = pods_get_instance( $info['object_name'], $info['item_id'], true );
91
92 if (
93 $pods instanceof Pods
94 && $pods->is_valid()
95 && (
96 empty( $info['object_type'] )
97 || $info['object_type'] === $pods->pod_data->get_type()
98 )
99 ) {
100 $info['pods'] = $pods;
101
102 if ( ! $info['pod'] instanceof Pod ) {
103 $info['pod'] = clone $pods->pod_data;
104 }
105 }
106 } elseif (
107 $info['pods'] instanceof Pods
108 && $info['pods']->is_valid()
109 && ! $info['pod'] instanceof Pod
110 ) {
111 $info['pod'] = clone $info['pods']->pod_data;
112 }
113
114 // Maybe build the Pod object from the info.
115 if (
116 $build_pod
117 && $object_name_set
118 && ! $info['pod'] instanceof Pod
119 ) {
120 try {
121 $pod = pods_api()->load_pod( [
122 'name' => $info['object_name'],
123 ] );
124 } catch ( Exception $e ) {
125 $pod = null;
126 }
127
128 if (
129 $pod instanceof Pod
130 && (
131 empty( $info['object_type'] )
132 || $info['object_type'] === $pod->get_type()
133 )
134 ) {
135 $info['pod'] = $pod;
136 }
137 }
138
139 if ( $info['pod'] instanceof Pod ) {
140 $info['object_type'] = $info['pod']->get_type();
141 $info['object_name'] = $info['pod']->get_name();
142 }
143
144 return $info;
145 }
146
147 /**
148 * Determine whether the current user has access to an object.
149 *
150 * @since 3.1.0
151 *
152 * @param array $args {
153 * The arguments to use.
154 *
155 * @type string|null $object_type The object type.
156 * @type string|null $object_name The object name.
157 * @type int|string|null $item_id The item ID.
158 * @type Pods|null $pods The Pods object.
159 * @type Pod|null $pod The Pod object.
160 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
161 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
162 * }
163 * @param int|null $user_id The user ID to check against, set to 0 or null for anonymous access check.
164 * @param string $access_type The type of access to check for (read, add, edit, delete).
165 * @param string|null $context The unique slug that can be referenced by hooks for context.
166 *
167 * @return bool Whether the current user has access to an object.
168 */
169 function pods_user_can_access_object( array $args, ?int $user_id, string $access_type = 'edit', ?string $context = null ): bool {
170 $info = pods_info_from_args( $args );
171
172 if ( null === $user_id ) {
173 $user_id = 0;
174 }
175
176 // Check if the user exists.
177 $user = get_userdata( $user_id );
178
179 if ( ! $user instanceof WP_User ) {
180 // If the user does not exist and it was not anonymous, do not allow access to an invalid user.
181 if ( 0 < $user_id ) {
182 return false;
183 }
184
185 // If the user was 0 to begin with (anonymous) then set up a user object to work with.
186 $user = new WP_User();
187 }
188
189 // Determine if this is a user in WP that has full access.
190 if ( $user_id && pods_is_user_admin( $user_id ) ) {
191 return true;
192 }
193
194 if ( 'pod' === $info['object_type'] || 'table' === $info['object_type'] ) {
195 // If no object name is provided, we cannot check access.
196 if ( empty( $info['object_name'] ) ) {
197 return false;
198 }
199
200 // Determine if this user has full content access.
201 if ( $user->has_cap('pods_content' ) ) {
202 return true;
203 }
204 }
205
206 $capabilities = pods_access_map_capabilities( $info, $user_id );
207
208 // Unsupported capabilities returned.
209 if ( null === $capabilities ) {
210 return false;
211 }
212
213 /**
214 * Allow filtering the list of capabilities used for checking access against an object.
215 *
216 * @since 3.1.0
217 *
218 * @param array $capabilities The list of capabilities used for checking access against an object.
219 * @param int $user_id The user ID to check against.
220 * @param array $info {
221 * The normalized Pod information referenced.
222 *
223 * @type string|null $object_type The object type (if set).
224 * @type string|null $object_name The object name (if set).
225 * @type int|string|null $item_id The item ID (if set).
226 * @type Pods|null $pods The Pods object (if built or provided).
227 * @type Pod|null $pod The Pod object (if built or provided).
228 * }
229 * @param string $access_type The type of access to check for (read, add, edit, delete).
230 * @param string|null $context The unique slug that can be referenced by hooks for context.
231 */
232 $capabilities = (array) apply_filters(
233 'pods_user_can_access_object_get_capabilities',
234 $capabilities,
235 $user_id,
236 $info,
237 $access_type,
238 $context
239 );
240
241 // No capability mapped, do not allow access.
242 if ( ! array_key_exists( $access_type, $capabilities ) ) {
243 return false;
244 }
245
246 /**
247 * Allow filtering whether a user has access to an object before the normal capability check runs.
248 *
249 * @since 3.1.0
250 *
251 * @param null|bool $can_access Whether a user has access to an object (return null to run normal check).
252 * @param int $user_id The user ID to check against.
253 * @param array $info {
254 * The normalized Pod information referenced.
255 *
256 * @type string|null $object_type The object type (if set).
257 * @type string|null $object_name The object name (if set).
258 * @type int|string|null $item_id The item ID (if set).
259 * @type Pods|null $pods The Pods object (if built or provided).
260 * @type Pod|null $pod The Pod object (if built or provided).
261 * }
262 * @param string $access_type The type of access to check for (read, add, edit, delete).
263 * @param string|null $context The unique slug that can be referenced by hooks for context.
264 * @param array $capabilities The list of capabilities used for checking access against an object.
265 */
266 $can_access = apply_filters(
267 'pods_user_can_access_object_pre_check',
268 null,
269 $user_id,
270 $info,
271 $access_type,
272 $context,
273 $capabilities
274 );
275
276 // Check for access override and return that instead.
277 if ( null !== $can_access ) {
278 return $can_access;
279 }
280
281 // If we are allowing all access, null will be set for the capability.
282 if ( null === $capabilities[ $access_type ] ) {
283 $can_access = true;
284 } else {
285 // Support multiple capability checks ("OR" logic).
286 $capabilities[ $access_type ] = (array) $capabilities[ $access_type ];
287
288 $can_access = false;
289
290 foreach ( $capabilities[ $access_type ] as $capability ) {
291 if ( $info['item_id'] ) {
292 $can_access = $user->has_cap( $capability, $info['item_id'] );
293 } else {
294 $can_access = $user->has_cap( $capability );
295 }
296
297 if ( $can_access ) {
298 break;
299 }
300 }
301 }
302
303 $is_read_access = 'read' === $access_type;
304
305 // Check for password-protected post.
306 if (
307 $can_access
308 && 'post_type' === $info['object_type']
309 && $info['item_id']
310 && (
311 (
312 $is_read_access
313 && pods_access_bypass_post_with_password( $info )
314 )
315 || (
316 ! $is_read_access
317 && post_password_required( $info['item_id'] )
318 )
319 )
320 ) {
321 $can_access = false;
322 }
323
324 /**
325 * Allow filtering whether a user has access to an object after the normal capability check runs.
326 *
327 * @since 3.1.0
328 *
329 * @param bool $can_access Whether a user has access to an object.
330 * @param int $user_id The user ID to check against.
331 * @param array $info {
332 * The normalized Pod information referenced.
333 *
334 * @type string|null $object_type The object type (if set).
335 * @type string|null $object_name The object name (if set).
336 * @type int|string|null $item_id The item ID (if set).
337 * @type Pods|null $pods The Pods object (if built or provided).
338 * @type Pod|null $pod The Pod object (if built or provided).
339 * }
340 * @param string $access_type The type of access to check for (read, add, edit, delete).
341 * @param string|null $context The unique slug that can be referenced by hooks for context.
342 * @param array $capabilities The list of capabilities used for checking access against an object.
343 */
344 return (bool) apply_filters(
345 'pods_user_can_access_object',
346 $can_access,
347 $user_id,
348 $info,
349 $access_type,
350 $context,
351 $capabilities
352 );
353 }
354
355 /**
356 * Determine whether the current user has access to an object.
357 *
358 * @since 3.1.0
359 *
360 * @param array $args {
361 * The arguments to use.
362 *
363 * @type string|null $object_type The object type.
364 * @type string|null $object_name The object name.
365 * @type int|string|null $item_id The item ID.
366 * @type Pods|null $pods The Pods object.
367 * @type Pod|null $pod The Pod object.
368 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
369 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
370 * }
371 * @param string $access_type The type of access to check for (read, add, edit, delete).
372 * @param string|null $context The unique slug that can be referenced by hooks for context.
373 *
374 * @return bool Whether the current user has access to an object.
375 */
376 function pods_current_user_can_access_object( array $args, string $access_type = 'edit', ?string $context = null ): bool {
377 $user_id = null;
378
379 if ( is_user_logged_in() ) {
380 $user_id = get_current_user_id();
381 }
382
383 return pods_user_can_access_object( $args, $user_id, $access_type, $context );
384 }
385
386 /**
387 * Build and map the capabilities that a specific object type/name/ID have in relation to a user ID.
388 *
389 * @since 3.1.0
390 *
391 * @param array $args {
392 * The arguments to use.
393 *
394 * @type string|null $object_type The object type.
395 * @type string|null $object_name The object name.
396 * @type int|string|null $item_id The item ID.
397 * @type Pods|null $pods The Pods object.
398 * @type Pod|null $pod The Pod object.
399 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
400 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
401 * }
402 * @param int|null $user_id The user ID accessing the object.
403 * @param bool $strict Whether to strictly get the capabilities or have the 'read' capability evaluate to null if it's public (defaults to false).
404 *
405 * @return array|null The capabilities that a specific object type/name/ID have in relation to a user ID, or null if invalid.
406 */
407 function pods_access_map_capabilities( array $args, ?int $user_id = null, bool $strict = false ): ?array {
408 $args['build_pods'] = true;
409 $args['build_pod'] = true;
410
411 $info = pods_info_from_args( $args );
412
413 // If no object type or name, we cannot check access.
414 if ( empty( $info['object_type'] ) || empty( $info['object_name'] ) ) {
415 return null;
416 }
417
418 $wp_object = null;
419
420 $capabilities = [];
421
422 if ( 'post_type' === $info['object_type'] ) {
423 $info['item_id'] = (int) $info['item_id'];
424
425 if ( $info['item_id'] ) {
426 $capabilities['read'] = 'read_post';
427 $capabilities['edit'] = 'edit_post';
428 $capabilities['delete'] = 'delete_post';
429 } else {
430 $capabilities['read'] = 'read';
431 $capabilities['edit'] = 'edit_posts';
432 $capabilities['delete'] = 'delete_posts';
433 }
434
435 $capabilities['add'] = 'create_posts';
436 $capabilities['read_private'] = 'read_private_posts';
437 $capabilities['edit_others'] = 'edit_others_posts';
438 $capabilities['delete_others'] = 'delete_others_posts';
439 $capabilities['delete_published'] = 'delete_published_posts';
440 $capabilities['delete_private'] = 'delete_private_posts';
441
442 // Maybe map capabilities to the post type.
443 $wp_object = get_post_type_object( $info['object_name'] );
444
445 if ( $info['item_id'] ) {
446 $post = get_post( $info['item_id'] );
447
448 // If the post was found, do fine-grained access checks.
449 if ( $post instanceof WP_Post ) {
450 $status_obj = get_post_status_object( $post->post_status );
451
452 // Check if the person is allowed to read other posts.
453 if (
454 $user_id
455 && $post->post_author
456 && (int) $user_id === (int) $post->post_author
457 ) {
458 // This is their own post, they can have access.
459 $capabilities['read'] = 'read';
460 } elseif (
461 ! $status_obj
462 || $status_obj->private
463 ) {
464 // This is a private post, check private post capability.
465 $capabilities['read'] = $capabilities['read_private'];
466 }
467 }
468 }
469 } elseif ( 'taxonomy' === $info['object_type'] ) {
470 $info['item_id'] = (int) $info['item_id'];
471
472 $capabilities['read'] = 'read';
473 $capabilities['add'] = 'manage_terms';
474 $capabilities['edit'] = 'edit_terms';
475 $capabilities['delete'] = 'delete_terms';
476
477 // Maybe map capabilities to the post type.
478 $wp_object = get_taxonomy( $info['object_name'] );
479 } elseif ( 'user' === $info['object_type'] ) {
480 $info['item_id'] = (int) $info['item_id'];
481
482 $capabilities['read'] = 'list_users';
483 $capabilities['add'] = 'create_users';
484 $capabilities['edit'] = 'edit_users';
485 $capabilities['delete'] = 'delete_users';
486
487 // If an object ID is provided, check for access for that specific user.
488 if ( ! empty( $info['item_id'] ) ) {
489 $capabilities['edit'] = 'edit_user';
490 $capabilities['delete'] = 'delete_user';
491 }
492
493 // Fake the WP object for the logic below.
494 $wp_object = (object) [
495 'public' => false,
496 'cap' => (object) [],
497 ];
498 } elseif ( 'media' === $info['object_type'] ) {
499 $info['item_id'] = (int) $info['item_id'];
500
501 $capabilities['read'] = 'read';
502 $capabilities['add'] = 'upload_files';
503 $capabilities['edit'] = 'upload_files';
504 $capabilities['delete'] = 'upload_files';
505
506 // Fake the WP object for the logic below.
507 $wp_object = (object) [
508 'public' => false,
509 'cap' => (object) [],
510 ];
511 } elseif ( 'comment' === $info['object_type'] ) {
512 $info['item_id'] = (int) $info['item_id'];
513
514 $capabilities['read'] = 'read';
515 $capabilities['add'] = 1 === (int) get_option( 'comment_registration' ) ? 'read' : null;
516 $capabilities['edit'] = 'moderate_comments';
517 $capabilities['delete'] = 'moderate_comments';
518
519 // If an object ID is provided, check for access for that specific user.
520 if ( ! empty( $info['item_id'] ) ) {
521 $capabilities['edit'] = 'edit_comment';
522 }
523
524 // Fake the WP object for the logic below.
525 $wp_object = (object) [
526 'public' => true,
527 'cap' => (object) [],
528 ];
529 } elseif ( 'settings' === $info['object_type'] ) {
530 $capabilities['read'] = 'manage_options';
531 $capabilities['edit'] = 'pods_edit_' . $info['object_name'];
532 $capabilities['delete'] = 'manage_options';
533
534 // Fake the WP object for the logic below.
535 $wp_object = (object) [
536 'public' => false,
537 'cap' => (object) [],
538 ];
539 } elseif ( 'pod' === $info['object_type'] || 'table' === $info['object_type'] ) {
540 $info['item_id'] = (int) $info['item_id'];
541
542 $capabilities['read'] = 'pods_read_' . $info['object_name'];
543 $capabilities['add'] = 'pods_add_' . $info['object_name'];
544 $capabilities['edit'] = 'pods_edit_' . $info['object_name'];
545 $capabilities['delete'] = 'pods_delete_' . $info['object_name'];
546 $capabilities['edit_others'] = 'pods_edit_others_' . $info['object_name'];
547 $capabilities['delete_others'] = 'pods_delete_others_' . $info['object_name'];
548
549 $is_public = false;
550
551 if ( $info['pods'] instanceof Pods && $info['pod'] instanceof Pod ) {
552 // If an object ID is provided, check for access for that specific item.
553 if ( $info['item_id'] && $info['pods']->exists() ) {
554 // Check for author field.
555 $author_field = $info['pod']->get_field( 'author' );
556
557 $author_user_id = $author_field ? (int) $info['pods']->field( $author_field->get_name() . '.ID' ) : null;
558
559 // If we have an author field, check if they are the author.
560 if ( $author_field ) {
561 if ( $user_id && $author_user_id === $user_id ) {
562 // This is their own post, they can also have access if have edit access.
563 $capabilities['read'] = [
564 $capabilities['read'],
565 'pods_edit_' . $info['object_name'],
566 ];
567 } else {
568 // This is not their post, check if they have access to others.
569 $capabilities['edit'] = 'pods_edit_others_' . $info['object_name'];
570 $capabilities['delete'] = 'pods_delete_others_' . $info['object_name'];
571 }
572 }
573 }
574
575 $is_public = $info['pod']->get_arg( 'public', '0', true );
576 $is_public = filter_var( $is_public, FILTER_VALIDATE_BOOLEAN );
577
578 // Fake the WP object for the logic below.
579 $wp_object = (object) [
580 'public' => $is_public,
581 'cap' => (object) [],
582 ];
583 }
584
585 if ( $is_public ) {
586 $capabilities['read'] = 'read';
587 }
588 }
589
590 // If no post type object is found, we cannot check access.
591 if ( ! $wp_object ) {
592 return null;
593 }
594
595 // Check if there are any capabilities mapped for this type object.
596 foreach ( $capabilities as $access_type => $capability ) {
597 if ( $capability ) {
598 if ( is_array( $capability ) ) {
599 foreach ( $capability as $k => $cap ) {
600 if ( isset( $wp_object->cap->{$cap} ) ) {
601 $capabilities[ $access_type ][ $k ] = $wp_object->cap->{$cap};
602 }
603 }
604 } elseif ( isset( $wp_object->cap->{$capability} ) ) {
605 $capabilities[ $access_type ] = $wp_object->cap->{$capability};
606 }
607 }
608 }
609
610 // If the object is public, allow read for anyone even logged out.
611 if ( ! $strict && $wp_object->public && 'read' === $capabilities['read'] && ! $user_id ) {
612 $capabilities['read'] = null;
613 }
614
615 /**
616 * Allow filtering the list of capabilities used for checking access against an object type or singular object.
617 *
618 * @since 3.1.0
619 *
620 * @param array $capabilities The list of capabilities used for checking access against an object type or singular object.
621 * @param int $user_id The user ID to check against.
622 * @param array $info {
623 * The normalized Pod information referenced.
624 *
625 * @type string|null $object_type The object type (if set).
626 * @type string|null $object_name The object name (if set).
627 * @type int|string|null $item_id The item ID (if set).
628 * @type Pods|null $pods The Pods object (if built or provided).
629 * @type Pod|null $pod The Pod object (if built or provided).
630 * }
631 */
632 return (array) apply_filters(
633 'pods_access_map_capabilities',
634 $capabilities,
635 $user_id,
636 $info
637 );
638 }
639
640 /**
641 * Determine whether the object type/name is public.
642 *
643 * @since 3.1.0
644 *
645 * @param array $args {
646 * The arguments to use.
647 *
648 * @type string|null $object_type The object type.
649 * @type string|null $object_name The object name.
650 * @type int|string|null $item_id The item ID.
651 * @type Pods|null $pods The Pods object.
652 * @type Pod|null $pod The Pod object.
653 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
654 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
655 * }
656 * @param string $context The context we are checking from (defaults to shortcode).
657 *
658 * @return bool Whether the object type/name is public.
659 */
660 function pods_is_type_public( array $args, string $context = 'shortcode' ): bool {
661 $args['build_pod'] = true;
662
663 $info = pods_info_from_args( $args );
664
665 $is_public = true;
666
667 $pod_has_public = null;
668
669 $is_post_type = 'post_type' === $info['object_type'];
670 $is_taxonomy = 'taxonomy' === $info['object_type'];
671 $is_pod = 'pod' === $info['object_type'];
672 $is_settings_pod = 'settings' === $info['object_type'];
673
674 $is_shortcode_context = 'shortcode' === $context;
675
676 if (
677 $info['pod'] instanceof Pod
678 && (
679 $is_post_type
680 || $is_taxonomy
681 || $is_pod
682 || $is_settings_pod
683 )
684 ) {
685 $is_extended = $info['pod']->is_extended();
686
687 if ( ! $is_extended ) {
688 $is_public = $info['pod']->get_arg( 'public', null, true );
689
690 if ( null !== $is_public ) {
691 $pod_has_public = true;
692
693 $is_public = filter_var( $is_public, FILTER_VALIDATE_BOOLEAN );
694
695 if ( $is_post_type || $is_taxonomy ) {
696 $is_public = $is_public && 1 === (int) $info['pod']->get_arg( 'publicly_queryable', $is_public, true );
697 }
698 }
699 }
700 }
701
702 // Maybe handle looking up the visibility based on the object type.
703 if ( null === $pod_has_public ) {
704 if ( $is_post_type ) {
705 // If no object name is provided, we cannot check if it is public.
706 if ( empty( $info['object_name'] ) ) {
707 $is_public = false;
708 } else {
709 $post_type_object = get_post_type_object( $info['object_name'] );
710
711 // Post type not found.
712 if ( ! $post_type_object ) {
713 $is_public = false;
714 } else {
715 $is_public = $post_type_object->public && $post_type_object->publicly_queryable;
716 }
717 }
718 } elseif ( $is_taxonomy ) {
719 // If no object name is provided, we cannot check if it is public.
720 if ( empty( $info['object_name'] ) ) {
721 $is_public = false;
722 } else {
723 $taxonomy_object = get_taxonomy( $info['object_name'] );
724
725 // Post type not found.
726 if ( ! $taxonomy_object ) {
727 $is_public = false;
728 } else {
729 $is_public = $taxonomy_object->public && $taxonomy_object->publicly_queryable;
730 }
731 }
732 } elseif ( 'user' === $info['object_type'] ) {
733 // Users are not public for shortcodes.
734 if ( $is_shortcode_context ) {
735 $is_public = false;
736 }
737 } elseif ( $is_pod || $is_settings_pod ) {
738 // Pods need special default handling for shortcodes.
739 if ( $is_shortcode_context ) {
740 $first_pods_version = get_option( 'pods_framework_version_first' );
741 $first_pods_version = '' === $first_pods_version ? PODS_VERSION : $first_pods_version;
742
743 $is_public = version_compare( $first_pods_version, '3.1.0-a-1', '<' ) ? true : false;
744 }
745 }
746 }
747
748 /**
749 * Allow filtering whether the object type/name is public.
750 *
751 * @since 3.1.0
752 *
753 * @param bool $is_public Whether the object type/name is public.
754 * @param array $info {
755 * The normalized Pod information referenced.
756 *
757 * @type string|null $object_type The object type (if set).
758 * @type string|null $object_name The object name (if set).
759 * @type int|string|null $item_id The item ID (if set).
760 * @type Pods|null $pods The Pods object (if built or provided).
761 * @type Pod|null $pod The Pod object (if built or provided).
762 * }
763 * @param string|null $context The context we are checking from (shortcode or null).
764 */
765 return (bool) apply_filters(
766 'pods_is_type_public',
767 $is_public,
768 $info,
769 $context
770 );
771 }
772
773 /**
774 * Determine whether a post should be bypassed because it it has a password.
775 *
776 * @since 3.1.0
777 *
778 * @param array $args {
779 * The arguments to use.
780 *
781 * @type string|null $object_type The object type.
782 * @type string|null $object_name The object name.
783 * @type int|string|null $item_id The item ID.
784 * @type Pods|null $pods The Pods object.
785 * @type Pod|null $pod The Pod object.
786 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
787 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
788 * }
789 *
790 * @return bool Whether a post should be bypassed because it it has a password.
791 */
792 function pods_access_bypass_post_with_password( array $args ): bool {
793 $info = pods_info_from_args( $args );
794
795 if ( 'post_type' !== $info['object_type'] || ! $info['item_id'] ) {
796 return false;
797 }
798
799 $post = get_post( (int) $info['item_id'] );
800
801 if ( ! $post instanceof WP_Post ) {
802 return false;
803 }
804
805 // Bypass posts that have a password required but not provided.
806 $bypass_post_with_password = post_password_required( $post );
807
808 /**
809 * Allow filtering whether a post should be bypassed because it it has a password.
810 *
811 * @since 3.1.0
812 *
813 * @param bool $bypass_post_with_password Whether a post should be bypassed because it it has a password.
814 * @param array $info {
815 * The normalized Pod information referenced.
816 *
817 * @type string|null $object_type The object type (if set).
818 * @type string|null $object_name The object name (if set).
819 * @type int|string|null $item_id The item ID (if set).
820 * @type Pods|null $pods The Pods object (if built or provided).
821 * @type Pod|null $pod The Pod object (if built or provided).
822 * }
823 */
824 return (bool) apply_filters(
825 'pods_access_bypass_post_with_password',
826 $bypass_post_with_password,
827 $info
828 );
829 }
830
831 /**
832 * Determine whether a post should be bypassed because it is private and capabilities are not met.
833 *
834 * @since 3.1.0
835 *
836 * @param array $args {
837 * The arguments to use.
838 *
839 * @type string|null $object_type The object type.
840 * @type string|null $object_name The object name.
841 * @type int|string|null $item_id The item ID.
842 * @type Pods|null $pods The Pods object.
843 * @type Pod|null $pod The Pod object.
844 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
845 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
846 * }
847 *
848 * @return bool Whether a post should be bypassed because it is private and capabilities are not met.
849 */
850 function pods_access_bypass_private_post( array $args ): bool {
851 $info = pods_info_from_args( $args );
852
853 if ( 'post_type' !== $info['object_type'] || ! $info['item_id'] ) {
854 return false;
855 }
856
857 $post = get_post( $info['item_id'] );
858
859 if ( ! $post instanceof WP_Post ) {
860 return false;
861 }
862
863 $bypass_private_post = false;
864
865 if ( ! is_post_publicly_viewable( $post ) ) {
866 $can_use_unrestricted = false;
867
868 // Check Pod dynamic features if the status is public.
869 if ( is_post_status_viewable( $post->post_status ) ) {
870 $can_use_unrestricted = pods_can_use_dynamic_feature_unrestricted( $info, 'display', 'read' );
871 }
872
873 if ( $can_use_unrestricted ) {
874 $bypass_private_post = false;
875 } else {
876 $bypass_private_post = ! pods_current_user_can_access_object( $info, 'read' );
877 }
878 }
879
880 /**
881 * Allow filtering whether a post should be bypassed because it is private.
882 *
883 * @since 3.1.0
884 *
885 * @param bool $bypass_private_post Whether a post should be bypassed because it is private.
886 * @param array $info {
887 * The normalized Pod information referenced.
888 *
889 * @type string|null $object_type The object type (if set).
890 * @type string|null $object_name The object name (if set).
891 * @type int|string|null $item_id The item ID (if set).
892 * @type Pods|null $pods The Pods object (if built or provided).
893 * @type Pod|null $pod The Pod object (if built or provided).
894 * }
895 */
896 return (bool) apply_filters(
897 'pods_access_bypass_private_post',
898 $bypass_private_post,
899 $info
900 );
901 }
902
903 /**
904 * Determine whether dynamic features can be used.
905 *
906 * @since 3.1.0
907 *
908 * @return bool Whether dynamic features can be used.
909 */
910 function pods_can_use_dynamic_features( ?Pod $pod = null ): bool {
911 // Check if the constant is defined and only override if no $pod is set or dynamic features are totally disabled.
912 if (
913 defined( 'PODS_DYNAMIC_FEATURES_ALLOW' )
914 && (
915 ! $pod
916 || ! PODS_DYNAMIC_FEATURES_ALLOW
917 )
918 ) {
919 return PODS_DYNAMIC_FEATURES_ALLOW;
920 }
921
922 $can_use_dynamic_features = apply_filters( 'pods_access_can_use_dynamic_features', null, $pod );
923
924 if ( is_bool( $can_use_dynamic_features ) ) {
925 return $can_use_dynamic_features;
926 }
927
928 // Check if all dynamic features are disabled.
929 $dynamic_features_allow = pods_get_setting( 'dynamic_features_allow', '1' );
930 $dynamic_features_allow = filter_var( $dynamic_features_allow, FILTER_VALIDATE_BOOLEAN );
931
932 if ( $dynamic_features_allow && $pod instanceof Pod ) {
933 // Check if all dynamic features are disabled for the Pod.
934 $dynamic_features_allow = $pod->get_arg( 'dynamic_features_allow', 'inherit' );
935
936 if ( 'inherit' === $dynamic_features_allow ) {
937 $dynamic_features_allow = pods_is_type_public(
938 [
939 'pod' => $pod,
940 ]
941 );
942 } else {
943 $dynamic_features_allow = filter_var( $dynamic_features_allow, FILTER_VALIDATE_BOOLEAN );
944 }
945 }
946
947 return $dynamic_features_allow;
948 }
949
950 /**
951 * Determine whether any or a specific dynamic feature can be used.
952 *
953 * @since 3.1.0
954 *
955 * @param string $type The dynamic feature type.
956 *
957 * @return bool Whether any or a specific dynamic feature can be used.
958 */
959 function pods_can_use_dynamic_feature( string $type ): bool {
960 if ( ! pods_can_use_dynamic_features() ) {
961 return false;
962 }
963
964 if ( empty( $type ) ) {
965 return false;
966 }
967
968 // Handle the constants.
969 if ( 'view' === $type && defined( 'PODS_SHORTCODE_ALLOW_VIEWS' ) && ! PODS_SHORTCODE_ALLOW_VIEWS ) {
970 return false;
971 }
972
973 $can_use_dynamic_feature = apply_filters( 'pods_access_can_use_dynamic_feature', null, $type );
974
975 if ( is_bool( $can_use_dynamic_feature ) ) {
976 return $can_use_dynamic_feature;
977 }
978
979 $dynamic_features_enabled = (array) pods_get_setting( 'dynamic_features_enabled', [
980 'display',
981 'form',
982 ] );
983 $dynamic_features_enabled = array_filter( $dynamic_features_enabled );
984
985 $constant_dynamic_features_enabled = defined( 'PODS_DYNAMIC_FEATURES_ENABLED' ) ? PODS_DYNAMIC_FEATURES_ENABLED : false;
986
987 if ( false !== $constant_dynamic_features_enabled && ! is_array( $constant_dynamic_features_enabled ) ) {
988 $constant_dynamic_features_enabled = explode( ',', $constant_dynamic_features_enabled );
989 $constant_dynamic_features_enabled = array_filter( $constant_dynamic_features_enabled );
990
991 $dynamic_features_enabled = $constant_dynamic_features_enabled;
992 }
993
994 if ( empty( $dynamic_features_enabled ) ) {
995 return false;
996 }
997
998 return in_array( $type, $dynamic_features_enabled, true );
999 }
1000
1001 /**
1002 * Determine whether specific dynamic feature is unrestricted.
1003 *
1004 * @since 3.1.0
1005 *
1006 * @param array $args {
1007 * The arguments to use.
1008 *
1009 * @type string|null $object_type The object type.
1010 * @type string|null $object_name The object name.
1011 * @type int|string|null $item_id The item ID.
1012 * @type Pods|null $pods The Pods object.
1013 * @type Pod|null $pod The Pod object.
1014 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
1015 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
1016 * }
1017 * @param string $type The dynamic feature type.
1018 * @param string $mode The dynamic feature mode (like "add" or "edit" for the form feature).
1019 *
1020 * @return bool Whether specific dynamic feature is unrestricted.
1021 */
1022 function pods_can_use_dynamic_feature_unrestricted( array $args, string $type, ?string $mode = null ): bool {
1023 if ( ! pods_can_use_dynamic_feature( $type ) ) {
1024 return false;
1025 }
1026
1027 if ( defined( 'PODS_DYNAMIC_FEATURES_RESTRICT' ) && ! PODS_DYNAMIC_FEATURES_RESTRICT ) {
1028 return true;
1029 }
1030
1031 $can_use_dynamic_features_unrestricted = apply_filters( 'pods_access_can_use_dynamic_features_unrestricted', null, $args, $type, $mode );
1032
1033 if ( is_bool( $can_use_dynamic_features_unrestricted ) ) {
1034 return $can_use_dynamic_features_unrestricted;
1035 }
1036
1037 $can_use_unrestricted = false;
1038
1039 $args['build_pod'] = true;
1040
1041 $info = pods_info_from_args( $args );
1042
1043 if ( ! $info['pod'] ) {
1044 $can_use_unrestricted = false;
1045 } else {
1046 $is_public_content_type = pods_is_type_public( $info );
1047
1048 $default_restricted_dynamic_features = [
1049 'form',
1050 ];
1051
1052 if ( ! $is_public_content_type ) {
1053 $default_restricted_dynamic_features[] = 'display';
1054 }
1055
1056 $default_restricted_dynamic_features_forms = [
1057 'edit',
1058 ];
1059
1060 if ( ! $is_public_content_type ) {
1061 $default_restricted_dynamic_features_forms[] = 'add';
1062 }
1063
1064 // Check if all dynamic features are unrestricted.
1065 $restrict_dynamic_features = $info['pod']->get_arg( 'restrict_dynamic_features', '1' );
1066 $restrict_dynamic_features = filter_var( $restrict_dynamic_features, FILTER_VALIDATE_BOOLEAN );
1067
1068 if ( ! $restrict_dynamic_features ) {
1069 $can_use_unrestricted = true;
1070 } elseif ( ! empty( $type ) ) {
1071 if ( defined( 'PODS_DYNAMIC_FEATURES_RESTRICTED' ) && false !== PODS_DYNAMIC_FEATURES_RESTRICTED ) {
1072 $constant_restricted_dynamic_features = PODS_DYNAMIC_FEATURES_RESTRICTED;
1073
1074 if ( ! is_array( $constant_restricted_dynamic_features ) ) {
1075 $constant_restricted_dynamic_features = explode( ',', $constant_restricted_dynamic_features );
1076 }
1077
1078 $restricted_dynamic_features = $constant_restricted_dynamic_features;
1079 } else {
1080 $restricted_dynamic_features = (array) $info['pod']->get_arg( 'restricted_dynamic_features', $default_restricted_dynamic_features );
1081 }
1082
1083 $restricted_dynamic_features = array_filter( $restricted_dynamic_features );
1084
1085 if ( empty( $restricted_dynamic_features ) ) {
1086 $can_use_unrestricted = true;
1087 } else {
1088 $can_use_unrestricted = ! in_array( $type, $restricted_dynamic_features, true );
1089 }
1090
1091 if ( ! $can_use_unrestricted && 'form' === $type && $mode ) {
1092 if ( defined( 'PODS_DYNAMIC_FEATURES_RESTRICTED_FORMS' ) && false !== PODS_DYNAMIC_FEATURES_RESTRICTED_FORMS ) {
1093 $constant_restricted_dynamic_features_forms = PODS_DYNAMIC_FEATURES_RESTRICTED_FORMS;
1094
1095 if ( ! is_array( $constant_restricted_dynamic_features_forms ) ) {
1096 $constant_restricted_dynamic_features_forms = explode( ',', $constant_restricted_dynamic_features_forms );
1097 }
1098
1099 $restricted_dynamic_features_forms = $constant_restricted_dynamic_features_forms;
1100 } else {
1101 $restricted_dynamic_features_forms = (array) $info['pod']->get_arg( 'restricted_dynamic_features_forms', $default_restricted_dynamic_features_forms );
1102 }
1103
1104 $restricted_dynamic_features_forms = array_filter( $restricted_dynamic_features_forms );
1105
1106 if ( empty( $restricted_dynamic_features_forms ) ) {
1107 $can_use_unrestricted = true;
1108 } else {
1109 $can_use_unrestricted = ! in_array( $mode, $restricted_dynamic_features_forms, true );
1110 }
1111 }
1112 }
1113 }
1114
1115 return $can_use_unrestricted;
1116 }
1117
1118 /**
1119 * Get the access notice for admin user based on object type and object name.
1120 *
1121 * @since 3.1.0
1122 *
1123 * @param array $args {
1124 * The arguments to use.
1125 *
1126 * @type string|null $object_type The object type.
1127 * @type string|null $object_name The object name.
1128 * @type int|string|null $item_id The item ID.
1129 * @type Pods|null $pods The Pods object.
1130 * @type Pod|null $pod The Pod object.
1131 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
1132 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
1133 * }
1134 * @param bool $force_message Whether to force the message to show even if messages are hidden by a setting.
1135 *
1136 * @return string The access notice for admin user based on object type and object name.
1137 */
1138 function pods_get_access_admin_notice( array $args, bool $force_message = false ): string {
1139 $args['build_pod'] = true;
1140
1141 $info = pods_info_from_args( $args );
1142
1143 $identifier_for_html = esc_html( json_encode( [
1144 'object_type' => $info['object_type'],
1145 'object_name' => $info['object_name'],
1146 'item_id' => $info['item_id'],
1147 ] ) );
1148
1149 // Check if constant is hiding all notices.
1150 if ( ! $force_message && defined( 'PODS_ACCESS_HIDE_NOTICES' ) && PODS_ACCESS_HIDE_NOTICES ) {
1151 return '<!-- pods:access-notices/admin/hidden-by-constant ' . $identifier_for_html . ' -->';
1152 }
1153
1154 // Check notice setting for the Pod itself.
1155 if ( $info['pod'] instanceof Pod ) {
1156 $show_access_admin_notices_for_pod = $info['pod']->get_arg( 'show_access_admin_notices', 'inherit' );
1157
1158 if ( 'inherit' !== $show_access_admin_notices_for_pod ) {
1159 $show_access_admin_notices_for_pod = filter_var( $show_access_admin_notices_for_pod, FILTER_VALIDATE_BOOLEAN );
1160
1161 // Check if all notices have been dismissed for the pod.
1162 if ( ! $force_message && ! $show_access_admin_notices_for_pod ) {
1163 return '<!-- pods:access-notices/admin/hidden-by-pod ' . $identifier_for_html . ' -->';
1164 }
1165 }
1166 }
1167
1168 // Show notice that this content may not be visible to others.
1169 $show_access_admin_notices = pods_get_setting( 'show_access_admin_notices', true );
1170 $show_access_admin_notices = filter_var( $show_access_admin_notices, FILTER_VALIDATE_BOOLEAN );
1171
1172 // Check if all notices have been dismissed.
1173 if ( ! $force_message && ! $show_access_admin_notices ) {
1174 return '<!-- pods:access-notices/admin/hidden-by-setting ' . $identifier_for_html . ' -->';
1175 }
1176
1177 $summary = esc_html__( 'Pods Access Rights: Admin-only Notice', 'pods' );
1178
1179 $content = sprintf(
1180 '
1181 <p>
1182 %1$s
1183 <br />
1184 <span class="pods-ui-notice-action-links">
1185 <a href="%2$s" target="_blank" rel="noopener noreferrer">%3$s</a>
1186 | <a href="%4$s" target="_blank" rel="noopener noreferrer">%5$s</a>
1187 </span>
1188 </p>
1189 ',
1190 esc_html__( 'The content type or the content below is not public and may not be available to everyone else.', 'pods' ),
1191 esc_url( 'https://docs.pods.io/displaying-pods/access-rights-in-pods/' ),
1192 esc_html__( 'How access rights work with Pods (Documentation)', 'pods' ),
1193 esc_url( admin_url( 'admin.php?page=pods-settings#heading-security' ) ),
1194 esc_html__( 'Edit other access right options', 'pods' )
1195 );
1196
1197 return '<!-- pods:access-notices/admin/message ' . $identifier_for_html . ' -->'
1198 . pods_message(
1199 sprintf(
1200 '
1201 <details open>
1202 <summary><strong>%1$s</strong></summary>
1203 %2$s
1204 </details>
1205 ',
1206 strip_tags( ! empty( $info['summary'] ) ? $info['summary'] : $summary ),
1207 ! empty( $info['content'] ) ? wpautop( $info['content'] ) : $content
1208 ),
1209 'notice',
1210 true
1211 );
1212 }
1213
1214 /**
1215 * Get the access notice for non-admin user based on object type and object name.
1216 *
1217 * @since 3.1.0
1218 *
1219 * @param array $args {
1220 * The arguments to use.
1221 *
1222 * @type string|null $object_type The object type.
1223 * @type string|null $object_name The object name.
1224 * @type int|string|null $item_id The item ID.
1225 * @type Pods|null $pods The Pods object.
1226 * @type Pod|null $pod The Pod object.
1227 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
1228 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
1229 * }
1230 * @param bool $force_message Whether to force the message to show even if messages are hidden by a setting.
1231 * @param string|null $message A custom message to use for the notice text.
1232 *
1233 * @return string The access notice for non-admin user based on object type and object name.
1234 */
1235 function pods_get_access_user_notice( array $args, bool $force_message = false, ?string $message = null ): string {
1236 $args['build_pod'] = true;
1237
1238 $info = pods_info_from_args( $args );
1239
1240 $identifier_for_html = esc_html( json_encode( [
1241 'object_type' => $info['object_type'],
1242 'object_name' => $info['object_name'],
1243 'item_id' => $info['item_id'],
1244 ] ) );
1245
1246 // Check for password-protected post.
1247 if ( $info['item_id'] && pods_access_bypass_post_with_password( $info ) ) {
1248 $message = get_the_password_form( $info['item_id'] );
1249
1250 return '<!-- pods:access-notices/user/protected/message ' . $identifier_for_html . ' -->'
1251 . pods_message(
1252 sprintf(
1253 '<p><strong>%1$s</strong></p> %2$s',
1254 esc_html__( 'Access Restricted', 'pods' ),
1255 $message
1256 ),
1257 'error',
1258 true
1259 );
1260 }
1261
1262 // Check if constant is hiding all notices.
1263 if ( ! $force_message && defined( 'PODS_ACCESS_HIDE_NOTICES' ) && PODS_ACCESS_HIDE_NOTICES ) {
1264 return '<!-- pods:access-notices/user/hidden-by-constant ' . $identifier_for_html . ' -->';
1265 }
1266
1267 // Check notice setting for the Pod itself.
1268 if ( $info['pod'] instanceof Pod ) {
1269 $show_access_restricted_messages_for_pod = $info['pod']->get_arg( 'show_access_restricted_messages', 'inherit' );
1270
1271 if ( 'inherit' !== $show_access_restricted_messages_for_pod ) {
1272 $show_access_restricted_messages_for_pod = filter_var( $show_access_restricted_messages_for_pod, FILTER_VALIDATE_BOOLEAN );
1273
1274 // Check if all notices have been dismissed for the pod.
1275 if ( ! $force_message && ! $show_access_restricted_messages_for_pod ) {
1276 return '<!-- pods:access-notices/user/hidden-by-pod ' . $identifier_for_html . ' -->';
1277 }
1278 }
1279 }
1280
1281 // Show notice that this content may not be visible to others.
1282 $show_access_restricted_messages = pods_get_setting( 'show_access_restricted_messages', false );
1283 $show_access_restricted_messages = filter_var( $show_access_restricted_messages, FILTER_VALIDATE_BOOLEAN );
1284
1285 // Check if all notices have been dismissed.
1286 if ( ! $force_message && ! $show_access_restricted_messages ) {
1287 return '<!-- pods:access-notices/user/hidden-by-setting ' . $identifier_for_html . ' -->';
1288 }
1289
1290 $message = $message ?? esc_html__( 'You do not have access to this embedded content.', 'pods' );
1291
1292 return '<!-- pods:access-notices/user/message ' . $identifier_for_html . ' -->'
1293 . pods_message(
1294 sprintf(
1295 '<p><strong>%1$s:</strong> %2$s</p>',
1296 esc_html__( 'Access Restricted', 'pods' ),
1297 $message
1298 ),
1299 'error',
1300 true
1301 );
1302 }
1303
1304 /**
1305 * Determine whether SQL clauses can be used with dynamic features.
1306 *
1307 * @since 3.1.0
1308 *
1309 * @param null|string $clause_type The clause type to check if allowed, if null used then it checks if any clauses are allowed.
1310 *
1311 * @return bool Whether SQL clauses can be used with dynamic features.
1312 */
1313 function pods_can_use_dynamic_feature_sql_clauses( ?string $clause_type = null ): bool {
1314 // Set default to most simple clause type check (simple).
1315 $clause_type = $clause_type ?: 'simple';
1316
1317 if ( defined( 'PODS_DISABLE_SHORTCODE_SQL' ) ) {
1318 // Negate the check since this is a "disable" constant.
1319 return ! PODS_DISABLE_SHORTCODE_SQL;
1320 }
1321
1322 if ( defined( 'PODS_DYNAMIC_FEATURES_ALLOW_SQL_CLAUSES' ) ) {
1323 $allow_sql_clauses = PODS_DYNAMIC_FEATURES_ALLOW_SQL_CLAUSES;
1324 } else {
1325 $cached_allow_sql_clauses = pods_transient_get( 'pods_dynamic_features_allow_sql_clauses' );
1326
1327 if ( is_string( $cached_allow_sql_clauses ) ) {
1328 $allow_sql_clauses = $cached_allow_sql_clauses;
1329 } else {
1330 $first_pods_version = get_option( 'pods_framework_version_first' );
1331 $first_pods_version = '' === $first_pods_version ? PODS_VERSION : $first_pods_version;
1332
1333 $allow_sql_clauses = pods_get_setting( 'dynamic_features_allow_sql_clauses', version_compare( $first_pods_version, '3.1.0-a-1', '<' ) ? 'simple' : '0' );
1334
1335 pods_transient_set( 'pods_dynamic_features_allow_sql_clauses', (string) $allow_sql_clauses );
1336 }
1337 }
1338
1339 if (
1340 false === $allow_sql_clauses
1341 || '0' === (string) $allow_sql_clauses
1342 ) {
1343 return false;
1344 }
1345
1346 // The "all" option is inclusive of "simple".
1347 if ( 'simple' === $clause_type && 'all' === $allow_sql_clauses ) {
1348 return true;
1349 }
1350
1351 return $clause_type === $allow_sql_clauses;
1352 }
1353
1354 /**
1355 * Determine whether a callback can be used.
1356 *
1357 * @since 3.1.0
1358 *
1359 * @param string|callable $callback The callback to check.
1360 * @param array $params Parameters used by Pods::helper() method.
1361 *
1362 * @return bool Whether the callback can be used.
1363 */
1364 function pods_access_callback_allowed( $callback, array $params = [] ): bool {
1365 // Real callables are allowed because they are done through PHP calls.
1366 if ( ! is_string( $callback ) ) {
1367 return true;
1368 }
1369
1370 if ( ! pods_can_use_dynamic_feature( 'display' ) ) {
1371 return false;
1372 }
1373
1374 if (
1375 defined( 'PODS_DISPLAY_CALLBACKS' )
1376 && ! PODS_DISPLAY_CALLBACKS
1377 ) {
1378 return false;
1379 }
1380
1381 /**
1382 * Allows changing whether callbacks are allowed to run.
1383 *
1384 * @param bool $allow_callbacks Whether callbacks are allowed to run.
1385 * @param array $params Parameters used by Pods::helper() method.
1386 *
1387 * @since 2.8.0
1388 */
1389 $allow_callbacks = (bool) apply_filters( 'pods_helper_allow_callbacks', true, $params );
1390
1391 if ( ! $allow_callbacks ) {
1392 return false;
1393 }
1394
1395 $disallowed = [
1396 // Regex related.
1397 'preg_replace',
1398 'preg_replace_array',
1399 'preg_replace_callback',
1400 'preg_replace_callback_array',
1401 'preg_match',
1402 'preg_match_all',
1403 // Shell/Eval related.
1404 'system',
1405 'exec',
1406 'passthru',
1407 'proc_close',
1408 'proc_get_status',
1409 'proc_nice',
1410 'proc_open',
1411 'proc_terminate',
1412 'shell_exec',
1413 'system',
1414 'eval',
1415 'create_function',
1416 // File related.
1417 'popen',
1418 'include',
1419 'include_once',
1420 'require',
1421 'require_once',
1422 'file_get_contents',
1423 'file_put_contents',
1424 'get_template_part',
1425 // Nonce related.
1426 'wp_nonce_url',
1427 'wp_nonce_field',
1428 'wp_create_nonce',
1429 'check_admin_referer',
1430 'check_ajax_referer',
1431 'wp_verify_nonce',
1432 // PHP related.
1433 'constant',
1434 'defined',
1435 'get_current_user',
1436 'get_defined_constants',
1437 'get_defined_functions',
1438 'get_defined_vars',
1439 'get_extension_funcs',
1440 'get_include_path',
1441 'get_included_files',
1442 'get_loaded_extensions',
1443 'get_required_files',
1444 'get_resources',
1445 'getenv',
1446 'getopt',
1447 'ini_alter',
1448 'ini_get',
1449 'ini_get_all',
1450 'ini_restore',
1451 'ini_set',
1452 'php_ini_loaded_file',
1453 'php_ini_scanned_files',
1454 'php_sapi_name',
1455 'php_uname',
1456 'phpinfo',
1457 'phpversion',
1458 'putenv',
1459 // WordPress related.
1460 'get_userdata',
1461 'get_currentuserinfo',
1462 'get_post',
1463 'get_term',
1464 'get_comment',
1465 ];
1466
1467 $allowed = [];
1468
1469 if ( defined( 'PODS_DISPLAY_CALLBACKS' ) ) {
1470 $display_callbacks = PODS_DISPLAY_CALLBACKS;
1471 } else {
1472 $first_pods_version = get_option( 'pods_framework_version_first' );
1473 $first_pods_version = '' === $first_pods_version ? PODS_VERSION : $first_pods_version;
1474
1475 $display_callbacks = pods_get_setting( 'display_callbacks', version_compare( $first_pods_version, '3.1.0-a-1', '<' ) ? 'restricted' : 'customized' );
1476 }
1477
1478 if ( '0' === $display_callbacks ) {
1479 return false;
1480 }
1481
1482 // Maybe specify the list of allowed callbacks.
1483 if ( 'customized' === $display_callbacks ) {
1484 if ( defined( 'PODS_DISPLAY_CALLBACKS_ALLOWED' ) ) {
1485 $display_callbacks_allowed = PODS_DISPLAY_CALLBACKS_ALLOWED;
1486 } else {
1487 // Maybe specify the list of allowed callbacks
1488 $display_callbacks_allowed = pods_get_setting( 'display_callbacks_allowed', 'esc_attr,esc_html' );
1489 }
1490
1491 if ( ! is_array( $display_callbacks_allowed ) ) {
1492 $display_callbacks_allowed = str_replace( "\n", ',', $display_callbacks_allowed );
1493 $display_callbacks_allowed = explode( ',', $display_callbacks_allowed );
1494 }
1495
1496 $display_callbacks_allowed = array_map( 'trim', $display_callbacks_allowed );
1497 $display_callbacks_allowed = array_filter( $display_callbacks_allowed );
1498
1499 if ( ! empty( $display_callbacks_allowed ) ) {
1500 $allowed = $display_callbacks_allowed;
1501 }
1502 }
1503
1504 /**
1505 * Allows adjusting the disallowed callbacks as needed.
1506 *
1507 * @param array $disallowed List of callbacks not allowed.
1508 * @param array $params Parameters used by Pods::helper() method.
1509 *
1510 * @since 2.7.0
1511 */
1512 $disallowed = apply_filters( 'pods_helper_disallowed_callbacks', $disallowed, $params );
1513
1514 /**
1515 * Allows adjusting the allowed callbacks as needed.
1516 *
1517 * @param array $allowed List of callbacks explicitly allowed.
1518 * @param array $params Parameters used by Pods::helper() method.
1519 *
1520 * @since 2.7.0
1521 */
1522 $allowed = apply_filters( 'pods_helper_allowed_callbacks', $allowed, $params );
1523
1524 // Clean up helper callback (if string).
1525 if ( is_string( $callback ) ) {
1526 $callback = strip_tags( str_replace( array( '`', chr( 96 ) ), "'", $callback ) );
1527 }
1528
1529 return (
1530 ! in_array( $callback, $disallowed, true )
1531 && (
1532 empty( $allowed )
1533 || in_array( $callback, $allowed, true )
1534 )
1535 );
1536 }
1537
1538 /**
1539 * Get the pod access tab options for a specific pod.
1540 *
1541 * @since 3.1.0
1542 *
1543 * @param string $pod_type The pod type.
1544 * @param string $pod_name The pod name.
1545 * @param null|Pod $pod The pod object.
1546 *
1547 * @return array The pod access tab options for a specific pod.
1548 */
1549 function pods_access_pod_options( string $pod_type, string $pod_name, ?Pod $pod = null ): array {
1550 $first_pods_version = get_option( 'pods_framework_version_first' );
1551 $first_pods_version = '' === $first_pods_version ? PODS_VERSION : $first_pods_version;
1552
1553 $options = [];
1554
1555 $options['security_access_rights_info'] = [
1556 'label' => __( 'How access rights work in Pods', 'pods' ),
1557 'type' => 'html',
1558 'html_content' => sprintf(
1559 '
1560 <p>%1$s</p>
1561 <p><a href="https://docs.pods.io/displaying-pods/access-rights-in-pods/" target="_blank" rel="noopener noreferrer">%2$s</a> <span class="dashicon dashicons dashicons-external"></span></p>
1562 ',
1563 __( 'Pods handles access rights similar to how WordPress itself works.', 'pods' ),
1564 __( 'Read more about how access rights work in Pods on our Documentation site', 'pods' )
1565 ),
1566 ];
1567
1568 if ( 'pod' === $pod_type ) {
1569 $options['public'] = [
1570 'label' => __( 'Public', 'pods' ),
1571 'help' => __( 'You can still embed Pods Content and Forms through PHP and make use of other features directly through code.', 'pods' ),
1572 'description' => __( 'When a content type is public, it can be viewed by anyone when it is embedded through Dynamic Features. Otherwise, a user will need to have the corresponding "read" capability for the content type.', 'pods' ),
1573 'type' => 'boolean',
1574 'default' => version_compare( $first_pods_version, '3.1.0-a-1', '<' ) ? true : false,
1575 'boolean_yes_label' => '',
1576 ];
1577 }
1578
1579 if ( pods_can_use_dynamic_features() ) {
1580 $options['dynamic_features_allow'] = [
1581 'label' => __( 'Dynamic Features', 'pods' ),
1582 'help' => [
1583 __( 'Enabling Dynamic Features will also enable the additional access rights checks for user access. This ensures that people viewing embedded content and forms have the required capabilities. Even when Dynamic Features are disabled, you can still embed Pods Content and Forms through PHP and make use of other features directly through code.', 'pods' ),
1584 'https://docs.pods.io/displaying-pods/access-rights-in-pods/',
1585 ],
1586 'description' => __( 'Dynamic features include Pods Shortcodes, Blocks, and Widgets which let you embed content and forms on your site.', 'pods' ),
1587 'type' => 'pick',
1588 'default' => 'inherit',
1589 'pick_format_type' => 'single',
1590 'pick_format_single' => 'radio',
1591 'data' => [
1592 'inherit' => __( 'WP Default - If the content type is marked "Public" with WordPress then Dynamic Features will be enabled.', 'pods' ),
1593 '1' => __( 'Enable Dynamic Features including Pods Shortcodes, Blocks, and Widgets for this content type', 'pods' ),
1594 '0' => __( 'Disable All Dynamic Features in Pods for this content type', 'pods' ),
1595 ],
1596 'dependency' => true,
1597 ];
1598
1599 $is_public_content_type = pods_is_type_public(
1600 [
1601 'pod' => $pod,
1602 ]
1603 );
1604
1605 $options['restrict_dynamic_features'] = [
1606 'label' => __( 'Restrict Dynamic Features', 'pods' ),
1607 'help' => [
1608 __( 'This will check access rights for whether someone should have access to specific content before a they can view, modify, or interact with that content.', 'pods' ),
1609 'https://docs.pods.io/displaying-pods/access-rights-in-pods/',
1610 ],
1611 'description' => sprintf(
1612 '<strong>%1$s</strong> %2$s',
1613 esc_html__( 'Warning:', 'pods' ),
1614 esc_html__( 'If you have authors/contributors on your site then disabling this would give them access to embedding content/forms without access checks for them or whoever views the embeds on the front of your site. Caution is always advised before giving access to other users you may not trust.', 'pods' )
1615 ),
1616 'type' => 'pick',
1617 'default' => '1',
1618 'pick_format_type' => 'single',
1619 'pick_format_single' => 'radio',
1620 'data' => [
1621 '0' => __( 'Unrestricted - Do not check for access rights for embedded content (only use this if you trust ALL users who have access to create content)', 'pods' ),
1622 '1' => __( 'Restricted - Check access rights for embedded content', 'pods' ),
1623 ],
1624 'excludes-on' => [ 'dynamic_features_allow' => '0' ],
1625 ];
1626
1627 $default_restricted_dynamic_features = [
1628 'form',
1629 ];
1630
1631 if ( ! $is_public_content_type ) {
1632 $default_restricted_dynamic_features[] = 'display';
1633 }
1634
1635 $options['restricted_dynamic_features'] = [
1636 'label' => __( 'Dynamic Features to Restrict', 'pods' ),
1637 'help' => [
1638 __( 'This will check access rights for the dynamic feature for whether someone should have access to specific content before a they can view, modify, or interact with that content.', 'pods' ),
1639 'https://docs.pods.io/displaying-pods/access-rights-in-pods/',
1640 ],
1641 'type' => 'pick',
1642 'default' => $default_restricted_dynamic_features,
1643 'pick_format_type' => 'multi',
1644 'pick_format_multi' => 'checkbox',
1645 'data' => [
1646 'display' => __( 'Restricted Display - Shortcodes and Blocks that allow querying content from this Pod and displaying any field will check access rights.', 'pods' ),
1647 'form' => __( 'Restricted Forms - The Form Shortcode and Block submitting new content or editing existing content will check access rights.', 'pods' ),
1648 ],
1649 'depends-on' => [ 'restrict_dynamic_features' => '1' ],
1650 'excludes-on' => [ 'dynamic_features_allow' => '0' ],
1651 ];
1652
1653 $default_restricted_dynamic_features_forms = [
1654 'edit',
1655 ];
1656
1657 if ( ! $is_public_content_type ) {
1658 $default_restricted_dynamic_features_forms[] = 'add';
1659 }
1660
1661 $options['restricted_dynamic_features_forms'] = [
1662 'label' => __( 'Dynamic Features to Restrict for Forms', 'pods' ),
1663 'help' => [
1664 __( 'This will check access rights for whether someone should have access to specific content before a they can add or edit content.', 'pods' ),
1665 'https://docs.pods.io/displaying-pods/access-rights-in-pods/',
1666 ],
1667 'type' => 'pick',
1668 'default' => $default_restricted_dynamic_features_forms,
1669 'pick_format_type' => 'multi',
1670 'pick_format_multi' => 'checkbox',
1671 'data' => [
1672 'add' => __( 'Restricted Add New Forms - Embedding the Form Shortcode and Block to allow for adding new content will check access rights.', 'pods' ),
1673 'edit' => __( 'Restricted Edit Forms - Embedding the Form Shortcode and Block to allow for editing existing content will check access rights.', 'pods' ),
1674 ],
1675 'depends-on-multi' => [ 'restricted_dynamic_features' => 'form' ],
1676 'excludes-on' => [ 'dynamic_features_allow' => '0' ],
1677 ];
1678
1679 $options['show_access_restricted_messages'] = [
1680 'label' => __( 'Access-related Restricted Messages', 'pods' ),
1681 'help' => [
1682 __( 'Access-related Restricted Messages will show to anyone who does not have access to add/edit/read a specific item from a content type.', 'pods' ),
1683 'https://docs.pods.io/displaying-pods/access-rights-in-pods/',
1684 ],
1685 'type' => 'pick',
1686 'default' => 'inherit',
1687 'pick_format_type' => 'single',
1688 'pick_format_single' => 'radio',
1689 'data' => [
1690 '1' => __( 'Enable access-related restricted messages for forms/content displayed (instead of the form/content output)', 'pods' ),
1691 '0' => __( 'Disable access-related restricted messages for forms/content displayed (the form/content output will be blank)', 'pods' ),
1692 'inherit' => __( 'Default - Use the global Pods setting for this', 'pods' ),
1693 ],
1694 'depends-on' => [ 'restrict_dynamic_features' => '1' ],
1695 'excludes-on' => [ 'dynamic_features_allow' => '0' ],
1696 ];
1697
1698 $options['show_access_admin_notices'] = [
1699 'label' => __( 'Access-related Admin Notices', 'pods' ),
1700 'help' => [
1701 __( 'Access-related Admin Notices will only show to admins and will appear above content/forms that may not be entirely public.', 'pods' ),
1702 'https://docs.pods.io/displaying-pods/access-rights-in-pods/',
1703 ],
1704 'type' => 'pick',
1705 'default' => 'inherit',
1706 'pick_format_type' => 'single',
1707 'pick_format_single' => 'radio',
1708 'data' => [
1709 '1' => __( 'Enable access-related admin notices above forms/content displayed', 'pods' ),
1710 '0' => __( 'Disable access-related admin notices above forms/content displayed', 'pods' ),
1711 'inherit' => __( 'Default - Use the global Pods setting for this', 'pods' ),
1712 ],
1713 'depends-on' => [ 'restrict_dynamic_features' => '1' ],
1714 'excludes-on' => [ 'dynamic_features_allow' => '0' ],
1715 ];
1716 }
1717
1718 $options['security_access_rights_preview'] = [
1719 'label' => __( 'Capabilities preview', 'pods' ),
1720 'type' => 'html',
1721 'html_content' => '
1722 <p>' . esc_html__( 'Below is a list of capabilities that a user will normally need for this content.' ) . '</p>
1723 ' . pods_access_get_capabilities_preview( $pod_type, $pod_name ),
1724 ];
1725
1726 return $options;
1727 }
1728
1729 /**
1730 * Get the list of dynamic features allow options.
1731 *
1732 * @since 3.1.0
1733 *
1734 * @return array The list of dynamic features allow options.
1735 */
1736 function pods_access_get_dynamic_features_allow_options(): array {
1737 return [
1738 'inherit' => __( 'WP Default (if content type is Public)', 'pods' ),
1739 '1' => __( 'Enabled', 'pods' ),
1740 '0' => '🔒 ' . __( 'Disabled', 'pods' ),
1741 ];
1742 }
1743
1744 /**
1745 * Get the list of restricted dynamic features options.
1746 *
1747 * @since 3.1.0
1748 *
1749 * @return array The list of restricted dynamic features options.
1750 */
1751 function pods_access_get_restricted_dynamic_features_options(): array {
1752 return [
1753 'display' => '🔒 ' . __( 'Display', 'pods' ),
1754 'form' => '🔒 ' . __( 'Form', 'pods' ),
1755 ];
1756 }
1757
1758 /**
1759 * Get the access rights capabilities preview HTML.
1760 *
1761 * @since 3.1.0
1762 *
1763 * @param string $pod_type The pod type.
1764 * @param string $pod_name The pod name.
1765 *
1766 * @return string The access rights capabilities preview HTML.
1767 */
1768 function pods_access_get_capabilities_preview( string $pod_type, string $pod_name ): string {
1769 $capabilities = pods_access_map_capabilities(
1770 [
1771 'object_type' => $pod_type,
1772 'object_name' => $pod_name,
1773 ],
1774 null,
1775 true
1776 );
1777
1778 if ( null === $capabilities ) {
1779 $capabilities = [
1780 'read' => null,
1781 'add' => null,
1782 'edit' => null,
1783 'delete' => null,
1784 ];
1785 }
1786
1787 $capabilities_preview = [
1788 'read' => esc_html__( 'Read capability', 'pods' ),
1789 'add' => esc_html__( 'Add New capability', 'pods' ),
1790 'edit' => esc_html__( 'Edit capability', 'pods' ),
1791 'delete' => esc_html__( 'Delete capability', 'pods' ),
1792 'read_private' => esc_html__( 'Read Private capability', 'pods' ),
1793 'edit_others' => esc_html__( 'Edit Others capability', 'pods' ),
1794 'delete_others' => esc_html__( 'Delete Others capability', 'pods' ),
1795 'delete_published' => esc_html__( 'Delete Published capability', 'pods' ),
1796 'delete_private' => esc_html__( 'Delete Private capability', 'pods' ),
1797 ];
1798
1799 $capabilities_preview_list = [
1800 '<strong>' . $capabilities_preview['read'] . ':</strong> ' . ( $capabilities['read'] ?: __( 'Not restricted', 'pods' ) ),
1801 ];
1802
1803 if ( 'settings' !== $pod_type ) {
1804 $capabilities_preview_list[] = '<strong>' . $capabilities_preview['add'] . ':</strong> ' . ( $capabilities['add'] ?: __( 'Not restricted', 'pods' ) );
1805 }
1806
1807 $capabilities_preview_list[] = '<strong>' . $capabilities_preview['edit'] . ':</strong> ' . ( $capabilities['edit'] ?: __( 'Not restricted', 'pods' ) );
1808
1809 if ( 'settings' !== $pod_type ) {
1810 $capabilities_preview_list[] = '<strong>' . $capabilities_preview['delete'] . ':</strong> ' . ( $capabilities['delete'] ?: __( 'Not restricted', 'pods' ) );
1811 }
1812
1813 if ( $capabilities && array_key_exists( 'read_private', $capabilities ) ) {
1814 $capabilities_preview_list[] = '<strong>' . $capabilities_preview['read_private'] . ':</strong> ' . ( $capabilities['read_private'] ?: __( 'Not restricted', 'pods' ) );
1815 }
1816
1817 if ( $capabilities && array_key_exists( 'edit_others', $capabilities ) ) {
1818 $capabilities_preview_list[] = '<strong>' . $capabilities_preview['edit_others'] . ':</strong> ' . ( $capabilities['edit_others'] ?: __( 'Not restricted', 'pods' ) );
1819 }
1820
1821 if ( $capabilities && array_key_exists( 'delete_others', $capabilities ) ) {
1822 $capabilities_preview_list[] = '<strong>' . $capabilities_preview['delete_others'] . ':</strong> ' . ( $capabilities['delete_others'] ?: __( 'Not restricted', 'pods' ) );
1823 }
1824
1825 if ( $capabilities && array_key_exists( 'delete_published', $capabilities ) ) {
1826 $capabilities_preview_list[] = '<strong>' . $capabilities_preview['delete_published'] . ':</strong> ' . ( $capabilities['delete_published'] ?: __( 'Not restricted', 'pods' ) );
1827 }
1828
1829 if ( $capabilities && array_key_exists( 'delete_private', $capabilities ) ) {
1830 $capabilities_preview_list[] = '<strong>' . $capabilities_preview['delete_private'] . ':</strong> ' . ( $capabilities['delete_private'] ?: __( 'Not restricted', 'pods' ) );
1831 }
1832
1833 return '
1834 <ul>
1835 <li>' . implode( '</li><li>', $capabilities_preview_list ) . '</li>
1836 </ul>
1837 ';
1838 }
1839
1840 /**
1841 * Get the pod settings config for access-related settings.
1842 *
1843 * @since 3.1.0
1844 *
1845 * @return array The pod settings config for access-related settings.
1846 */
1847 function pods_access_settings_config(): array {
1848 // Only use translation functions after `init` to prevent a WP core notice.
1849 $did_init = doing_action( 'init' ) || did_action( 'init' );
1850
1851 $first_pods_version = get_option( 'pods_framework_version_first' );
1852 $first_pods_version = '' === $first_pods_version ? PODS_VERSION : $first_pods_version;
1853
1854 $fields = [];
1855
1856 $fields['dynamic_features_allow'] = [
1857 'name' => 'dynamic_features_allow',
1858 'label' => $did_init ? __( 'Dynamic Features', 'pods' ) : '',
1859 'help' => [
1860 $did_init ? __( 'Enabling Dynamic Features will also enable the additional access rights checks for user access. This ensures that people viewing embedded content and forms have the required capabilities. Even when Dynamic Features are disabled, you can still embed Pods Content and Forms through PHP and make use of other features directly through code.', 'pods' ) : '',
1861 'https://docs.pods.io/displaying-pods/access-rights-in-pods/',
1862 ],
1863 'description' => $did_init ? __( 'Dynamic features include Pods Shortcodes, Blocks, and Widgets which let you embed content and forms on your site.', 'pods' ) : '',
1864 'type' => 'pick',
1865 'default' => '1',
1866 'pick_format_type' => 'single',
1867 'pick_format_single' => 'radio',
1868 'data' => [
1869 '1' => $did_init ? __( 'Enable Dynamic Features including Pods Shortcodes, Blocks, and Widgets', 'pods' ) : '',
1870 '0' => $did_init ? __( 'Disable All Dynamic Features in Pods', 'pods' ) : '',
1871 ],
1872 'site_health_data' => [
1873 '1' => $did_init ? __( 'Enable', 'pods' ) : '',
1874 '0' => $did_init ? __( 'Disable', 'pods' ) : '',
1875 ],
1876 'site_health_include_in_info' => true,
1877 ];
1878
1879 $fields['security_access_rights_info'] = [
1880 'name' => 'security_access_rights_info',
1881 'label' => $did_init ? __( 'How access rights work in Pods', 'pods' ) : '',
1882 'type' => 'html',
1883 'html_content' => sprintf(
1884 '
1885 <p>%1$s</p>
1886 <p><a href="https://docs.pods.io/displaying-pods/access-rights-in-pods/" target="_blank" rel="noopener noreferrer">%2$s</a> <span class="dashicon dashicons dashicons-external"></span></p>
1887 ',
1888 $did_init ? __( 'Pods handles access rights similar to how WordPress itself works.', 'pods' ) : '',
1889 $did_init ? __( 'Read more about how access rights work in Pods on our Documentation site', 'pods' ) : ''
1890 ),
1891 'depends-on' => [ 'dynamic_features_allow' => '1' ],
1892 ];
1893
1894 $fields['dynamic_features_enabled'] = [
1895 'name' => 'dynamic_features_enabled',
1896 'label' => $did_init ? __( 'Dynamic Features to Enable', 'pods' ) : '',
1897 'help' => [
1898 $did_init ? __( 'You can choose one or more dynamic features to enable. By default, only Display and Form are enabled.', 'pods' ) : '',
1899 'https://docs.pods.io/displaying-pods/access-rights-in-pods/',
1900 ],
1901 'type' => 'pick',
1902 'default' => [
1903 'display',
1904 'form',
1905 ],
1906 'pick_format_type' => 'multi',
1907 'pick_format_multi' => 'checkbox',
1908 'data' => [
1909 'display' => $did_init ? __( 'Display - Shortcodes and Blocks that allow querying content from *any* Pod and displaying any field (WordPress access rights are still checked).', 'pods' ) : '',
1910 'form' => $did_init ? __( 'Form - The Form Shortcode and Block that allows submitting new content or editing existing content from *any* Pod (WordPress access rights are still checked).', 'pods' ) : '',
1911 'view' => $did_init ? __( 'View - The View Shortcode and Block that allows embedding *any* theme file on a page.', 'pods' ) : '',
1912 ],
1913 'site_health_data' => [
1914 'display' => $did_init ? __( 'Display', 'pods' ) : '',
1915 'form' => $did_init ? __( 'Form', 'pods' ) : '',
1916 'view' => $did_init ? __( 'View', 'pods' ) : '',
1917 ],
1918 'depends-on' => [ 'dynamic_features_allow' => '1' ],
1919 'site_health_include_in_info' => true,
1920 ];
1921
1922 $fields['show_access_restricted_messages'] = [
1923 'name' => 'show_access_restricted_messages',
1924 'label' => $did_init ? __( 'Access-related Restricted Messages', 'pods' ) : '',
1925 'help' => [
1926 $did_init ? __( 'Access-related Restricted Messages will show to anyone who does not have access to add/edit/read a specific item from a content type.', 'pods' ) : '',
1927 'https://docs.pods.io/displaying-pods/access-rights-in-pods/',
1928 ],
1929 'type' => 'pick',
1930 'default' => '0',
1931 'pick_format_type' => 'single',
1932 'pick_format_single' => 'radio',
1933 'data' => [
1934 '1' => $did_init ? __( 'Enable access-related restricted messages for forms/content displayed (instead of the form/content output)', 'pods' ) : '',
1935 '0' => $did_init ? __( 'Disable access-related restricted messages for forms/content displayed (the form/content output will be blank)', 'pods' ) : '',
1936 ],
1937 'site_health_data' => [
1938 '1' => $did_init ? __( 'Enable', 'pods' ) : '',
1939 '0' => $did_init ? __( 'Disable', 'pods' ) : '',
1940 ],
1941 'site_health_include_in_info' => true,
1942 'depends-on' => [ 'dynamic_features_allow' => '1' ],
1943 ];
1944
1945 $fields['show_access_admin_notices'] = [
1946 'name' => 'show_access_admin_notices',
1947 'label' => $did_init ? __( 'Access-related Admin Notices', 'pods' ) : '',
1948 'help' => [
1949 $did_init ? __( 'Access-related Admin Notices will only show to admins and will appear above content/forms that may not be entirely public.', 'pods' ) : '',
1950 'https://docs.pods.io/displaying-pods/access-rights-in-pods/',
1951 ],
1952 'type' => 'pick',
1953 'default' => '1',
1954 'pick_format_type' => 'single',
1955 'pick_format_single' => 'radio',
1956 'data' => [
1957 '1' => $did_init ? __( 'Enable access-related admin notices above forms/content displayed', 'pods' ) : '',
1958 '0' => $did_init ? __( 'Disable access-related admin notices above forms/content displayed', 'pods' ) : '',
1959 ],
1960 'site_health_data' => [
1961 '1' => $did_init ? __( 'Enable', 'pods' ) : '',
1962 '0' => $did_init ? __( 'Disable', 'pods' ) : '',
1963 ],
1964 'site_health_include_in_info' => true,
1965 'depends-on' => [ 'dynamic_features_allow' => '1' ],
1966 ];
1967
1968 $fields['dynamic_features_allow_sql_clauses'] = [
1969 'name' => 'dynamic_features_allow_sql_clauses',
1970 'label' => $did_init ? __( 'Allow SQL clauses to be used in Dynamic Features', 'pods' ) : '',
1971 'description' => $did_init ? __( 'SQL clauses in general should only be enabled for sites with trusted users. Since WordPress allows anyone to enter any shortcode or block in the editor, any person with the Contributor role or higher could have access to use this.', 'pods' ) : '',
1972 'type' => 'pick',
1973 'default' => version_compare( $first_pods_version, '3.1.0-a-1', '<' ) ? 'simple' : '0',
1974 'pick_format_type' => 'single',
1975 'pick_format_single' => 'radio',
1976 'data' => [
1977 'all' => $did_init ? __( 'Unrestricted - Enable ALL SQL clause usage through dynamic features (only use this if you trust ALL users who have access to create content)', 'pods' ) : '',
1978 'simple' => $did_init ? __( 'Restricted - Enable Simple SQL clause usage (only SELECT, WHERE, and ORDER BY) through dynamic features (only use this if you trust ALL users who have access to create content)', 'pods' ) : '',
1979 '0' => $did_init ? __( 'Disable SQL clause usage through dynamic features', 'pods' ) : '',
1980 ],
1981 'site_health_data' => [
1982 'all' => $did_init ? __( 'Unrestricted', 'pods' ) : '',
1983 'simple' => $did_init ? __( 'Restricted', 'pods' ) : '',
1984 '0' => $did_init ? __( 'Disable', 'pods' ) : '',
1985 ],
1986 'depends-on' => [
1987 'dynamic_features_allow' => '1',
1988 ],
1989 'depends-on-multi' => [
1990 'dynamic_features_enabled' => 'display',
1991 ],
1992 'site_health_include_in_info' => true,
1993 ];
1994
1995 $fields['display_callbacks'] = [
1996 'name' => 'display_callbacks',
1997 'label' => $did_init ? __( 'Display callbacks', 'pods' ) : '',
1998 'description' => $did_init ? __( 'Callbacks can be used when using Pods Templating syntax like {@my_field,my_callback} in your magic tags.', 'pods' ) : '',
1999 'type' => 'pick',
2000 'default' => version_compare( $first_pods_version, '3.1.0-a-1', '<' ) ? 'restricted' : 'customized',
2001 'pick_format_type' => 'single',
2002 'pick_format_single' => 'radio',
2003 'data' => [
2004 'restricted' => $did_init ? __( 'Restricted - Certain system PHP functions are disallowed from being used for security reasons.', 'pods' ) : '',
2005 'customized' => $did_init ? __( 'Customized - Only allow a list of specific PHP function callbacks.', 'pods' ) : '',
2006 '0' => $did_init ? __( 'Disable display callbacks', 'pods' ) : '',
2007 ],
2008 'site_health_data' => [
2009 'restricted' => $did_init ? __( 'Restricted', 'pods' ) : '',
2010 'customized' => $did_init ? __( 'Customized', 'pods' ) : '',
2011 '0' => $did_init ? __( 'Disable', 'pods' ) : '',
2012 ],
2013 'depends-on' => [
2014 'dynamic_features_allow' => '1',
2015 ],
2016 'depends-on-multi' => [
2017 'dynamic_features_enabled' => 'display',
2018 ],
2019 'site_health_include_in_info' => true,
2020 ];
2021
2022 $fields['display_callbacks_allowed'] = [
2023 'name' => 'display_callbacks_allowed',
2024 'label' => $did_init ? __( 'Display callbacks allowed', 'pods' ) : '',
2025 'description' => $did_init ? __( 'Please provide a comma-separated list of PHP function names to allow in callbacks.', 'pods' ) : '',
2026 'type' => 'text',
2027 'default' => 'esc_attr,esc_html',
2028 'depends-on' => [
2029 'dynamic_features_allow' => '1',
2030 'display_callbacks' => 'customized',
2031 ],
2032 'depends-on-multi' => [
2033 'dynamic_features_enabled' => 'display',
2034 ],
2035 'site_health_include_in_info' => true,
2036 ];
2037
2038 return $fields;
2039 }
2040
2041 /**
2042 * Get the bleep placeholder text.
2043 *
2044 * @since 3.1.0
2045 *
2046 * @return string The bleep placeholder text.
2047 */
2048 function pods_access_bleep_placeholder(): string {
2049 return '****************';
2050 }
2051
2052 /**
2053 * Process the value and bleep it if it needs to be.
2054 *
2055 * @since 3.1.0
2056 *
2057 * @param string|mixed $value The value to be bleeped.
2058 *
2059 * @return string|mixed The bleeped text if not empty, otherwise the value as it was.
2060 */
2061 function pods_access_bleep_text( $value ) {
2062 $bleep_text = pods_access_bleep_placeholder();
2063
2064 if ( 0 < strlen( (string) $value ) ) {
2065 $value = $bleep_text;
2066 }
2067
2068 return $value;
2069 }
2070
2071 /**
2072 * Process the data and bleep anything that needs to be.
2073 *
2074 * @since 3.1.0
2075 *
2076 * @param array|object $data The data to be bleeped.
2077 * @param array $additional_bleep_properties The additional properties to be bleeped from objects and arrays.
2078 *
2079 * @return array|object The bleeped data.
2080 */
2081 function pods_access_bleep_data( $data, array $additional_bleep_properties = [] ) {
2082 $bleep_properties = [
2083 'user_pass',
2084 'user_activation_key',
2085 'post_password',
2086 ];
2087
2088 /**
2089 * Allow filtering the additional properties to be bleeped from objects and arrays.
2090 *
2091 * @since 3.1.0
2092 *
2093 * @param array $additional_bleep_properties The additional properties to be bleeped from objects and arrays.
2094 * @param array|object $data The data to be bleeped.
2095 */
2096 $additional_bleep_properties = apply_filters( 'pods_access_bleep_properties', $additional_bleep_properties, $data );
2097
2098 $bleep_properties = array_merge( $bleep_properties, $additional_bleep_properties );
2099
2100 $bleep_text = pods_access_bleep_placeholder();
2101
2102 if ( is_object( $data ) ) {
2103 foreach ( $bleep_properties as $bleep_property ) {
2104 if ( isset( $data->{$bleep_property} ) ) {
2105 $data->{$bleep_property} = 0 < strlen( (string) $data->{$bleep_property} ) ? $bleep_text : '';
2106 }
2107 }
2108 } elseif ( is_array( $data ) ) {
2109 foreach ( $bleep_properties as $bleep_property ) {
2110 if ( isset( $data[ $bleep_property ] ) ) {
2111 $data[ $bleep_property ] = 0 < strlen( (string) $data[ $bleep_property ] ) ? $bleep_text : '';
2112 }
2113 }
2114 }
2115
2116 return $data;
2117 }
2118
2119 /**
2120 * Process the data and bleep anything that needs to be.
2121 *
2122 * @since 3.1.0
2123 *
2124 * @param array $items The items to be bleeped.
2125 * @param array $additional_bleep_properties The additional properties to be bleeped from objects and arrays.
2126 *
2127 * @return array|object The bleeped data.
2128 */
2129 function pods_access_bleep_items( array $items, array $additional_bleep_properties = [] ) {
2130 // Call the pods_access_bleep_data() function for all items in the $items array.
2131 return array_map(
2132 static function ( $item ) use ( $additional_bleep_properties ) {
2133 return pods_access_bleep_data( $item, $additional_bleep_properties );
2134 },
2135 $items
2136 );
2137 }
2138
2139 /**
2140 * Determine whether the SQL fragment is allowed to be used.
2141 *
2142 * @since 3.1.0
2143 *
2144 * @param string $sql The SQL fragment to check.
2145 * @param string $context The SQL fragment context.
2146 * @param array $args {
2147 * The arguments to use.
2148 *
2149 * @type string|null $object_type The object type.
2150 * @type string|null $object_name The object name.
2151 * @type int|string|null $item_id The item ID.
2152 * @type Pods|null $pods The Pods object.
2153 * @type Pod|null $pod The Pod object.
2154 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
2155 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
2156 * }
2157 *
2158 * @return bool Whether the SQL fragment is allowed to be used.
2159 */
2160 function pods_access_sql_fragment_is_allowed( string $sql, string $context, array $args = [] ): bool {
2161 $context = strtoupper( $context );
2162
2163 $info = pods_info_from_args( $args );
2164
2165 /**
2166 * Allows filtering whether the SQL fragment is allowed to be used.
2167 *
2168 * @since 3.1.0
2169 *
2170 * @param bool $allowed Whether the SQL fragment is allowed to be used.
2171 * @param string $sql The SQL fragment to check.
2172 * @param string $context The SQL fragment context.
2173 * @param array $info Pod information.
2174 */
2175 return (bool) apply_filters( 'pods_access_sql_fragment_is_allowed', true, $sql, $context, $info );
2176 }
2177
2178 add_filter( 'pods_access_sql_fragment_is_allowed', 'pods_access_sql_fragment_disallow_mismatch_parenthesis', 10, 2 );
2179 add_filter( 'pods_access_sql_fragment_is_allowed', 'pods_access_sql_fragment_disallow_unsafe_functions', 10, 2 );
2180 add_filter( 'pods_access_sql_fragment_is_allowed', 'pods_access_sql_fragment_disallow_unsafe_tables', 10, 2 );
2181 add_filter( 'pods_access_sql_fragment_is_allowed', 'pods_access_sql_fragment_disallow_double_hyphens', 10, 2 );
2182 add_filter( 'pods_access_sql_fragment_is_allowed', 'pods_access_sql_fragment_disallow_subqueries', 10, 2 );
2183 add_filter( 'pods_access_sql_fragment_is_allowed', 'pods_access_sql_fragment_disallow_post_status', 10, 4 );
2184
2185 /**
2186 * Disallow mismatched parenthesis from being used in SQL fragments.
2187 *
2188 * @since 3.1.0
2189 *
2190 * @param bool $allowed Whether the SQL fragment is allowed to be used.
2191 * @param string $sql The SQL fragment to check.
2192 *
2193 * @return bool Whether the SQL fragment is allowed to be used.
2194 */
2195 function pods_access_sql_fragment_disallow_mismatch_parenthesis( bool $allowed, string $sql ): bool {
2196 return (
2197 $allowed
2198 && substr_count( $sql, '(' ) === substr_count( $sql, ')' )
2199 );
2200 }
2201
2202 /**
2203 * Disallow unsafe functions from being used in SQL fragments.
2204 *
2205 * @since 3.1.0
2206 *
2207 * @param bool $allowed Whether the SQL fragment is allowed to be used.
2208 * @param string $sql The SQL fragment to check.
2209 *
2210 * @return bool Whether the SQL fragment is allowed to be used.
2211 */
2212 function pods_access_sql_fragment_disallow_unsafe_functions( bool $allowed, string $sql ): bool {
2213 if ( ! $allowed ) {
2214 return $allowed;
2215 }
2216
2217 $unsafe_functions = [
2218 'USER',
2219 'DATABASE',
2220 'VERSION',
2221 'FROM_BASE64',
2222 'TO_BASE64',
2223 'SLEEP',
2224 'WAIT_FOR_EXECUTED_GTID_SET',
2225 'WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS',
2226 'MASTER_POS_WAIT',
2227 'SOURCE_POS_WAIT',
2228 'LOAD_FILE',
2229 ];
2230
2231 /**
2232 * Allow filtering the list of unsafe functions to disallow.
2233 *
2234 * @since 3.1.0
2235 *
2236 * @param array $unsafe_functions The list of unsafe functions to disallow.
2237 * @param string $sql The SQL fragment to check.
2238 */
2239 $unsafe_functions = (array) apply_filters( 'pods_access_sql_fragment_disallow_unsafe_functions', $unsafe_functions, $sql );
2240
2241 $unsafe_functions = array_filter( $unsafe_functions );
2242
2243 foreach ( $unsafe_functions as $unsafe_function ) {
2244 if ( 1 === (int) preg_match( '/\s*' . preg_quote( $unsafe_function, '/' ) . '\s*\(/i', $sql ) ) {
2245 return false;
2246 }
2247 }
2248
2249 return $allowed;
2250 }
2251
2252 /**
2253 * Disallow unsafe tables from being used in SQL fragments.
2254 *
2255 * @since 3.1.0
2256 *
2257 * @param bool $allowed Whether the SQL fragment is allowed to be used.
2258 * @param string $sql The SQL fragment to check.
2259 *
2260 * @return bool Whether the SQL fragment is allowed to be used.
2261 */
2262 function pods_access_sql_fragment_disallow_unsafe_tables( bool $allowed, string $sql ): bool {
2263 if ( ! $allowed ) {
2264 return $allowed;
2265 }
2266
2267 $unsafe_tables = [
2268 'mysql.',
2269 'information_schema.',
2270 'performance_schema.',
2271 'sys.',
2272 ];
2273
2274 /**
2275 * Allow filtering the list of unsafe tables to disallow.
2276 *
2277 * @since 3.1.0
2278 *
2279 * @param array $unsafe_tables The list of unsafe tables to disallow.
2280 * @param string $sql The SQL fragment to check.
2281 */
2282 $unsafe_tables = (array) apply_filters( 'pods_access_sql_fragment_disallow_unsafe_tables', $unsafe_tables, $sql );
2283
2284 $unsafe_tables = array_filter( $unsafe_tables );
2285
2286 foreach ( $unsafe_tables as $unsafe_table ) {
2287 if ( 1 === (int) preg_match( '/\s*' . preg_quote( $unsafe_table, '/' ) . '/i', $sql ) ) {
2288 return false;
2289 }
2290 }
2291
2292 return $allowed;
2293 }
2294
2295 /**
2296 * Disallow double hyphens from being used in SQL fragments.
2297 *
2298 * @since 3.1.0
2299 *
2300 * @param bool $allowed Whether the SQL fragment is allowed to be used.
2301 * @param string $sql The SQL fragment to check.
2302 *
2303 * @return bool Whether the SQL fragment is allowed to be used.
2304 */
2305 function pods_access_sql_fragment_disallow_double_hyphens( bool $allowed, string $sql ): bool {
2306 return (
2307 $allowed
2308 && false === strpos( $sql, '--' )
2309 );
2310 }
2311
2312 /**
2313 * Disallow subqueries from being used in SQL fragments.
2314 *
2315 * @since 3.1.0
2316 *
2317 * @param bool $allowed Whether the SQL fragment is allowed to be used.
2318 * @param string $sql The SQL fragment to check.
2319 *
2320 * @return bool Whether the SQL fragment is allowed to be used.
2321 */
2322 function pods_access_sql_fragment_disallow_subqueries( bool $allowed, string $sql ): bool {
2323 return (
2324 $allowed
2325 && 0 === (int) preg_match( '/\s*SELECT(\s|\()+/i', $sql )
2326 );
2327 }
2328
2329 /**
2330 * Disallow post_status from being used in the WHERE/HAVING SQL fragment unless they have admin access.
2331 *
2332 * @since 3.1.0
2333 *
2334 * @param bool $allowed Whether the SQL fragment is allowed to be used.
2335 * @param string $sql The SQL fragment to check.
2336 * @param string $context The SQL fragment context.
2337 * @param array $info Pod information.
2338 *
2339 * @return bool Whether the SQL fragment is allowed to be used.
2340 */
2341 function pods_access_sql_fragment_disallow_post_status( bool $allowed, string $sql, string $context, array $info ): bool {
2342 if ( 'WHERE' !== $context && 'HAVING' !== $context ) {
2343 return $allowed;
2344 }
2345
2346 return (
2347 $allowed
2348 && (
2349 false === stripos( $sql, 'post_status' )
2350 || pods_is_admin( 'edit_posts' )
2351 )
2352 );
2353 }
2354
2355 /**
2356 * Safely unserialize data if it's PHP serialized.
2357 *
2358 * @since 3.1.0
2359 *
2360 * @param string|mixed $data The data to unserialize.
2361 *
2362 * @return array|string|mixed The unserialized data if it was PHP serialized, otherwise the data as it was.
2363 */
2364 function pods_maybe_safely_unserialize( $data ) {
2365 // The $options parameter of unserialize() requires PHP 7.0+.
2366 if ( version_compare( PHP_VERSION, '7.0', '<' ) ) {
2367 // Fall back to normal WP function.
2368 return maybe_unserialize( $data );
2369 }
2370
2371 // Check if the data is serialized.
2372 if ( is_serialized( $data ) ) {
2373 $data = trim( $data );
2374
2375 // Unserialize the data but exclude classes.
2376 return @unserialize( $data, [ 'allowed_classes' => false ] );
2377 }
2378
2379 return $data;
2380 }
2381