PluginProbe
Broken Link Notifier / trunk
Broken Link Notifier vtrunk
1.3.8 1.3.7.6 trunk 1.3.0 1.3.1.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.7.1 1.3.7.2 1.3.7.3 1.3.7.4 1.3.7.5
broken-link-notifier / includes / api.php

api.php in Broken Link Notifier trunk, at includes/api.php

278 lines 9.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * REST API class file.
4 */
5
6 // Exit if accessed directly.
7 if ( !defined( 'ABSPATH' ) ) {
8 exit;
9 }
10
11
12 /**
13 * Initiate the class
14 */
15 new BLNOTIFIER_API();
16
17
18 /**
19 * Main plugin class.
20 */
21 class BLNOTIFIER_API {
22
23 /**
24 * The REST API namespace
25 *
26 * @var string
27 */
28 private $namespace = 'blnotifier/v1';
29
30
31 /**
32 * Constructor
33 */
34 public function __construct() {
35 add_action( 'rest_api_init', [ $this, 'register_routes' ] );
36 } // End __construct()
37
38
39 /**
40 * Check if the REST API is enabled
41 *
42 * @return boolean
43 */
44 public function is_enabled() {
45 return (bool) get_option( 'blnotifier_enable_rest_api', false );
46 } // End is_enabled()
47
48
49 /**
50 * Get the API key from the request
51 *
52 * @param WP_REST_Request $request
53 * @return string
54 */
55 private function get_request_api_key( $request ) {
56 $header = $request->get_header( 'X-API-Key' );
57 if ( $header ) {
58 return sanitize_text_field( $header );
59 }
60
61 $param = $request->get_param( 'api_key' );
62 if ( $param ) {
63 return sanitize_text_field( $param );
64 }
65
66 return '';
67 } // End get_request_api_key()
68
69
70 /**
71 * Permission callback for all routes
72 *
73 * @param WP_REST_Request $request
74 * @return boolean|WP_Error
75 */
76 public function permission_callback( $request ) {
77 if ( !$this->is_enabled() ) {
78 return new WP_Error( 'rest_disabled', __( 'The Broken Link Notifier REST API is disabled.', 'broken-link-notifier' ), [ 'status' => 403 ] );
79 }
80
81 $saved_key = get_option( 'blnotifier_api_key', '' );
82 if ( !$saved_key ) {
83 return new WP_Error( 'rest_no_key', __( 'No API key has been configured.', 'broken-link-notifier' ), [ 'status' => 403 ] );
84 }
85
86 if ( !hash_equals( $saved_key, $this->get_request_api_key( $request ) ) ) {
87 return new WP_Error( 'rest_forbidden', __( 'Invalid API key.', 'broken-link-notifier' ), [ 'status' => 401 ] );
88 }
89
90 return true;
91 } // End permission_callback()
92
93
94 /**
95 * Register REST routes
96 *
97 * @return void
98 */
99 public function register_routes() {
100 register_rest_route( $this->namespace, '/results', [
101 'methods' => WP_REST_Server::READABLE,
102 'callback' => [ $this, 'get_results' ],
103 'permission_callback' => [ $this, 'permission_callback' ],
104 'args' => [
105 'type' => [
106 'default' => 'all',
107 'sanitize_callback' => 'sanitize_key',
108 'validate_callback' => function( $value ) {
109 return in_array( $value, [ 'all', 'broken', 'warning' ] );
110 },
111 ],
112 'per_page' => [
113 'default' => 100,
114 'sanitize_callback' => 'absint',
115 'validate_callback' => function( $value ) {
116 return $value > 0 && $value <= 500;
117 },
118 ],
119 'page' => [
120 'default' => 1,
121 'sanitize_callback' => 'absint',
122 'validate_callback' => function( $value ) {
123 return $value > 0;
124 },
125 ],
126 ],
127 ] );
128
129 register_rest_route( $this->namespace, '/results/(?P<id>\d+)', [
130 'methods' => WP_REST_Server::READABLE,
131 'callback' => [ $this, 'get_result' ],
132 'permission_callback' => [ $this, 'permission_callback' ],
133 'args' => [
134 'id' => [
135 'sanitize_callback' => 'absint',
136 ],
137 ],
138 ] );
139
140 register_rest_route( $this->namespace, '/results/(?P<id>\d+)', [
141 'methods' => WP_REST_Server::DELETABLE,
142 'callback' => [ $this, 'delete_result' ],
143 'permission_callback' => [ $this, 'permission_callback' ],
144 'args' => [
145 'id' => [
146 'sanitize_callback' => 'absint',
147 ],
148 ],
149 ] );
150 } // End register_routes()
151
152
153 /**
154 * GET /results
155 *
156 * @param WP_REST_Request $request
157 * @return WP_REST_Response
158 */
159 public function get_results( $request ) {
160 global $wpdb;
161
162 $table = $wpdb->prefix . 'blnotifier_results';
163 $type = $request->get_param( 'type' );
164 $per_page = $request->get_param( 'per_page' );
165 $page = $request->get_param( 'page' );
166 $offset = ( $page - 1 ) * $per_page;
167
168 $where = '';
169 $args = [];
170
171 if ( $type !== 'all' ) {
172 $where = 'WHERE type = %s';
173 $args[] = $type;
174 }
175
176 $args[] = $per_page;
177 $args[] = $offset;
178
179 $rows = $wpdb->get_results(
180 $wpdb->prepare( "SELECT id, link, text, type, code, source, location, method, created_at FROM {$table} {$where} ORDER BY created_at DESC LIMIT %d OFFSET %d", $args ),
181 ARRAY_A
182 );
183
184 $total = (int) $wpdb->get_var(
185 $type !== 'all'
186 ? $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE type = %s", $type )
187 : "SELECT COUNT(*) FROM {$table}"
188 );
189
190 $source_cache = [];
191
192 $results = array_map( function( $row ) use ( &$source_cache ) {
193 $source_url = $row[ 'source' ];
194
195 if ( !isset( $source_cache[ $source_url ] ) ) {
196 $source_cache[ $source_url ] = url_to_postid( $source_url );
197 }
198
199 return [
200 'id' => absint( $row[ 'id' ] ),
201 'link' => esc_url( $row[ 'link' ] ),
202 'text' => sanitize_text_field( $row[ 'text' ] ),
203 'type' => sanitize_key( $row[ 'type' ] ),
204 'code' => absint( $row[ 'code' ] ),
205 'source_url' => esc_url( $source_url ),
206 'source_post_id' => $source_cache[ $source_url ] ?: null,
207 'location' => sanitize_key( $row[ 'location' ] ),
208 'method' => sanitize_key( $row[ 'method' ] ),
209 'created_at' => sanitize_text_field( $row[ 'created_at' ] ),
210 ];
211 }, $rows );
212
213 $response = new WP_REST_Response( $results, 200 );
214 $response->header( 'X-Total', $total );
215 $response->header( 'X-Total-Pages', (int) ceil( $total / $per_page ) );
216 $response->header( 'X-Page', $page );
217
218 return $response;
219 } // End get_results()
220
221
222 /**
223 * GET /results/{id}
224 *
225 * @param WP_REST_Request $request
226 * @return WP_REST_Response
227 */
228 public function get_result( $request ) {
229 global $wpdb;
230
231 $table = $wpdb->prefix . 'blnotifier_results';
232 $id = $request->get_param( 'id' );
233
234 $row = $wpdb->get_row(
235 $wpdb->prepare( "SELECT id, link, text, type, code, source, location, method, created_at FROM {$table} WHERE id = %d", $id ),
236 ARRAY_A
237 );
238
239 if ( !$row ) {
240 return new WP_REST_Response( [ 'message' => __( 'Result not found.', 'broken-link-notifier' ) ], 404 );
241 }
242
243 return new WP_REST_Response( [
244 'id' => absint( $row[ 'id' ] ),
245 'link' => esc_url( $row[ 'link' ] ),
246 'text' => sanitize_text_field( $row[ 'text' ] ),
247 'type' => sanitize_key( $row[ 'type' ] ),
248 'code' => absint( $row[ 'code' ] ),
249 'source_url' => esc_url( $row[ 'source' ] ),
250 'source_post_id' => url_to_postid( $row[ 'source' ] ) ?: null,
251 'location' => sanitize_key( $row[ 'location' ] ),
252 'method' => sanitize_key( $row[ 'method' ] ),
253 'created_at' => sanitize_text_field( $row[ 'created_at' ] ),
254 ], 200 );
255 } // End get_result()
256
257
258 /**
259 * DELETE /results/{id}
260 *
261 * @param WP_REST_Request $request
262 * @return WP_REST_Response
263 */
264 public function delete_result( $request ) {
265 global $wpdb;
266
267 $table = $wpdb->prefix . 'blnotifier_results';
268 $id = $request->get_param( 'id' );
269 $deleted = $wpdb->delete( $table, [ 'id' => $id ], [ '%d' ] );
270
271 if ( !$deleted ) {
272 return new WP_REST_Response( [ 'message' => __( 'Result not found.', 'broken-link-notifier' ) ], 404 );
273 }
274
275 return new WP_REST_Response( [ 'message' => __( 'Result deleted.', 'broken-link-notifier' ), 'id' => $id ], 200 );
276 } // End delete_result()
277
278 }