PluginProbe
Parse.ly / 3.24.2
Parse.ly v3.24.2
3.24.2 3.24.1 3.24.0 3.23.7 3.23.6 3.23.5 3.23.4 3.23.3 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.17.0 3.18.0 3.18.1 3.19.0 3.19.1 3.19.2 3.19.3 3.2.0 3.2.1 3.20.0 3.20.1 3.20.2 All 106 releases
wp-parsely / src / rest-api / class-base-endpoint.php

class-base-endpoint.php in Parse.ly 3.24.2, at src/rest-api/class-base-endpoint.php

322 lines 8.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Base API Endpoint
4 *
5 * @package Parsely
6 * @since 3.17.0
7 */
8
9 declare(strict_types=1);
10
11 namespace Parsely\REST_API;
12
13 use Parsely\Parsely;
14 use Parsely\Utils\Utils;
15 use WP_Error;
16 use WP_REST_Request;
17
18 /**
19 * Base class for API endpoints.
20 *
21 * Most endpoint classes should derive from this class. Child classes should
22 * implement the `get_endpoint_name` and `register_routes` methods.
23 *
24 * @since 3.17.0
25 */
26 abstract class Base_Endpoint {
27 /**
28 * The Parsely instance.
29 *
30 * @since 3.17.0
31 *
32 * @var Parsely
33 */
34 protected $parsely;
35
36 /**
37 * The REST API instance.
38 *
39 * @since 3.17.0
40 *
41 * @var Base_API_Controller $api_controller
42 */
43 protected $api_controller;
44
45 /**
46 * The registered routes.
47 *
48 * @since 3.17.0
49 *
50 * @var array<string>
51 */
52 protected $registered_routes = array();
53
54 /**
55 * Constructor.
56 *
57 * @since 3.17.0
58 *
59 * @param Base_API_Controller $controller The REST API controller.
60 */
61 public function __construct( Base_API_Controller $controller ) {
62 $this->api_controller = $controller;
63 $this->parsely = $controller->get_parsely();
64 }
65
66 /**
67 * Initializes the API endpoint, by registering the routes.
68 *
69 * Allows for the endpoint to be disabled via the
70 * `wp_parsely_api_{endpoint}_endpoint_enabled` filter.
71 *
72 * @since 3.17.0
73 */
74 public function init(): void {
75 /**
76 * Filter to enable/disable the endpoint.
77 *
78 * @return bool
79 */
80 $filter_name = 'wp_parsely_api_' .
81 Utils::convert_endpoint_to_filter_key( static::get_endpoint_name() ) .
82 '_endpoint_enabled';
83 if ( ! apply_filters( $filter_name, true ) ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
84 return;
85 }
86
87 // Register the routes.
88 add_action( 'rest_api_init', array( $this, 'register_routes' ) );
89 }
90
91 /**
92 * Returns the endpoint's name.
93 *
94 * This method should be overridden by child classes and used to return the
95 * endpoint name.
96 *
97 * @since 3.17.0
98 *
99 * @return string
100 */
101 abstract public static function get_endpoint_name(): string;
102
103 /**
104 * Returns the default access capability for the endpoint.
105 *
106 * This method can be overridden by child classes to return a different
107 * default access capability.
108 *
109 * @since 3.17.0
110 *
111 * @return string
112 */
113 protected function get_default_access_capability(): string {
114 return 'publish_posts';
115 }
116
117 /**
118 * Registers the routes for the endpoint.
119 *
120 * This method should be overridden by child classes and used to register
121 * the routes for the endpoint.
122 *
123 * @since 3.17.0
124 */
125 abstract public function register_routes(): void;
126
127 /**
128 * Registers a REST route.
129 *
130 * @since 3.17.0
131 * @since 3.24.2 Added the `$permission_callback` parameter.
132 *
133 * @param string $route The route to register.
134 * @param string[] $methods Array with the allowed methods.
135 * @param callable $callback Callback function to call when the endpoint is hit.
136 * @param array<mixed> $args The endpoint arguments definition.
137 * @param callable|null $permission_callback Permission callback to use instead of the default one.
138 */
139 public function register_rest_route(
140 string $route,
141 array $methods,
142 callable $callback,
143 array $args = array(),
144 ?callable $permission_callback = null
145 ): void {
146 // Trim any possible slashes from the route.
147 $route = trim( $route, '/' );
148
149 // Store the route for later reference.
150 $this->registered_routes[] = $route;
151
152 // Create the full route for the endpoint.
153 $route = static::get_endpoint_name() . '/' . $route;
154
155 // Register the route.
156 register_rest_route(
157 $this->api_controller->get_full_namespace(),
158 $this->api_controller->prefix_route( $route ),
159 array(
160 array(
161 'methods' => $methods,
162 'callback' => $callback,
163 'permission_callback' => $permission_callback ??
164 array( $this, 'is_available_to_current_user' ),
165 'args' => $args,
166 'show_in_index' => ! is_wp_error( $this->is_available_to_current_user() ),
167 ),
168 )
169 );
170 }
171
172 /**
173 * Returns the full endpoint path for a given route.
174 *
175 * @since 3.17.0
176 *
177 * @param string $route The route.
178 * @return string
179 */
180 public function get_full_endpoint( string $route = '' ): string {
181 $route = trim( $route, '/' );
182
183 if ( '' !== $route ) {
184 $route = static::get_endpoint_name() . '/' . $route;
185 } else {
186 $route = static::get_endpoint_name();
187 }
188
189 return '/' .
190 $this->api_controller->get_full_namespace() .
191 '/' .
192 $this->api_controller->prefix_route( $route );
193 }
194
195 /**
196 * Returns the endpoint slug.
197 *
198 * The slug is the endpoint's name prefixed with the route prefix, from
199 * the API controller.
200 *
201 * Used as an identifier for the endpoint, when registering routes.
202 *
203 * @since 3.17.0
204 *
205 * @return string
206 */
207 public function get_endpoint_slug(): string {
208 return $this->api_controller->prefix_route( '' ) . static::get_endpoint_name();
209 }
210
211 /**
212 * Returns the registered routes.
213 *
214 * @since 3.17.0
215 *
216 * @return array<string>
217 */
218 public function get_registered_routes(): array {
219 return $this->registered_routes;
220 }
221
222 /**
223 * Returns whether the endpoint is available for access by the current
224 * user.
225 *
226 * @since 3.14.0 Replaced `is_public_endpoint`, `user_capability` and `permission_callback()`.
227 * @since 3.16.0 Added the `$request` parameter.
228 * @since 3.17.0 Moved to the new API structure.
229 *
230 * @param WP_REST_Request|null $request The request object.
231 * @return WP_Error|bool True if the endpoint is available.
232 */
233 public function is_available_to_current_user( ?WP_REST_Request $request = null ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
234 // Validate the API key and secret.
235 $api_key_validation = $this->validate_site_id_and_secret();
236 if ( is_wp_error( $api_key_validation ) ) {
237 return $api_key_validation;
238 }
239
240 // Validate the user capability.
241 $capability = $this->get_default_access_capability();
242 return current_user_can(
243 // phpcs:ignore WordPress.WP.Capabilities.Undetermined
244 $this->apply_capability_filters( $capability )
245 );
246 }
247
248 /**
249 * Returns the user capability allowing access to the endpoint, after having
250 * applied capability filters.
251 *
252 * The default access capability is not passed here by default, to allow for
253 * a more explicit declaration in child classes.
254 *
255 * The result is checked with current_user_can(), so a capability such as
256 * `exist` makes the endpoint available to everyone, unless other checks
257 * apply: post-specific routes also require edit access to the post, and
258 * Content Intelligence routes also check the user's role.
259 *
260 * @since 3.14.0
261 * @since 3.17.0 Moved to the new API structure.
262 *
263 * @param string $capability The original capability allowing access.
264 * @return string The capability allowing access after applying the filters.
265 */
266 public function apply_capability_filters( string $capability ): string {
267 /**
268 * Filter to change the default user capability for all private endpoints.
269 *
270 * @var string
271 */
272 $default_user_capability = apply_filters(
273 'wp_parsely_user_capability_for_all_private_apis',
274 $capability
275 );
276
277 /**
278 * Filter to change the user capability for the specific endpoint.
279 *
280 * @var string
281 */
282 $endpoint_specific_user_capability = apply_filters(
283 'wp_parsely_user_capability_for_' .
284 Utils::convert_endpoint_to_filter_key( static::get_endpoint_name() ) .
285 '_api',
286 $default_user_capability
287 );
288
289 return $endpoint_specific_user_capability;
290 }
291
292 /**
293 * Validates that the Site ID and secret are set.
294 * If the API secret is not required, it will not be validated.
295 *
296 * @since 3.13.0
297 * @since 3.17.0 Moved to the new API structure and renamed from `validate_apikey_and_secret`.
298 *
299 * @param bool $require_api_secret Specifies if the API Secret is required.
300 * @return WP_Error|bool
301 */
302 public function validate_site_id_and_secret( bool $require_api_secret = true ) {
303 if ( false === $this->parsely->site_id_is_set() ) {
304 return new WP_Error(
305 'parsely_site_id_not_set',
306 __( 'A Parse.ly Site ID must be set in site options to use this endpoint', 'wp-parsely' ),
307 array( 'status' => 403 )
308 );
309 }
310
311 if ( $require_api_secret && false === $this->parsely->api_secret_is_set() ) {
312 return new WP_Error(
313 'parsely_api_secret_not_set',
314 __( 'A Parse.ly API Secret must be set in site options to use this endpoint', 'wp-parsely' ),
315 array( 'status' => 403 )
316 );
317 }
318
319 return true;
320 }
321 }
322