PluginProbe
Polylang / 3.7.1
Polylang v3.7.1
3.8.9 3.8.8 3.8.7 3.8.6 3.8.5 3.8.4 3.8.3 2.7 2.7.0.1 2.7.1 2.7.2 2.7.3 2.7.4 2.8 2.8.1 2.8.2 2.8.3 2.8.4 2.9 2.9.1 2.9.2 3.0 3.0.1 3.0.2 3.0.3 All 233 releases
polylang / modules / REST / V1 / Languages.php

Languages.php in Polylang 3.7.1, at modules/REST/V1/Languages.php

793 lines 23.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package Polylang
4 */
5
6 namespace WP_Syntex\Polylang\REST\V1;
7
8 use PLL_Language;
9 use PLL_Model;
10 use PLL_Translatable_Objects;
11 use stdClass;
12 use WP_Error;
13 use WP_REST_Request;
14 use WP_REST_Response;
15 use WP_REST_Server;
16 use WP_Syntex\Polylang\Model\Languages as Languages_Model;
17 use WP_Syntex\Polylang\REST\Abstract_Controller;
18
19 defined( 'ABSPATH' ) || exit;
20
21 /**
22 * Languages REST controller.
23 *
24 * @since 3.7
25 */
26 class Languages extends Abstract_Controller {
27 /**
28 * @var Languages_Model
29 */
30 private $languages;
31
32 /**
33 * @var PLL_Translatable_Objects
34 */
35 private $translatable_objects;
36
37 /**
38 * Constructor.
39 *
40 * @since 3.7
41 *
42 * @param PLL_Model $model Polylang's model.
43 */
44 public function __construct( PLL_Model $model ) {
45 $this->namespace = 'pll/v1';
46 $this->rest_base = 'languages';
47 $this->languages = $model->languages;
48 $this->translatable_objects = $model->translatable_objects;
49 }
50
51 /**
52 * Registers the routes for languages.
53 *
54 * @since 3.7
55 *
56 * @return void
57 */
58 public function register_routes(): void {
59 register_rest_route(
60 $this->namespace,
61 "/{$this->rest_base}",
62 array(
63 array(
64 'methods' => WP_REST_Server::READABLE,
65 'callback' => array( $this, 'get_items' ),
66 'permission_callback' => array( $this, 'get_items_permissions_check' ),
67 ),
68 array(
69 'methods' => WP_REST_Server::CREATABLE,
70 'callback' => array( $this, 'create_item' ),
71 'permission_callback' => array( $this, 'create_item_permissions_check' ),
72 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
73 ),
74 'schema' => array( $this, 'get_public_item_schema' ),
75 'allow_batch' => array( 'v1' => true ),
76 )
77 );
78
79 $readable = array(
80 'methods' => WP_REST_Server::READABLE,
81 'callback' => array( $this, 'get_item' ),
82 'permission_callback' => array( $this, 'get_item_permissions_check' ),
83 'args' => array(
84 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
85 ),
86 );
87
88 register_rest_route(
89 $this->namespace,
90 "/{$this->rest_base}/(?P<term_id>[\d]+)",
91 array(
92 'args' => array(
93 'term_id' => array(
94 'description' => __( 'Unique identifier for the language.', 'polylang' ),
95 'type' => 'integer',
96 ),
97 ),
98 $readable,
99 array(
100 'methods' => WP_REST_Server::EDITABLE,
101 'callback' => array( $this, 'update_item' ),
102 'permission_callback' => array( $this, 'update_item_permissions_check' ),
103 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
104 ),
105 array(
106 'methods' => WP_REST_Server::DELETABLE,
107 'callback' => array( $this, 'delete_item' ),
108 'permission_callback' => array( $this, 'delete_item_permissions_check' ),
109 ),
110 'schema' => array( $this, 'get_public_item_schema' ),
111 'allow_batch' => array( 'v1' => true ),
112 )
113 );
114 register_rest_route(
115 $this->namespace,
116 sprintf( '/%1$s/(?P<slug>%2$s)', $this->rest_base, Languages_Model::INNER_SLUG_PATTERN ),
117 array(
118 'args' => array(
119 'slug' => array(
120 'description' => __( 'Language code - preferably 2-letters ISO 639-1 (for example: en).', 'polylang' ),
121 'type' => 'string',
122 ),
123 ),
124 $readable,
125 'schema' => array( $this, 'get_public_item_schema' ),
126 'allow_batch' => array( 'v1' => true ),
127 )
128 );
129 }
130
131 /**
132 * Retrieves all languages.
133 *
134 * @since 3.7
135 *
136 * @param WP_REST_Request $request Full details about the request.
137 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
138 *
139 * @phpstan-template T of array
140 * @phpstan-param WP_REST_Request<T> $request
141 */
142 public function get_items( $request ) {
143 $response = array();
144
145 foreach ( $this->languages->get_list() as $language ) {
146 $language = $this->prepare_item_for_response( $language, $request );
147 $response[] = $this->prepare_response_for_collection( $language );
148 }
149
150 return rest_ensure_response( $response );
151 }
152
153 /**
154 * Creates one language from the collection.
155 *
156 * @since 3.7
157 *
158 * @param WP_REST_Request $request Full details about the request.
159 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
160 *
161 * @phpstan-template T of array
162 * @phpstan-param WP_REST_Request<T> $request
163 */
164 public function create_item( $request ) {
165 if ( isset( $request['term_id'] ) ) {
166 return new WP_Error(
167 'rest_exists',
168 __( 'Cannot create existing language.', 'polylang' ),
169 array( 'status' => 400 )
170 );
171 }
172
173 // At this point, `$request['locale']` is provided (but maybe not valid).
174 $prepared = $this->prepare_item_for_database( $request );
175
176 if ( is_wp_error( $prepared ) ) {
177 return $prepared;
178 }
179
180 /**
181 * @phpstan-var array{
182 * locale: non-empty-string,
183 * slug: non-empty-string,
184 * name: non-empty-string,
185 * rtl: bool,
186 * term_group: int,
187 * flag: non-empty-string,
188 * no_default_cat: bool
189 * } $args
190 */
191 $args = (array) $prepared;
192 $result = $this->languages->add( $args );
193
194 if ( is_wp_error( $result ) ) {
195 return $this->add_status_to_error( $result );
196 }
197
198 /** @var PLL_Language */
199 $language = $this->languages->get( $args['locale'] );
200 return $this->prepare_item_for_response( $language, $request );
201 }
202
203 /**
204 * Retrieves one language from the collection.
205 *
206 * @since 3.7
207 *
208 * @param WP_REST_Request $request Full details about the request.
209 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
210 *
211 * @phpstan-template T of array
212 * @phpstan-param WP_REST_Request<T> $request
213 */
214 public function get_item( $request ) {
215 $language = $this->get_language( $request );
216
217 if ( is_wp_error( $language ) ) {
218 return $language;
219 }
220
221 return $this->prepare_item_for_response( $language, $request );
222 }
223
224 /**
225 * Updates one language from the collection.
226 *
227 * @since 3.7
228 *
229 * @param WP_REST_Request $request Full details about the request.
230 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
231 *
232 * @phpstan-template T of array
233 * @phpstan-param WP_REST_Request<T> $request
234 */
235 public function update_item( $request ) {
236 $prepared = $this->prepare_item_for_database( $request );
237
238 if ( is_wp_error( $prepared ) ) {
239 // Should not happen, but if it does, it's our fault.
240 return $prepared;
241 }
242
243 /**
244 * @phpstan-var array{
245 * lang_id: int,
246 * locale: non-empty-string,
247 * slug: non-empty-string,
248 * name: non-empty-string,
249 * rtl: bool,
250 * term_group: int,
251 * flag?: non-empty-string
252 * } $args
253 */
254 $args = (array) $prepared;
255 $update = $this->languages->update( $args );
256
257 if ( is_wp_error( $update ) ) {
258 return $this->add_status_to_error( $update );
259 }
260
261 /** @var PLL_Language */
262 $language = $this->languages->get( $args['lang_id'] );
263 return $this->prepare_item_for_response( $language, $request );
264 }
265
266 /**
267 * Deletes one language from the collection.
268 *
269 * @since 3.7
270 *
271 * @param WP_REST_Request $request Full details about the request.
272 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
273 *
274 * @phpstan-template T of array
275 * @phpstan-param WP_REST_Request<T> $request
276 */
277 public function delete_item( $request ) {
278 $language = $this->get_language( $request );
279
280 if ( is_wp_error( $language ) ) {
281 return $language;
282 }
283
284 $this->languages->delete( $language->term_id );
285
286 $previous = $this->prepare_item_for_response( $language, $request );
287 $response = new WP_REST_Response();
288 $response->set_data(
289 array(
290 'deleted' => true,
291 'previous' => $previous->get_data(),
292 )
293 );
294
295 return $response;
296 }
297
298 /**
299 * Checks if a given request has access to get the languages.
300 *
301 * @since 3.7
302 *
303 * @param WP_REST_Request $request Full details about the request.
304 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
305 *
306 * @phpstan-template T of array
307 * @phpstan-param WP_REST_Request<T> $request
308 */
309 public function get_items_permissions_check( $request ) {
310 if ( 'edit' === $request['context'] && ! $this->check_update_permission() ) {
311 return new WP_Error(
312 'rest_forbidden_context',
313 __( 'Sorry, you are not allowed to edit languages.', 'polylang' ),
314 array( 'status' => rest_authorization_required_code() )
315 );
316 }
317 return true;
318 }
319
320 /**
321 * Checks if a given request has access to create a language.
322 *
323 * @since 3.7
324 *
325 * @param WP_REST_Request $request Full details about the request.
326 * @return true|WP_Error True if the request has access to create languages, WP_Error object otherwise.
327 *
328 * @phpstan-template T of array
329 * @phpstan-param WP_REST_Request<T> $request
330 */
331 public function create_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
332 if ( ! $this->check_update_permission() ) {
333 return new WP_Error(
334 'rest_cannot_create',
335 __( 'Sorry, you are not allowed to create a language.', 'polylang' ),
336 array( 'status' => rest_authorization_required_code() )
337 );
338 }
339 return true;
340 }
341
342 /**
343 * Checks if a given request has access to get a specific language.
344 *
345 * @since 3.7
346 *
347 * @param WP_REST_Request $request Full details about the request.
348 * @return true|WP_Error True if the request has read access for the language, WP_Error object otherwise.
349 *
350 * @phpstan-template T of array
351 * @phpstan-param WP_REST_Request<T> $request
352 */
353 public function get_item_permissions_check( $request ) {
354 return $this->get_items_permissions_check( $request );
355 }
356
357 /**
358 * Checks if a given request has access to update a specific language.
359 *
360 * @since 3.7
361 *
362 * @param WP_REST_Request $request Full details about the request.
363 * @return true|WP_Error True if the request has access to update the language, WP_Error object otherwise.
364 *
365 * @phpstan-template T of array
366 * @phpstan-param WP_REST_Request<T> $request
367 */
368 public function update_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
369 if ( ! $this->check_update_permission() ) {
370 return new WP_Error(
371 'rest_cannot_update',
372 __( 'Sorry, you are not allowed to edit this language.', 'polylang' ),
373 array( 'status' => rest_authorization_required_code() )
374 );
375 }
376 return true;
377 }
378
379 /**
380 * Checks if a given request has access to delete a specific language.
381 *
382 * @since 3.7
383 *
384 * @param WP_REST_Request $request Full details about the request.
385 * @return true|WP_Error True if the request has access to delete the language, WP_Error object otherwise.
386 *
387 * @phpstan-template T of array
388 * @phpstan-param WP_REST_Request<T> $request
389 */
390 public function delete_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
391 if ( ! $this->check_update_permission() ) {
392 return new WP_Error(
393 'rest_cannot_delete',
394 __( 'Sorry, you are not allowed to delete this language.', 'polylang' ),
395 array( 'status' => rest_authorization_required_code() )
396 );
397 }
398 return true;
399 }
400
401 /**
402 * Prepares the language for the REST response.
403 *
404 * @since 3.7
405 *
406 * @param PLL_Language $item Language object.
407 * @param WP_REST_Request $request Request object.
408 * @return WP_REST_Response Response object.
409 *
410 * @phpstan-template T of array
411 * @phpstan-param WP_REST_Request<T> $request
412 */
413 public function prepare_item_for_response( $item, $request ) {
414 $data = $item->to_array();
415 $fields = $this->get_fields_for_response( $request );
416 $response = array();
417
418 $data['is_rtl'] = (bool) $data['is_rtl'];
419 $data['host'] = (string) $data['host'];
420
421 foreach ( $data as $language_prop => $prop_value ) {
422 if ( rest_is_field_included( $language_prop, $fields ) ) {
423 $response[ $language_prop ] = $prop_value;
424 }
425 }
426
427 /** @var WP_REST_Response */
428 return rest_ensure_response( $response );
429 }
430
431 /**
432 * Retrieves the language's schema, conforming to JSON Schema.
433 *
434 * @since 3.7
435 *
436 * @return array Item schema data.
437 */
438 public function get_item_schema(): array {
439 if ( $this->schema ) {
440 return $this->add_additional_fields_schema( $this->schema );
441 }
442
443 $this->schema = array(
444 '$schema' => 'http://json-schema.org/draft-04/schema#',
445 'title' => 'language',
446 'type' => 'object',
447 'properties' => array(
448 'term_id' => array(
449 'description' => __( 'Unique identifier for the language.', 'polylang' ),
450 'type' => 'integer',
451 'minimum' => 1,
452 'context' => array( 'view', 'edit' ),
453 'readonly' => true,
454 ),
455 'name' => array(
456 'description' => __( 'The name is how it is displayed on your site (for example: English).', 'polylang' ),
457 'type' => 'string',
458 'minLength' => 1,
459 'context' => array( 'view', 'edit' ),
460 ),
461 'slug' => array(
462 'description' => __( 'Language code - preferably 2-letters ISO 639-1 (for example: en).', 'polylang' ),
463 'type' => 'string',
464 'pattern' => Languages_Model::SLUG_PATTERN,
465 'context' => array( 'view', 'edit' ),
466 ),
467 'locale' => array(
468 'description' => __( 'WordPress Locale for the language (for example: en_US).', 'polylang' ),
469 'type' => 'string',
470 'pattern' => Languages_Model::LOCALE_PATTERN,
471 'context' => array( 'view', 'edit' ),
472 'required' => true,
473 ),
474 'w3c' => array(
475 'description' => __( 'W3C Locale for the language (for example: en-US).', 'polylang' ),
476 'type' => 'string',
477 'context' => array( 'view', 'edit' ),
478 'readonly' => true,
479 ),
480 'facebook' => array(
481 'description' => __( 'Facebook Locale for the language (for example: en_US).', 'polylang' ),
482 'type' => 'string',
483 'context' => array( 'view', 'edit' ),
484 'readonly' => true,
485 ),
486 'is_rtl' => array(
487 'description' => sprintf(
488 /* translators: %s is a value. */
489 __( 'Text direction. %s for right-to-left.', 'polylang' ),
490 '`true`'
491 ),
492 'type' => 'boolean',
493 'context' => array( 'view', 'edit' ),
494 ),
495 'term_group' => array(
496 'description' => __( 'Position of the language in the language switcher.', 'polylang' ),
497 'type' => 'integer',
498 'context' => array( 'view', 'edit' ),
499 ),
500 'flag_code' => array(
501 'description' => __( 'Flag code corresponding to ISO 3166-1 (for example: us for the United States flag).', 'polylang' ),
502 'type' => 'string',
503 'context' => array( 'view', 'edit' ),
504 ),
505 'flag_url' => array(
506 'description' => __( 'Flag URL.', 'polylang' ),
507 'type' => 'string',
508 'format' => 'uri',
509 'context' => array( 'view', 'edit' ),
510 'readonly' => true,
511 ),
512 'flag' => array(
513 'description' => __( 'HTML tag for the flag.', 'polylang' ),
514 'type' => 'string',
515 'context' => array( 'view', 'edit' ),
516 'readonly' => true,
517 ),
518 'custom_flag_url' => array(
519 'description' => __( 'Custom flag URL.', 'polylang' ),
520 'type' => 'string',
521 'format' => 'uri',
522 'context' => array( 'view', 'edit' ),
523 'readonly' => true,
524 ),
525 'custom_flag' => array(
526 'description' => __( 'HTML tag for the custom flag.', 'polylang' ),
527 'type' => 'string',
528 'context' => array( 'view', 'edit' ),
529 'readonly' => true,
530 ),
531 'is_default' => array(
532 'description' => __( 'Tells whether the language is the default one.', 'polylang' ),
533 'type' => 'boolean',
534 'context' => array( 'view', 'edit' ),
535 'readonly' => true,
536 ),
537 'active' => array(
538 'description' => __( 'Tells whether the language is active.', 'polylang' ),
539 'type' => 'boolean',
540 'context' => array( 'view', 'edit' ),
541 'readonly' => true,
542 ),
543 'home_url' => array(
544 'description' => __( 'Home URL in this language.', 'polylang' ),
545 'type' => 'string',
546 'format' => 'uri',
547 'context' => array( 'view', 'edit' ),
548 'readonly' => true,
549 ),
550 'search_url' => array(
551 'description' => __( 'Search URL in this language.', 'polylang' ),
552 'type' => 'string',
553 'format' => 'uri',
554 'context' => array( 'view', 'edit' ),
555 'readonly' => true,
556 ),
557 'host' => array(
558 'description' => __( 'Host for this language.', 'polylang' ),
559 'type' => 'string',
560 'format' => 'uri',
561 'context' => array( 'view', 'edit' ),
562 'readonly' => true,
563 ),
564 'page_on_front' => array(
565 'description' => __( 'Page on front ID in this language.', 'polylang' ),
566 'type' => 'integer',
567 'minimum' => 0,
568 'context' => array( 'view', 'edit' ),
569 'readonly' => true,
570 ),
571 'page_for_posts' => array(
572 'description' => __( 'Identifier of the page for posts in this language.', 'polylang' ),
573 'type' => 'integer',
574 'minimum' => 0,
575 'context' => array( 'view', 'edit' ),
576 'readonly' => true,
577 ),
578 'fallbacks' => array(
579 'description' => __( 'List of language locale fallbacks.', 'polylang' ),
580 'type' => 'array',
581 'uniqueItems' => true,
582 'items' => array(
583 'type' => 'string',
584 'pattern' => Languages_Model::LOCALE_PATTERN,
585 ),
586 'context' => array( 'view', 'edit' ),
587 'readonly' => true,
588 ),
589 'term_props' => array(
590 'description' => __( 'Language properties.', 'polylang' ),
591 'type' => 'object',
592 'properties' => array(),
593 'context' => array( 'view', 'edit' ),
594 'readonly' => true,
595 ),
596 'no_default_cat' => array(
597 'description' => __( 'Tells whether the default category must be created when creating a new language.', 'polylang' ),
598 'type' => 'boolean',
599 'context' => array( 'edit' ),
600 'default' => false,
601 ),
602 ),
603 );
604
605 foreach ( $this->translatable_objects as $translatable_object ) {
606 $this->schema['properties']['term_props']['properties'][ $translatable_object->get_tax_language() ] = array(
607 'description' => $translatable_object->get_rest_description(),
608 'type' => 'object',
609 'properties' => array(
610 'term_id' => array(
611 /* translators: %s is the name of the term property (`term_id` or `term_taxonomy_id`). */
612 'description' => sprintf( __( 'The %s of the language term for this translatable entity.', 'polylang' ), '`term_id`' ),
613 'type' => 'integer',
614 'minimum' => 1,
615 ),
616 'term_taxonomy_id' => array(
617 /* translators: %s is the name of the term property (`term_id` or `term_taxonomy_id`). */
618 'description' => sprintf( __( 'The %s of the language term for this translatable entity.', 'polylang' ), '`term_taxonomy_id`' ),
619 'type' => 'integer',
620 'minimum' => 1,
621 ),
622 'count' => array(
623 'description' => __( 'Number of items of this type of content in this language.', 'polylang' ),
624 'type' => 'integer',
625 'minimum' => 0,
626 ),
627 ),
628 );
629 }
630
631 return $this->add_additional_fields_schema( $this->schema );
632 }
633
634 /**
635 * Retrieves an array of endpoint arguments from the item schema for the controller.
636 * Ensures that the `no_default_cat` property is returned only for `CREATABLE` requests.
637 *
638 * @since 3.7
639 *
640 * @param string $method Optional. HTTP method of the request. Default WP_REST_Server::CREATABLE.
641 * @return array Endpoint arguments.
642 */
643 public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) {
644 $schema = $this->get_item_schema();
645 if ( WP_REST_Server::CREATABLE !== $method ) {
646 unset( $schema['properties']['no_default_cat'] );
647 }
648 return rest_get_endpoint_args_for_schema( $schema, $method );
649 }
650
651 /**
652 * Prepares one language for create or update operation.
653 *
654 * @since 3.7
655 *
656 * @param WP_REST_Request $request Request object.
657 * @return object|WP_Error The prepared language, or WP_Error object on failure.
658 *
659 * @phpstan-template T of array
660 * @phpstan-param WP_REST_Request<T> $request
661 */
662 protected function prepare_item_for_database( $request ) {
663 if ( isset( $request['term_id'] ) ) {
664 // Update a language.
665 $language = $this->get_language( $request );
666
667 if ( is_wp_error( $language ) ) {
668 return $language;
669 }
670
671 return (object) array(
672 'lang_id' => $language->term_id,
673 'name' => $request['name'] ?? $language->name,
674 'slug' => $request['slug'] ?? $language->slug,
675 'locale' => $request['locale'] ?? $language->locale,
676 'rtl' => $request['is_rtl'] ?? (bool) $language->is_rtl,
677 'flag' => $request['flag_code'] ?? $language->flag_code,
678 'term_group' => $request['term_group'] ?? $language->term_group,
679 );
680 }
681
682 // Create a language.
683 if ( empty( $request['locale'] ) ) {
684 // Should not happen.
685 return new WP_Error(
686 'rest_invalid_locale',
687 __( 'The locale is invalid.', 'polylang' ),
688 array( 'status' => 400 )
689 );
690 }
691
692 if ( isset( $request['name'], $request['slug'], $request['is_rtl'], $request['flag_code'] ) ) {
693 return (object) array(
694 'name' => $request['name'],
695 'slug' => $request['slug'],
696 'locale' => $request['locale'],
697 'rtl' => $request['is_rtl'],
698 'flag' => $request['flag_code'],
699 'term_group' => $request['term_group'] ?? 0,
700 'no_default_cat' => $request['no_default_cat'] ?? false,
701 );
702 }
703
704 // Create a language from our default list with only the locale.
705 $languages = include POLYLANG_DIR . '/settings/languages.php';
706
707 if ( empty( $languages[ $request['locale'] ] ) ) {
708 return new WP_Error(
709 'pll_rest_invalid_locale',
710 __( 'The locale is invalid.', 'polylang' ),
711 array( 'status' => 400 )
712 );
713 }
714
715 $language = (object) $languages[ $request['locale'] ];
716
717 return (object) array(
718 'name' => $request['name'] ?? $language->name,
719 'slug' => $request['slug'] ?? $language->code,
720 'locale' => $request['locale'],
721 'rtl' => $request['is_rtl'] ?? 'rtl' === $language->dir,
722 'flag' => $request['flag_code'] ?? $language->flag,
723 'term_group' => $request['term_group'] ?? 0,
724 'no_default_cat' => $request['no_default_cat'] ?? false,
725 );
726 }
727
728 /**
729 * Tells if languages can be edited.
730 *
731 * @since 3.7
732 *
733 * @return bool
734 */
735 protected function check_update_permission(): bool {
736 return current_user_can( 'manage_options' );
737 }
738
739 /**
740 * Returns the language, if the ID is valid.
741 *
742 * @since 3.7
743 *
744 * @param WP_REST_Request $request Full details about the request.
745 * @return PLL_Language|WP_Error Language object if the ID or slug is valid, WP_Error otherwise.
746 *
747 * @phpstan-template T of array
748 * @phpstan-param WP_REST_Request<T> $request
749 */
750 private function get_language( WP_REST_Request $request ) {
751 if ( isset( $request['term_id'] ) ) {
752 $error = new WP_Error(
753 'rest_invalid_id',
754 __( 'Invalid language ID', 'polylang' ),
755 array( 'status' => 404 )
756 );
757
758 if ( $request['term_id'] <= 0 ) {
759 return $error;
760 }
761
762 $language = $this->languages->get( (int) $request['term_id'] );
763
764 if ( ! $language instanceof PLL_Language ) {
765 return $error;
766 }
767
768 return $language;
769 }
770
771 if ( isset( $request['slug'] ) ) {
772 $language = $this->languages->get( (string) $request['slug'] );
773
774 if ( ! $language instanceof PLL_Language ) {
775 return new WP_Error(
776 'rest_invalid_slug',
777 __( 'Invalid language slug', 'polylang' ),
778 array( 'status' => 404 )
779 );
780 }
781
782 return $language;
783 }
784
785 // Should not happen.
786 return new WP_Error(
787 'rest_invalid_identifier',
788 __( 'Invalid language identifier', 'polylang' ),
789 array( 'status' => 404 )
790 );
791 }
792 }
793