PluginProbe
Redirection / 5.8.1
Redirection v5.8.1
5.10.0 5.9.0 5.8.1 5.8.0 3.7.2 3.7.3 4.0 4.0.1 4.1 4.1.1 4.2 4.2.1 4.2.2 4.2.3 4.3 4.3.1 4.3.2 4.3.3 4.4 4.4.1 4.4.2 4.5 4.5.1 4.6.2 4.7.1 All 130 releases
redirection / api / api-redirect.php

api-redirect.php in Redirection 5.8.1, at api/api-redirect.php

459 lines 15.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * @api {get} /redirection/v1/redirect Get redirects
5 * @apiName GetRedirects
6 * @apiDescription Get a paged list of redirects based after applying a set of filters and result ordering.
7 * @apiGroup Redirect
8 *
9 * @apiUse RedirectQueryParams
10 *
11 * @apiUse RedirectList
12 * @apiUse 401Error
13 * @apiUse 404Error
14 */
15
16 /**
17 * @api {post} /redirection/v1/redirect Create redirect
18 * @apiName CreateRedirect
19 * @apiDescription Create a new redirect, and return a paged list of redirects.
20 * @apiGroup Redirect
21 *
22 * @apiUse RedirectItem
23 * @apiUse RedirectQueryParams
24 *
25 * @apiUse RedirectList
26 * @apiUse 401Error
27 * @apiUse 404Error
28 * @apiError (Error 400) redirect_create_failed Failed to create redirect
29 * @apiErrorExample {json} 404 Error Response:
30 * HTTP/1.1 400 Bad Request
31 * {
32 * "code": "redirect_create_failed",
33 * "message": "Failed to create redirect"
34 * }
35 */
36
37 /**
38 * @api {post} /redirection/v1/redirect/:id Update redirect
39 * @apiName UpdateRedirect
40 * @apiDescription Update an existing redirect.
41 * @apiGroup Redirect
42 *
43 * @apiParam (URL) {Integer} :id Redirect ID to update
44 *
45 * @apiUse RedirectItem
46 *
47 * @apiUse RedirectList
48 * @apiUse 401Error
49 * @apiUse 404Error
50 * @apiError (Error 400) redirect_update_failed Failed to update redirect
51 * @apiErrorExample {json} 404 Error Response:
52 * HTTP/1.1 400 Bad Request
53 * {
54 * "code": "redirect_update_failed",
55 * "message": "Failed to update redirect"
56 * }
57 */
58
59 /**
60 * @api {post} /redirection/v1/bulk/redirect/:type Bulk action
61 * @apiName BulkAction
62 * @apiDescription Enable, disable, and delete a set of redirects. The endpoint will return the next page of results after.
63 * performing the action, based on the supplied query parameters. This information can be used to refresh a list displayed to the client.
64 * @apiGroup Redirect
65 *
66 * @apiParam (URL) {String="delete","enable","disable","reset"} :type Type of bulk action that is applied to every item.
67 * @apiParam (Query Parameter) {String[]} [items] Array of redirect IDs to perform the action on
68 * @apiParam (Query Parameter) {Boolean=false} [global] Perform action globally using the filter parameters
69 * @apiUse RedirectQueryParams
70 *
71 * @apiUse RedirectList
72 * @apiUse 401Error
73 * @apiUse 404Error
74 * @apiUse 400MissingError
75 */
76
77 /**
78 * @apiDefine RedirectItem Redirect
79 * All data associated with a redirect
80 *
81 * @apiParam {String="enabled","disabled"} status Status of the redirect
82 * @apiParam {Integer} position Redirect position, used to determine order multiple redirects occur
83 * @apiParam {Object} match_data Additional match parameters
84 * @apiParam {Object} match_data.source Match against the source
85 * @apiParam {Boolean} match_data.source.flag_regex `true` for regular expression, `false` otherwise
86 * @apiParam {String="ignore","exact","pass"} match_data.source.flag_query Which query parameter matching to use
87 * @apiParam {Boolean} match_data.source.flag_case] `true` for case insensitive matches, `false` otherwise
88 * @apiParam {Boolean} match_data.source.flag_trailing] `true` to ignore trailing slashes, `false` otherwise
89 * @apiParam {Object} match_data.options Options for the redirect match
90 * @apiParam {Boolean} match_data.options.log_exclude `true` to exclude this from any logs, `false` otherwise (default)
91 * @apiParam {Boolean} regex True for regular expression, `false` otherwise
92 * @apiParam {String} url The source URL
93 * @apiParam {String="url","referrer","agent","login","header","custom","cookie","role","server","ip","page","language"} match_type What URL matching to use
94 * @apiParam {String} [title] A descriptive title for the redirect, or empty for no title
95 * @apiParam {Integer} group_id The group this redirect belongs to
96 * @apiParam {String} action_type What to do when the URL is matched
97 * @apiParam {Integer} action_code The HTTP code to return
98 * @apiParam {Object} action_data Any data associated with the `action_type` and `match_type`. For example, the target URL
99 */
100
101 /**
102 * @apiDefine RedirectList A list of redirects
103 * A list of redirects
104 *
105 * @apiSuccess {Object[]} items Array of redirect objects
106 * @apiSuccess {Integer} items.id ID of redirect
107 * @apiSuccess {String} items.url Source URL to match
108 * @apiSuccess {String} items.match_url Match URL
109 * @apiSuccess {Object} items.match_data Match against the source
110 * @apiSuccess {String} items.match_type What URL matching to use
111 * @apiSuccess {String} items.action_type What to do when the URL is matched
112 * @apiSuccess {Integer} items.action_code The HTTP code to return
113 * @apiSuccess {String} items.action_data Any data associated with the action_type. For example, the target URL
114 * @apiSuccess {String} items.title Optional A descriptive title for the redirect, or empty for no title
115 * @apiSuccess {String} items.hits Number of hits this redirect has received
116 * @apiSuccess {String} items.regex True for regular expression, false otherwise
117 * @apiSuccess {String} items.group_id The group this redirect belongs to
118 * @apiSuccess {String} items.position Redirect position, used to determine order multiple redirects occur
119 * @apiSuccess {String} items.last_access The date this redirect was last hit
120 * @apiSuccess {String} items.status Status of the redirect
121 * @apiSuccess {Integer} total Number of items
122 *
123 * @apiSuccessExample {json} Success-Response:
124 * HTTP/1.1 200 OK
125 * {
126 * "items": [
127 * {
128 * id: 3,
129 * url: "/source",
130 * match_url: "/source",
131 * match_data: "",
132 * action_code: "",
133 * action_type: "",
134 * action_data: "",
135 * match_type: "url",
136 * title: "Redirect title",
137 * hits: 5,
138 * regex: true,
139 * group_id: 15,
140 * position: 1,
141 * last_access: "2019-01-01 01:01:01"
142 * status: "enabled"
143 * }
144 * ],
145 * "total": 1
146 * }
147 */
148
149 /**
150 * @apiDefine RedirectQueryParams
151 *
152 * @apiParam (Query Parameter) {String="enabled","disabled"} [filterBy[status]] Filter the results by the supplied status
153 * @apiParam (Query Parameter) {String} [filterBy[url]] Filter the results by the supplied URL
154 * @apiParam (Query Parameter) {String="regular","plain"} [filterBy[url-match]] Filter the results by `regular` expressions or non regular expressions
155 * @apiParam (Query Parameter) {String} [filterBy[match]] Filter the results by the supplied match type
156 * @apiParam (Query Parameter) {String} [filterBy[action]] Filter the results by the supplied action type
157 * @apiParam (Query Parameter) {Integer} [filterBy[http]] Filter the results by the supplied redirect HTTP code
158 * @apiParam (Query Parameter) {String="year","month","all"} [filterBy[access]] Filter the results by how long the redirect was last accessed
159 * @apiParam (Query Parameter) {String} [filterBy[target]] Filter the results by the supplied redirect target
160 * @apiParam (Query Parameter) {String} [filterBy[title]] Filter the results by the supplied redirect title
161 * @apiParam (Query Parameter) {Integer} [filterBy[group]] Filter the results by the supplied redirect group ID
162 * @apiParam (Query Parameter) {Integer} [filterBy[id]] Filter the results to the redirect ID
163 * @apiParam (Query Parameter) {Integer="1","2","3"} [filterBy[module]] Filter the results by the supplied module ID
164 * @apiParam (Query Parameter) {String="source","last_count","last_access","position","id"} [orderby=id] Order in which results are returned
165 * @apiParam (Query Parameter) {String="asc","desc"} [direction=desc] Direction to order the results by (ascending or descending)
166 * @apiParam (Query Parameter) {Integer{1...200}} [per_page=25] Number of results per request
167 * @apiParam (Query Parameter) {Integer} [page=0] Current page of results
168 */
169
170 /**
171 * @phpstan-type RedirectListResponse array{
172 * items: list<array<string, mixed>>,
173 * total: int
174 * }
175 *
176 * Redirect API endpoint
177 */
178 class Redirection_Api_Redirect extends Redirection_Api_Filter_Route {
179 /**
180 * Redirect API endpoint constructor
181 *
182 * @param non-falsy-string $api_namespace Namespace.
183 */
184 public function __construct( $api_namespace ) {
185 $orders = [ 'source', 'last_count', 'last_access', 'position', 'id', '' ];
186 $filters = [ 'status', 'url-match', 'match', 'action', 'http', 'access', 'url', 'target', 'title', 'group', 'id' ];
187
188 // GET /redirect - List redirects
189 // POST /redirect - Create redirect
190 register_rest_route(
191 $api_namespace,
192 '/redirect',
193 [
194 [
195 'methods' => WP_REST_Server::READABLE,
196 'callback' => [ $this, 'route_list' ],
197 'permission_callback' => [ $this, 'permission_callback_manage' ],
198 'args' => $this->get_filter_args( $orders, $filters ),
199 ],
200 [
201 'methods' => WP_REST_Server::EDITABLE,
202 'callback' => [ $this, 'route_create' ],
203 'permission_callback' => [ $this, 'permission_callback_add' ],
204 'args' => $this->get_filter_args( $orders, $filters ),
205 ],
206 ]
207 );
208
209 // POST /redirect/:id - Update redirect
210 register_rest_route(
211 $api_namespace,
212 '/redirect/(?P<id>[\d]+)',
213 [
214 [
215 'methods' => WP_REST_Server::EDITABLE,
216 'callback' => [ $this, 'route_update' ],
217 'permission_callback' => [ $this, 'permission_callback_add' ],
218 ],
219 ]
220 );
221
222 // GET /redirect/post - Search for posts
223 register_rest_route(
224 $api_namespace,
225 '/redirect/post',
226 [
227 [
228 'methods' => WP_REST_Server::READABLE,
229 'callback' => [ $this, 'route_match_post' ],
230 'permission_callback' => [ $this, 'permission_callback_manage' ],
231 'args' => [
232 'text' => [
233 'description' => 'Text to match',
234 'type' => 'string',
235 'required' => true,
236 ],
237 ],
238 ],
239 ]
240 );
241
242 // POST /bulk/redirect/:bulk - Bulk actions on redirects
243 register_rest_route(
244 $api_namespace,
245 '/bulk/redirect/(?P<bulk>delete|enable|disable|reset)',
246 [
247 [
248 'methods' => WP_REST_Server::EDITABLE,
249 'callback' => [ $this, 'route_bulk' ],
250 'permission_callback' => [ $this, 'permission_callback_bulk' ],
251 'args' => array_merge(
252 $this->get_filter_args( $orders, $filters ),
253 [
254 'global' => [
255 'description' => 'Apply bulk action globally, as per filters',
256 'type' => 'boolean',
257 ],
258 'items' => [
259 'description' => 'Array of IDs to perform action on',
260 'type' => 'array',
261 'items' => [
262 'description' => 'Item ID',
263 'type' => [ 'string', 'number' ],
264 ],
265 ],
266 ]
267 ),
268 ],
269 ]
270 );
271 }
272
273 /**
274 * Checks a manage capability
275 *
276 * @param WP_REST_Request<array<string, mixed>> $request Request.
277 * @return bool
278 */
279 public function permission_callback_manage( WP_REST_Request $request ) {
280 return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_REDIRECT_MANAGE );
281 }
282
283 /**
284 * Checks a bulk capability
285 *
286 * @param WP_REST_Request<array<string, mixed>> $request Request.
287 * @return bool
288 */
289 public function permission_callback_bulk( WP_REST_Request $request ) {
290 if ( $request['bulk'] === 'delete' ) {
291 return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_REDIRECT_DELETE );
292 }
293
294 return $this->permission_callback_add( $request );
295 }
296
297 /**
298 * Checks a create capability
299 *
300 * @param WP_REST_Request<array<string, mixed>> $request Request.
301 * @return bool
302 */
303 public function permission_callback_add( WP_REST_Request $request ) {
304 return Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_REDIRECT_ADD );
305 }
306
307 /**
308 * Get redirect list
309 *
310 * @param WP_REST_Request<array<string, mixed>> $request The request.
311 * @return RedirectListResponse
312 */
313 public function route_list( WP_REST_Request $request ) {
314 return Red_Item::get_filtered( $request->get_params() );
315 }
316
317 /**
318 * Create new redirect(s)
319 *
320 * @param WP_REST_Request<array<string, mixed>> $request The request.
321 * @return RedirectListResponse|WP_Error
322 */
323 public function route_create( WP_REST_Request $request ) {
324 $params = $request->get_params();
325 $urls = array();
326
327 if ( isset( $params['url'] ) ) {
328 $urls = array( $params['url'] );
329
330 if ( is_array( $params['url'] ) ) {
331 $urls = $params['url'];
332 }
333
334 // Remove duplicates
335 $unique = [];
336 foreach ( $urls as $url ) {
337 $unique[ $url ] = $url;
338 }
339
340 foreach ( $unique as $url ) {
341 $params['url'] = $url;
342
343 // Data is sanitized in the create function
344 $redirect = Red_Item::create( $params );
345
346 if ( is_wp_error( $redirect ) ) {
347 return $this->add_error_details( $redirect, __LINE__ );
348 }
349 }
350 }
351
352 return $this->route_list( $request );
353 }
354
355 /**
356 * Update redirect
357 *
358 * @param WP_REST_Request<array<string, mixed>> $request The request.
359 * @return array{item: array<string, mixed>}|WP_Error
360 */
361 public function route_update( WP_REST_Request $request ) {
362 $params = $request->get_params();
363 $redirect = Red_Item::get_by_id( intval( $params['id'], 10 ) );
364
365 if ( $redirect !== false ) {
366 $result = $redirect->update( $params );
367
368 if ( is_wp_error( $result ) ) {
369 return $this->add_error_details( $result, __LINE__ );
370 }
371
372 return [ 'item' => $redirect->to_json() ];
373 }
374
375 return $this->add_error_details( new WP_Error( 'redirect_update_failed', 'Invalid redirect details' ), __LINE__ );
376 }
377
378 /**
379 * Perform bulk action on redirects
380 *
381 * @param WP_REST_Request<array<string, mixed>> $request The request.
382 * @return RedirectListResponse|WP_Error
383 */
384 public function route_bulk( WP_REST_Request $request ) {
385 $params = $request->get_params();
386 $action = sanitize_text_field( $request['bulk'] );
387
388 if ( isset( $params['items'] ) && is_array( $params['items'] ) && count( $params['items'] ) > 0 ) {
389 $items = $params['items'];
390
391 foreach ( $items as $item ) {
392 $redirect = Red_Item::get_by_id( intval( $item, 10 ) );
393
394 if ( $redirect === false ) {
395 return $this->add_error_details( new WP_Error( 'redirect_bulk_failed', 'Invalid redirect' ), __LINE__ );
396 }
397
398 if ( $action === 'delete' ) {
399 $redirect->delete();
400 } elseif ( $action === 'disable' ) {
401 $redirect->disable();
402 } elseif ( $action === 'enable' ) {
403 $redirect->enable();
404 } elseif ( $action === 'reset' ) {
405 $redirect->reset();
406 }
407 }
408 } elseif ( isset( $params['global'] ) && $params['global'] !== false ) {
409 // Params are sanitized in the filter class
410 if ( $action === 'delete' ) {
411 Red_Item::delete_all( $params );
412 } elseif ( $action === 'reset' ) {
413 Red_Item::reset_all( $params );
414 } elseif ( $action === 'enable' || $action === 'disable' ) {
415 Red_Item::set_status_all( $action, $params );
416 }
417 }
418
419 return $this->route_list( $request );
420 }
421
422 /**
423 * Search for a post
424 *
425 * @param WP_REST_Request<array<string, mixed>> $request The request.
426 * @return list<array{title: string, value: string|false}>
427 */
428 public function route_match_post( WP_REST_Request $request ) {
429 global $wpdb;
430
431 $params = $request->get_params();
432 $search = sanitize_text_field( $params['text'] );
433 $results = [];
434
435 $posts = $wpdb->get_results(
436 $wpdb->prepare(
437 "SELECT ID,post_title,post_name FROM $wpdb->posts WHERE post_status='publish' AND (post_title LIKE %s OR post_name LIKE %s) " .
438 "AND post_type IN ('post','page')",
439 '%' . $wpdb->esc_like( $search ) . '%',
440 '%' . $wpdb->esc_like( $search ) . '%'
441 )
442 );
443
444 foreach ( (array) $posts as $post ) {
445 $title = $post->post_name;
446 if ( strpos( $post->post_title, $search ) !== false ) {
447 $title = $post->post_title;
448 }
449
450 $results[] = [
451 'title' => $title,
452 'value' => get_permalink( $post->ID ),
453 ];
454 }
455
456 return $results;
457 }
458 }
459