PluginProbe
Elementor Website Builder – more than just a page builder / 3.27.4
Elementor Website Builder – more than just a page builder v3.27.4
4.3.0-beta3 4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 All 452 releases
← All changes | modules/global-classes/global-classes-rest-api.php +90 -407 4.1.0-dev23.27.4 View file →
@@ -1,13 +1,11 @@
1 1 <?php
2 2
3 3 namespace Elementor\Modules\GlobalClasses;
4 4
5 -use Elementor\Core\Kits\Documents\Kit;
6 -use Elementor\Core\Utils\Api\Error_Builder;
7 -use Elementor\Core\Utils\Api\Response_Builder;
8 -use Elementor\Modules\GlobalClasses\Database\Migrations\Add_Capabilities;
9 -use Elementor\Modules\GlobalClasses\Usage\Applied_Global_Classes_Usage;
5 +use Elementor\Core\Utils\Collection;
6 +use Elementor\Modules\AtomicWidgets\Styles\Style_Schema;
7 +use Elementor\Modules\AtomicWidgets\Parsers\Style_Parser;
10 8 use Elementor\Plugin;
11 9
12 10 if ( ! defined( 'ABSPATH' ) ) {
13 11 exit; // Exit if accessed directly.
@@ -15,503 +13,188 @@
15 13
16 14 class Global_Classes_REST_API {
17 15 const API_NAMESPACE = 'elementor/v1';
18 16 const API_BASE = 'global-classes';
19 - const API_BASE_USAGE = self::API_BASE . '/usage';
20 - const API_BASE_POST = self::API_BASE . '/post';
21 - const API_BASE_STYLES = self::API_BASE . '/styles';
22 - const MAX_ITEMS = 1000;
23 - const LABEL_PREFIX = 'DUP_';
24 - const MAX_LABEL_LENGTH = 50;
25 - private ?Global_Classes_Repository $repository = null;
26 - private ?Global_Classes_Relations $relations = null;
27 - private ?Kit $kit = null;
28 17
18 + private $repository = null;
19 +
29 20 public function register_hooks() {
30 21 add_action( 'rest_api_init', fn() => $this->register_routes() );
31 22 }
32 23
33 - public function invalidate_cache() {
34 - $this->kit = null;
35 - $this->repository = null;
36 - $this->relations = null;
37 - }
38 -
39 - private function get_kit(): ?Kit {
40 - if ( ! $this->kit ) {
41 - $this->kit = Plugin::$instance->kits_manager->get_active_kit();
42 - }
43 -
44 - return $this->kit;
45 - }
46 -
47 24 private function get_repository() {
48 25 if ( ! $this->repository ) {
49 - $this->repository = new Global_Classes_Repository( $this->get_kit() );
26 + $this->repository = new Global_Classes_Repository();
50 27 }
51 28
52 29 return $this->repository;
53 30 }
54 31
55 - private function get_classes_relations(): Global_Classes_Relations {
56 - if ( ! $this->relations ) {
57 - $this->relations = new Global_Classes_Relations();
58 - }
59 -
60 - return $this->relations;
61 - }
62 -
32 + // TODO: Add sanitization when implemented on prop types [EDS-574]
63 33 private function register_routes() {
64 - // cache invalidation at this point is solely for tests, in particular - Test_Global_Classes_Rest_Api
65 - $this->invalidate_cache();
66 -
67 34 register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE, [
68 35 [
69 36 'methods' => 'GET',
70 - 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->all( $request ) ),
71 - 'permission_callback' => fn() => is_user_logged_in(),
72 - 'args' => [
73 - 'context' => [
74 - 'type' => 'string',
75 - 'required' => false,
76 - 'default' => Global_Classes_Repository::CONTEXT_FRONTEND,
77 - 'enum' => [
78 - Global_Classes_Repository::CONTEXT_FRONTEND,
79 - Global_Classes_Repository::CONTEXT_PREVIEW,
80 - ],
81 - ],
82 - ],
37 + 'callback' => fn() => $this->route_wrapper( fn() => $this->all() ),
38 + 'permission_callback' => fn() => current_user_can( 'manage_options' ),
83 39 ],
84 40 ] );
85 41
86 - register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE_POST, [
42 + register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/(?P<id>[\w-]+)', [
87 43 [
88 44 'methods' => 'GET',
89 - 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->styles_for_post( $request ) ),
90 - 'permission_callback' => fn() => is_user_logged_in(),
45 + 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->get( $request ) ),
91 46 'args' => [
92 - 'context' => [
47 + 'id' => [
93 48 'type' => 'string',
94 - 'required' => false,
95 - 'default' => Global_Classes_Repository::CONTEXT_FRONTEND,
96 - 'enum' => [
97 - Global_Classes_Repository::CONTEXT_FRONTEND,
98 - Global_Classes_Repository::CONTEXT_PREVIEW,
99 - ],
100 - ],
101 - 'post_id' => [
102 - 'type' => 'integer',
103 49 'required' => true,
104 50 ],
105 51 ],
52 + 'permission_callback' => fn() => current_user_can( 'manage_options' ),
106 53 ],
107 54 ] );
108 55
109 - register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE_STYLES, [
56 + register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/(?P<id>[\w-]+)', [
110 57 [
111 - 'methods' => 'GET',
112 - 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->styles_by_ids( $request ) ),
113 - 'permission_callback' => fn() => is_user_logged_in(),
58 + 'methods' => 'DELETE',
59 + 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->delete( $request ) ),
114 60 'args' => [
115 - 'context' => [
61 + 'id' => [
116 62 'type' => 'string',
117 - 'required' => false,
118 - 'default' => Global_Classes_Repository::CONTEXT_FRONTEND,
119 - 'enum' => [
120 - Global_Classes_Repository::CONTEXT_FRONTEND,
121 - Global_Classes_Repository::CONTEXT_PREVIEW,
122 - ],
123 - ],
124 - 'ids' => [
125 - 'type' => 'string',
126 63 'required' => true,
127 - 'description' => 'Comma-separated list of global class IDs',
128 64 ],
129 65 ],
66 + 'permission_callback' => fn() => current_user_can( 'manage_options' ),
130 67 ],
131 68 ] );
132 69
133 - register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE_USAGE, [
70 + register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/(?P<id>[\w-]+)', [
134 71 [
135 - 'methods' => 'GET',
136 - 'callback' => fn() => $this->route_wrapper( fn() => $this->get_usage() ),
72 + 'methods' => 'PUT',
73 + 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->put( $request ) ),
137 74 'permission_callback' => fn() => current_user_can( 'manage_options' ),
138 - 'args' => [
139 - 'context' => [
140 - 'type' => 'string',
141 - 'required' => false,
142 - 'default' => Global_Classes_Repository::CONTEXT_FRONTEND,
143 - 'enum' => [
144 - Global_Classes_Repository::CONTEXT_FRONTEND,
145 - Global_Classes_Repository::CONTEXT_PREVIEW,
146 - ],
147 - ],
148 - ],
149 75 ],
150 76 ] );
151 77
152 78 register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE, [
153 79 [
154 - 'methods' => 'PUT',
155 - 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->put( $request ) ),
156 - 'permission_callback' => fn() => current_user_can( Add_Capabilities::UPDATE_CLASS ),
157 - 'args' => [
158 - 'context' => [
159 - 'type' => 'string',
160 - 'required' => false,
161 - 'default' => Global_Classes_Repository::CONTEXT_FRONTEND,
162 - 'enum' => [
163 - Global_Classes_Repository::CONTEXT_FRONTEND,
164 - Global_Classes_Repository::CONTEXT_PREVIEW,
165 - ],
166 - ],
167 - 'changes' => [
168 - 'type' => 'object',
169 - 'required' => true,
170 - 'additionalProperties' => false,
171 - 'properties' => [
172 - 'added' => [
173 - 'type' => 'array',
174 - 'required' => true,
175 - 'items' => [ 'type' => 'string' ],
176 - ],
177 - 'deleted' => [
178 - 'type' => 'array',
179 - 'required' => true,
180 - 'items' => [ 'type' => 'string' ],
181 - ],
182 - 'modified' => [
183 - 'type' => 'array',
184 - 'required' => true,
185 - 'items' => [ 'type' => 'string' ],
186 - ],
187 - 'order' => [
188 - 'type' => 'boolean',
189 - 'required' => false,
190 - ],
191 - ],
192 - ],
193 - 'items' => [
194 - 'required' => true,
195 - 'type' => 'object',
196 - 'additionalProperties' => [
197 - 'type' => 'object',
198 - 'properties' => [
199 - 'id' => [
200 - 'type' => 'string',
201 - 'required' => true,
202 - ],
203 - 'variants' => [
204 - 'type' => 'array',
205 - 'required' => true,
206 - ],
207 - 'type' => [
208 - 'type' => 'string',
209 - 'enum' => [ 'class' ],
210 - 'required' => true,
211 - ],
212 - 'label' => [
213 - 'type' => 'string',
214 - 'required' => true,
215 - ],
216 - ],
217 - ],
218 - ],
219 - 'order' => [
220 - 'required' => true,
221 - 'type' => 'array',
222 - 'items' => [
223 - 'type' => 'string',
224 - ],
225 - ],
226 - ],
80 + 'methods' => 'POST',
81 + 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->create( $request ) ),
82 + 'permission_callback' => fn() => current_user_can( 'manage_options' ),
227 83 ],
228 84 ] );
229 - }
230 85
231 - private function all( \WP_REST_Request $request ) {
232 - $context = $request->get_param( 'context' );
233 - $is_preview = Global_Classes_Repository::CONTEXT_PREVIEW === $context;
234 - $label_by_id = $this->get_repository()->set_preview( $is_preview )->all_labels();
235 - $list = [];
86 + register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '-order', [
87 + [
88 + 'methods' => 'PUT',
89 + 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->arrange( $request ) ),
90 + 'validate_callback' => function( \WP_REST_Request $request ) {
91 + $order = $request->get_params();
236 92
237 - foreach ( $label_by_id as $id => $label ) {
238 - $list[] = [
239 - 'id' => $id,
240 - 'label' => $label,
241 - ];
242 - }
93 + if ( ! is_array( $order ) ) {
94 + return false;
95 + }
243 96
244 - return Response_Builder::make( $list )->build();
245 - }
97 + $classes = $this->get_repository()->all();
246 98
247 - private function styles_for_post( \WP_REST_Request $request ) {
248 - $context = $request->get_param( 'context' );
249 - $is_preview = Global_Classes_Repository::CONTEXT_PREVIEW === $context;
250 - $post_id = (int) $request->get_param( 'post_id' );
99 + $missing_items = Collection::make( $classes->get_items()->keys() )->diff( $order );
100 + $extra_items = Collection::make( $order )->diff( $classes->get_items()->keys() );
251 101
252 - $document_class_ids = $this->get_classes_relations()->set_preview( $is_preview )->get_styles_by_post( $post_id );
102 + return $missing_items->is_empty() && $extra_items->is_empty();
103 + },
104 + 'permission_callback' => fn() => current_user_can( 'manage_options' ),
105 + ],
106 + ] );
107 + }
253 108
254 - if ( empty( $document_class_ids ) ) {
255 - return Response_Builder::make( (object) [] )->set_meta( [ 'order' => [] ] )->build();
256 - }
109 + private function all() {
110 + $classes = $this->get_repository()->all();
257 111
258 - $repository = $this->get_repository()->set_preview( $is_preview );
259 - $global_order = array_keys( $repository->all_labels() );
260 - $filtered_order = array_values( array_intersect( $global_order, $document_class_ids ) );
261 - $items = $repository->get_by_ids( $document_class_ids );
112 + return $classes->get();
113 + }
262 114
263 - $result = [];
115 + private function get( \WP_REST_Request $request ) {
116 + $id = $request->get_param( 'id' );
117 + $class = $this->get_repository()->get( $id );
264 118
265 - foreach ( $document_class_ids as $id ) {
266 - $result[ $id ] = $items[ $id ] ?? null;
119 + if ( null === $class ) {
120 + return new \WP_Error( 'entity_not_found', __( 'Global class not found', 'elementor' ), [ 'status' => 404 ] );
267 121 }
268 122
269 - return Response_Builder::make( (object) $result )
270 - ->set_meta( [ 'order' => $filtered_order ] )
271 - ->build();
123 + return $class;
272 124 }
273 125
274 - private function styles_by_ids( \WP_REST_Request $request ) {
275 - $context = $request->get_param( 'context' );
276 - $is_preview = Global_Classes_Repository::CONTEXT_PREVIEW === $context;
277 - $ids_param = $request->get_param( 'ids' );
126 + private function delete( \WP_REST_Request $request ) {
127 + $id = $request->get_param( 'id' );
128 + $class = $this->get_repository()->get( $id );
278 129
279 - $requested_ids = array_map( 'trim', explode( ',', $ids_param ) );
280 - $requested_ids = array_filter( $requested_ids );
281 -
282 - if ( empty( $requested_ids ) ) {
283 - return Response_Builder::make( (object) [] )->set_meta( [ 'order' => [] ] )->build();
130 + if ( null === $class ) {
131 + return new \WP_Error( 'entity_not_found', __( 'Global class not found', 'elementor' ), [ 'status' => 404 ] );
284 132 }
285 133
286 - $repository = $this->get_repository()->set_preview( $is_preview );
287 - $global_order = array_keys( $repository->all_labels() );
288 - $filtered_order = array_values( array_intersect( $global_order, $requested_ids ) );
289 - $items = $repository->get_by_ids( $requested_ids );
134 + $this->get_repository()->delete( $id );
290 135
291 - $result = [];
292 -
293 - foreach ( $requested_ids as $id ) {
294 - $result[ $id ] = $items[ $id ] ?? null;
295 - }
296 -
297 - return Response_Builder::make( (object) $result )
298 - ->set_meta( [ 'order' => $filtered_order ] )
299 - ->build();
136 + return new \WP_REST_Response( null, 204 );
300 137 }
301 138
302 - private function get_usage() {
303 - $classes_usage = ( new Applied_Global_Classes_Usage() )->get_detailed_usage();
304 -
305 - return Response_Builder::make( (object) $classes_usage )->build();
306 - }
307 -
308 139 private function put( \WP_REST_Request $request ) {
309 - $context = $request->get_param( 'context' );
310 - $is_preview = Global_Classes_Repository::CONTEXT_PREVIEW === $context;
311 - $changes = $request->get_param( 'changes' ) ?? [];
312 - $added_ids = $changes['added'] ?? [];
313 - $deleted_ids = $changes['deleted'] ?? [];
314 - $order = $request->get_param( 'order' ) ?? [];
140 + $id = $request->get_param( 'id' );
141 + $values = $request->get_params();
315 142
316 - $repository = $this->get_repository()->set_preview( $is_preview );
317 - $all_label_by_id = $repository->all_labels();
318 - $existing_label_list = $this->global_classes_existing_label_list( $all_label_by_id, $deleted_ids );
319 - $total_count = count( $all_label_by_id ) - count( $deleted_ids ) + count( $added_ids );
143 + // Ignore id to simplify the patch, and allow passing the entity as it is
144 + unset( $values['id'] );
320 145
321 - $parser = Global_Classes_Parser::make();
322 - $items_result = $parser->parse_items( $request->get_param( 'items' ) ?? [] );
146 + $class = $this->get_repository()->get( $id );
323 147
324 - if ( ! $items_result->is_valid() ) {
325 - return Error_Builder::make( 'invalid_items' )
326 - ->set_status( 400 )
327 - ->set_message( 'Invalid items: ' . $items_result->errors()->to_string() )
328 - ->build();
148 + if ( null === $class ) {
149 + return new \WP_Error( 'entity_not_found', __( 'Global class not found', 'elementor' ), [ 'status' => 404 ] );
329 150 }
330 151
331 - $touched_items = $items_result->unwrap();
152 + [$is_valid, $parsed, $errors] = Style_Parser::make( Style_Schema::get() )
153 + ->without_id()
154 + ->parse( $values );
332 155
333 - if ( $total_count > self::MAX_ITEMS ) {
334 - return Error_Builder::make( 'global_classes_limit_exceeded' )
335 - ->set_status( 400 )
336 - ->set_meta( [
337 - 'current_count' => $total_count,
338 - 'max_allowed' => self::MAX_ITEMS,
339 - ] )
340 - ->set_message( sprintf(
341 - /* translators: %d: Maximum allowed items. */
342 - __( 'Global classes limit exceeded. Maximum allowed: %d', 'elementor' ),
343 - self::MAX_ITEMS
344 - ) )
345 - ->build();
156 + if ( ! $is_valid ) {
157 + return $this->fail_with_validation_errors( $errors );
346 158 }
347 159
348 - $duplicated_labels = Global_Classes_Parser::check_for_duplicate_labels(
349 - $all_label_by_id,
350 - $deleted_ids,
351 - $touched_items,
352 - $added_ids
353 - );
160 + $values = $this->get_repository()->put( $id, $parsed );
354 161
355 - $duplicate_validation_result = null;
162 + return new \WP_REST_Response( $values, 200 );
163 + }
356 164
357 - if ( ! empty( $duplicated_labels ) ) {
358 - $modified_labels = $this->handle_duplicates( $duplicated_labels, $existing_label_list );
359 - $duplicate_validation_result = $modified_labels;
165 + private function create( \WP_REST_Request $request ) {
166 + $class = $request->get_params();
167 + [$is_valid, $parsed, $errors] = Style_Parser::make( Style_Schema::get() )
168 + ->without_id()
169 + ->parse( $class );
360 170
361 - foreach ( $modified_labels as $item_id => $labels ) {
362 - $touched_items[ $item_id ]['label'] = $labels['modified'];
363 - }
171 + if ( ! $is_valid ) {
172 + return $this->fail_with_validation_errors( $errors );
364 173 }
365 174
366 - $final_item_ids = array_keys( $this->merge_touched_with_existing_labels( $all_label_by_id, $touched_items, $deleted_ids ) );
175 + $new = $this->get_repository()->create( $parsed );
367 176
368 - $order_result = $parser->parse_order( $order, $final_item_ids );
369 -
370 - if ( ! $order_result->is_valid() ) {
371 - return Error_Builder::make( 'invalid_order' )
372 - ->set_status( 400 )
373 - ->set_message( 'Invalid order: ' . $order_result->errors()->to_string() )
374 - ->build();
375 - }
376 -
377 - $repository->apply_changes( $touched_items, [
378 - 'added' => $added_ids,
379 - 'deleted' => $changes['deleted'] ?? [],
380 - 'modified' => $changes['modified'] ?? [],
381 - 'order' => isset( $changes['order'] ) && $changes['order'], // boolean indicating if the order has changed
382 - ], $order_result->unwrap() );
383 -
384 - if ( $duplicate_validation_result ) {
385 - return Response_Builder::make( [
386 - 'code' => 'DUPLICATED_LABEL',
387 - 'modifiedLabels' => $duplicate_validation_result,
388 - ] )->build();
389 - }
390 -
391 - return Response_Builder::make()->no_content()->build();
177 + return new \WP_REST_Response( $new, 201 );
392 178 }
393 179
394 - private function global_classes_existing_label_list( array $label_by_id, array $deleted_ids ): array {
395 - $labels = [];
180 + private function arrange( \WP_REST_Request $request ) {
181 + $order = $request->get_params();
182 + $updated = $this->get_repository()->arrange( $order );
396 183
397 - foreach ( $label_by_id as $id => $label ) {
398 - if ( in_array( $id, $deleted_ids, true ) ) {
399 - continue;
400 - }
401 -
402 - $labels[] = $label;
403 - }
404 -
405 - return $labels;
184 + return new \WP_REST_Response( $updated, 200 );
406 185 }
407 186
408 - private function merge_touched_with_existing_labels( array $label_by_id, array $touched_items, array $deleted_ids ): array {
409 - $final = [];
410 -
411 - foreach ( $label_by_id as $id => $label ) {
412 - if ( in_array( $id, $deleted_ids, true ) ) {
413 - continue;
414 - }
415 -
416 - if ( isset( $touched_items[ $id ] ) ) {
417 - $final[ $id ] = $touched_items[ $id ];
418 - } else {
419 - $final[ $id ] = [
420 - 'id' => $id,
421 - 'label' => $label,
422 - 'type' => 'class',
423 - 'variants' => [],
424 - ];
425 - }
426 - }
427 -
428 - foreach ( $touched_items as $id => $item ) {
429 - if ( ! isset( $final[ $id ] ) ) {
430 - $final[ $id ] = $item;
431 - }
432 - }
433 -
434 - return $final;
435 - }
436 -
437 187 private function route_wrapper( callable $cb ) {
438 188 try {
439 189 $response = $cb();
440 190 } catch ( \Exception $e ) {
441 - return Error_Builder::make( 'unexpected_error' )
442 - ->set_message( __( 'Something went wrong', 'elementor' ) )
443 - ->build();
191 + return new \WP_Error( 'unexpected_error', __( 'Something went wrong', 'elementor' ), [ 'status' => 500 ] );
444 192 }
445 193
446 194 return $response;
447 195 }
448 196
449 - private function handle_duplicates( array $duplicate_labels, array $existing_labels ) {
450 -
451 - $modified_labels = [];
452 -
453 - foreach ( $duplicate_labels as $duplicate_label ) {
454 - $item_id = $duplicate_label['item_id'];
455 - $original_label = $duplicate_label['label'];
456 -
457 - $modified_label = $this->generate_unique_label( $original_label, $existing_labels );
458 -
459 - $modified_labels[ $item_id ] = [
460 - 'original' => $original_label,
461 - 'modified' => $modified_label,
462 - ];
463 - }
464 -
465 - return $modified_labels;
466 - }
467 -
468 -
469 - private function generate_unique_label( $original_label, $existing_labels ) {
470 - $prefix = self::LABEL_PREFIX;
471 - $max_length = self::MAX_LABEL_LENGTH;
472 -
473 - $has_prefix = strpos( $original_label, $prefix ) === 0;
474 -
475 - if ( $has_prefix ) {
476 - $base_label = substr( $original_label, strlen( $prefix ) );
477 -
478 - $counter = 1;
479 - $new_label = $prefix . $base_label . $counter;
480 -
481 - while ( in_array( $new_label, $existing_labels, true ) ) {
482 - ++$counter;
483 - $new_label = $prefix . $base_label . $counter;
484 - }
485 -
486 - if ( strlen( $new_label ) > $max_length ) {
487 - $available_length = $max_length - strlen( $prefix . $counter );
488 - $base_label = substr( $base_label, 0, $available_length );
489 - $new_label = $prefix . $base_label . $counter;
490 - }
491 - } else {
492 - $new_label = $prefix . $original_label;
493 -
494 - if ( strlen( $new_label ) > $max_length ) {
495 - $available_length = $max_length - strlen( $prefix );
496 - $new_label = $prefix . substr( $original_label, 0, $available_length );
497 - }
498 -
499 - $counter = 1;
500 - $base_label = substr( $original_label, 0, $available_length ?? strlen( $original_label ) );
501 -
502 - while ( in_array( $new_label, $existing_labels, true ) ) {
503 - $new_label = $prefix . $base_label . $counter;
504 -
505 - if ( strlen( $new_label ) > $max_length ) {
506 - $available_length = $max_length - strlen( $prefix . $counter );
507 - $base_label = substr( $original_label, 0, $available_length );
508 - $new_label = $prefix . $base_label . $counter;
509 - }
510 -
511 - ++$counter;
512 - }
513 - }
514 -
515 - return $new_label;
197 + private function fail_with_validation_errors( array $errors ) {
198 + return new \WP_Error( 'Invalid data: ', join( ', ', $errors ), [ 'status' => 400 ] );
516 199 }
517 200 }