|
1 <?php |
|
2 /** |
|
3 * REST API: WP_REST_Block_Directory_Controller class |
|
4 * |
|
5 * @package WordPress |
|
6 * @subpackage REST_API |
|
7 * @since 5.5.0 |
|
8 */ |
|
9 |
|
10 /** |
|
11 * Controller which provides REST endpoint for the blocks. |
|
12 * |
|
13 * @since 5.5.0 |
|
14 * |
|
15 * @see WP_REST_Controller |
|
16 */ |
|
17 class WP_REST_Block_Directory_Controller extends WP_REST_Controller { |
|
18 |
|
19 /** |
|
20 * Constructs the controller. |
|
21 */ |
|
22 public function __construct() { |
|
23 $this->namespace = 'wp/v2'; |
|
24 $this->rest_base = 'block-directory'; |
|
25 } |
|
26 |
|
27 /** |
|
28 * Registers the necessary REST API routes. |
|
29 */ |
|
30 public function register_routes() { |
|
31 register_rest_route( |
|
32 $this->namespace, |
|
33 '/' . $this->rest_base . '/search', |
|
34 array( |
|
35 array( |
|
36 'methods' => WP_REST_Server::READABLE, |
|
37 'callback' => array( $this, 'get_items' ), |
|
38 'permission_callback' => array( $this, 'get_items_permissions_check' ), |
|
39 'args' => $this->get_collection_params(), |
|
40 ), |
|
41 'schema' => array( $this, 'get_public_item_schema' ), |
|
42 ) |
|
43 ); |
|
44 } |
|
45 |
|
46 /** |
|
47 * Checks whether a given request has permission to install and activate plugins. |
|
48 * |
|
49 * @since 5.5.0 |
|
50 * |
|
51 * @param WP_REST_Request $request Full details about the request. |
|
52 * |
|
53 * @return WP_Error|bool True if the request has permission, WP_Error object otherwise. |
|
54 */ |
|
55 public function get_items_permissions_check( $request ) { |
|
56 if ( ! current_user_can( 'install_plugins' ) || ! current_user_can( 'activate_plugins' ) ) { |
|
57 return new WP_Error( |
|
58 'rest_block_directory_cannot_view', |
|
59 __( 'Sorry, you are not allowed to browse the block directory.' ), |
|
60 array( 'status' => rest_authorization_required_code() ) |
|
61 ); |
|
62 } |
|
63 |
|
64 return true; |
|
65 } |
|
66 |
|
67 /** |
|
68 * Search and retrieve blocks metadata |
|
69 * |
|
70 * @since 5.5.0 |
|
71 * |
|
72 * @param WP_REST_Request $request Full details about the request. |
|
73 * |
|
74 * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. |
|
75 */ |
|
76 public function get_items( $request ) { |
|
77 require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; |
|
78 require_once ABSPATH . 'wp-admin/includes/plugin.php'; |
|
79 |
|
80 $response = plugins_api( |
|
81 'query_plugins', |
|
82 array( |
|
83 'block' => $request['term'], |
|
84 'per_page' => $request['per_page'], |
|
85 'page' => $request['page'], |
|
86 ) |
|
87 ); |
|
88 |
|
89 if ( is_wp_error( $response ) ) { |
|
90 $response->add_data( array( 'status' => 500 ) ); |
|
91 |
|
92 return $response; |
|
93 } |
|
94 |
|
95 $result = array(); |
|
96 |
|
97 foreach ( $response->plugins as $plugin ) { |
|
98 // If the API returned a plugin with empty data for 'blocks', skip it. |
|
99 if ( empty( $plugin['blocks'] ) ) { |
|
100 continue; |
|
101 } |
|
102 |
|
103 $data = $this->prepare_item_for_response( $plugin, $request ); |
|
104 $result[] = $this->prepare_response_for_collection( $data ); |
|
105 } |
|
106 |
|
107 return rest_ensure_response( $result ); |
|
108 } |
|
109 |
|
110 /** |
|
111 * Parse block metadata for a block, and prepare it for an API repsonse. |
|
112 * |
|
113 * @since 5.5.0 |
|
114 * |
|
115 * @param array $plugin The plugin metadata. |
|
116 * @param WP_REST_Request $request Request object. |
|
117 * |
|
118 * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. |
|
119 */ |
|
120 public function prepare_item_for_response( $plugin, $request ) { |
|
121 // There might be multiple blocks in a plugin. Only the first block is mapped. |
|
122 $block_data = reset( $plugin['blocks'] ); |
|
123 |
|
124 // A data array containing the properties we'll return. |
|
125 $block = array( |
|
126 'name' => $block_data['name'], |
|
127 'title' => ( $block_data['title'] ? $block_data['title'] : $plugin['name'] ), |
|
128 'description' => wp_trim_words( $plugin['description'], 30, '...' ), |
|
129 'id' => $plugin['slug'], |
|
130 'rating' => $plugin['rating'] / 20, |
|
131 'rating_count' => intval( $plugin['num_ratings'] ), |
|
132 'active_installs' => intval( $plugin['active_installs'] ), |
|
133 'author_block_rating' => $plugin['author_block_rating'] / 20, |
|
134 'author_block_count' => intval( $plugin['author_block_count'] ), |
|
135 'author' => wp_strip_all_tags( $plugin['author'] ), |
|
136 'icon' => ( isset( $plugin['icons']['1x'] ) ? $plugin['icons']['1x'] : 'block-default' ), |
|
137 'last_updated' => gmdate( 'Y-m-d\TH:i:s', strtotime( $plugin['last_updated'] ) ), |
|
138 'humanized_updated' => sprintf( |
|
139 /* translators: %s: Human-readable time difference. */ |
|
140 __( '%s ago' ), |
|
141 human_time_diff( strtotime( $plugin['last_updated'] ) ) |
|
142 ), |
|
143 ); |
|
144 |
|
145 $this->add_additional_fields_to_object( $block, $request ); |
|
146 |
|
147 $response = new WP_REST_Response( $block ); |
|
148 $response->add_links( $this->prepare_links( $plugin ) ); |
|
149 |
|
150 return $response; |
|
151 } |
|
152 |
|
153 /** |
|
154 * Generates a list of links to include in the response for the plugin. |
|
155 * |
|
156 * @since 5.5.0 |
|
157 * |
|
158 * @param array $plugin The plugin data from WordPress.org. |
|
159 * |
|
160 * @return array |
|
161 */ |
|
162 protected function prepare_links( $plugin ) { |
|
163 $links = array( |
|
164 'https://api.w.org/install-plugin' => array( |
|
165 'href' => add_query_arg( 'slug', urlencode( $plugin['slug'] ), rest_url( 'wp/v2/plugins' ) ), |
|
166 ), |
|
167 ); |
|
168 |
|
169 $plugin_file = $this->find_plugin_for_slug( $plugin['slug'] ); |
|
170 |
|
171 if ( $plugin_file ) { |
|
172 $links['https://api.w.org/plugin'] = array( |
|
173 'href' => rest_url( 'wp/v2/plugins/' . substr( $plugin_file, 0, - 4 ) ), |
|
174 'embeddable' => true, |
|
175 ); |
|
176 } |
|
177 |
|
178 return $links; |
|
179 } |
|
180 |
|
181 /** |
|
182 * Finds an installed plugin for the given slug. |
|
183 * |
|
184 * @since 5.5.0 |
|
185 * |
|
186 * @param string $slug The WordPress.org directory slug for a plugin. |
|
187 * |
|
188 * @return string The plugin file found matching it. |
|
189 */ |
|
190 protected function find_plugin_for_slug( $slug ) { |
|
191 require_once ABSPATH . 'wp-admin/includes/plugin.php'; |
|
192 |
|
193 $plugin_files = get_plugins( '/' . $slug ); |
|
194 |
|
195 if ( ! $plugin_files ) { |
|
196 return ''; |
|
197 } |
|
198 |
|
199 $plugin_files = array_keys( $plugin_files ); |
|
200 |
|
201 return $slug . '/' . reset( $plugin_files ); |
|
202 } |
|
203 |
|
204 /** |
|
205 * Retrieves the theme's schema, conforming to JSON Schema. |
|
206 * |
|
207 * @since 5.5.0 |
|
208 * |
|
209 * @return array Item schema data. |
|
210 */ |
|
211 public function get_item_schema() { |
|
212 if ( $this->schema ) { |
|
213 return $this->add_additional_fields_schema( $this->schema ); |
|
214 } |
|
215 |
|
216 $this->schema = array( |
|
217 '$schema' => 'http://json-schema.org/draft-04/schema#', |
|
218 'title' => 'block-directory-item', |
|
219 'type' => 'object', |
|
220 'properties' => array( |
|
221 'name' => array( |
|
222 'description' => __( 'The block name, in namespace/block-name format.' ), |
|
223 'type' => 'string', |
|
224 'context' => array( 'view' ), |
|
225 ), |
|
226 'title' => array( |
|
227 'description' => __( 'The block title, in human readable format.' ), |
|
228 'type' => 'string', |
|
229 'context' => array( 'view' ), |
|
230 ), |
|
231 'description' => array( |
|
232 'description' => __( 'A short description of the block, in human readable format.' ), |
|
233 'type' => 'string', |
|
234 'context' => array( 'view' ), |
|
235 ), |
|
236 'id' => array( |
|
237 'description' => __( 'The block slug.' ), |
|
238 'type' => 'string', |
|
239 'context' => array( 'view' ), |
|
240 ), |
|
241 'rating' => array( |
|
242 'description' => __( 'The star rating of the block.' ), |
|
243 'type' => 'integer', |
|
244 'context' => array( 'view' ), |
|
245 ), |
|
246 'rating_count' => array( |
|
247 'description' => __( 'The number of ratings.' ), |
|
248 'type' => 'integer', |
|
249 'context' => array( 'view' ), |
|
250 ), |
|
251 'active_installs' => array( |
|
252 'description' => __( 'The number sites that have activated this block.' ), |
|
253 'type' => 'string', |
|
254 'context' => array( 'view' ), |
|
255 ), |
|
256 'author_block_rating' => array( |
|
257 'description' => __( 'The average rating of blocks published by the same author.' ), |
|
258 'type' => 'integer', |
|
259 'context' => array( 'view' ), |
|
260 ), |
|
261 'author_block_count' => array( |
|
262 'description' => __( 'The number of blocks published by the same author.' ), |
|
263 'type' => 'integer', |
|
264 'context' => array( 'view' ), |
|
265 ), |
|
266 'author' => array( |
|
267 'description' => __( 'The WordPress.org username of the block author.' ), |
|
268 'type' => 'string', |
|
269 'context' => array( 'view' ), |
|
270 ), |
|
271 'icon' => array( |
|
272 'description' => __( 'The block icon.' ), |
|
273 'type' => 'string', |
|
274 'format' => 'uri', |
|
275 'context' => array( 'view' ), |
|
276 ), |
|
277 'last_updated' => array( |
|
278 'description' => __( 'The date when the block was last updated, in fuzzy human readable format.' ), |
|
279 'type' => 'string', |
|
280 'format' => 'date-time', |
|
281 'context' => array( 'view' ), |
|
282 ), |
|
283 'humanized_updated' => array( |
|
284 'description' => __( 'The date when the block was last updated, in fuzzy human readable format.' ), |
|
285 'type' => 'string', |
|
286 'context' => array( 'view' ), |
|
287 ), |
|
288 ), |
|
289 ); |
|
290 |
|
291 return $this->add_additional_fields_schema( $this->schema ); |
|
292 } |
|
293 |
|
294 /** |
|
295 * Retrieves the search params for the blocks collection. |
|
296 * |
|
297 * @since 5.5.0 |
|
298 * |
|
299 * @return array Collection parameters. |
|
300 */ |
|
301 public function get_collection_params() { |
|
302 $query_params = parent::get_collection_params(); |
|
303 |
|
304 $query_params['context']['default'] = 'view'; |
|
305 |
|
306 $query_params['term'] = array( |
|
307 'description' => __( 'Limit result set to blocks matching the search term.' ), |
|
308 'type' => 'string', |
|
309 'required' => true, |
|
310 'minLength' => 1, |
|
311 ); |
|
312 |
|
313 unset( $query_params['search'] ); |
|
314 |
|
315 /** |
|
316 * Filter collection parameters for the block directory controller. |
|
317 * |
|
318 * @since 5.5.0 |
|
319 * |
|
320 * @param array $query_params JSON Schema-formatted collection parameters. |
|
321 */ |
|
322 return apply_filters( 'rest_block_directory_collection_params', $query_params ); |
|
323 } |
|
324 } |