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