PluginProbe
Polylang / 3.2.2
Polylang v3.2.2
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 / include / model.php

model.php in Polylang 3.2.2, at include/model.php

710 lines 24.0 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 /**
7 * Setups the language and translations model based on WordPress taxonomies
8 *
9 * @since 1.2
10 */
11 class PLL_Model {
12 /**
13 * Internal non persistent cache object.
14 *
15 * @var PLL_Cache
16 */
17 public $cache;
18
19 /**
20 * Stores the plugin options.
21 *
22 * @var array
23 */
24 public $options;
25
26 /**
27 * Translated post model.
28 *
29 * @var PLL_Translated_Post
30 */
31 public $post;
32
33 /**
34 * Translated term model.
35 *
36 * @var PLL_Translated_Term
37 */
38 public $term;
39
40 /**
41 * Constructor.
42 * Setups translated objects sub models.
43 * Setups filters and actions.
44 *
45 * @since 1.2
46 *
47 * @param array $options Polylang options.
48 */
49 public function __construct( &$options ) {
50 $this->options = &$options;
51
52 $this->cache = new PLL_Cache();
53 $this->post = new PLL_Translated_Post( $this ); // Translated post sub model.
54 $this->term = new PLL_Translated_Term( $this ); // Translated term sub model.
55
56 // We need to clean languages cache when editing a language and when modifying the permalink structure.
57 add_action( 'edited_term_taxonomy', array( $this, 'clean_languages_cache' ), 10, 2 );
58 add_action( 'update_option_permalink_structure', array( $this, 'clean_languages_cache' ) );
59 add_action( 'update_option_siteurl', array( $this, 'clean_languages_cache' ) );
60 add_action( 'update_option_home', array( $this, 'clean_languages_cache' ) );
61
62 add_filter( 'get_terms_args', array( $this, 'get_terms_args' ) );
63
64 // Just in case someone would like to display the language description ;).
65 add_filter( 'language_description', '__return_empty_string' );
66 }
67
68 /**
69 * Returns the list of available languages.
70 * - Stores the list in a db transient ( except flags ), unless PLL_CACHE_LANGUAGES is set to false.
71 * - Caches the list ( with flags ) in a PLL_Cache object.
72 *
73 * @since 0.1
74 *
75 * @param array $args {
76 * @type bool $hide_empty Hides languages with no posts if set to true ( defaults to false ).
77 * @type string $fields Returns only that field if set; {@see PLL_Language} for a list of fields.
78 * }
79 * @return array List of PLL_Language objects or PLL_Language object properties.
80 */
81 public function get_languages_list( $args = array() ) {
82 if ( false === $languages = $this->cache->get( 'languages' ) ) {
83 $languages = array();
84
85 // Create the languages from taxonomies.
86 if ( ( defined( 'PLL_CACHE_LANGUAGES' ) && ! PLL_CACHE_LANGUAGES ) || false === ( $languages = get_transient( 'pll_languages_list' ) ) ) {
87 $languages = array();
88
89 $post_languages = get_terms( 'language', array( 'hide_empty' => false, 'orderby' => 'term_group' ) );
90 $post_languages = empty( $post_languages ) || is_wp_error( $post_languages ) ? array() : $post_languages;
91
92 $term_languages = get_terms( 'term_language', array( 'hide_empty' => false ) );
93 $term_languages = empty( $term_languages ) || is_wp_error( $term_languages ) ?
94 array() : array_combine( wp_list_pluck( $term_languages, 'slug' ), $term_languages );
95
96 if ( ! empty( $post_languages ) && ! empty( $term_languages ) ) {
97 foreach ( $post_languages as $k => $v ) {
98 if ( isset( $term_languages[ 'pll_' . $v->slug ] ) ) {
99 $languages[ $k ] = new PLL_Language( $v, $term_languages[ 'pll_' . $v->slug ] );
100 }
101 }
102
103 // We will need the languages list to allow its access in the filter below.
104 $this->cache->set( 'languages', $languages );
105
106 /**
107 * Filters the list of languages *before* it is stored in the persistent cache.
108 * /!\ This filter is fired *before* the $polylang object is available.
109 *
110 * @since 1.7.5
111 *
112 * @param PLL_Language[] $languages The list of language objects.
113 * @param PLL_Model $model PLL_Model object.
114 */
115 $languages = apply_filters( 'pll_languages_list', $languages, $this );
116
117 /*
118 * Don't store directly objects as it badly break with some hosts ( GoDaddy ) due to race conditions when using object cache.
119 * Thanks to captin411 for catching this!
120 * @see https://wordpress.org/support/topic/fatal-error-pll_model_languages_list?replies=8#post-6782255
121 */
122 set_transient( 'pll_languages_list', array_map( 'get_object_vars', $languages ) );
123 }
124 }
125
126 // Create the languages directly from arrays stored in transients.
127 else {
128 foreach ( $languages as $k => $v ) {
129 $languages[ $k ] = new PLL_Language( $v );
130 }
131 }
132
133 /**
134 * Filters the list of languages *after* it is stored in the persistent cache.
135 * /!\ This filter is fired *before* the $polylang object is available.
136 *
137 * @since 1.8
138 *
139 * @param PLL_Language[] $languages The list of language objects.
140 */
141 $languages = apply_filters( 'pll_after_languages_cache', $languages );
142 $this->cache->set( 'languages', $languages );
143 }
144
145 $args = wp_parse_args( $args, array( 'hide_empty' => false ) );
146
147 // Remove empty languages if requested.
148 if ( $args['hide_empty'] ) {
149 $languages = wp_list_filter( $languages, array( 'count' => 0 ), 'NOT' );
150 }
151
152 return empty( $args['fields'] ) ? $languages : wp_list_pluck( $languages, $args['fields'] );
153 }
154
155 /**
156 * Cleans language cache
157 * can be called directly with no parameter
158 * called by the 'edited_term_taxonomy' filter with 2 parameters when count needs to be updated
159 *
160 * @since 1.2
161 *
162 * @param int $term not used
163 * @param string $taxonomy taxonomy name
164 * @return void
165 */
166 public function clean_languages_cache( $term = 0, $taxonomy = null ) {
167 if ( empty( $taxonomy ) || 'language' == $taxonomy ) {
168 delete_transient( 'pll_languages_list' );
169 $this->cache->clean();
170 }
171 }
172
173 /**
174 * Don't query term metas when only our taxonomies are queried
175 *
176 * @since 2.3
177 *
178 * @param array $args WP_Term_Query arguments
179 * @return array
180 */
181 public function get_terms_args( $args ) {
182 if ( isset( $args['taxonomy'] ) && ! array_diff( (array) $args['taxonomy'], array( 'language', 'term_language', 'post_translations', 'term_translations' ) ) ) {
183 $args['update_term_meta_cache'] = false;
184 }
185 return $args;
186 }
187
188 /**
189 * Returns the language by its term_id, tl_term_id, slug or locale.
190 *
191 * @since 0.1
192 *
193 * @param mixed $value term_id, tl_term_id, slug or locale of the queried language.
194 * @return PLL_Language|false Language object, false if no language found.
195 */
196 public function get_language( $value ) {
197 if ( is_object( $value ) ) {
198 return $value instanceof PLL_Language ? $value : $this->get_language( $value->term_id ); // will force cast to PLL_Language
199 }
200
201 if ( false === $return = $this->cache->get( 'language:' . $value ) ) {
202 foreach ( $this->get_languages_list() as $lang ) {
203 $this->cache->set( 'language:' . $lang->term_id, $lang );
204 $this->cache->set( 'language:' . $lang->tl_term_id, $lang );
205 $this->cache->set( 'language:' . $lang->slug, $lang );
206 $this->cache->set( 'language:' . $lang->locale, $lang );
207 $this->cache->set( 'language:' . $lang->w3c, $lang );
208 }
209 $return = $this->cache->get( 'language:' . $value );
210 }
211
212 return $return;
213 }
214
215 /**
216 * Adds terms clauses to the term query to filter them by languages.
217 *
218 * @since 1.2
219 *
220 * @param string[] $clauses The list of sql clauses in terms query.
221 * @param PLL_Language $lang PLL_Language object.
222 * @return string[] Modified list of clauses.
223 */
224 public function terms_clauses( $clauses, $lang ) {
225 if ( ! empty( $lang ) && false === strpos( $clauses['join'], 'pll_tr' ) ) {
226 $clauses['join'] .= $this->term->join_clause();
227 $clauses['where'] .= $this->term->where_clause( $lang );
228 }
229 return $clauses;
230 }
231
232 /**
233 * Returns post types that need to be translated.
234 * The post types list is cached for better better performance.
235 * The method waits for 'after_setup_theme' to apply the cache
236 * to allow themes adding the filter in functions.php.
237 *
238 * @since 1.2
239 *
240 * @param bool $filter True if we should return only valid registered post types.
241 * @return string[] Post type names for which Polylang manages languages and translations.
242 */
243 public function get_translated_post_types( $filter = true ) {
244 if ( false === $post_types = $this->cache->get( 'post_types' ) ) {
245 $post_types = array( 'post' => 'post', 'page' => 'page', 'wp_block' => 'wp_block' );
246
247 if ( ! empty( $this->options['media_support'] ) ) {
248 $post_types['attachment'] = 'attachment';
249 }
250
251 if ( ! empty( $this->options['post_types'] ) && is_array( $this->options['post_types'] ) ) {
252 $post_types = array_merge( $post_types, array_combine( $this->options['post_types'], $this->options['post_types'] ) );
253 }
254
255 /**
256 * Filters the list of post types available for translation.
257 * The default are post types which have the parameter ‘public’ set to true.
258 * The filter must be added soon in the WordPress loading process:
259 * in a function hooked to ‘plugins_loaded’ or directly in functions.php for themes.
260 *
261 * @since 0.8
262 *
263 * @param string[] $post_types List of post type names.
264 * @param bool $is_settings True when displaying the list of custom post types in Polylang settings.
265 */
266 $post_types = apply_filters( 'pll_get_post_types', $post_types, false );
267
268 if ( did_action( 'after_setup_theme' ) ) {
269 $this->cache->set( 'post_types', $post_types );
270 }
271 }
272
273 return $filter ? array_intersect( $post_types, get_post_types() ) : $post_types;
274 }
275
276 /**
277 * Returns true if Polylang manages languages and translations for this post type.
278 *
279 * @since 1.2
280 *
281 * @param string|string[] $post_type Post type name or array of post type names.
282 * @return bool
283 */
284 public function is_translated_post_type( $post_type ) {
285 $post_types = $this->get_translated_post_types( false );
286 return ( is_array( $post_type ) && array_intersect( $post_type, $post_types ) || in_array( $post_type, $post_types ) || 'any' === $post_type && ! empty( $post_types ) );
287 }
288
289 /**
290 * Returns taxonomies that need to be translated.
291 *
292 * @since 1.2
293 *
294 * @param bool $filter True if we should return only valid registered taxonomies.
295 * @return string[] Array of registered taxonomy names for which Polylang manages languages and translations.
296 */
297 public function get_translated_taxonomies( $filter = true ) {
298 if ( false === $taxonomies = $this->cache->get( 'taxonomies' ) ) {
299 $taxonomies = array( 'category' => 'category', 'post_tag' => 'post_tag' );
300
301 if ( ! empty( $this->options['taxonomies'] ) && is_array( $this->options['taxonomies'] ) ) {
302 $taxonomies = array_merge( $taxonomies, array_combine( $this->options['taxonomies'], $this->options['taxonomies'] ) );
303 }
304
305 /**
306 * Filters the list of taxonomies available for translation.
307 * The default are taxonomies which have the parameter ‘public’ set to true.
308 * The filter must be added soon in the WordPress loading process:
309 * in a function hooked to ‘plugins_loaded’ or directly in functions.php for themes.
310 *
311 * @since 0.8
312 *
313 * @param string[] $taxonomies List of taxonomy names.
314 * @param bool $is_settings True when displaying the list of custom taxonomies in Polylang settings.
315 */
316 $taxonomies = apply_filters( 'pll_get_taxonomies', $taxonomies, false );
317 if ( did_action( 'after_setup_theme' ) ) {
318 $this->cache->set( 'taxonomies', $taxonomies );
319 }
320 }
321
322 return $filter ? array_intersect( $taxonomies, get_taxonomies() ) : $taxonomies;
323 }
324
325 /**
326 * Returns true if Polylang manages languages and translations for this taxonomy.
327 *
328 * @since 1.2
329 *
330 * @param string|string[] $tax Taxonomy name or array of taxonomy names.
331 * @return bool
332 */
333 public function is_translated_taxonomy( $tax ) {
334 $taxonomies = $this->get_translated_taxonomies( false );
335 return ( is_array( $tax ) && array_intersect( $tax, $taxonomies ) || in_array( $tax, $taxonomies ) );
336 }
337
338 /**
339 * Return staxonomies that need to be filtered ( post_format like ).
340 *
341 * @since 1.7
342 *
343 * @param bool $filter True if we should return only valid registered taxonomies.
344 * @return string[] Array of registered taxonomy names.
345 */
346 public function get_filtered_taxonomies( $filter = true ) {
347 if ( did_action( 'after_setup_theme' ) ) {
348 static $taxonomies = null;
349 }
350
351 if ( empty( $taxonomies ) ) {
352 $taxonomies = array( 'post_format' => 'post_format' );
353
354 /**
355 * Filters the list of taxonomies not translatable but filtered by language.
356 * Includes only the post format by default
357 * The filter must be added soon in the WordPress loading process:
358 * in a function hooked to ‘plugins_loaded’ or directly in functions.php for themes.
359 *
360 * @since 1.7
361 *
362 * @param string[] $taxonomies List of taxonomy names.
363 * @param bool $is_settings True when displaying the list of custom taxonomies in Polylang settings.
364 */
365 $taxonomies = apply_filters( 'pll_filtered_taxonomies', $taxonomies, false );
366 }
367
368 return $filter ? array_intersect( $taxonomies, get_taxonomies() ) : $taxonomies;
369 }
370
371 /**
372 * Returns true if Polylang filters this taxonomy per language.
373 *
374 * @since 1.7
375 *
376 * @param string|string[] $tax Taxonomy name or array of taxonomy names.
377 * @return bool
378 */
379 public function is_filtered_taxonomy( $tax ) {
380 $taxonomies = $this->get_filtered_taxonomies( false );
381 return ( is_array( $tax ) && array_intersect( $tax, $taxonomies ) || in_array( $tax, $taxonomies ) );
382 }
383
384 /**
385 * Returns the query vars of all filtered taxonomies.
386 *
387 * @since 1.7
388 *
389 * @return array
390 */
391 public function get_filtered_taxonomies_query_vars() {
392 $query_vars = array();
393 foreach ( $this->get_filtered_taxonomies() as $filtered_tax ) {
394 $tax = get_taxonomy( $filtered_tax );
395 if ( ! empty( $tax ) ) {
396 $query_vars[] = $tax->query_var;
397 }
398 }
399 return $query_vars;
400 }
401
402 /**
403 * It is possible to have several terms with the same name in the same taxonomy ( one per language )
404 * but the native term_exists() will return true even if only one exists.
405 * So here the function adds the language parameter.
406 *
407 * @since 1.4
408 *
409 * @param string $term_name The term name.
410 * @param string $taxonomy Taxonomy name.
411 * @param int $parent Parent term id.
412 * @param string|PLL_Language $language The language slug or object.
413 * @return null|int The term_id of the found term.
414 */
415 public function term_exists( $term_name, $taxonomy, $parent, $language ) {
416 global $wpdb;
417
418 $term_name = trim( wp_unslash( $term_name ) );
419 $term_name = _wp_specialchars( $term_name );
420
421 $select = "SELECT t.term_id FROM $wpdb->terms AS t";
422 $join = " INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id";
423 $join .= $this->term->join_clause();
424 $where = $wpdb->prepare( ' WHERE tt.taxonomy = %s AND t.name = %s', $taxonomy, $term_name );
425 $where .= $this->term->where_clause( $this->get_language( $language ) );
426
427 if ( $parent > 0 ) {
428 $where .= $wpdb->prepare( ' AND tt.parent = %d', $parent );
429 }
430
431 // PHPCS:ignore WordPress.DB.PreparedSQL.NotPrepared
432 return $wpdb->get_var( $select . $join . $where );
433 }
434
435 /**
436 * Checks if a term slug exists in a given language, taxonomy, hierarchy.
437 *
438 * @since 1.9
439 * @since 2.8 Moved from PLL_Share_Term_Slug::term_exists() to PLL_Model::term_exists_by_slug().
440 *
441 * @param string $slug The term slug to test.
442 * @param string|PLL_Language $language The language slug or object.
443 * @param string $taxonomy Optional taxonomy name.
444 * @param int $parent Optional parent term id.
445 * @return null|int The term_id of the found term.
446 */
447 public function term_exists_by_slug( $slug, $language, $taxonomy = '', $parent = 0 ) {
448 global $wpdb;
449
450 $select = "SELECT t.term_id FROM {$wpdb->terms} AS t";
451 $join = " INNER JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id";
452 $join .= $this->term->join_clause();
453 $where = $wpdb->prepare( ' WHERE t.slug = %s', $slug );
454 $where .= $this->term->where_clause( $this->get_language( $language ) );
455
456 if ( ! empty( $taxonomy ) ) {
457 $where .= $wpdb->prepare( ' AND tt.taxonomy = %s', $taxonomy );
458 }
459
460 if ( $parent > 0 ) {
461 $where .= $wpdb->prepare( ' AND tt.parent = %d', $parent );
462 }
463
464 // PHPCS:ignore WordPress.DB.PreparedSQL.NotPrepared
465 return $wpdb->get_var( $select . $join . $where );
466 }
467
468
469 /**
470 * Gets the number of posts per language in a date, author or post type archive.
471 *
472 * @since 1.2
473 *
474 * @param PLL_Language $lang PLL_Language instance.
475 * @param array $q {
476 * WP_Query arguments:
477 *
478 * @type string|string[] $post_type Post type or array of post types.
479 * @type int $m Combination YearMonth. Accepts any four-digit year and month.
480 * @type int $year Four-digit year.
481 * @type int $monthnum Two-digit month.
482 * @type int $day Day of the month.
483 * @type int $author Author id.
484 * @type string $author_name User 'user_nicename'.
485 * @type string $post_format Post format.
486 * @type string $post_status Post status.
487 * }
488 * @return int
489 */
490 public function count_posts( $lang, $q = array() ) {
491 global $wpdb;
492
493 $q = wp_parse_args( $q, array( 'post_type' => 'post', 'post_status' => 'publish' ) );
494
495 if ( ! is_array( $q['post_type'] ) ) {
496 $q['post_type'] = array( $q['post_type'] );
497 }
498
499 foreach ( $q['post_type'] as $key => $type ) {
500 if ( ! post_type_exists( $type ) ) {
501 unset( $q['post_type'][ $key ] );
502 }
503 }
504
505 if ( empty( $q['post_type'] ) ) {
506 $q['post_type'] = array( 'post' ); // We *need* a post type.
507 }
508
509 $cache_key = 'pll_count_posts_' . md5( maybe_serialize( $q ) );
510 $counts = wp_cache_get( $cache_key, 'counts' );
511
512 if ( false === $counts ) {
513 $counts = array();
514
515 $select = "SELECT pll_tr.term_taxonomy_id, COUNT( * ) AS num_posts FROM {$wpdb->posts}";
516 $join = $this->post->join_clause();
517 $where = sprintf( " WHERE post_status = '%s'", esc_sql( $q['post_status'] ) );
518 $where .= sprintf( " AND {$wpdb->posts}.post_type IN ( '%s' )", implode( "', '", esc_sql( $q['post_type'] ) ) );
519 $where .= $this->post->where_clause( $this->get_languages_list() );
520 $groupby = ' GROUP BY pll_tr.term_taxonomy_id';
521
522 if ( ! empty( $q['m'] ) ) {
523 $q['m'] = '' . preg_replace( '|[^0-9]|', '', $q['m'] );
524 $where .= $wpdb->prepare( " AND YEAR( {$wpdb->posts}.post_date ) = %d", substr( $q['m'], 0, 4 ) );
525 if ( strlen( $q['m'] ) > 5 ) {
526 $where .= $wpdb->prepare( " AND MONTH( {$wpdb->posts}.post_date ) = %d", substr( $q['m'], 4, 2 ) );
527 }
528 if ( strlen( $q['m'] ) > 7 ) {
529 $where .= $wpdb->prepare( " AND DAYOFMONTH( {$wpdb->posts}.post_date ) = %d", substr( $q['m'], 6, 2 ) );
530 }
531 }
532
533 if ( ! empty( $q['year'] ) ) {
534 $where .= $wpdb->prepare( " AND YEAR( {$wpdb->posts}.post_date ) = %d", $q['year'] );
535 }
536
537 if ( ! empty( $q['monthnum'] ) ) {
538 $where .= $wpdb->prepare( " AND MONTH( {$wpdb->posts}.post_date ) = %d", $q['monthnum'] );
539 }
540
541 if ( ! empty( $q['day'] ) ) {
542 $where .= $wpdb->prepare( " AND DAYOFMONTH( {$wpdb->posts}.post_date ) = %d", $q['day'] );
543 }
544
545 if ( ! empty( $q['author_name'] ) ) {
546 $author = get_user_by( 'slug', sanitize_title_for_query( $q['author_name'] ) );
547 if ( $author ) {
548 $q['author'] = $author->ID;
549 }
550 }
551
552 if ( ! empty( $q['author'] ) ) {
553 $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_author = %d", $q['author'] );
554 }
555
556 // Filtered taxonomies ( post_format ).
557 foreach ( $this->get_filtered_taxonomies_query_vars() as $tax_qv ) {
558
559 if ( ! empty( $q[ $tax_qv ] ) ) {
560 $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON tr.object_id = {$wpdb->posts}.ID";
561 $join .= " INNER JOIN {$wpdb->term_taxonomy} AS tt ON tt.term_taxonomy_id = tr.term_taxonomy_id";
562 $join .= " INNER JOIN {$wpdb->terms} AS t ON t.term_id = tt.term_id";
563 $where .= $wpdb->prepare( ' AND t.slug = %s', $q[ $tax_qv ] );
564 }
565 }
566
567 // PHPCS:ignore WordPress.DB.PreparedSQL.NotPrepared
568 $res = $wpdb->get_results( $select . $join . $where . $groupby, ARRAY_A );
569 foreach ( (array) $res as $row ) {
570 $counts[ $row['term_taxonomy_id'] ] = $row['num_posts'];
571 }
572
573 wp_cache_set( $cache_key, $counts, 'counts' );
574 }
575
576 return empty( $counts[ $lang->term_taxonomy_id ] ) ? 0 : $counts[ $lang->term_taxonomy_id ];
577 }
578
579 /**
580 * Setup the links model based on options.
581 *
582 * @since 1.2
583 *
584 * @return PLL_Links_Model
585 */
586 public function get_links_model() {
587 $c = array( 'Directory', 'Directory', 'Subdomain', 'Domain' );
588 $class = get_option( 'permalink_structure' ) ? 'PLL_Links_' . $c[ $this->options['force_lang'] ] : 'PLL_Links_Default';
589
590 /**
591 * Filters the links model class to use.
592 * /!\ this filter is fired *before* the $polylang object is available.
593 *
594 * @since 2.1.1
595 *
596 * @param string $class A class name: PLL_Links_Default, PLL_Links_Directory, PLL_Links_Subdomain, PLL_Links_Domain.
597 */
598 $class = apply_filters( 'pll_links_model', $class );
599
600 return new $class( $this );
601 }
602
603 /**
604 * Returns posts and terms ids without language ( used in settings ).
605 *
606 * @since 0.9
607 * @since 2.2.6 Add the $limit argument.
608 *
609 * @param int $limit Max number of posts or terms to return. Defaults to -1 (no limit).
610 * @return array {
611 * Objects without language.
612 *
613 * @type int[] $posts Array of post ids.
614 * @type int[] $terms Array of term ids.
615 * }
616 */
617 public function get_objects_with_no_lang( $limit = -1 ) {
618 /**
619 * Filters the max number of posts or terms to return when searching objects with no language.
620 * This filter can be used to decrease the memory usage in case the number of objects
621 * without language is too big. Using a negative value is equivalent to have no limit.
622 *
623 * @since 2.2.6
624 *
625 * @param int $limit Max number of posts or terms to retrieve from the database.
626 */
627 $limit = (int) apply_filters( 'get_objects_with_no_lang_limit', $limit );
628
629 $posts = $this->get_posts_with_no_lang( $this->get_translated_post_types(), $limit );
630 $terms = $this->get_terms_with_no_lang( $this->get_translated_taxonomies(), $limit );
631
632 /**
633 * Filters the list of untranslated posts ids and terms ids
634 *
635 * @since 0.9
636 *
637 * @param array|false $objects false if no ids found, list of post and/or term ids otherwise.
638 */
639 return apply_filters( 'pll_get_objects_with_no_lang', empty( $posts ) && empty( $terms ) ? false : array( 'posts' => $posts, 'terms' => $terms ) );
640 }
641
642 /**
643 * Returns ids of post without language.
644 *
645 * @since 3.1
646 *
647 * @param string|string[] $post_types A translated post type or an array of translated post types.
648 * @param int $limit Max number of posts to return.
649 * @return int[]
650 */
651 public function get_posts_with_no_lang( $post_types, $limit ) {
652 return get_posts(
653 array(
654 'numberposts' => $limit,
655 'nopaging' => $limit <= 0,
656 'post_type' => $post_types,
657 'post_status' => 'any',
658 'fields' => 'ids',
659 'tax_query' => array(
660 array(
661 'taxonomy' => 'language',
662 'terms' => $this->get_languages_list( array( 'fields' => 'term_id' ) ),
663 'operator' => 'NOT IN',
664 ),
665 ),
666 )
667 );
668 }
669
670 /**
671 * Returns ids of terms without language.
672 *
673 * @since 3.1
674 *
675 * @param string|string[] $taxonomies A translated taxonomy or an array of taxonomies post types.
676 * @param int $limit Max number of terms to return.
677 * @return int[]
678 */
679 public function get_terms_with_no_lang( $taxonomies, $limit ) {
680 global $wpdb;
681
682 $taxonomies = (array) $taxonomies;
683
684 $sql = sprintf(
685 "SELECT {$wpdb->term_taxonomy}.term_id FROM {$wpdb->term_taxonomy}
686 WHERE taxonomy IN ('%s')
687 AND {$wpdb->term_taxonomy}.term_id NOT IN (
688 SELECT object_id FROM {$wpdb->term_relationships} WHERE term_taxonomy_id IN (%s)
689 )
690 %s",
691 implode( "','", array_map( 'esc_sql', $taxonomies ) ),
692 implode( ',', array_map( 'intval', $this->get_languages_list( array( 'fields' => 'tl_term_taxonomy_id' ) ) ) ),
693 $limit > 0 ? sprintf( 'LIMIT %d', intval( $limit ) ) : ''
694 );
695
696 $key = md5( $sql );
697 $last_changed = wp_cache_get_last_changed( 'terms' );
698 $cache_key = "terms_no_lang:{$key}:{$last_changed}";
699
700 $term_ids = wp_cache_get( $cache_key, 'terms' );
701
702 if ( false === $term_ids ) {
703 $term_ids = $wpdb->get_col( $sql ); // PHPCS:ignore WordPress.DB.PreparedSQL.NotPrepared
704 wp_cache_set( $cache_key, $term_ids, 'terms' );
705 }
706
707 return $term_ids;
708 }
709 }
710