PluginProbe ʕ •ᴥ•ʔ
Kirki – Freeform Page Builder, Website Builder & Customizer / 6.2.1
Kirki – Freeform Page Builder, Website Builder & Customizer v6.2.1
6.2.1 6.2.0 6.1.1 6.1.0 6.0.14 6.0.13 6.0.12 6.0.11 6.0.10 6.0.9 6.0.8 6.0.7 6.0.6 6.0.5 6.0.4 6.0.3 6.0.2 6.0.1 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 4.0.19 4.0.20 4.0.21 4.0.22 4.0.23 4.0.24 4.1 4.2.0 5.0.0 5.1.0 5.1.1 5.2.0 5.2.1 5.2.2 5.2.3 6.0.0 trunk 3.0.40 3.0.41 3.0.42 3.0.43 3.0.44 3.0.45 3.1.0 3.1.1 3.1.2
kirki / includes / API / Frontend / Controllers / FrontendRESTController.php
kirki / includes / API / Frontend / Controllers Last commit date
CollectionController.php 4 days ago FormController.php 4 days ago FrontendRESTController.php 4 days ago
FrontendRESTController.php
148 lines
1 <?php
2 /**
3 * FrontendRESTController
4 *
5 * @package kirki
6 */
7
8 namespace Kirki\API\Frontend\Controllers;
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit; // Exit if accessed directly.
12 }
13 use WP_Error;
14 use WP_REST_Controller;
15
16
17 /**
18 * FrontendRESTController class
19 */
20 abstract class FrontendRESTController extends WP_REST_Controller {
21 /**
22 * Initialize the class
23 *
24 * @return void
25 */
26 public function __construct() {
27 $this->namespace ='kirki/v1';
28 $this->rest_base = 'frontend';
29 }
30
31
32 /**
33 * Permission gate for all frontend endpoints.
34 *
35 * Checks:
36 * 1. A JSON `context` param — validates the user can read the referenced post.
37 * 2. A direct `post_id` param — validates the user can read that post.
38 *
39 * @param \WP_REST_Request $request
40 * @return bool|\WP_Error
41 */
42 public function get_item_permissions_check( $request ) {
43 // --- Check 1: context-based permission ---
44 $raw_context = $request->get_param( 'context' );
45
46 if ( $raw_context ) {
47 $context = json_decode( $raw_context, true );
48
49 $context_type = $context['type'];
50
51 // For user contexts, require list_users capability.
52 if ( 'user' === $context_type ) {
53 $target_user_id = absint( $context['id'] ?? 0 );
54 // Allow access only if the requester is querying their own record OR list_users capability
55 if ( $target_user_id !== get_current_user_id() && ! current_user_can( 'list_users' ) ) {
56 return new WP_Error(
57 'rest_forbidden',
58 'You do not have permission to read this user.',
59 array( 'status' => 403 )
60 );
61 }
62 } elseif ( 'term' === $context_type ) {
63 // Term contexts are publicly accessible (terms are public by default).
64 } elseif ( 'post' === $context_type || 'comment' === $context_type ) {
65 $post_id = $this->extract_post_id_from_context( $context );
66 if ( $post_id && ! $this->can_user_read_post( $post_id ) ) {
67 return new WP_Error(
68 'rest_forbidden',
69 'You do not have permission to read this post.',
70 array( 'status' => 403 )
71 );
72 }
73 } else {
74 return new WP_Error(
75 'rest_forbidden',
76 'Unsupported context type.',
77 array( 'status' => 403 )
78 );
79 }
80 }
81
82 // --- Check 2: direct post_id param ---
83 $post_id = absint( $request->get_param( 'post_id' ) );
84
85 if ( $post_id && ! $this->can_user_read_post( $post_id ) ) {
86 return new WP_Error(
87 'rest_forbidden',
88 'You do not have permission to read this post.',
89 array( 'status' => 403 )
90 );
91 }
92
93 return true;
94 }
95
96 /**
97 * Extracts the relevant post ID from a decoded context array.
98 * Only handles 'post' and 'comment' types; all other types return null
99 * because they do not reference a post that requires a read-access check.
100 * NOTE: Callers MUST perform their own capability checks for non-post
101 * context types (user, term, etc.) before reaching this method.
102 *
103 * @param mixed $context Decoded JSON context.
104 * @return int|null Sanitized post ID, or null if not applicable.
105 */
106 private function extract_post_id_from_context( $context ): ?int {
107 if ( ! is_array( $context ) || empty( $context['type'] ) ) {
108 return null;
109 }
110
111 switch( $context['type'] ) {
112 case 'post':
113 return isset( $context['id'] ) ? absint( $context['id'] ) : null;
114 case 'comment':
115 return isset( $context['post_id'] ) ? absint( $context['post_id'] ) : null;
116 default:
117 return null;
118 }
119 }
120
121 /**
122 * Determines whether the current user can read the given post.
123 * Publicly published posts are readable by anyone (no login required).
124 * Private/draft/etc. fall back to WordPress capability check.
125 *
126 * @param int $post_id
127 * @return bool
128 */
129 protected function can_user_read_post( int $post_id ): bool {
130 $post = get_post( $post_id );
131
132 if ( ! $post ) {
133 return false;
134 }
135
136 $is_password_protected = !empty( $post->post_password );
137
138 if ( 'publish' === $post->post_status && ! $is_password_protected ) {
139 return true;
140 }
141
142 if ( 'publish' === $post->post_status && $is_password_protected ) {
143 return ! post_password_required( $post ) || current_user_can( 'read_post', $post_id );
144 }
145
146 return current_user_can( 'read_post', $post_id );
147 }
148 }