PluginProbe ʕ •ᴥ•ʔ
Pods – Custom Content Types and Fields / 3.3.9.2
Pods – Custom Content Types and Fields v3.3.9.2
2.7.31.4 2.8.23.5 2.9.19.5 3.0.10.5 3.1.4.3 3.2.8.4 3.3.9.2 2.8.23.4 2.9.19.4 3.0.10.4 3.1.4.2 3.2.8.3 3.3.9.1 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 4 days ago access.php 4 days ago classes.php 4 days ago compatibility.php 4 days ago data.php 4 days ago forms.php 4 days ago general.php 4 days ago media.php 4 days ago
access.php
3094 lines
1 <?php
2
3 // Don't load directly.
4 if ( ! defined( 'ABSPATH' ) ) {
5 die( '-1' );
6 }
7
8 /**
9 * @package Pods\Global\Functions\Access
10 */
11
12 use Pods\Whatsit\Pod;
13
14 /**
15 * Normalize Pod information with a Pods object or object info.
16 *
17 * @since 3.1.0
18 *
19 * @param array $args {
20 * The arguments to use.
21 *
22 * @type string|null $object_type The object type.
23 * @type string|null $object_name The object name.
24 * @type int|string|null $item_id The item ID.
25 * @type Pods|null $pods The Pods object.
26 * @type Pod|null $pod The Pod object.
27 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
28 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
29 * }
30 *
31 * @return array {
32 * The arguments to use.
33 *
34 * @type string|null $object_type The object type (if set).
35 * @type string|null $object_name The object name (if set).
36 * @type int|string|null $item_id The item ID (if set).
37 * @type Pods|null $pods The Pods object (if built or provided).
38 * @type Pod|null $pod The Pod object (if built or provided).
39 * }
40 */
41 function pods_info_from_args( array $args ): array {
42 $info = [
43 'object_type' => null,
44 'object_name' => null,
45 'item_id' => null,
46 'pods' => null,
47 'pod' => null,
48 ];
49
50 $build_pods = false;
51 $build_pod = false;
52
53 if ( isset( $args['build_pods'] ) ) {
54 $build_pods = $args['build_pods'];
55
56 unset( $args['build_pods'] );
57 }
58
59 if ( isset( $args['build_pod'] ) ) {
60 $build_pod = $args['build_pod'];
61
62 unset( $args['build_pod'] );
63 }
64
65 // Merge in the args with the defaults.
66 $info = array_merge( $info, $args );
67
68 $object_type_set = null !== $info['object_type'];
69 $object_name_set = null !== $info['object_name'];
70
71 // Maybe auto-set the object name from the type if we can.
72 if (
73 $object_type_set
74 && ! $object_name_set
75 && in_array( $info['object_type'], [ 'comment', 'media', 'user' ], true )
76 ) {
77 $info['object_name'] = $info['object_type'];
78
79 $object_name_set = true;
80 }
81
82 // Normalize the Pods info to null if it's not valid.
83 if (
84 $info['pods'] instanceof Pods
85 && ! $info['pods']->is_valid()
86 ) {
87 $info['pods'] = null;
88 }
89
90 // Maybe build the Pods object from the info.
91 if (
92 $build_pods
93 && $object_name_set
94 && ! $info['pods'] instanceof Pods
95 ) {
96 $pods = pods_get_instance( $info['object_name'], $info['item_id'], true );
97
98 if (
99 $pods instanceof Pods
100 && $pods->is_valid()
101 && (
102 empty( $info['object_type'] )
103 || $info['object_type'] === $pods->pod_data->get_type()
104 )
105 ) {
106 $info['pods'] = $pods;
107
108 if ( ! $info['pod'] instanceof Pod ) {
109 $info['pod'] = clone $pods->pod_data;
110 }
111 }
112 } elseif (
113 $info['pods'] instanceof Pods
114 && $info['pods']->is_valid()
115 && ! $info['pod'] instanceof Pod
116 ) {
117 $info['pod'] = clone $info['pods']->pod_data;
118 }
119
120 // Maybe build the Pod object from the info.
121 if (
122 $build_pod
123 && $object_name_set
124 && ! $info['pod'] instanceof Pod
125 ) {
126 try {
127 $pod = pods_api()->load_pod( [
128 'name' => $info['object_name'],
129 ] );
130 } catch ( Exception $e ) {
131 $pod = null;
132 }
133
134 if (
135 $pod instanceof Pod
136 && (
137 empty( $info['object_type'] )
138 || $info['object_type'] === $pod->get_type()
139 )
140 ) {
141 $info['pod'] = $pod;
142 }
143 }
144
145 if ( $info['pod'] instanceof Pod ) {
146 $info['object_type'] = $info['pod']->get_type();
147 $info['object_name'] = $info['pod']->get_name();
148 }
149
150 return $info;
151 }
152
153 /**
154 * Determine whether the current user has access to an object.
155 *
156 * @since 3.1.0
157 *
158 * @param array $args {
159 * The arguments to use.
160 *
161 * @type string|null $object_type The object type.
162 * @type string|null $object_name The object name.
163 * @type int|string|null $item_id The item ID.
164 * @type Pods|null $pods The Pods object.
165 * @type Pod|null $pod The Pod object.
166 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
167 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
168 * }
169 *
170 * @param int|null $user_id The user ID to check against, set to 0 or null for anonymous access check.
171 * @param string $access_type The type of access to check for (read, add, edit, delete).
172 * @param string|null $context The unique slug that can be referenced by hooks for context.
173 *
174 * @return bool Whether the current user has access to an object.
175 */
176 function pods_user_can_access_object( array $args, ?int $user_id, string $access_type = 'edit', ?string $context = null ): bool {
177 $info = pods_info_from_args( $args );
178
179 if ( null === $user_id ) {
180 $user_id = 0;
181 }
182
183 // Check if the user exists.
184 $user = get_userdata( $user_id );
185
186 if ( ! $user instanceof WP_User ) {
187 // If the user does not exist and it was not anonymous, do not allow access to an invalid user.
188 if ( 0 < $user_id ) {
189 return false;
190 }
191
192 // If the user was 0 to begin with (anonymous) then set up a user object to work with.
193 $user = new WP_User();
194 }
195
196 // Determine if this is a user in WP that has full access.
197 if ( $user_id && pods_is_user_admin( $user_id ) ) {
198 return true;
199 }
200
201 if ( 'pod' === $info['object_type'] || 'table' === $info['object_type'] ) {
202 // If no object name is provided, we cannot check access.
203 if ( empty( $info['object_name'] ) ) {
204 return false;
205 }
206
207 // Determine if this user has full content access.
208 if ( $user->has_cap( 'pods_content' ) ) {
209 return true;
210 }
211 }
212
213 $capabilities = pods_access_map_capabilities( $info, $user_id );
214
215 // Unsupported capabilities returned.
216 if ( null === $capabilities ) {
217 return false;
218 }
219
220 /**
221 * Allow filtering the list of capabilities used for checking access against an object.
222 *
223 * @since 3.1.0
224 *
225 * @param array $capabilities The list of capabilities used for checking access against an object.
226 * @param int $user_id The user ID to check against.
227 * @param array $info {
228 * The normalized Pod information referenced.
229 *
230 * @type string|null $object_type The object type (if set).
231 * @type string|null $object_name The object name (if set).
232 * @type int|string|null $item_id The item ID (if set).
233 * @type Pods|null $pods The Pods object (if built or provided).
234 * @type Pod|null $pod The Pod object (if built or provided).
235 * }
236 *
237 * @param string $access_type The type of access to check for (read, add, edit, delete).
238 * @param string|null $context The unique slug that can be referenced by hooks for context.
239 */
240 $capabilities = (array) apply_filters(
241 'pods_user_can_access_object_get_capabilities',
242 $capabilities,
243 $user_id,
244 $info,
245 $access_type,
246 $context
247 );
248
249 // No capability mapped, do not allow access.
250 if ( ! array_key_exists( $access_type, $capabilities ) ) {
251 return false;
252 }
253
254 /**
255 * Allow filtering whether a user has access to an object before the normal capability check runs.
256 *
257 * @since 3.1.0
258 *
259 * @param null|bool $can_access Whether a user has access to an object (return null to run normal check).
260 * @param int $user_id The user ID to check against.
261 * @param array $info {
262 * The normalized Pod information referenced.
263 *
264 * @type string|null $object_type The object type (if set).
265 * @type string|null $object_name The object name (if set).
266 * @type int|string|null $item_id The item ID (if set).
267 * @type Pods|null $pods The Pods object (if built or provided).
268 * @type Pod|null $pod The Pod object (if built or provided).
269 * }
270 *
271 * @param string $access_type The type of access to check for (read, add, edit, delete).
272 * @param string|null $context The unique slug that can be referenced by hooks for context.
273 * @param array $capabilities The list of capabilities used for checking access against an object.
274 */
275 $can_access = apply_filters(
276 'pods_user_can_access_object_pre_check',
277 null,
278 $user_id,
279 $info,
280 $access_type,
281 $context,
282 $capabilities
283 );
284
285 // Check for access override and return that instead.
286 if ( null !== $can_access ) {
287 return $can_access;
288 }
289
290 // If we are allowing all access, null will be set for the capability.
291 if ( null === $capabilities[ $access_type ] ) {
292 $can_access = true;
293 } else {
294 // Support multiple capability checks ("OR" logic).
295 $capabilities[ $access_type ] = (array) $capabilities[ $access_type ];
296
297 $can_access = false;
298
299 foreach ( $capabilities[ $access_type ] as $capability ) {
300 if ( $info['item_id'] ) {
301 $can_access = $user->has_cap( $capability, $info['item_id'] );
302 } else {
303 $can_access = $user->has_cap( $capability );
304 }
305
306 if ( $can_access ) {
307 break;
308 }
309 }
310 }
311
312 $is_read_access = 'read' === $access_type;
313
314 // Check for password-protected post.
315 if (
316 $can_access
317 && 'post_type' === $info['object_type']
318 && $info['item_id']
319 && (
320 (
321 $is_read_access
322 && pods_access_bypass_post_with_password( $info )
323 )
324 || (
325 ! $is_read_access
326 && post_password_required( $info['item_id'] )
327 )
328 )
329 ) {
330 $can_access = false;
331 }
332
333 /**
334 * Allow filtering whether a user has access to an object after the normal capability check runs.
335 *
336 * @since 3.1.0
337 *
338 * @param bool $can_access Whether a user has access to an object.
339 * @param int $user_id The user ID to check against.
340 * @param array $info {
341 * The normalized Pod information referenced.
342 *
343 * @type string|null $object_type The object type (if set).
344 * @type string|null $object_name The object name (if set).
345 * @type int|string|null $item_id The item ID (if set).
346 * @type Pods|null $pods The Pods object (if built or provided).
347 * @type Pod|null $pod The Pod object (if built or provided).
348 * }
349 *
350 * @param string $access_type The type of access to check for (read, add, edit, delete).
351 * @param string|null $context The unique slug that can be referenced by hooks for context.
352 * @param array $capabilities The list of capabilities used for checking access against an object.
353 */
354 return (bool) apply_filters(
355 'pods_user_can_access_object',
356 $can_access,
357 $user_id,
358 $info,
359 $access_type,
360 $context,
361 $capabilities
362 );
363 }
364
365 /**
366 * Determine whether the current user has access to an object.
367 *
368 * @since 3.1.0
369 *
370 * @param array $args {
371 * The arguments to use.
372 *
373 * @type string|null $object_type The object type.
374 * @type string|null $object_name The object name.
375 * @type int|string|null $item_id The item ID.
376 * @type Pods|null $pods The Pods object.
377 * @type Pod|null $pod The Pod object.
378 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
379 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
380 * }
381 *
382 * @param string $access_type The type of access to check for (read, add, edit, delete).
383 * @param string|null $context The unique slug that can be referenced by hooks for context.
384 *
385 * @return bool Whether the current user has access to an object.
386 */
387 function pods_current_user_can_access_object( array $args, string $access_type = 'edit', ?string $context = null ): bool {
388 $user_id = null;
389
390 if ( is_user_logged_in() ) {
391 $user_id = get_current_user_id();
392 }
393
394 return pods_user_can_access_object( $args, $user_id, $access_type, $context );
395 }
396
397 /**
398 * Build and map the capabilities that a specific object type/name/ID have in relation to a user ID.
399 *
400 * @since 3.1.0
401 *
402 * @param array $args {
403 * The arguments to use.
404 *
405 * @type string|null $object_type The object type.
406 * @type string|null $object_name The object name.
407 * @type int|string|null $item_id The item ID.
408 * @type Pods|null $pods The Pods object.
409 * @type Pod|null $pod The Pod object.
410 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
411 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
412 * }
413 *
414 * @param int|null $user_id The user ID accessing the object.
415 * @param bool $strict Whether to strictly get the capabilities or have the 'read' capability evaluate to null if it's public (defaults to false).
416 *
417 * @return array|null The capabilities that a specific object type/name/ID have in relation to a user ID, or null if invalid.
418 */
419 function pods_access_map_capabilities( array $args, ?int $user_id = null, bool $strict = false ): ?array {
420 $args['build_pods'] = true;
421 $args['build_pod'] = true;
422
423 $info = pods_info_from_args( $args );
424
425 // If no object type or name, we cannot check access.
426 if ( empty( $info['object_type'] ) || empty( $info['object_name'] ) ) {
427 return null;
428 }
429
430 $wp_object = null;
431
432 $capabilities = [];
433
434 if ( 'post_type' === $info['object_type'] ) {
435 $info['item_id'] = (int) $info['item_id'];
436
437 if ( $info['item_id'] ) {
438 $capabilities['read'] = 'read_post';
439 $capabilities['edit'] = 'edit_post';
440 $capabilities['delete'] = 'delete_post';
441 } else {
442 $capabilities['read'] = 'read';
443 $capabilities['edit'] = 'edit_posts';
444 $capabilities['delete'] = 'delete_posts';
445 }
446
447 $capabilities['add'] = 'create_posts';
448 $capabilities['read_private'] = 'read_private_posts';
449 $capabilities['edit_others'] = 'edit_others_posts';
450 $capabilities['delete_others'] = 'delete_others_posts';
451 $capabilities['delete_published'] = 'delete_published_posts';
452 $capabilities['delete_private'] = 'delete_private_posts';
453
454 // Maybe map capabilities to the post type.
455 $wp_object = get_post_type_object( $info['object_name'] );
456
457 if ( $info['item_id'] ) {
458 $post = get_post( $info['item_id'] );
459
460 // If the post was found, do fine-grained access checks.
461 if ( $post instanceof WP_Post ) {
462 $status_obj = get_post_status_object( $post->post_status );
463
464 // Check if the person is allowed to read other posts.
465 if (
466 $user_id
467 && $post->post_author
468 && (int) $user_id === (int) $post->post_author
469 ) {
470 // This is their own post, they can have access.
471 $capabilities['read'] = 'read';
472 } elseif (
473 ! $status_obj
474 || $status_obj->private
475 ) {
476 // This is a private post, check private post capability.
477 $capabilities['read'] = $capabilities['read_private'];
478 }
479 }
480 }
481 } elseif ( 'taxonomy' === $info['object_type'] ) {
482 $info['item_id'] = (int) $info['item_id'];
483
484 $capabilities['read'] = 'read';
485 $capabilities['add'] = 'manage_terms';
486 $capabilities['edit'] = 'edit_terms';
487 $capabilities['delete'] = 'delete_terms';
488
489 // Maybe map capabilities to the post type.
490 $wp_object = get_taxonomy( $info['object_name'] );
491 } elseif ( 'user' === $info['object_type'] ) {
492 $info['item_id'] = (int) $info['item_id'];
493
494 $capabilities['read'] = 'list_users';
495 $capabilities['add'] = 'create_users';
496 $capabilities['edit'] = 'edit_users';
497 $capabilities['delete'] = 'delete_users';
498
499 // If an object ID is provided, check for access for that specific user.
500 if ( ! empty( $info['item_id'] ) ) {
501 $capabilities['edit'] = 'edit_user';
502 $capabilities['delete'] = 'delete_user';
503 }
504
505 // Fake the WP object for the logic below.
506 $wp_object = (object) [
507 'public' => false,
508 'cap' => (object) [],
509 ];
510 } elseif ( 'media' === $info['object_type'] ) {
511 $info['item_id'] = (int) $info['item_id'];
512
513 $capabilities['read'] = 'read';
514 $capabilities['add'] = 'upload_files';
515 $capabilities['edit'] = 'upload_files';
516 $capabilities['delete'] = 'upload_files';
517
518 // Fake the WP object for the logic below.
519 $wp_object = (object) [
520 'public' => false,
521 'cap' => (object) [],
522 ];
523 } elseif ( 'comment' === $info['object_type'] ) {
524 $info['item_id'] = (int) $info['item_id'];
525
526 $capabilities['read'] = 'read';
527 $capabilities['add'] = 1 === (int) get_option( 'comment_registration' ) ? 'read' : null;
528 $capabilities['edit'] = 'moderate_comments';
529 $capabilities['delete'] = 'moderate_comments';
530
531 // If an object ID is provided, check for access for that specific user.
532 if ( ! empty( $info['item_id'] ) ) {
533 $capabilities['edit'] = 'edit_comment';
534 }
535
536 // Fake the WP object for the logic below.
537 $wp_object = (object) [
538 'public' => true,
539 'cap' => (object) [],
540 ];
541 } elseif ( 'settings' === $info['object_type'] ) {
542 $capabilities['read'] = 'manage_options';
543 $capabilities['edit'] = 'pods_edit_' . $info['object_name'];
544 $capabilities['delete'] = 'manage_options';
545
546 // Fake the WP object for the logic below.
547 $wp_object = (object) [
548 'public' => false,
549 'cap' => (object) [],
550 ];
551 } elseif ( 'pod' === $info['object_type'] || 'table' === $info['object_type'] ) {
552 $info['item_id'] = (int) $info['item_id'];
553
554 $capabilities['read'] = 'pods_read_' . $info['object_name'];
555 $capabilities['add'] = 'pods_add_' . $info['object_name'];
556 $capabilities['edit'] = 'pods_edit_' . $info['object_name'];
557 $capabilities['delete'] = 'pods_delete_' . $info['object_name'];
558 $capabilities['edit_others'] = 'pods_edit_others_' . $info['object_name'];
559 $capabilities['delete_others'] = 'pods_delete_others_' . $info['object_name'];
560
561 $is_public = false;
562
563 if ( $info['pods'] instanceof Pods && $info['pod'] instanceof Pod ) {
564 // If an object ID is provided, check for access for that specific item.
565 if ( $info['item_id'] && $info['pods']->exists() ) {
566 // Check for author field.
567 $author_field = $info['pod']->get_field( 'author' );
568
569 $author_user_id = $author_field ? (int) $info['pods']->field( $author_field->get_name() . '.ID' ) : null;
570
571 // If we have an author field, check if they are the author.
572 if ( $author_field ) {
573 if ( $user_id && $author_user_id === $user_id ) {
574 // This is their own post, they can also have access if have edit access.
575 $capabilities['read'] = [
576 $capabilities['read'],
577 'pods_edit_' . $info['object_name'],
578 ];
579 } else {
580 // This is not their post, check if they have access to others.
581 $capabilities['edit'] = 'pods_edit_others_' . $info['object_name'];
582 $capabilities['delete'] = 'pods_delete_others_' . $info['object_name'];
583 }
584 }
585 }
586
587 $is_public = $info['pod']->get_arg( 'public', '0', true );
588 $is_public = filter_var( $is_public, FILTER_VALIDATE_BOOLEAN );
589
590 // Fake the WP object for the logic below.
591 $wp_object = (object) [
592 'public' => $is_public,
593 'cap' => (object) [],
594 ];
595 }
596
597 if ( $is_public ) {
598 $capabilities['read'] = 'read';
599 }
600 }
601
602 // If no post type object is found, we cannot check access.
603 if ( ! $wp_object ) {
604 return null;
605 }
606
607 // Check if there are any capabilities mapped for this type object.
608 foreach ( $capabilities as $access_type => $capability ) {
609 if ( $capability ) {
610 if ( is_array( $capability ) ) {
611 foreach ( $capability as $k => $cap ) {
612 if ( isset( $wp_object->cap->{$cap} ) ) {
613 $capabilities[ $access_type ][ $k ] = $wp_object->cap->{$cap};
614 }
615 }
616 } elseif ( isset( $wp_object->cap->{$capability} ) ) {
617 $capabilities[ $access_type ] = $wp_object->cap->{$capability};
618 }
619 }
620 }
621
622 // If the object is public, allow read for anyone even logged out.
623 if ( ! $strict && $wp_object->public && 'read' === $capabilities['read'] && ! $user_id ) {
624 $capabilities['read'] = null;
625 }
626
627 /**
628 * Allow filtering the list of capabilities used for checking access against an object type or singular object.
629 *
630 * @since 3.1.0
631 *
632 * @param array $capabilities The list of capabilities used for checking access against an object type or singular object.
633 * @param int $user_id The user ID to check against.
634 * @param array $info {
635 * The normalized Pod information referenced.
636 *
637 * @type string|null $object_type The object type (if set).
638 * @type string|null $object_name The object name (if set).
639 * @type int|string|null $item_id The item ID (if set).
640 * @type Pods|null $pods The Pods object (if built or provided).
641 * @type Pod|null $pod The Pod object (if built or provided).
642 * }
643 */
644 return (array) apply_filters(
645 'pods_access_map_capabilities',
646 $capabilities,
647 $user_id,
648 $info
649 );
650 }
651
652 /**
653 * Determine whether the object type/name is public.
654 *
655 * @since 3.1.0
656 *
657 * @param array $args {
658 * The arguments to use.
659 *
660 * @type string|null $object_type The object type.
661 * @type string|null $object_name The object name.
662 * @type int|string|null $item_id The item ID.
663 * @type Pods|null $pods The Pods object.
664 * @type Pod|null $pod The Pod object.
665 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
666 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
667 * }
668 *
669 * @param string $context The context we are checking from (defaults to shortcode).
670 *
671 * @return bool Whether the object type/name is public.
672 */
673 function pods_is_type_public( array $args, string $context = 'shortcode' ): bool {
674 $args['build_pod'] = true;
675
676 $info = pods_info_from_args( $args );
677
678 $is_public = true;
679
680 $pod_has_public = null;
681
682 $is_post_type = 'post_type' === $info['object_type'];
683 $is_taxonomy = 'taxonomy' === $info['object_type'];
684 $is_pod = 'pod' === $info['object_type'];
685 $is_settings_pod = 'settings' === $info['object_type'];
686
687 $is_shortcode_context = 'shortcode' === $context;
688
689 if (
690 $info['pod'] instanceof Pod
691 && (
692 $is_post_type
693 || $is_taxonomy
694 || $is_pod
695 || $is_settings_pod
696 )
697 ) {
698 $is_extended = $info['pod']->is_extended();
699
700 if ( ! $is_extended ) {
701 $is_public = $info['pod']->get_arg( 'public', null, true );
702
703 if ( null !== $is_public ) {
704 $pod_has_public = true;
705
706 $is_public = filter_var( $is_public, FILTER_VALIDATE_BOOLEAN );
707
708 if ( $is_post_type || $is_taxonomy ) {
709 $is_public = $is_public && 1 === (int) $info['pod']->get_arg( 'publicly_queryable', $is_public, true );
710 }
711 }
712 }
713 }
714
715 // Maybe handle looking up the visibility based on the object type.
716 if ( null === $pod_has_public ) {
717 if ( $is_post_type ) {
718 // If no object name is provided, we cannot check if it is public.
719 if ( empty( $info['object_name'] ) ) {
720 $is_public = false;
721 } else {
722 $post_type_object = get_post_type_object( $info['object_name'] );
723
724 // Post type not found.
725 if ( ! $post_type_object ) {
726 $is_public = false;
727 } else {
728 $is_public = $post_type_object->public && $post_type_object->publicly_queryable;
729 }
730 }
731 } elseif ( $is_taxonomy ) {
732 // If no object name is provided, we cannot check if it is public.
733 if ( empty( $info['object_name'] ) ) {
734 $is_public = false;
735 } else {
736 $taxonomy_object = get_taxonomy( $info['object_name'] );
737
738 // Post type not found.
739 if ( ! $taxonomy_object ) {
740 $is_public = false;
741 } else {
742 $is_public = $taxonomy_object->public && $taxonomy_object->publicly_queryable;
743 }
744 }
745 } elseif ( 'user' === $info['object_type'] ) {
746 // Users are not public for shortcodes.
747 if ( $is_shortcode_context ) {
748 $is_public = false;
749 }
750 } elseif ( $is_pod || $is_settings_pod ) {
751 // Pods need special default handling for shortcodes.
752 if ( $is_shortcode_context ) {
753 $first_pods_version = get_option( 'pods_framework_version_first' );
754 $first_pods_version = '' === $first_pods_version ? PODS_VERSION : $first_pods_version;
755
756 $is_public = version_compare( $first_pods_version, '3.1.0-a-1', '<' ) ? true : false;
757 }
758 }
759 }
760
761 /**
762 * Allow filtering whether the object type/name is public.
763 *
764 * @since 3.1.0
765 *
766 * @param bool $is_public Whether the object type/name is public.
767 * @param array $info {
768 * The normalized Pod information referenced.
769 *
770 * @type string|null $object_type The object type (if set).
771 * @type string|null $object_name The object name (if set).
772 * @type int|string|null $item_id The item ID (if set).
773 * @type Pods|null $pods The Pods object (if built or provided).
774 * @type Pod|null $pod The Pod object (if built or provided).
775 * }
776 *
777 * @param string|null $context The context we are checking from (shortcode or null).
778 */
779 return (bool) apply_filters(
780 'pods_is_type_public',
781 $is_public,
782 $info,
783 $context
784 );
785 }
786
787 /**
788 * Determine whether a post should be bypassed because it it has a password.
789 *
790 * @since 3.1.0
791 *
792 * @param array $args {
793 * The arguments to use.
794 *
795 * @type string|null $object_type The object type.
796 * @type string|null $object_name The object name.
797 * @type int|string|null $item_id The item ID.
798 * @type Pods|null $pods The Pods object.
799 * @type Pod|null $pod The Pod object.
800 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
801 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
802 * }
803 *
804 * @return bool Whether a post should be bypassed because it it has a password.
805 */
806 function pods_access_bypass_post_with_password( array $args ): bool {
807 $info = pods_info_from_args( $args );
808
809 if ( 'post_type' !== $info['object_type'] || ! $info['item_id'] ) {
810 return false;
811 }
812
813 $post = get_post( (int) $info['item_id'] );
814
815 if ( ! $post instanceof WP_Post ) {
816 return false;
817 }
818
819 // Bypass posts that have a password required but not provided.
820 $bypass_post_with_password = post_password_required( $post );
821
822 /**
823 * Allow filtering whether a post should be bypassed because it it has a password.
824 *
825 * @since 3.1.0
826 *
827 * @param bool $bypass_post_with_password Whether a post should be bypassed because it it has a password.
828 * @param array $info {
829 * The normalized Pod information referenced.
830 *
831 * @type string|null $object_type The object type (if set).
832 * @type string|null $object_name The object name (if set).
833 * @type int|string|null $item_id The item ID (if set).
834 * @type Pods|null $pods The Pods object (if built or provided).
835 * @type Pod|null $pod The Pod object (if built or provided).
836 * }
837 */
838 return (bool) apply_filters(
839 'pods_access_bypass_post_with_password',
840 $bypass_post_with_password,
841 $info
842 );
843 }
844
845 /**
846 * Determine whether a post should be bypassed because it is private and capabilities are not met.
847 *
848 * @since 3.1.0
849 *
850 * @param array $args {
851 * The arguments to use.
852 *
853 * @type string|null $object_type The object type.
854 * @type string|null $object_name The object name.
855 * @type int|string|null $item_id The item ID.
856 * @type Pods|null $pods The Pods object.
857 * @type Pod|null $pod The Pod object.
858 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
859 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
860 * }
861 *
862 * @return bool Whether a post should be bypassed because it is private and capabilities are not met.
863 */
864 function pods_access_bypass_private_post( array $args ): bool {
865 $info = pods_info_from_args( $args );
866
867 if ( 'post_type' !== $info['object_type'] || ! $info['item_id'] ) {
868 return false;
869 }
870
871 $post = get_post( $info['item_id'] );
872
873 if ( ! $post instanceof WP_Post ) {
874 return false;
875 }
876
877 $bypass_private_post = false;
878
879 if ( ! is_post_publicly_viewable( $post ) ) {
880 $can_use_unrestricted = false;
881
882 // Check Pod dynamic features if the status is public.
883 if ( is_post_status_viewable( $post->post_status ) ) {
884 $can_use_unrestricted = pods_can_use_dynamic_feature_unrestricted( $info, 'display', 'read' );
885 }
886
887 if ( $can_use_unrestricted ) {
888 $bypass_private_post = false;
889 } else {
890 $bypass_private_post = ! pods_current_user_can_access_object( $info, 'read' );
891 }
892 }
893
894 /**
895 * Allow filtering whether a post should be bypassed because it is private.
896 *
897 * @since 3.1.0
898 *
899 * @param bool $bypass_private_post Whether a post should be bypassed because it is private.
900 * @param array $info {
901 * The normalized Pod information referenced.
902 *
903 * @type string|null $object_type The object type (if set).
904 * @type string|null $object_name The object name (if set).
905 * @type int|string|null $item_id The item ID (if set).
906 * @type Pods|null $pods The Pods object (if built or provided).
907 * @type Pod|null $pod The Pod object (if built or provided).
908 * }
909 */
910 return (bool) apply_filters(
911 'pods_access_bypass_private_post',
912 $bypass_private_post,
913 $info
914 );
915 }
916
917 /**
918 * Determine whether dynamic features can be used.
919 *
920 * @since 3.1.0
921 *
922 * @return bool Whether dynamic features can be used.
923 */
924 function pods_can_use_dynamic_features( ?Pod $pod = null ): bool {
925 // Check if the constant is defined and only override if no $pod is set or dynamic features are totally disabled.
926 if (
927 defined( 'PODS_DYNAMIC_FEATURES_ALLOW' )
928 && (
929 ! $pod
930 || ! PODS_DYNAMIC_FEATURES_ALLOW
931 )
932 ) {
933 return PODS_DYNAMIC_FEATURES_ALLOW;
934 }
935
936 $can_use_dynamic_features = apply_filters( 'pods_access_can_use_dynamic_features', null, $pod );
937
938 if ( is_bool( $can_use_dynamic_features ) ) {
939 return $can_use_dynamic_features;
940 }
941
942 // Check if all dynamic features are disabled.
943 $dynamic_features_allow = pods_get_setting( 'dynamic_features_allow', '1' );
944 $dynamic_features_allow = filter_var( $dynamic_features_allow, FILTER_VALIDATE_BOOLEAN );
945
946 if ( $dynamic_features_allow && $pod instanceof Pod ) {
947 // Check if all dynamic features are disabled for the Pod.
948 $dynamic_features_allow = $pod->get_arg( 'dynamic_features_allow', 'inherit' );
949
950 if ( 'inherit' === $dynamic_features_allow ) {
951 $dynamic_features_allow = pods_is_type_public(
952 [
953 'pod' => $pod,
954 ]
955 );
956 } else {
957 $dynamic_features_allow = filter_var( $dynamic_features_allow, FILTER_VALIDATE_BOOLEAN );
958 }
959 }
960
961 return $dynamic_features_allow;
962 }
963
964 /**
965 * Determine whether any or a specific dynamic feature can be used.
966 *
967 * @since 3.1.0
968 *
969 * @param string $type The dynamic feature type.
970 *
971 * @return bool Whether any or a specific dynamic feature can be used.
972 */
973 function pods_can_use_dynamic_feature( string $type ): bool {
974 if ( ! pods_can_use_dynamic_features() ) {
975 return false;
976 }
977
978 if ( empty( $type ) ) {
979 return false;
980 }
981
982 // Handle the constants.
983 if ( 'view' === $type && defined( 'PODS_SHORTCODE_ALLOW_VIEWS' ) && ! PODS_SHORTCODE_ALLOW_VIEWS ) {
984 return false;
985 }
986
987 $can_use_dynamic_feature = apply_filters( 'pods_access_can_use_dynamic_feature', null, $type );
988
989 if ( is_bool( $can_use_dynamic_feature ) ) {
990 return $can_use_dynamic_feature;
991 }
992
993 $dynamic_features_enabled = (array) pods_get_setting( 'dynamic_features_enabled', [
994 'display',
995 'form',
996 ] );
997 $dynamic_features_enabled = array_filter( $dynamic_features_enabled );
998
999 $constant_dynamic_features_enabled = defined( 'PODS_DYNAMIC_FEATURES_ENABLED' ) ? PODS_DYNAMIC_FEATURES_ENABLED : false;
1000
1001 if ( false !== $constant_dynamic_features_enabled && ! is_array( $constant_dynamic_features_enabled ) ) {
1002 $constant_dynamic_features_enabled = explode( ',', $constant_dynamic_features_enabled );
1003 $constant_dynamic_features_enabled = array_filter( $constant_dynamic_features_enabled );
1004
1005 $dynamic_features_enabled = $constant_dynamic_features_enabled;
1006 }
1007
1008 if ( empty( $dynamic_features_enabled ) ) {
1009 return false;
1010 }
1011
1012 return in_array( $type, $dynamic_features_enabled, true );
1013 }
1014
1015 /**
1016 * Determine whether specific dynamic feature is unrestricted.
1017 *
1018 * @since 3.1.0
1019 *
1020 * @param array $args {
1021 * The arguments to use.
1022 *
1023 * @type string|null $object_type The object type.
1024 * @type string|null $object_name The object name.
1025 * @type int|string|null $item_id The item ID.
1026 * @type Pods|null $pods The Pods object.
1027 * @type Pod|null $pod The Pod object.
1028 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
1029 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
1030 * }
1031 *
1032 * @param string $type The dynamic feature type.
1033 * @param string $mode The dynamic feature mode (like "add" or "edit" for the form feature).
1034 *
1035 * @return bool Whether specific dynamic feature is unrestricted.
1036 */
1037 function pods_can_use_dynamic_feature_unrestricted( array $args, string $type, ?string $mode = null ): bool {
1038 if ( ! pods_can_use_dynamic_feature( $type ) ) {
1039 return false;
1040 }
1041
1042 if ( defined( 'PODS_DYNAMIC_FEATURES_RESTRICT' ) && ! PODS_DYNAMIC_FEATURES_RESTRICT ) {
1043 return true;
1044 }
1045
1046 $can_use_dynamic_features_unrestricted = apply_filters( 'pods_access_can_use_dynamic_features_unrestricted', null, $args, $type, $mode );
1047
1048 if ( is_bool( $can_use_dynamic_features_unrestricted ) ) {
1049 return $can_use_dynamic_features_unrestricted;
1050 }
1051
1052 $can_use_unrestricted = false;
1053
1054 $args['build_pod'] = true;
1055
1056 $info = pods_info_from_args( $args );
1057
1058 if ( ! $info['pod'] ) {
1059 $can_use_unrestricted = false;
1060 } else {
1061 $is_public_content_type = pods_is_type_public( $info );
1062
1063 $default_restricted_dynamic_features = [
1064 'form',
1065 ];
1066
1067 if ( ! $is_public_content_type ) {
1068 $default_restricted_dynamic_features[] = 'display';
1069 }
1070
1071 $default_restricted_dynamic_features_forms = [
1072 'edit',
1073 ];
1074
1075 if ( ! $is_public_content_type ) {
1076 $default_restricted_dynamic_features_forms[] = 'add';
1077 }
1078
1079 // Check if all dynamic features are unrestricted.
1080 $restrict_dynamic_features = $info['pod']->get_arg( 'restrict_dynamic_features', '1' );
1081 $restrict_dynamic_features = filter_var( $restrict_dynamic_features, FILTER_VALIDATE_BOOLEAN );
1082
1083 if ( ! $restrict_dynamic_features ) {
1084 $can_use_unrestricted = true;
1085 } elseif ( ! empty( $type ) ) {
1086 if ( defined( 'PODS_DYNAMIC_FEATURES_RESTRICTED' ) && false !== PODS_DYNAMIC_FEATURES_RESTRICTED ) {
1087 $constant_restricted_dynamic_features = PODS_DYNAMIC_FEATURES_RESTRICTED;
1088
1089 if ( ! is_array( $constant_restricted_dynamic_features ) ) {
1090 $constant_restricted_dynamic_features = explode( ',', $constant_restricted_dynamic_features );
1091 }
1092
1093 $restricted_dynamic_features = $constant_restricted_dynamic_features;
1094 } else {
1095 $restricted_dynamic_features = (array) $info['pod']->get_arg( 'restricted_dynamic_features', $default_restricted_dynamic_features );
1096 }
1097
1098 $restricted_dynamic_features = array_filter( $restricted_dynamic_features );
1099
1100 if ( empty( $restricted_dynamic_features ) ) {
1101 $can_use_unrestricted = true;
1102 } else {
1103 $can_use_unrestricted = ! in_array( $type, $restricted_dynamic_features, true );
1104 }
1105
1106 if ( ! $can_use_unrestricted && 'form' === $type && $mode ) {
1107 if ( defined( 'PODS_DYNAMIC_FEATURES_RESTRICTED_FORMS' ) && false !== PODS_DYNAMIC_FEATURES_RESTRICTED_FORMS ) {
1108 $constant_restricted_dynamic_features_forms = PODS_DYNAMIC_FEATURES_RESTRICTED_FORMS;
1109
1110 if ( ! is_array( $constant_restricted_dynamic_features_forms ) ) {
1111 $constant_restricted_dynamic_features_forms = explode( ',', $constant_restricted_dynamic_features_forms );
1112 }
1113
1114 $restricted_dynamic_features_forms = $constant_restricted_dynamic_features_forms;
1115 } else {
1116 $restricted_dynamic_features_forms = (array) $info['pod']->get_arg( 'restricted_dynamic_features_forms', $default_restricted_dynamic_features_forms );
1117 }
1118
1119 $restricted_dynamic_features_forms = array_filter( $restricted_dynamic_features_forms );
1120
1121 if ( empty( $restricted_dynamic_features_forms ) ) {
1122 $can_use_unrestricted = true;
1123 } else {
1124 $can_use_unrestricted = ! in_array( $mode, $restricted_dynamic_features_forms, true );
1125 }
1126 }
1127 }
1128 }
1129
1130 return $can_use_unrestricted;
1131 }
1132
1133 /**
1134 * Get the access notice for admin user based on object type and object name.
1135 *
1136 * @since 3.1.0
1137 *
1138 * @param array $args {
1139 * The arguments to use.
1140 *
1141 * @type string|null $object_type The object type.
1142 * @type string|null $object_name The object name.
1143 * @type int|string|null $item_id The item ID.
1144 * @type Pods|null $pods The Pods object.
1145 * @type Pod|null $pod The Pod object.
1146 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
1147 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
1148 * }
1149 *
1150 * @param bool $force_message Whether to force the message to show even if messages are hidden by a setting.
1151 * @param string|null $message A custom message to use for the notice text.
1152 *
1153 * @return string The access notice for admin user based on object type and object name.
1154 */
1155 function pods_get_access_admin_notice( array $args, bool $force_message = false, ?string $message = null ): string {
1156 $args['build_pod'] = true;
1157
1158 $info = pods_info_from_args( $args );
1159
1160 $identifier_for_html = esc_html( json_encode( [
1161 'object_type' => $info['object_type'],
1162 'object_name' => $info['object_name'],
1163 'item_id' => $info['item_id'],
1164 ] ) );
1165
1166 // Check if constant is hiding all notices.
1167 if ( ! $force_message && defined( 'PODS_ACCESS_HIDE_NOTICES' ) && PODS_ACCESS_HIDE_NOTICES ) {
1168 return '<!-- pods:access-notices/admin/hidden-by-constant ' . $identifier_for_html . ' -->';
1169 }
1170
1171 // Check notice setting for the Pod itself.
1172 if ( $info['pod'] instanceof Pod ) {
1173 $show_access_admin_notices_for_pod = $info['pod']->get_arg( 'show_access_admin_notices', 'inherit' );
1174
1175 if ( 'inherit' !== $show_access_admin_notices_for_pod ) {
1176 $show_access_admin_notices_for_pod = filter_var( $show_access_admin_notices_for_pod, FILTER_VALIDATE_BOOLEAN );
1177
1178 // Check if all notices have been dismissed for the pod.
1179 if ( ! $force_message && ! $show_access_admin_notices_for_pod ) {
1180 return '<!-- pods:access-notices/admin/hidden-by-pod ' . $identifier_for_html . ' -->';
1181 }
1182 }
1183 }
1184
1185 // Show notice that this content may not be visible to others.
1186 $show_access_admin_notices = pods_get_setting( 'show_access_admin_notices', true );
1187 $show_access_admin_notices = filter_var( $show_access_admin_notices, FILTER_VALIDATE_BOOLEAN );
1188
1189 // Check if all notices have been dismissed.
1190 if ( ! $force_message && ! $show_access_admin_notices ) {
1191 return '<!-- pods:access-notices/admin/hidden-by-setting ' . $identifier_for_html . ' -->';
1192 }
1193
1194 $summary = esc_html__( 'Pods Access Rights: Admin-only Notice', 'pods' );
1195
1196 if ( $message ) {
1197 $content = wpautop( $message );
1198 } else {
1199 $content = sprintf(
1200 '
1201 <p>
1202 %1$s
1203 <br />
1204 <span class="pods-ui-notice-action-links">
1205 <a href="%2$s" target="_blank" rel="noopener noreferrer">%3$s</a>
1206 | <a href="%4$s" target="_blank" rel="noopener noreferrer">%5$s</a>
1207 </span>
1208 </p>
1209 ',
1210 esc_html__( 'The content type or the content below is not public and may not be available to everyone else.', 'pods' ),
1211 esc_url( 'https://docs.pods.io/displaying-pods/access-rights-in-pods/' ),
1212 esc_html__( 'How access rights work with Pods (Documentation)', 'pods' ),
1213 esc_url( admin_url( 'admin.php?page=pods-settings#heading-security' ) ),
1214 esc_html__( 'Edit other access right options', 'pods' )
1215 );
1216 }
1217
1218 return '<!-- pods:access-notices/admin/message ' . $identifier_for_html . ' -->'
1219 . pods_message(
1220 sprintf(
1221 '
1222 <details open>
1223 <summary><strong>%1$s</strong></summary>
1224 %2$s
1225 </details>
1226 ',
1227 wp_strip_all_tags( ! empty( $info['summary'] ) ? $info['summary'] : $summary ),
1228 ! empty( $info['content'] ) ? wpautop( $info['content'] ) : $content
1229 ),
1230 'notice',
1231 true
1232 );
1233 }
1234
1235 /**
1236 * Get the access notice for non-admin user based on object type and object name.
1237 *
1238 * @since 3.1.0
1239 *
1240 * @param array $args {
1241 * The arguments to use.
1242 *
1243 * @type string|null $object_type The object type.
1244 * @type string|null $object_name The object name.
1245 * @type int|string|null $item_id The item ID.
1246 * @type Pods|null $pods The Pods object.
1247 * @type Pod|null $pod The Pod object.
1248 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
1249 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
1250 * }
1251 *
1252 * @param bool $force_message Whether to force the message to show even if messages are hidden by a setting.
1253 * @param string|null $message A custom message to use for the notice text.
1254 *
1255 * @return string The access notice for non-admin user based on object type and object name.
1256 */
1257 function pods_get_access_user_notice( array $args, bool $force_message = false, ?string $message = null ): string {
1258 $args['build_pod'] = true;
1259
1260 $info = pods_info_from_args( $args );
1261
1262 $identifier_for_html = esc_html( json_encode( [
1263 'object_type' => $info['object_type'],
1264 'object_name' => $info['object_name'],
1265 'item_id' => $info['item_id'],
1266 ] ) );
1267
1268 // Check for password-protected post.
1269 if ( $info['item_id'] && pods_access_bypass_post_with_password( $info ) ) {
1270 $message = get_the_password_form( $info['item_id'] );
1271
1272 return '<!-- pods:access-notices/user/protected/message ' . $identifier_for_html . ' -->'
1273 . pods_message(
1274 sprintf(
1275 '<p><strong>%1$s</strong></p> %2$s',
1276 esc_html__( 'Access Restricted', 'pods' ),
1277 $message
1278 ),
1279 'error',
1280 true
1281 );
1282 }
1283
1284 // Check if constant is hiding all notices.
1285 if ( ! $force_message && defined( 'PODS_ACCESS_HIDE_NOTICES' ) && PODS_ACCESS_HIDE_NOTICES ) {
1286 return '<!-- pods:access-notices/user/hidden-by-constant ' . $identifier_for_html . ' -->';
1287 }
1288
1289 // Check notice setting for the Pod itself.
1290 if ( $info['pod'] instanceof Pod ) {
1291 $show_access_restricted_messages_for_pod = $info['pod']->get_arg( 'show_access_restricted_messages', 'inherit' );
1292
1293 if ( 'inherit' !== $show_access_restricted_messages_for_pod ) {
1294 $show_access_restricted_messages_for_pod = filter_var( $show_access_restricted_messages_for_pod, FILTER_VALIDATE_BOOLEAN );
1295
1296 // Check if all notices have been dismissed for the pod.
1297 if ( ! $force_message && ! $show_access_restricted_messages_for_pod ) {
1298 return '<!-- pods:access-notices/user/hidden-by-pod ' . $identifier_for_html . ' -->';
1299 }
1300 }
1301 }
1302
1303 // Show notice that this content may not be visible to others.
1304 $show_access_restricted_messages = pods_get_setting( 'show_access_restricted_messages', false );
1305 $show_access_restricted_messages = filter_var( $show_access_restricted_messages, FILTER_VALIDATE_BOOLEAN );
1306
1307 // Check if all notices have been dismissed.
1308 if ( ! $force_message && ! $show_access_restricted_messages ) {
1309 return '<!-- pods:access-notices/user/hidden-by-setting ' . $identifier_for_html . ' -->';
1310 }
1311
1312 $message = $message ?? esc_html__( 'You do not have access to this embedded content.', 'pods' );
1313
1314 return '<!-- pods:access-notices/user/message ' . $identifier_for_html . ' -->'
1315 . pods_message(
1316 sprintf(
1317 '<p><strong>%1$s:</strong> %2$s</p>',
1318 esc_html__( 'Access Restricted', 'pods' ),
1319 $message
1320 ),
1321 'error',
1322 true
1323 );
1324 }
1325
1326 /**
1327 * Determine whether SQL clauses can be used with dynamic features.
1328 *
1329 * @since 3.1.0
1330 *
1331 * @param null|string $clause_type The clause type to check if allowed, if null used then it checks if any clauses are allowed.
1332 *
1333 * @return bool Whether SQL clauses can be used with dynamic features.
1334 */
1335 function pods_can_use_dynamic_feature_sql_clauses( ?string $clause_type = null ): bool {
1336 // Set default to most simple clause type check (simple).
1337 $clause_type = $clause_type ?: 'simple';
1338
1339 if ( defined( 'PODS_DISABLE_SHORTCODE_SQL' ) ) {
1340 // Negate the check since this is a "disable" constant.
1341 return ! PODS_DISABLE_SHORTCODE_SQL;
1342 }
1343
1344 if ( defined( 'PODS_DYNAMIC_FEATURES_ALLOW_SQL_CLAUSES' ) ) {
1345 $allow_sql_clauses = PODS_DYNAMIC_FEATURES_ALLOW_SQL_CLAUSES;
1346 } else {
1347 $cached_allow_sql_clauses = pods_transient_get( 'pods_dynamic_features_allow_sql_clauses' );
1348
1349 if ( is_string( $cached_allow_sql_clauses ) ) {
1350 $allow_sql_clauses = $cached_allow_sql_clauses;
1351 } else {
1352 $first_pods_version = get_option( 'pods_framework_version_first' );
1353 $first_pods_version = '' === $first_pods_version ? PODS_VERSION : $first_pods_version;
1354
1355 $allow_sql_clauses = pods_get_setting( 'dynamic_features_allow_sql_clauses', version_compare( $first_pods_version, '3.1.0-a-1', '<' ) ? 'simple' : '0' );
1356
1357 pods_transient_set( 'pods_dynamic_features_allow_sql_clauses', (string) $allow_sql_clauses );
1358 }
1359 }
1360
1361 if (
1362 false === $allow_sql_clauses
1363 || '0' === (string) $allow_sql_clauses
1364 ) {
1365 return false;
1366 }
1367
1368 // The "all" option is inclusive of "simple".
1369 if ( 'simple' === $clause_type && 'all' === $allow_sql_clauses ) {
1370 return true;
1371 }
1372
1373 return $clause_type === $allow_sql_clauses;
1374 }
1375
1376 /**
1377 * Determine whether a callback can be used.
1378 *
1379 * Only plain function-name string callbacks are permitted by default. Closures,
1380 * invokable objects, array callables ( [ $object, 'method' ] / [ 'Class', 'method' ] ),
1381 * and string class method references ( "Class::method" ) are rejected unless
1382 * class callbacks are enabled via the PODS_ALLOW_CLASS_CALLBACKS constant or the
1383 * "pods_access_allow_class_callbacks" filter.
1384 *
1385 * @since 3.1.0
1386 *
1387 * @param string|callable $callback The callback to check.
1388 * @param array $params Parameters used by Pods::helper() method.
1389 *
1390 * @return bool Whether the callback can be used.
1391 */
1392 function pods_access_callback_allowed( $callback, array $params = [] ): bool {
1393 // Class-based callbacks are disabled by default; only plain function-name string callbacks are permitted. Set the PODS_ALLOW_CLASS_CALLBACKS constant to true (or use the "pods_access_allow_class_callbacks" filter) to permit closures, invokable objects, array callables, and "Class::method" strings.
1394 $allow_class_callbacks = defined( 'PODS_ALLOW_CLASS_CALLBACKS' ) && PODS_ALLOW_CLASS_CALLBACKS;
1395
1396 /**
1397 * Filter whether class-based callbacks are permitted (closures, invokable
1398 * objects, array callables, and "Class::method" strings).
1399 *
1400 * @since 3.3.9.1
1401 *
1402 * @param bool $allow_class_callbacks Whether class-based callbacks are allowed.
1403 * @param string|callable $callback The callback being checked.
1404 * @param array $params Parameters used by Pods::helper() method.
1405 */
1406 $allow_class_callbacks = (bool) apply_filters( 'pods_access_allow_class_callbacks', $allow_class_callbacks, $callback, $params );
1407
1408 if ( ! is_string( $callback ) ) {
1409 return $allow_class_callbacks;
1410 }
1411
1412 if ( ! pods_can_use_dynamic_feature( 'display' ) ) {
1413 return false;
1414 }
1415
1416 if (
1417 defined( 'PODS_DISPLAY_CALLBACKS' )
1418 && ! PODS_DISPLAY_CALLBACKS
1419 ) {
1420 return false;
1421 }
1422
1423 /**
1424 * Allows changing whether callbacks are allowed to run.
1425 *
1426 * @param bool $allow_callbacks Whether callbacks are allowed to run.
1427 * @param array $params Parameters used by Pods::helper() method.
1428 *
1429 * @since 2.8.0
1430 */
1431 $allow_callbacks = (bool) apply_filters( 'pods_helper_allow_callbacks', true, $params );
1432
1433 if ( ! $allow_callbacks ) {
1434 return false;
1435 }
1436
1437 /*
1438 * Allowed callbacks. A callback must appear here (or in a user/filter
1439 * addition) to be usable. Comparison is case- and namespace-insensitive,
1440 * so entries are lowercase.
1441 */
1442 $allowed = [
1443 // Escaping / output.
1444 'esc_attr',
1445 'esc_html',
1446 'esc_js',
1447 'esc_url',
1448
1449 // Post display-by-ID.
1450 'get_permalink',
1451 'get_the_date',
1452 'get_the_excerpt',
1453 'get_the_modified_date',
1454 'get_the_modified_time',
1455 'get_the_post_thumbnail',
1456 'get_the_post_thumbnail_url',
1457 'get_the_time',
1458 'get_the_title',
1459
1460 // Term display-by-ID.
1461 'get_cat_name',
1462 'get_category_link',
1463 'get_tag_link',
1464 'get_term_link',
1465
1466 // User display-by-ID.
1467 'get_author_posts_url',
1468 'get_avatar',
1469 'get_avatar_url',
1470
1471 // Formatting (PHP).
1472 'abs',
1473 'absint',
1474 'ceil',
1475 'floatval',
1476 'floor',
1477 'htmlentities',
1478 'htmlspecialchars',
1479 'intval',
1480 'ltrim',
1481 'nl2br',
1482 'normalize_whitespace',
1483 'number_format',
1484 'number_format_i18n',
1485 'round',
1486 'rtrim',
1487 'str_word_count',
1488 'strrev',
1489 'strtolower',
1490 'strtoupper',
1491 'trim',
1492 'ucfirst',
1493 'ucwords',
1494 'wordwrap',
1495 'wpautop',
1496
1497 // Formatting (WP)
1498 'make_clickable',
1499 'sanitize_html_class',
1500 'sanitize_title',
1501 'sanitize_title_with_dashes',
1502 'strip_tags',
1503 'wp_kses_data',
1504 'wp_kses_post',
1505 'wp_strip_all_tags',
1506 'wp_trim_words',
1507 'wptexturize',
1508
1509 // Formatting (Pods)
1510 'pods_serial_comma',
1511 ];
1512
1513 if ( defined( 'PODS_DISPLAY_CALLBACKS' ) ) {
1514 $display_callbacks = PODS_DISPLAY_CALLBACKS;
1515 } else {
1516 $display_callbacks = pods_get_setting( 'display_callbacks', 'restricted' );
1517 }
1518
1519 if ( '0' === $display_callbacks ) {
1520 return false;
1521 }
1522
1523 // Maybe specify additional allowed callbacks on top of the built-in list.
1524 if ( 'customized' === $display_callbacks ) {
1525 if ( defined( 'PODS_DISPLAY_CALLBACKS_ALLOWED' ) ) {
1526 $display_callbacks_allowed = PODS_DISPLAY_CALLBACKS_ALLOWED;
1527 } else {
1528 $display_callbacks_allowed = pods_get_setting( 'display_callbacks_allowed', '' );
1529 }
1530
1531 if ( ! is_array( $display_callbacks_allowed ) ) {
1532 $display_callbacks_allowed = str_replace( "\n", ',', $display_callbacks_allowed );
1533 $display_callbacks_allowed = explode( ',', $display_callbacks_allowed );
1534 }
1535
1536 $display_callbacks_allowed = array_map( 'trim', $display_callbacks_allowed );
1537 $display_callbacks_allowed = array_filter( $display_callbacks_allowed );
1538
1539 /**
1540 * Allow filtering the custom prefix used for the display callbacks that can be used with Pods.
1541 *
1542 * Default: custom_pods_callback_
1543 *
1544 * @since 3.3.9.2
1545 *
1546 * @param string $custom_prefix The custom prefix used for the display callbacks that can be used with Pods.
1547 */
1548 $custom_prefix = apply_filters( 'pods_access_callbacks_custom_prefix', 'custom_pods_callback_' );
1549
1550 $display_callbacks_allowed = array_values(
1551 array_filter(
1552 $display_callbacks_allowed,
1553 static function ( $name ) use ( $custom_prefix ) {
1554 $normalized = ltrim( strtolower( (string) $name ), '\\' );
1555
1556 return 0 === strpos( $normalized, $custom_prefix );
1557 }
1558 )
1559 );
1560
1561 if ( ! empty( $display_callbacks_allowed ) ) {
1562 $allowed = array_merge( $allowed, $display_callbacks_allowed );
1563 }
1564 }
1565
1566 /**
1567 * Allows adjusting the allowed callbacks as needed.
1568 *
1569 * @param array $allowed List of callbacks explicitly allowed.
1570 * @param array $params Parameters used by Pods::helper() method.
1571 *
1572 * @since 2.7.0
1573 */
1574 $allowed = apply_filters( 'pods_helper_allowed_callbacks', $allowed, $params );
1575
1576 // Clean up helper callback (if string).
1577 if ( is_string( $callback ) ) {
1578 $callback = wp_strip_all_tags( str_replace( [ '`', chr( 96 ) ], "'", $callback ) );
1579 }
1580
1581 /*
1582 * Normalize for comparison. PHP function/method names are case-insensitive
1583 * and may be written with a leading namespace separator, so "SYSTEM",
1584 * "System", and "\system" must all be treated as "system". The allowed list
1585 * is normalized the same way so matching is consistent.
1586 */
1587 $normalized_callback = ltrim( strtolower( trim( (string) $callback ) ), '\\' );
1588
1589 /*
1590 * Reject class method callbacks expressed as strings unless class callbacks
1591 * are explicitly enabled. The scope resolution operator "::" only appears in
1592 * static method references such as "Class::method", "\Namespace\Class::method",
1593 * or "parent::method".
1594 */
1595 if ( ! $allow_class_callbacks && false !== strpos( $normalized_callback, '::' ) ) {
1596 pods_access_record_disallowed_display_callback( $callback );
1597
1598 return false;
1599 }
1600
1601 $allowed = array_map( 'strtolower', $allowed );
1602
1603 /*
1604 * Class method strings skip the built-in allow list when class callbacks
1605 * are enabled.
1606 */
1607 if ( $allow_class_callbacks && false !== strpos( $normalized_callback, '::' ) ) {
1608 return true;
1609 }
1610
1611 $is_allowed = in_array( $normalized_callback, $allowed, true );
1612
1613 if ( ! $is_allowed ) {
1614 pods_access_record_disallowed_display_callback( $callback );
1615 }
1616
1617 return $is_allowed;
1618 }
1619
1620 /**
1621 * Get the unique list of disallowed display callbacks stored in cache.
1622 *
1623 * @since 3.3.9.2
1624 *
1625 * @return string[] Unique callback names.
1626 */
1627 function pods_get_disallowed_display_callbacks(): array {
1628 $existing = pods_transient_get( 'pods_disallowed_display_callbacks' );
1629
1630 if ( empty( $existing ) ) {
1631 return [];
1632 }
1633
1634 $existing = array_filter( array_map( 'trim', explode( ',', (string) $existing ) ) );
1635
1636 return array_values( array_unique( $existing ) );
1637 }
1638
1639 /**
1640 * Record a disallowed display callback into the cache.
1641 *
1642 * Stores a unique comma-separated list for up to 30 days. Recording is skipped
1643 * when display callback notices are disabled.
1644 *
1645 * @since 3.3.9.2
1646 *
1647 * @param string $callback The cleaned callback name that was rejected.
1648 */
1649 function pods_access_record_disallowed_display_callback( string $callback ): void {
1650 if ( ! pods_is_truthy( pods_get_setting( 'show_display_callback_notices', '1' ) ) ) {
1651 return;
1652 }
1653
1654 $callback = trim( $callback );
1655
1656 if ( '' === $callback ) {
1657 return;
1658 }
1659
1660 $existing = pods_get_disallowed_display_callbacks();
1661
1662 if ( in_array( $callback, $existing, true ) ) {
1663 return;
1664 }
1665
1666 $existing[] = $callback;
1667
1668 pods_transient_set(
1669 'pods_disallowed_display_callbacks',
1670 implode( ',', array_unique( $existing ) ),
1671 30 * DAY_IN_SECONDS
1672 );
1673 }
1674
1675 /**
1676 * Clear the cached list of disallowed display callbacks.
1677 *
1678 * @since 3.3.9.2
1679 */
1680 function pods_access_clear_disallowed_display_callbacks(): void {
1681 pods_transient_clear( 'pods_disallowed_display_callbacks' );
1682 }
1683
1684 /**
1685 * Get the pod access tab options for a specific pod.
1686 *
1687 * @since 3.1.0
1688 *
1689 * @param string $pod_type The pod type.
1690 * @param string $pod_name The pod name.
1691 * @param null|Pod $pod The pod object.
1692 *
1693 * @return array The pod access tab options for a specific pod.
1694 */
1695 function pods_access_pod_options( string $pod_type, string $pod_name, ?Pod $pod = null ): array {
1696 $first_pods_version = get_option( 'pods_framework_version_first' );
1697 $first_pods_version = '' === $first_pods_version ? PODS_VERSION : $first_pods_version;
1698
1699 $options = [];
1700
1701 $options['security_access_rights_info'] = [
1702 'label' => __( 'How access rights work in Pods', 'pods' ),
1703 'type' => 'html',
1704 'html_content' => sprintf(
1705 '
1706 <p>%1$s</p>
1707 <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>
1708 ',
1709 __( 'Pods handles access rights similar to how WordPress itself works.', 'pods' ),
1710 __( 'Read more about how access rights work in Pods on our Documentation site', 'pods' )
1711 ),
1712 ];
1713
1714 if ( 'pod' === $pod_type ) {
1715 $options['public'] = [
1716 'label' => __( 'Public', 'pods' ),
1717 'help' => __( 'You can still embed Pods Content and Forms through PHP and make use of other features directly through code.', 'pods' ),
1718 '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' ),
1719 'type' => 'boolean',
1720 'default' => version_compare( $first_pods_version, '3.1.0-a-1', '<' ) ? true : false,
1721 'boolean_yes_label' => '',
1722 ];
1723 }
1724
1725 if ( pods_can_use_dynamic_features() ) {
1726 $options['dynamic_features_allow'] = [
1727 'label' => __( 'Dynamic Features', 'pods' ),
1728 'help' => [
1729 __( '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' ),
1730 'https://docs.pods.io/displaying-pods/access-rights-in-pods/',
1731 ],
1732 'description' => __( 'Dynamic features include Pods Shortcodes, Blocks, and Widgets which let you embed content and forms on your site.', 'pods' ),
1733 'type' => 'pick',
1734 'default' => 'inherit',
1735 'pick_format_type' => 'single',
1736 'pick_format_single' => 'radio',
1737 'data' => [
1738 'inherit' => __( 'WP Default - If the content type is marked "Public" with WordPress then Dynamic Features will be enabled.', 'pods' ),
1739 '1' => __( 'Enable Dynamic Features including Pods Shortcodes, Blocks, and Widgets for this content type', 'pods' ),
1740 '0' => __( 'Disable All Dynamic Features in Pods for this content type', 'pods' ),
1741 ],
1742 'dependency' => true,
1743 ];
1744
1745 $is_public_content_type = pods_is_type_public(
1746 [
1747 'pod' => $pod,
1748 ]
1749 );
1750
1751 $options['restrict_dynamic_features'] = [
1752 'label' => __( 'Restrict Dynamic Features', 'pods' ),
1753 'help' => [
1754 __( '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' ),
1755 'https://docs.pods.io/displaying-pods/access-rights-in-pods/',
1756 ],
1757 'description' => sprintf(
1758 '<strong>%1$s</strong> %2$s',
1759 esc_html__( 'Warning:', 'pods' ),
1760 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' )
1761 ),
1762 'type' => 'pick',
1763 'default' => '1',
1764 'pick_format_type' => 'single',
1765 'pick_format_single' => 'radio',
1766 'data' => [
1767 '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' ),
1768 '1' => __( 'Restricted - Check access rights for embedded content', 'pods' ),
1769 ],
1770 'excludes-on' => [ 'dynamic_features_allow' => '0' ],
1771 ];
1772
1773 $default_restricted_dynamic_features = [
1774 'form',
1775 ];
1776
1777 if ( ! $is_public_content_type ) {
1778 $default_restricted_dynamic_features[] = 'display';
1779 }
1780
1781 $options['restricted_dynamic_features'] = [
1782 'label' => __( 'Dynamic Features to Restrict', 'pods' ),
1783 'help' => [
1784 __( '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' ),
1785 'https://docs.pods.io/displaying-pods/access-rights-in-pods/',
1786 ],
1787 'type' => 'pick',
1788 'default' => $default_restricted_dynamic_features,
1789 'pick_format_type' => 'multi',
1790 'pick_format_multi' => 'checkbox',
1791 'data' => [
1792 'display' => __( 'Restricted Display - Shortcodes and Blocks that allow querying content from this Pod and displaying any field will check access rights.', 'pods' ),
1793 'form' => __( 'Restricted Forms - The Form Shortcode and Block submitting new content or editing existing content will check access rights.', 'pods' ),
1794 ],
1795 'depends-on' => [ 'restrict_dynamic_features' => '1' ],
1796 'excludes-on' => [ 'dynamic_features_allow' => '0' ],
1797 ];
1798
1799 $default_restricted_dynamic_features_forms = [
1800 'edit',
1801 ];
1802
1803 if ( ! $is_public_content_type ) {
1804 $default_restricted_dynamic_features_forms[] = 'add';
1805 }
1806
1807 $options['restricted_dynamic_features_forms'] = [
1808 'label' => __( 'Dynamic Features to Restrict for Forms', 'pods' ),
1809 'help' => [
1810 __( 'This will check access rights for whether someone should have access to specific content before a they can add or edit content.', 'pods' ),
1811 'https://docs.pods.io/displaying-pods/access-rights-in-pods/',
1812 ],
1813 'type' => 'pick',
1814 'default' => $default_restricted_dynamic_features_forms,
1815 'pick_format_type' => 'multi',
1816 'pick_format_multi' => 'checkbox',
1817 'data' => [
1818 'add' => __( 'Restricted Add New Forms - Embedding the Form Shortcode and Block to allow for adding new content will check access rights.', 'pods' ),
1819 'edit' => __( 'Restricted Edit Forms - Embedding the Form Shortcode and Block to allow for editing existing content will check access rights.', 'pods' ),
1820 ],
1821 'depends-on-multi' => [ 'restricted_dynamic_features' => 'form' ],
1822 'excludes-on' => [ 'dynamic_features_allow' => '0' ],
1823 ];
1824
1825 $options['show_access_restricted_messages'] = [
1826 'label' => __( 'Access-related Restricted Messages', 'pods' ),
1827 'help' => [
1828 __( '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' ),
1829 'https://docs.pods.io/displaying-pods/access-rights-in-pods/',
1830 ],
1831 'type' => 'pick',
1832 'default' => 'inherit',
1833 'pick_format_type' => 'single',
1834 'pick_format_single' => 'radio',
1835 'data' => [
1836 '1' => __( 'Enable access-related restricted messages for forms/content displayed (instead of the form/content output)', 'pods' ),
1837 '0' => __( 'Disable access-related restricted messages for forms/content displayed (the form/content output will be blank)', 'pods' ),
1838 'inherit' => __( 'Default - Use the global Pods setting for this', 'pods' ),
1839 ],
1840 'depends-on' => [ 'restrict_dynamic_features' => '1' ],
1841 'excludes-on' => [ 'dynamic_features_allow' => '0' ],
1842 ];
1843
1844 $options['show_access_admin_notices'] = [
1845 'label' => __( 'Access-related Admin Notices', 'pods' ),
1846 'help' => [
1847 __( 'Access-related Admin Notices will only show to admins and will appear above content/forms that may not be entirely public.', 'pods' ),
1848 'https://docs.pods.io/displaying-pods/access-rights-in-pods/',
1849 ],
1850 'type' => 'pick',
1851 'default' => 'inherit',
1852 'pick_format_type' => 'single',
1853 'pick_format_single' => 'radio',
1854 'data' => [
1855 '1' => __( 'Enable access-related admin notices above forms/content displayed', 'pods' ),
1856 '0' => __( 'Disable access-related admin notices above forms/content displayed', 'pods' ),
1857 'inherit' => __( 'Default - Use the global Pods setting for this', 'pods' ),
1858 ],
1859 'depends-on' => [ 'restrict_dynamic_features' => '1' ],
1860 'excludes-on' => [ 'dynamic_features_allow' => '0' ],
1861 ];
1862 }
1863
1864 $options['security_access_rights_preview'] = [
1865 'label' => __( 'Capabilities preview', 'pods' ),
1866 'type' => 'html',
1867 'html_content' => '
1868 <p>' . esc_html__( 'Below is a list of capabilities that a user will normally need for this content.', 'pods' ) . '</p>
1869 ' . pods_access_get_capabilities_preview( $pod_type, $pod_name ),
1870 ];
1871
1872 return $options;
1873 }
1874
1875 /**
1876 * Get the list of dynamic features allow options.
1877 *
1878 * @since 3.1.0
1879 *
1880 * @return array The list of dynamic features allow options.
1881 */
1882 function pods_access_get_dynamic_features_allow_options(): array {
1883 return [
1884 'inherit' => __( 'WP Default (if content type is Public)', 'pods' ),
1885 '1' => __( 'Enabled', 'pods' ),
1886 '0' => '🔒 ' . __( 'Disabled', 'pods' ),
1887 ];
1888 }
1889
1890 /**
1891 * Get the list of restricted dynamic features options.
1892 *
1893 * @since 3.1.0
1894 *
1895 * @return array The list of restricted dynamic features options.
1896 */
1897 function pods_access_get_restricted_dynamic_features_options(): array {
1898 return [
1899 'display' => '🔒 ' . __( 'Display', 'pods' ),
1900 'form' => '🔒 ' . __( 'Form', 'pods' ),
1901 ];
1902 }
1903
1904 /**
1905 * Get the access rights capabilities preview HTML.
1906 *
1907 * @since 3.1.0
1908 *
1909 * @param string $pod_type The pod type.
1910 * @param string $pod_name The pod name.
1911 *
1912 * @return string The access rights capabilities preview HTML.
1913 */
1914 function pods_access_get_capabilities_preview( string $pod_type, string $pod_name ): string {
1915 $capabilities = pods_access_map_capabilities(
1916 [
1917 'object_type' => $pod_type,
1918 'object_name' => $pod_name,
1919 ],
1920 null,
1921 true
1922 );
1923
1924 if ( null === $capabilities ) {
1925 $capabilities = [
1926 'read' => null,
1927 'add' => null,
1928 'edit' => null,
1929 'delete' => null,
1930 ];
1931 }
1932
1933 $capabilities_preview = [
1934 'read' => esc_html__( 'Read capability', 'pods' ),
1935 'add' => esc_html__( 'Add New capability', 'pods' ),
1936 'edit' => esc_html__( 'Edit capability', 'pods' ),
1937 'delete' => esc_html__( 'Delete capability', 'pods' ),
1938 'read_private' => esc_html__( 'Read Private capability', 'pods' ),
1939 'edit_others' => esc_html__( 'Edit Others capability', 'pods' ),
1940 'delete_others' => esc_html__( 'Delete Others capability', 'pods' ),
1941 'delete_published' => esc_html__( 'Delete Published capability', 'pods' ),
1942 'delete_private' => esc_html__( 'Delete Private capability', 'pods' ),
1943 ];
1944
1945 $capabilities_preview_list = [
1946 '<strong>' . $capabilities_preview['read'] . ':</strong> ' . ( $capabilities['read'] ?: __( 'Not restricted', 'pods' ) ),
1947 ];
1948
1949 if ( 'settings' !== $pod_type ) {
1950 $capabilities_preview_list[] = '<strong>' . $capabilities_preview['add'] . ':</strong> ' . ( $capabilities['add'] ?: __( 'Not restricted', 'pods' ) );
1951 }
1952
1953 $capabilities_preview_list[] = '<strong>' . $capabilities_preview['edit'] . ':</strong> ' . ( $capabilities['edit'] ?: __( 'Not restricted', 'pods' ) );
1954
1955 if ( 'settings' !== $pod_type ) {
1956 $capabilities_preview_list[] = '<strong>' . $capabilities_preview['delete'] . ':</strong> ' . ( $capabilities['delete'] ?: __( 'Not restricted', 'pods' ) );
1957 }
1958
1959 if ( $capabilities && array_key_exists( 'read_private', $capabilities ) ) {
1960 $capabilities_preview_list[] = '<strong>' . $capabilities_preview['read_private'] . ':</strong> ' . ( $capabilities['read_private'] ?: __( 'Not restricted', 'pods' ) );
1961 }
1962
1963 if ( $capabilities && array_key_exists( 'edit_others', $capabilities ) ) {
1964 $capabilities_preview_list[] = '<strong>' . $capabilities_preview['edit_others'] . ':</strong> ' . ( $capabilities['edit_others'] ?: __( 'Not restricted', 'pods' ) );
1965 }
1966
1967 if ( $capabilities && array_key_exists( 'delete_others', $capabilities ) ) {
1968 $capabilities_preview_list[] = '<strong>' . $capabilities_preview['delete_others'] . ':</strong> ' . ( $capabilities['delete_others'] ?: __( 'Not restricted', 'pods' ) );
1969 }
1970
1971 if ( $capabilities && array_key_exists( 'delete_published', $capabilities ) ) {
1972 $capabilities_preview_list[] = '<strong>' . $capabilities_preview['delete_published'] . ':</strong> ' . ( $capabilities['delete_published'] ?: __( 'Not restricted', 'pods' ) );
1973 }
1974
1975 if ( $capabilities && array_key_exists( 'delete_private', $capabilities ) ) {
1976 $capabilities_preview_list[] = '<strong>' . $capabilities_preview['delete_private'] . ':</strong> ' . ( $capabilities['delete_private'] ?: __( 'Not restricted', 'pods' ) );
1977 }
1978
1979 return '
1980 <ul>
1981 <li>' . implode( '</li><li>', $capabilities_preview_list ) . '</li>
1982 </ul>
1983 ';
1984 }
1985
1986 /**
1987 * Get the pod settings config for access-related settings.
1988 *
1989 * @since 3.1.0
1990 *
1991 * @return array The pod settings config for access-related settings.
1992 */
1993 function pods_access_settings_config(): array {
1994 // Only use translation functions after `init` to prevent a WP core notice.
1995 $did_init = doing_action( 'init' ) || did_action( 'init' );
1996
1997 $first_pods_version = get_option( 'pods_framework_version_first' );
1998 $first_pods_version = '' === $first_pods_version ? PODS_VERSION : $first_pods_version;
1999
2000 $fields = [];
2001
2002 $fields['dynamic_features_allow'] = [
2003 'name' => 'dynamic_features_allow',
2004 'label' => $did_init ? __( 'Dynamic Features', 'pods' ) : '',
2005 'help' => [
2006 $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' ) : '',
2007 'https://docs.pods.io/displaying-pods/access-rights-in-pods/',
2008 ],
2009 'description' => $did_init ? __( 'Dynamic features include Pods Shortcodes, Blocks, and Widgets which let you embed content and forms on your site.', 'pods' ) : '',
2010 'type' => 'pick',
2011 'default' => '1',
2012 'pick_format_type' => 'single',
2013 'pick_format_single' => 'radio',
2014 'data' => [
2015 '1' => $did_init ? __( 'Enable Dynamic Features including Pods Shortcodes, Blocks, and Widgets', 'pods' ) : '',
2016 '0' => $did_init ? __( 'Disable All Dynamic Features in Pods', 'pods' ) : '',
2017 ],
2018 'site_health_data' => [
2019 '1' => $did_init ? __( 'Enable', 'pods' ) : '',
2020 '0' => $did_init ? __( 'Disable', 'pods' ) : '',
2021 ],
2022 'site_health_include_in_info' => true,
2023 ];
2024
2025 $fields['security_access_rights_info'] = [
2026 'name' => 'security_access_rights_info',
2027 'label' => $did_init ? __( 'How access rights work in Pods', 'pods' ) : '',
2028 'type' => 'html',
2029 'html_content' => sprintf(
2030 '
2031 <p>%1$s</p>
2032 <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>
2033 ',
2034 $did_init ? __( 'Pods handles access rights similar to how WordPress itself works.', 'pods' ) : '',
2035 $did_init ? __( 'Read more about how access rights work in Pods on our Documentation site', 'pods' ) : ''
2036 ),
2037 'depends-on' => [ 'dynamic_features_allow' => '1' ],
2038 ];
2039
2040 $fields['dynamic_features_enabled'] = [
2041 'name' => 'dynamic_features_enabled',
2042 'label' => $did_init ? __( 'Dynamic Features to Enable', 'pods' ) : '',
2043 'help' => [
2044 $did_init ? __( 'You can choose one or more dynamic features to enable. By default, only Display and Form are enabled.', 'pods' ) : '',
2045 'https://docs.pods.io/displaying-pods/access-rights-in-pods/',
2046 ],
2047 'type' => 'pick',
2048 'default' => [
2049 'display',
2050 'form',
2051 ],
2052 'pick_format_type' => 'multi',
2053 'pick_format_multi' => 'checkbox',
2054 'data' => [
2055 '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' ) : '',
2056 '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' ) : '',
2057 'view' => $did_init ? __( 'View - The View Shortcode and Block that allows embedding *any* theme file on a page.', 'pods' ) : '',
2058 ],
2059 'site_health_data' => [
2060 'display' => $did_init ? __( 'Display', 'pods' ) : '',
2061 'form' => $did_init ? __( 'Form', 'pods' ) : '',
2062 'view' => $did_init ? __( 'View', 'pods' ) : '',
2063 ],
2064 'depends-on' => [ 'dynamic_features_allow' => '1' ],
2065 'site_health_include_in_info' => true,
2066 ];
2067
2068 $fields['show_access_restricted_messages'] = [
2069 'name' => 'show_access_restricted_messages',
2070 'label' => $did_init ? __( 'Access-related Restricted Messages', 'pods' ) : '',
2071 'help' => [
2072 $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' ) : '',
2073 'https://docs.pods.io/displaying-pods/access-rights-in-pods/',
2074 ],
2075 'type' => 'pick',
2076 'default' => '0',
2077 'pick_format_type' => 'single',
2078 'pick_format_single' => 'radio',
2079 'data' => [
2080 '1' => $did_init ? __( 'Enable access-related restricted messages for forms/content displayed (instead of the form/content output)', 'pods' ) : '',
2081 '0' => $did_init ? __( 'Disable access-related restricted messages for forms/content displayed (the form/content output will be blank)', 'pods' ) : '',
2082 ],
2083 'site_health_data' => [
2084 '1' => $did_init ? __( 'Enable', 'pods' ) : '',
2085 '0' => $did_init ? __( 'Disable', 'pods' ) : '',
2086 ],
2087 'site_health_include_in_info' => true,
2088 'depends-on' => [ 'dynamic_features_allow' => '1' ],
2089 ];
2090
2091 $fields['show_access_admin_notices'] = [
2092 'name' => 'show_access_admin_notices',
2093 'label' => $did_init ? __( 'Access-related Admin Notices', 'pods' ) : '',
2094 'help' => [
2095 $did_init ? __( 'Access-related Admin Notices will only show to admins and will appear above content/forms that may not be entirely public.', 'pods' ) : '',
2096 'https://docs.pods.io/displaying-pods/access-rights-in-pods/',
2097 ],
2098 'type' => 'pick',
2099 'default' => '1',
2100 'pick_format_type' => 'single',
2101 'pick_format_single' => 'radio',
2102 'data' => [
2103 '1' => $did_init ? __( 'Enable access-related admin notices above forms/content displayed', 'pods' ) : '',
2104 '0' => $did_init ? __( 'Disable access-related admin notices above forms/content displayed', 'pods' ) : '',
2105 ],
2106 'site_health_data' => [
2107 '1' => $did_init ? __( 'Enable', 'pods' ) : '',
2108 '0' => $did_init ? __( 'Disable', 'pods' ) : '',
2109 ],
2110 'site_health_include_in_info' => true,
2111 'depends-on' => [ 'dynamic_features_allow' => '1' ],
2112 ];
2113
2114 $fields['dynamic_features_allow_sql_clauses'] = [
2115 'name' => 'dynamic_features_allow_sql_clauses',
2116 'label' => $did_init ? __( 'Allow SQL clauses to be used in Dynamic Features', 'pods' ) : '',
2117 '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' ) : '',
2118 'type' => 'pick',
2119 'default' => version_compare( $first_pods_version, '3.1.0-a-1', '<' ) ? 'simple' : '0',
2120 'pick_format_type' => 'single',
2121 'pick_format_single' => 'radio',
2122 'data' => [
2123 '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' ) : '',
2124 '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' ) : '',
2125 '0' => $did_init ? __( 'Disable SQL clause usage through dynamic features', 'pods' ) : '',
2126 ],
2127 'site_health_data' => [
2128 'all' => $did_init ? __( 'Unrestricted', 'pods' ) : '',
2129 'simple' => $did_init ? __( 'Restricted', 'pods' ) : '',
2130 '0' => $did_init ? __( 'Disable', 'pods' ) : '',
2131 ],
2132 'depends-on' => [
2133 'dynamic_features_allow' => '1',
2134 ],
2135 'depends-on-multi' => [
2136 'dynamic_features_enabled' => 'display',
2137 ],
2138 'site_health_include_in_info' => true,
2139 ];
2140
2141 $fields['display_callbacks'] = [
2142 'name' => 'display_callbacks',
2143 'label' => $did_init ? __( 'Display callbacks', 'pods' ) : '',
2144 'description' => $did_init ? __( 'Callbacks can be used when using Pods Templating syntax like {@my_field,my_callback} in your magic tags. You can allow additional callbacks through PHP by filtering the array using the filter "pods_helper_allowed_callbacks".', 'pods' ) : '',
2145 'type' => 'pick',
2146 'default' => 'restricted',
2147 'pick_format_type' => 'single',
2148 'pick_format_single' => 'radio',
2149 'data' => [
2150 'restricted' => $did_init ? __( 'Restricted - Only a built-in list of safe display and escaping functions are allowed.', 'pods' ) : '',
2151 'customized' => $did_init ? __( 'Customized - The restricted functions plus your own list of PHP function callbacks.', 'pods' ) : '',
2152 '0' => $did_init ? __( 'Disable display callbacks', 'pods' ) : '',
2153 ],
2154 'site_health_data' => [
2155 'restricted' => $did_init ? __( 'Restricted', 'pods' ) : '',
2156 'customized' => $did_init ? __( 'Customized', 'pods' ) : '',
2157 '0' => $did_init ? __( 'Disable', 'pods' ) : '',
2158 ],
2159 'depends-on' => [
2160 'dynamic_features_allow' => '1',
2161 ],
2162 'depends-on-multi' => [
2163 'dynamic_features_enabled' => 'display',
2164 ],
2165 'site_health_include_in_info' => true,
2166 ];
2167
2168 $fields['display_callbacks_allowed'] = [
2169 'name' => 'display_callbacks_allowed',
2170 'label' => $did_init ? __( 'Display callbacks allowed', 'pods' ) : '',
2171 'description' => $did_init ? __( 'Please provide a comma-separated list of additional PHP function names to allow in callbacks, on top of the built-in safe list. Each additional function name must start with "custom_pods_callback_" and any other names are ignored for security purposes. You may choose to add custom PHP filter for "pods_access_callbacks_custom_prefix" to change this prefix.', 'pods' ) : '',
2172 'type' => 'text',
2173 'default' => '',
2174 'depends-on' => [
2175 'dynamic_features_allow' => '1',
2176 'display_callbacks' => 'customized',
2177 ],
2178 'depends-on-multi' => [
2179 'dynamic_features_enabled' => 'display',
2180 ],
2181 'site_health_include_in_info' => true,
2182 ];
2183
2184 $fields['show_display_callback_notices'] = [
2185 'name' => 'show_display_callback_notices',
2186 'label' => $did_init ? __( 'Display callback notices', 'pods' ) : '',
2187 'description' => $did_init ? __( 'When enabled, Pods will record disallowed display callbacks as they are detected and show an admin notice on the Pods Settings page listing those callbacks.', 'pods' ) : '',
2188 'type' => 'pick',
2189 'default' => '1',
2190 'pick_format_type' => 'single',
2191 'pick_format_single' => 'radio',
2192 'data' => [
2193 '1' => $did_init ? __( 'Enable admin notices when disallowed display callbacks are detected', 'pods' ) : '',
2194 '0' => $did_init ? __( 'Disable admin notices when disallowed display callbacks are detected', 'pods' ) : '',
2195 ],
2196 'site_health_data' => [
2197 '1' => $did_init ? __( 'Enable', 'pods' ) : '',
2198 '0' => $did_init ? __( 'Disable', 'pods' ) : '',
2199 ],
2200 'depends-on' => [
2201 'dynamic_features_allow' => '1',
2202 ],
2203 'depends-on-multi' => [
2204 'dynamic_features_enabled' => 'display',
2205 ],
2206 'site_health_include_in_info' => true,
2207 ];
2208
2209 return $fields;
2210 }
2211
2212 /**
2213 * Get the bleep placeholder text.
2214 *
2215 * @since 3.1.0
2216 *
2217 * @return string The bleep placeholder text.
2218 */
2219 function pods_access_bleep_placeholder(): string {
2220 return '****************';
2221 }
2222
2223 /**
2224 * Process the value and bleep it if it needs to be.
2225 *
2226 * @since 3.1.0
2227 *
2228 * @param string|mixed $value The value to be bleeped.
2229 *
2230 * @return string|mixed The bleeped text if not empty, otherwise the value as it was.
2231 */
2232 function pods_access_bleep_text( $value ) {
2233 $bleep_text = pods_access_bleep_placeholder();
2234
2235 if ( 0 < strlen( (string) $value ) ) {
2236 $value = $bleep_text;
2237 }
2238
2239 return $value;
2240 }
2241
2242 /**
2243 * Process the data and bleep anything that needs to be.
2244 *
2245 * @since 3.1.0
2246 *
2247 * @param array|object $data The data to be bleeped.
2248 * @param array $additional_bleep_properties The additional properties to be bleeped from objects and arrays.
2249 *
2250 * @return array|object The bleeped data.
2251 */
2252 function pods_access_bleep_data( $data, array $additional_bleep_properties = [] ) {
2253 $bleep_properties = [
2254 'user_pass',
2255 'user_activation_key',
2256 'post_password',
2257 ];
2258
2259 /**
2260 * Allow filtering the additional properties to be bleeped from objects and arrays.
2261 *
2262 * @since 3.1.0
2263 *
2264 * @param array $additional_bleep_properties The additional properties to be bleeped from objects and arrays.
2265 * @param array|object $data The data to be bleeped.
2266 */
2267 $additional_bleep_properties = apply_filters( 'pods_access_bleep_properties', $additional_bleep_properties, $data );
2268
2269 $bleep_properties = array_merge( $bleep_properties, $additional_bleep_properties );
2270
2271 $bleep_text = pods_access_bleep_placeholder();
2272
2273 if ( is_object( $data ) ) {
2274 foreach ( $bleep_properties as $bleep_property ) {
2275 if ( isset( $data->{$bleep_property} ) ) {
2276 $data->{$bleep_property} = 0 < strlen( (string) $data->{$bleep_property} ) ? $bleep_text : '';
2277 }
2278 }
2279 } elseif ( is_array( $data ) ) {
2280 foreach ( $bleep_properties as $bleep_property ) {
2281 if ( isset( $data[ $bleep_property ] ) ) {
2282 $data[ $bleep_property ] = 0 < strlen( (string) $data[ $bleep_property ] ) ? $bleep_text : '';
2283 }
2284 }
2285 }
2286
2287 return $data;
2288 }
2289
2290 /**
2291 * Process the data and bleep anything that needs to be.
2292 *
2293 * @since 3.1.0
2294 *
2295 * @param array $items The items to be bleeped.
2296 * @param array $additional_bleep_properties The additional properties to be bleeped from objects and arrays.
2297 *
2298 * @return array|object The bleeped data.
2299 */
2300 function pods_access_bleep_items( array $items, array $additional_bleep_properties = [] ) {
2301 // Call the pods_access_bleep_data() function for all items in the $items array.
2302 return array_map(
2303 static function ( $item ) use ( $additional_bleep_properties ) {
2304 return pods_access_bleep_data( $item, $additional_bleep_properties );
2305 },
2306 $items
2307 );
2308 }
2309
2310 /**
2311 * Determine whether the SQL fragment is allowed to be used.
2312 *
2313 * @since 3.1.0
2314 *
2315 * @param string $sql The SQL fragment to check.
2316 * @param string $context The SQL fragment context.
2317 * @param array $args {
2318 * The arguments to use.
2319 *
2320 * @type string|null $object_type The object type.
2321 * @type string|null $object_name The object name.
2322 * @type int|string|null $item_id The item ID.
2323 * @type Pods|null $pods The Pods object.
2324 * @type Pod|null $pod The Pod object.
2325 * @type bool $build_pods Whether to try to build a Pods object from the object type/name/ID (false by default).
2326 * @type bool $build_pod Whether to try to build a Pod object from the object type/name (false by default).
2327 * }
2328 * @param object|null $params The parameters passed to Pods::find() or PodsData::select().
2329 *
2330 * @return bool Whether the SQL fragment is allowed to be used.
2331 */
2332 function pods_access_sql_fragment_is_allowed( string $sql, string $context, array $args = [], ?object $params = null ): bool {
2333 $context = strtoupper( $context );
2334
2335 $info = pods_info_from_args( $args );
2336
2337 /**
2338 * Allows filtering whether the SQL fragment is allowed to be used.
2339 *
2340 * @since 3.1.0
2341 *
2342 * @param bool $allowed Whether the SQL fragment is allowed to be used.
2343 * @param string $sql The SQL fragment to check.
2344 * @param string $context The SQL fragment context.
2345 * @param array $info Pod information.
2346 * @param object|null $params The parameters passed to Pods::find() or PodsData::select().
2347 */
2348 return (bool) apply_filters( 'pods_access_sql_fragment_is_allowed', true, $sql, $context, $info, $params );
2349 }
2350
2351 add_filter( 'pods_access_sql_fragment_is_allowed', 'pods_access_sql_fragment_disallow_mismatch_parenthesis', 10, 2 );
2352 add_filter( 'pods_access_sql_fragment_is_allowed', 'pods_access_sql_fragment_disallow_comments', 10, 2 );
2353 add_filter( 'pods_access_sql_fragment_is_allowed', 'pods_access_sql_fragment_disallow_unsafe_functions', 10, 2 );
2354 add_filter( 'pods_access_sql_fragment_is_allowed', 'pods_access_sql_fragment_disallow_unsafe_keywords', 10, 2 );
2355 add_filter( 'pods_access_sql_fragment_is_allowed', 'pods_access_sql_fragment_disallow_unsafe_tables', 10, 2 );
2356 add_filter( 'pods_access_sql_fragment_is_allowed', 'pods_access_sql_fragment_disallow_double_hyphens', 10, 2 );
2357 add_filter( 'pods_access_sql_fragment_is_allowed', 'pods_access_sql_fragment_disallow_subqueries', 10, 2 );
2358 add_filter( 'pods_access_sql_fragment_is_allowed', 'pods_access_sql_fragment_disallow_post_status', 10, 5 );
2359
2360 /**
2361 * Disallow parenthesis in SQL fragments that are not balanced at every position.
2362 *
2363 * @since 3.1.0
2364 *
2365 * @param bool $allowed Whether the SQL fragment is allowed to be used.
2366 * @param string $sql The SQL fragment to check.
2367 *
2368 * @return bool Whether the SQL fragment is allowed to be used.
2369 */
2370 function pods_access_sql_fragment_disallow_mismatch_parenthesis( bool $allowed, string $sql ): bool {
2371 if ( ! $allowed ) {
2372 return $allowed;
2373 }
2374
2375 // Remove quoted string literals ('' and "" quoting, with backslash/doubled-quote escaping).
2376 $stripped = preg_replace(
2377 [
2378 "/'(?:[^'\\\\]|\\\\.|'')*'/s",
2379 '/"(?:[^"\\\\]|\\\\.|"")*"/s',
2380 ],
2381 '',
2382 $sql
2383 );
2384
2385 if ( null === $stripped ) {
2386 // preg_replace failed (e.g. malformed input); fail closed.
2387 return false;
2388 }
2389
2390 $depth = 0;
2391 $length = strlen( $stripped );
2392
2393 for ( $i = 0; $i < $length; $i ++ ) {
2394 $char = $stripped[ $i ];
2395
2396 if ( '(' === $char ) {
2397 $depth ++;
2398 } elseif ( ')' === $char ) {
2399 $depth --;
2400
2401 // More closes than opens at this point: the fragment escapes its wrapping.
2402 if ( $depth < 0 ) {
2403 return false;
2404 }
2405 }
2406 }
2407
2408 return 0 === $depth;
2409 }
2410
2411 /**
2412 * Disallow unsafe functions from being used in SQL fragments.
2413 *
2414 * @since 3.1.0
2415 *
2416 * @param bool $allowed Whether the SQL fragment is allowed to be used.
2417 * @param string $sql The SQL fragment to check.
2418 *
2419 * @return bool Whether the SQL fragment is allowed to be used.
2420 */
2421 function pods_access_sql_fragment_disallow_unsafe_functions( bool $allowed, string $sql ): bool {
2422 if ( ! $allowed ) {
2423 return $allowed;
2424 }
2425
2426 $unsafe_functions = [
2427 // Server / database / session information functions.
2428 'USER',
2429 'CURRENT_USER',
2430 'SESSION_USER',
2431 'SYSTEM_USER',
2432 'DATABASE',
2433 'SCHEMA',
2434 'VERSION',
2435 'CONNECTION_ID',
2436 'CURRENT_ROLE',
2437 'ROW_COUNT',
2438 'LAST_INSERT_ID',
2439 'CHARSET',
2440 'COLLATION',
2441 'COERCIBILITY',
2442 'STATEMENT_DIGEST',
2443 'STATEMENT_DIGEST_TEXT',
2444
2445 // Filesystem access.
2446 'LOAD_FILE',
2447
2448 // Timing / locking functions.
2449 'SLEEP',
2450 'BENCHMARK',
2451 'GET_LOCK',
2452 'RELEASE_LOCK',
2453 'RELEASE_ALL_LOCKS',
2454 'IS_FREE_LOCK',
2455 'IS_USED_LOCK',
2456 'WAIT_FOR_EXECUTED_GTID_SET',
2457 'WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS',
2458 'MASTER_POS_WAIT',
2459 'SOURCE_POS_WAIT',
2460 'GTID_SUBSET',
2461 'GTID_SUBTRACT',
2462
2463 // Encoding / encryption / compression functions.
2464 'FROM_BASE64',
2465 'TO_BASE64',
2466 'UNHEX',
2467 'AES_ENCRYPT',
2468 'AES_DECRYPT',
2469 'DES_ENCRYPT',
2470 'DES_DECRYPT',
2471 'ENCODE',
2472 'DECODE',
2473 'COMPRESS',
2474 'UNCOMPRESS',
2475 'UNCOMPRESSED_LENGTH',
2476
2477 // Error-based extraction (leak data through forced XPath / other errors).
2478 'EXTRACTVALUE',
2479 'UPDATEXML',
2480
2481 // Deprecated analysis clause.
2482 'ANALYSE',
2483
2484 // Common lib_mysqludf_sys UDFs.
2485 'SYS_EXEC',
2486 'SYS_EVAL',
2487 ];
2488
2489 /**
2490 * Allow filtering the list of additional unsafe functions to disallow.
2491 *
2492 * @since 3.1.0
2493 *
2494 * @param array $unsafe_functions The list of unsafe functions to disallow.
2495 * @param string $sql The SQL fragment to check.
2496 */
2497 $additional_unsafe_functions = (array) apply_filters( 'pods_access_sql_fragment_disallow_unsafe_functions', $unsafe_functions, $sql );
2498
2499 $unsafe_functions = array_unique( array_filter( array_merge( $unsafe_functions, $additional_unsafe_functions ) ) );
2500
2501 foreach ( $unsafe_functions as $unsafe_function ) {
2502 if ( 1 === (int) preg_match( '/\s*' . preg_quote( $unsafe_function, '/' ) . '\s*\(/i', $sql ) ) {
2503 return false;
2504 }
2505 }
2506
2507 return $allowed;
2508 }
2509
2510 /**
2511 * Disallow unsafe tables from being used in SQL fragments.
2512 *
2513 * @since 3.1.0
2514 *
2515 * @param bool $allowed Whether the SQL fragment is allowed to be used.
2516 * @param string $sql The SQL fragment to check.
2517 *
2518 * @return bool Whether the SQL fragment is allowed to be used.
2519 */
2520 function pods_access_sql_fragment_disallow_unsafe_tables( bool $allowed, string $sql ): bool {
2521 if ( ! $allowed ) {
2522 return $allowed;
2523 }
2524
2525 $unsafe_tables = [
2526 'mysql.',
2527 'information_schema.',
2528 'performance_schema.',
2529 'sys.',
2530 ];
2531
2532 /**
2533 * Allow filtering the list of unsafe tables to disallow.
2534 *
2535 * @since 3.1.0
2536 *
2537 * @param array $unsafe_tables The list of unsafe tables to disallow.
2538 * @param string $sql The SQL fragment to check.
2539 */
2540 $unsafe_tables = (array) apply_filters( 'pods_access_sql_fragment_disallow_unsafe_tables', $unsafe_tables, $sql );
2541
2542 $unsafe_tables = array_filter( $unsafe_tables );
2543
2544 /*
2545 * Normalize the fragment before matching so that identifier quoting and
2546 * spacing around the "." separator cannot be used to evade the check, e.g.
2547 * "`information_schema`.`tables`" or "information_schema . tables" both
2548 * normalize to "information_schema.tables".
2549 */
2550 $normalized_sql = str_replace( '`', '', $sql );
2551 $normalized_sql = preg_replace( '/\s*\.\s*/', '.', $normalized_sql );
2552
2553 foreach ( $unsafe_tables as $unsafe_table ) {
2554 if ( 1 === (int) preg_match( '/' . preg_quote( $unsafe_table, '/' ) . '/i', $normalized_sql ) ) {
2555 return false;
2556 }
2557 }
2558
2559 return $allowed;
2560 }
2561
2562 /**
2563 * Disallow double hyphens from being used in SQL fragments.
2564 *
2565 * @since 3.1.0
2566 *
2567 * @param bool $allowed Whether the SQL fragment is allowed to be used.
2568 * @param string $sql The SQL fragment to check.
2569 *
2570 * @return bool Whether the SQL fragment is allowed to be used.
2571 */
2572 function pods_access_sql_fragment_disallow_double_hyphens( bool $allowed, string $sql ): bool {
2573 return (
2574 $allowed
2575 && false === strpos( $sql, '--' )
2576 );
2577 }
2578
2579 /**
2580 * Disallow SQL comment markers from being used in SQL fragments.
2581 *
2582 * @since 3.1.0
2583 *
2584 * @param bool $allowed Whether the SQL fragment is allowed to be used.
2585 * @param string $sql The SQL fragment to check.
2586 *
2587 * @return bool Whether the SQL fragment is allowed to be used.
2588 */
2589 function pods_access_sql_fragment_disallow_comments( bool $allowed, string $sql ): bool {
2590 if ( ! $allowed ) {
2591 return $allowed;
2592 }
2593
2594 if (
2595 false !== strpos( $sql, '--' )
2596 || false !== strpos( $sql, '/*' )
2597 || false !== strpos( $sql, '*/' )
2598 ) {
2599 return false;
2600 }
2601
2602 // Strip quoted string literals so a "#" inside a value is not treated as a comment.
2603 $stripped = preg_replace(
2604 [
2605 "/'(?:[^'\\\\]|\\\\.|'')*'/s",
2606 '/"(?:[^"\\\\]|\\\\.|"")*"/s',
2607 ],
2608 '',
2609 $sql
2610 );
2611
2612 if ( null === $stripped ) {
2613 // preg_replace failed (e.g. malformed input); fail closed.
2614 return false;
2615 }
2616
2617 return false === strpos( $stripped, '#' );
2618 }
2619
2620 /**
2621 * Disallow unsafe keywords from being used in SQL fragments.
2622 *
2623 * @since 3.1.0
2624 *
2625 * @param bool $allowed Whether the SQL fragment is allowed to be used.
2626 * @param string $sql The SQL fragment to check.
2627 *
2628 * @return bool Whether the SQL fragment is allowed to be used.
2629 */
2630 function pods_access_sql_fragment_disallow_unsafe_keywords( bool $allowed, string $sql ): bool {
2631 if ( ! $allowed ) {
2632 return $allowed;
2633 }
2634
2635 $unsafe_patterns = [
2636 // System / session variables.
2637 '/@@/',
2638 // Combining result sets.
2639 '/\bUNION\b/i',
2640 // File output keywords.
2641 '/\bINTO\s+(?:OUTFILE|DUMPFILE)\b/i',
2642 // File read keywords.
2643 '/\bLOAD\s+DATA\b/i',
2644 // Statement separator.
2645 '/;/',
2646 ];
2647
2648 /**
2649 * Allow filtering the list of unsafe keyword patterns to disallow.
2650 *
2651 * Each entry is a full PCRE pattern (including delimiters and flags) that is
2652 * tested against the SQL fragment; a match disallows the fragment.
2653 *
2654 * @since 3.1.0
2655 *
2656 * @param array $unsafe_patterns The list of unsafe keyword patterns to disallow.
2657 * @param string $sql The SQL fragment to check.
2658 */
2659 $unsafe_patterns = (array) apply_filters( 'pods_access_sql_fragment_disallow_unsafe_keywords', $unsafe_patterns, $sql );
2660
2661 $unsafe_patterns = array_filter( $unsafe_patterns );
2662
2663 foreach ( $unsafe_patterns as $unsafe_pattern ) {
2664 if ( 1 === (int) preg_match( $unsafe_pattern, $sql ) ) {
2665 return false;
2666 }
2667 }
2668
2669 return $allowed;
2670 }
2671
2672 /**
2673 * Disallow subqueries from being used in SQL fragments.
2674 *
2675 * @since 3.1.0
2676 *
2677 * @param bool $allowed Whether the SQL fragment is allowed to be used.
2678 * @param string $sql The SQL fragment to check.
2679 *
2680 * @return bool Whether the SQL fragment is allowed to be used.
2681 */
2682 function pods_access_sql_fragment_disallow_subqueries( bool $allowed, string $sql ): bool {
2683 return (
2684 $allowed
2685 && 0 === (int) preg_match( '/\s*SELECT(\s|\()+/i', $sql )
2686 );
2687 }
2688
2689 /**
2690 * Disallow post_status from being used in the WHERE/HAVING/FIELD SQL fragment unless they have admin access,
2691 * can edit posts for the post type, or the fragment only compares post_status to publish.
2692 *
2693 * @since 3.1.0
2694 *
2695 * @param bool $allowed Whether the SQL fragment is allowed to be used.
2696 * @param string $sql The SQL fragment to check.
2697 * @param string $context The SQL fragment context.
2698 * @param array $info Pod information.
2699 * @param object|null $params The parameters passed to Pods::find() or PodsData::select().
2700 *
2701 * @return bool Whether the SQL fragment is allowed to be used.
2702 */
2703 function pods_access_sql_fragment_disallow_post_status( bool $allowed, string $sql, string $context, array $info, ?object $params = null ): bool {
2704 if ( ! $allowed ) {
2705 return $allowed;
2706 }
2707
2708 if ( 'WHERE' !== $context && 'HAVING' !== $context && 'FIELD' !== $context ) {
2709 return true;
2710 }
2711
2712 // Check if post_status is allowed.
2713 if ( false === stripos( $sql, 'post_status' ) ) {
2714 return true;
2715 }
2716
2717 if ( empty( $params ) || empty( $params->from ) || ! in_array( $params->from, [ 'dynamic-embed', 'pick/get_object_data' ], true ) ) {
2718 return true;
2719 }
2720
2721 if ( pods_is_admin() ) {
2722 return true;
2723 }
2724
2725 if (
2726 ! empty( $info['object_type'] )
2727 && 'post_type' === $info['object_type']
2728 && ! empty( $info['object_name'] )
2729 ) {
2730 $post_type_object = get_post_type_object( $info['object_name'] );
2731
2732 if (
2733 $post_type_object instanceof WP_Post_Type
2734 && $post_type_object->cap->edit_posts
2735 && current_user_can( $post_type_object->cap->edit_posts )
2736 ) {
2737 return true;
2738 }
2739 }
2740
2741 // Check for variations and exclude them, if post_status still matches then return false.
2742 $safe_sql = preg_replace(
2743 '/post_status\s*=\s*(?:\'publish\'|"publish")/i',
2744 '',
2745 $sql
2746 );
2747
2748 return (
2749 null === $safe_sql
2750 || false === stripos( $safe_sql, 'post_status' )
2751 );
2752 }
2753
2754 /**
2755 * Safely unserialize data if it's PHP serialized.
2756 *
2757 * @since 3.1.0
2758 *
2759 * @param string|mixed $data The data to unserialize.
2760 *
2761 * @return array|string|mixed The unserialized data if it was PHP serialized, otherwise the data as it was.
2762 */
2763 function pods_maybe_safely_unserialize( $data ) {
2764 // The $options parameter of unserialize() requires PHP 7.0+.
2765 if ( version_compare( PHP_VERSION, '7.0', '<' ) ) {
2766 // On PHP < 7, refuse payloads that contain a serialized object; other data falls back to the normal WP function, to help prevent security issues.
2767 if ( is_string( $data ) && preg_match( '/(?:^|;|{)[OC]:\d+:"/', $data ) ) {
2768 return $data;
2769 }
2770
2771 // Fall back to normal WP function.
2772 return maybe_unserialize( $data );
2773 }
2774
2775 // Check if the data is serialized.
2776 if ( is_serialized( $data ) ) {
2777 $data = trim( $data );
2778
2779 // Unserialize the data but exclude classes.
2780 return @unserialize( $data, [ 'allowed_classes' => false ] );
2781 }
2782
2783 return $data;
2784 }
2785
2786 /**
2787 * Get the field name map used for Pods form nonce hidden inputs.
2788 *
2789 * @since 3.3.9.2
2790 *
2791 * @param string $context The form context. Accepts 'form' or 'meta'.
2792 * @param string $group_key Optional group key used to suffix the field names so multiple
2793 * groups on the same page do not collide (defaults to empty).
2794 *
2795 * @return array {
2796 * The hidden field names.
2797 *
2798 * @type string $nonce The nonce field name.
2799 * @type string $pod The pod field name.
2800 * @type string $id The item ID field name.
2801 * @type string $uri The URI hash field name.
2802 * @type string $form The field list field name.
2803 * }
2804 */
2805 function pods_access_form_field_names( $context, $group_key = '' ) {
2806 $prefix = '_pods_';
2807
2808 if ( 'meta' === $context ) {
2809 $prefix = 'pods_meta_';
2810 }
2811
2812 $suffix = '';
2813
2814 if ( is_scalar( $group_key ) && '' !== (string) $group_key ) {
2815 $group_key = sanitize_key( (string) $group_key );
2816
2817 if ( '' !== $group_key ) {
2818 $suffix = '_' . $group_key;
2819 }
2820 }
2821
2822 return [
2823 'nonce' => $prefix . 'nonce' . $suffix,
2824 'pod' => $prefix . 'pod' . $suffix,
2825 'id' => $prefix . 'id' . $suffix,
2826 'uri' => $prefix . 'uri' . $suffix,
2827 'form' => $prefix . 'form' . $suffix,
2828 ];
2829 }
2830
2831 /**
2832 * Get the UID used for Pods form nonces.
2833 *
2834 * @since 3.3.9.2
2835 *
2836 * @return string The UID.
2837 */
2838 function pods_access_form_uid() {
2839 if ( is_user_logged_in() ) {
2840 return 'user_' . get_current_user_id();
2841 }
2842
2843 return pods_session_id();
2844 }
2845
2846 /**
2847 * Get the URI hash used for Pods form nonces.
2848 *
2849 * @since 3.3.9.2
2850 *
2851 * @param string|null $path The request path. Defaults to the current path.
2852 *
2853 * @return string The URI hash.
2854 */
2855 function pods_access_form_uri_hash( $path = null ) {
2856 if ( null === $path || '' === $path ) {
2857 $path = pods_current_path();
2858 }
2859
2860 return wp_create_nonce( 'pods_uri_' . (string) $path );
2861 }
2862
2863 /**
2864 * Normalize a list of form fields to a comma-separated string.
2865 *
2866 * @since 3.3.9.2
2867 *
2868 * @param array|string $submitted_fields The fields array or comma-separated string.
2869 *
2870 * @return string The normalized field list.
2871 */
2872 function pods_access_form_normalize_fields( $submitted_fields ) {
2873 if ( is_string( $submitted_fields ) ) {
2874 return $submitted_fields;
2875 }
2876
2877 if ( ! is_array( $submitted_fields ) ) {
2878 return '';
2879 }
2880
2881 if ( isset( $submitted_fields[0] ) && is_string( $submitted_fields[0] ) ) {
2882 $names = [];
2883
2884 foreach ( $submitted_fields as $submitted_field ) {
2885 if ( ! is_scalar( $submitted_field ) ) {
2886 return implode( ',', array_keys( $submitted_fields ) );
2887 }
2888
2889 $names[] = (string) $submitted_field;
2890 }
2891
2892 return implode( ',', $names );
2893 }
2894
2895 return implode( ',', array_keys( $submitted_fields ) );
2896 }
2897
2898 /**
2899 * Get the field hash used for Pods form nonces.
2900 *
2901 * @since 3.3.9.2
2902 *
2903 * @param array|string $submitted_fields The fields array or comma-separated string.
2904 *
2905 * @return string The field hash.
2906 */
2907 function pods_access_form_field_hash( $submitted_fields ) {
2908 $form = pods_access_form_normalize_fields( $submitted_fields );
2909
2910 return wp_create_nonce( 'pods_fields_' . $form );
2911 }
2912
2913 /**
2914 * Build the nonce action string for a Pods form.
2915 *
2916 * @since 3.3.9.2
2917 *
2918 * @param string $pod The Pod name.
2919 * @param int|string $id The item ID.
2920 * @param array|string $submitted_fields The fields array or comma-separated string.
2921 * @param string|null $uri_hash The URI hash. Defaults to the current path hash.
2922 * @param string|null $uid The UID. Defaults to the current user or session ID.
2923 *
2924 * @return string The nonce action string.
2925 */
2926 function pods_access_form_nonce_action( $pod, $id, $submitted_fields, $uri_hash = null, $uid = null ) {
2927 if ( null === $uri_hash ) {
2928 $uri_hash = pods_access_form_uri_hash();
2929 }
2930
2931 if ( null === $uid ) {
2932 $uid = pods_access_form_uid();
2933 }
2934
2935 $field_hash = pods_access_form_field_hash( $submitted_fields );
2936
2937 return 'pods_form_' . (string) $pod . '_' . (string) $uid . '_' . (int) $id . '_' . (string) $uri_hash . '_' . (string) $field_hash;
2938 }
2939
2940 /**
2941 * Create a Pods form nonce.
2942 *
2943 * @since 3.3.9.2
2944 *
2945 * @param string $pod The Pod name.
2946 * @param int|string $id The item ID.
2947 * @param array|string $submitted_fields The fields array or comma-separated string.
2948 * @param string|null $uri_hash The URI hash. Defaults to the current path hash.
2949 *
2950 * @return string The nonce.
2951 */
2952 function pods_access_create_form_nonce( $pod, $id, $submitted_fields, $uri_hash = null ) {
2953 return wp_create_nonce( pods_access_form_nonce_action( $pod, $id, $submitted_fields, $uri_hash ) );
2954 }
2955
2956 /**
2957 * Verify a Pods form nonce.
2958 *
2959 * @since 3.3.9.2
2960 *
2961 * @param string $nonce The nonce value.
2962 * @param string $pod The Pod name.
2963 * @param int|string $id The item ID.
2964 * @param array|string $submitted_fields The fields array or comma-separated string.
2965 * @param string|null $uri_hash The URI hash. Defaults to the current path hash.
2966 *
2967 * @return bool Whether the nonce is valid.
2968 */
2969 function pods_access_verify_form_nonce( $nonce, $pod, $id, $submitted_fields, $uri_hash = null ) {
2970 if ( ! is_scalar( $nonce ) || '' === $nonce ) {
2971 return false;
2972 }
2973
2974 $uid = pods_access_form_uid();
2975
2976 if ( empty( $uid ) ) {
2977 return false;
2978 }
2979
2980 $action = pods_access_form_nonce_action( $pod, $id, $submitted_fields, $uri_hash, $uid );
2981
2982 return false !== wp_verify_nonce( (string) $nonce, $action );
2983 }
2984
2985 /**
2986 * Get hidden fields for a Pods form nonce as an HTML string.
2987 *
2988 * @since 3.3.9.2
2989 *
2990 * @param string $pod The Pod name.
2991 * @param int|string $id The item ID.
2992 * @param array|string $submitted_fields The fields array or comma-separated string.
2993 * @param array|null $nonce_field_names The hidden nonce field names. Defaults to standard nonce form fields.
2994 * @param string|null $uri_hash The URI hash. Defaults to the current path hash.
2995 *
2996 * @return string The hidden field HTML.
2997 */
2998 function pods_access_get_form_nonce_fields( $pod, $id, $submitted_fields, $nonce_field_names = null, $uri_hash = null ) {
2999 if ( null === $nonce_field_names ) {
3000 $nonce_field_names = pods_access_form_field_names( 'form' );
3001 }
3002
3003 if ( null === $uri_hash ) {
3004 $uri_hash = pods_access_form_uri_hash();
3005 }
3006
3007 $form = pods_access_form_normalize_fields( $submitted_fields );
3008 $nonce = pods_access_create_form_nonce( $pod, $id, $submitted_fields, $uri_hash );
3009
3010 $html = PodsForm::field( $nonce_field_names['nonce'], $nonce, 'hidden' );
3011 $html .= PodsForm::field( $nonce_field_names['pod'], (string) $pod, 'hidden' );
3012 $html .= PodsForm::field( $nonce_field_names['id'], (int) $id, 'hidden' );
3013 $html .= PodsForm::field( $nonce_field_names['uri'], (string) $uri_hash, 'hidden' );
3014 $html .= PodsForm::field( $nonce_field_names['form'], $form, 'hidden' );
3015
3016 return $html;
3017 }
3018
3019 /**
3020 * Output hidden fields for a Pods form nonce.
3021 *
3022 * @since 3.3.9.2
3023 *
3024 * @param string $pod The Pod name.
3025 * @param int|string $id The item ID.
3026 * @param array|string $submitted_fields The fields array or comma-separated string.
3027 * @param array|null $nonce_field_names The hidden nonce field names. Defaults to standard nonce form fields.
3028 * @param string|null $uri_hash The URI hash. Defaults to the current path hash.
3029 *
3030 * @return void
3031 */
3032 function pods_access_output_form_nonce_fields( $pod, $id, $submitted_fields, $nonce_field_names = null, $uri_hash = null ) {
3033 echo pods_access_get_form_nonce_fields( $pod, $id, $submitted_fields, $nonce_field_names, $uri_hash ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
3034 }
3035
3036 /**
3037 * Verify a Pods form nonce from the request.
3038 *
3039 * @since 3.3.9.2
3040 *
3041 * @param array|null $nonce_field_names The hidden nonce field names. Defaults to standard nonce form fields.
3042 * @param string $source The request source. Defaults to 'post'.
3043 *
3044 * @return bool Whether the nonce is valid.
3045 */
3046 function pods_access_verify_form_nonce_from_request( $nonce_field_names = null, $source = 'post' ) {
3047 if ( null === $nonce_field_names ) {
3048 $nonce_field_names = pods_access_form_field_names( 'form' );
3049 }
3050
3051 $nonce = pods_v( $nonce_field_names['nonce'], $source );
3052 $pod = pods_v( $nonce_field_names['pod'], $source );
3053 $id = pods_v( $nonce_field_names['id'], $source );
3054 $uri = pods_v( $nonce_field_names['uri'], $source );
3055 $form = pods_v( $nonce_field_names['form'], $source );
3056
3057 if (
3058 ! is_string( $nonce )
3059 || ! is_string( $pod )
3060 || ( ! is_string( $id ) && ! is_numeric( $id ) )
3061 || ! is_string( $uri )
3062 || ! is_string( $form )
3063 || '' === $nonce
3064 || '' === $pod
3065 || '' === $uri
3066 || '' === $form
3067 ) {
3068 return false;
3069 }
3070
3071 return pods_access_verify_form_nonce( (string) $nonce, (string) $pod, (int) $id, (string) $form, (string) $uri );
3072 }
3073
3074 /**
3075 * Determine whether a Pods form nonce is present in the request.
3076 *
3077 * This does not verify the nonce value, only whether the nonce field was submitted.
3078 *
3079 * @since 3.3.9.2
3080 *
3081 * @param string $context The form context. Accepts 'form' or 'meta'.
3082 * @param string $group_key Optional group key used to suffix the field names so multiple
3083 * groups on the same page do not collide (defaults to empty).
3084 * @param string $source The request source. Defaults to 'post'.
3085 *
3086 * @return bool Whether the nonce field is present.
3087 */
3088 function pods_access_form_nonce_present_in_request( $context = 'form', $group_key = '', $source = 'post' ) {
3089 $nonce_field_names = pods_access_form_field_names( $context, $group_key );
3090 $nonce = pods_v( $nonce_field_names['nonce'], $source );
3091
3092 return is_string( $nonce );
3093 }
3094