PluginProbe ʕ •ᴥ•ʔ
Kirki – Freeform Page Builder, Website Builder & Customizer / trunk
Kirki – Freeform Page Builder, Website Builder & Customizer vtrunk
6.3.0 6.2.5 6.2.4 6.2.3 6.2.2 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 weeks ago FormController.php 4 weeks ago FrontendRESTController.php 3 days ago
FrontendRESTController.php
176 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 if( 'kirki_utility' === $context_type ) {
74 // Utility contexts are publicly accessible.
75 $post_id = $this->extract_post_id_from_context( $context );
76 if ( $post_id && ! $this->can_user_read_post( $post_id ) ) {
77 return new WP_Error(
78 'rest_forbidden',
79 'You do not have permission to read this post.',
80 array( 'status' => 403 )
81 );
82 }
83 } else {
84 return new WP_Error(
85 'rest_forbidden',
86 'Unsupported context type.',
87 array( 'status' => 403 )
88 );
89 }
90 }
91
92 // --- Check 2: direct post_id param ---
93 $post_id = absint( $request->get_param( 'post_id' ) );
94
95 if ( $post_id && ! $this->can_user_read_post( $post_id ) ) {
96 return new WP_Error(
97 'rest_forbidden',
98 'You do not have permission to read this post.',
99 array( 'status' => 403 )
100 );
101 }
102
103 // --- Check 3: post_id inside kirki_data (the post actually rendered) ---
104 $raw_kirki_data = $request->get_param( 'kirki_data' );
105
106 if ( $raw_kirki_data ) {
107 $kirki_data = json_decode( $raw_kirki_data, true );
108 $kirki_data_post_id = is_array( $kirki_data ) && isset( $kirki_data['post_id'] ) ? absint( $kirki_data['post_id'] ) : 0;
109
110 if ( $kirki_data_post_id && ! $this->can_user_read_post( $kirki_data_post_id ) ) {
111 return new WP_Error(
112 'rest_forbidden',
113 'You do not have permission to read this post.',
114 array( 'status' => 403 )
115 );
116 }
117 }
118
119 return true;
120 }
121
122 /**
123 * Extracts the relevant post ID from a decoded context array.
124 * Only handles 'post' and 'comment' types; all other types return null
125 * because they do not reference a post that requires a read-access check.
126 * NOTE: Callers MUST perform their own capability checks for non-post
127 * context types (user, term, etc.) before reaching this method.
128 *
129 * @param mixed $context Decoded JSON context.
130 * @return int|null Sanitized post ID, or null if not applicable.
131 */
132 private function extract_post_id_from_context( $context ): ?int {
133 if ( ! is_array( $context ) || empty( $context['type'] ) ) {
134 return null;
135 }
136
137 switch( $context['type'] ) {
138 case 'post':
139 return isset( $context['id'] ) ? absint( $context['id'] ) : null;
140 case 'comment':
141 return isset( $context['post_id'] ) ? absint( $context['post_id'] ) : null;
142 case 'kirki_utility':
143 return isset( $context['kirki_utility_page_id'] ) ? absint( $context['kirki_utility_page_id'] ) : null;
144 default:
145 return null;
146 }
147 }
148
149 /**
150 * Determines whether the current user can read the given post.
151 * Publicly published posts are readable by anyone (no login required).
152 * Private/draft/etc. fall back to WordPress capability check.
153 *
154 * @param int $post_id
155 * @return bool
156 */
157 protected function can_user_read_post( int $post_id ): bool {
158 $post = get_post( $post_id );
159
160 if ( ! $post ) {
161 return false;
162 }
163
164 $is_password_protected = !empty( $post->post_password );
165
166 if ( 'publish' === $post->post_status && ! $is_password_protected ) {
167 return true;
168 }
169
170 if ( 'publish' === $post->post_status && $is_password_protected ) {
171 return ! post_password_required( $post ) || current_user_can( 'read_post', $post_id );
172 }
173
174 return current_user_can( 'read_post', $post_id );
175 }
176 }