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