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