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