PluginProbe
Polylang / 1.5.3
Polylang v1.5.3
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 1.5.3, at include/model.php

780 lines 26.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * setups the language and translations model based on WordPress taxonomies
5 *
6 * @since 1.2
7 */
8 class PLL_Model {
9 public $options;
10 public $languages; // used to cache the list of languages
11 public $blog_id; // used to get one cache array per site
12
13 /*
14 * constructor: registers custom taxonomies and setups filters and actions
15 *
16 * @since 1.2
17 *
18 * @param array $options Polylang options
19 */
20 public function __construct(&$options) {
21 $this->options = &$options;
22 $this->blog_id = get_current_blog_id();
23
24 // register our taxonomies as soon as possible
25 // this is early registration, not ready for rewrite rules as wp_rewrite will be setup later
26 $args = array('label' => false, 'public' => false, 'query_var' => false, 'rewrite' => false, '_pll' => true);
27 register_taxonomy('language', null, $args);
28 register_taxonomy('term_language', 'term', $args);
29 register_taxonomy('term_translations', 'term', $args);
30 $args['update_count_callback'] = '_update_generic_term_count'; // count *all* posts to avoid deleting in clean_translations_terms
31 register_taxonomy('post_translations', null, $args);
32
33 add_filter('get_terms', array(&$this, '_prime_terms_cache'), 10, 2);
34 add_filter('wp_get_object_terms', array(&$this, 'wp_get_object_terms'), 10, 3);
35
36 // we need to clean languages cache when editing a language,
37 // when editing page of front or when modifying the permalink structure
38 add_action('edited_term_taxonomy', array(&$this, 'clean_languages_cache'), 10, 2);
39 add_action('update_option_page_on_front', array(&$this, 'clean_languages_cache'));
40 add_action('update_option_permalink_structure', array(&$this, 'clean_languages_cache'));
41
42 // registers completely the language taxonomy
43 add_action('setup_theme', array(&$this, 'register_taxonomy'), 1);
44
45 // setups post types to translate
46 add_action('registered_post_type', array(&$this, 'registered_post_type'));
47
48 // just in case someone would like to display the language description ;-)
49 add_filter('language_description', create_function('$v', "return '';"));
50 }
51
52 /*
53 * cache language and translations when terms are queried by get_terms
54 *
55 * @since 1.2
56 *
57 * @param array $terms queried terms
58 * @param array $taxonomies queried taxonomies
59 * @return array unmodified $terms
60 */
61 public function _prime_terms_cache($terms, $taxonomies) {
62 if ($this->is_translated_taxonomy($taxonomies)) {
63 foreach ($terms as $term) {
64 $term_ids[] = is_object($term) ? $term->term_id : $term;
65 }
66 }
67
68 if (!empty($term_ids))
69 update_object_term_cache(array_unique($term_ids), 'term'); // adds language and translation of terms to cache
70 return $terms;
71 }
72
73 /*
74 * when terms are found for posts, add their language and translations to cache
75 *
76 * @since 1.2
77 *
78 * @param array $terms terms found
79 * @param array $object_ids not used
80 * @param array $taxonomies terms taxonomies
81 * @return array unmodified $terms
82 */
83 // FIXME is that useful? or even desirable?
84 public function wp_get_object_terms($terms, $object_ids, $taxonomies) {
85 $taxonomies = explode("', '", trim($taxonomies, "'"));
86 if (!in_array('term_translations', $taxonomies))
87 $this->_prime_terms_cache($terms, $taxonomies);
88 return $terms;
89 }
90
91 /*
92 * wrap wp_get_object_terms to cache it and return only one object
93 * inspired by the function get_the_terms
94 *
95 * @since 1.2
96 *
97 * @param int $object_id post_id or term_id
98 * @param string $taxonomy Polylang taxonomy depending if we are looking for a post (or term) language (or translation)
99 * @return bool|object the term associated to the object in the requested taxonomy if exists, false otherwise
100 */
101 public function get_object_term($object_id, $taxonomy) {
102 $term = get_object_term_cache($object_id, $taxonomy);
103
104 if ( false === $term ) {
105 // query language and translations at the same time
106 $taxonomies = (false !== strpos($taxonomy, 'term_')) ?
107 array('term_language', 'term_translations') :
108 array('language', 'post_translations');
109
110 foreach (wp_get_object_terms($object_id, $taxonomies) as $t) {
111 wp_cache_add($object_id, array($t), $t->taxonomy . '_relationships'); // store it the way WP wants it
112 if ($t->taxonomy == $taxonomy)
113 $term = $t;
114 }
115 }
116 else {
117 $term = reset($term);
118 }
119
120 return empty($term) ? false : $term;
121 }
122
123 /*
124 * returns the list of available languages
125 * caches the list in a db transient (except flags), unless PLL_CACHE_LANGUAGES is set to false
126 * caches the list (with flags) in the private property $languages
127 *
128 * list of parameters accepted in $args:
129 *
130 * hide_empty => hides languages with no posts if set to true (defaults to false)
131 * fields => return only that field if set (see PLL_Language for a list of fields)
132 *
133 * @since 0.1
134 *
135 * @param array $args
136 * @return array|string|int list of PLL_Language objects or PLL_Language object properties
137 */
138 public function get_languages_list($args = array()) {
139 if (empty($this->languages[$this->blog_id])) {
140 if ((defined('PLL_CACHE_LANGUAGES') && !PLL_CACHE_LANGUAGES) || false === ($languages = get_transient('pll_languages_list'))) {
141 $languages = get_terms('language', array('hide_empty' => false, 'orderby'=> 'term_group'));
142 $languages = empty($languages) || is_wp_error($languages) ? array() : $languages;
143
144 $term_languages = get_terms('term_language', array('hide_empty' => false));
145 $term_languages = empty($term_languages) || is_wp_error($term_languages) ?
146 array() : array_combine(wp_list_pluck($term_languages, 'name'), $term_languages);
147
148 if (!empty($languages) && !empty($term_languages)) {
149 // don't use array_map + create_function to instantiate an autoloaded class as it breaks badly in old versions of PHP
150 foreach ($languages as $k => $v) {
151 $languages[$k] = new PLL_Language($v, $term_languages[$v->name]);
152 }
153 }
154 else {
155 $languages = array(); // in case something went wrong
156 }
157 }
158
159 $this->languages[$this->blog_id] = $languages;
160
161 // need to wait for $wp_rewrite availibility to set homepage urls
162 did_action('setup_theme') ? $this->_languages_list() : add_action('setup_theme', array(&$this, '_languages_list'));
163 }
164
165 $args = wp_parse_args($args, array('hide_empty' => false));
166
167 // remove empty languages if requested
168 $languages = array_filter($this->languages[$this->blog_id], create_function('$v', sprintf('return $v->count || !%d;', $args['hide_empty'])));
169
170 return empty($args['fields']) ? $languages : wp_list_pluck($languages, $args['fields']);
171 }
172
173 /*
174 * fills home urls and flags in language list and set transient in db
175 * delayed to be sure we have access to $wp_rewrite for home urls
176 * languages objects are not cached in db if PLL_CACHE_LANGUAGES is set to false
177 * home urls are not cached in db if PLL_CACHE_HOME_URL is set to false
178 *
179 * @since 1.4
180 */
181 public function _languages_list() {
182 if ((defined('PLL_CACHE_LANGUAGES') && !PLL_CACHE_LANGUAGES) || false === get_transient('pll_languages_list')) {
183 if (!defined('PLL_CACHE_HOME_URL') || PLL_CACHE_HOME_URL) {
184 foreach ($this->languages[$this->blog_id] as $language)
185 $language->set_home_url();
186 }
187
188 if (!defined('PLL_CACHE_LANGUAGES') || PLL_CACHE_LANGUAGES)
189 set_transient('pll_languages_list', $this->languages[$this->blog_id]);
190 }
191
192 foreach ($this->languages[$this->blog_id] as $language) {
193 if (defined('PLL_CACHE_HOME_URL') && !PLL_CACHE_HOME_URL)
194 $language->set_home_url();
195
196 // add flags (not in db cache as they may be different on frontend and admin)
197 $language->set_flag();
198 }
199 }
200
201 /*
202 * cleans language cache
203 * can be called directly with no parameter
204 * called by the 'edited_term_taxonomy' filter with 2 parameters when count needs to be updated
205 *
206 * @since 1.2
207 *
208 * @param int $term not used
209 * @param string $taxonomy taxonomy name
210 */
211 public function clean_languages_cache($term = 0, $taxonomy = null) {
212 if (empty($taxonomy->name) || 'language' == $taxonomy->name) {
213 delete_transient('pll_languages_list');
214 $this->languages[$this->blog_id] = array();
215 }
216 }
217
218 /*
219 * returns the language by its term_id, tl_term_id, slug or locale
220 *
221 * @since 0.1
222 *
223 * @param int|string term_id, tl_term_id, slug or locale of the queried language
224 * @return object|bool PLL_Language object, false if no language found
225 */
226 public function get_language($value) {
227 static $language;
228
229 if (is_object($value))
230 return $this->get_language($value->term_id); // will force cast to PLL_Language
231
232 if (empty($language[$this->blog_id][$value])) {
233 foreach ($this->get_languages_list() as $lang)
234 $language[$this->blog_id][$lang->term_id]
235 = $language[$this->blog_id][$lang->tl_term_id]
236 = $language[$this->blog_id][$lang->slug]
237 = $language[$this->blog_id][$lang->locale] = $lang;
238 }
239
240 return empty($language[$this->blog_id][$value]) ? false : $language[$this->blog_id][$value];
241 }
242
243 /*
244 * removes unused translations terms in database
245 *
246 * @since 1.5
247 *
248 * @param string $type either 'post' or 'term'
249 */
250 protected function clean_translations_terms($type) {
251 // FIXME does nothing since 1.5.2 as count seems not to be reliable enough
252 /*
253 global $wpdb;
254 $ids = $wpdb->get_col("SELECT term_id FROM $wpdb->term_taxonomy WHERE taxonomy IN ('{$type}_translations') AND count = 0");
255 foreach ($ids as $id)
256 wp_delete_term((int) $id, $type . '_translations');
257 */
258 }
259
260 /*
261 * saves translations for posts or terms
262 *
263 * @since 0.5
264 *
265 * @param string $type either 'post' or 'term'
266 * @param int $id post id or term id
267 * @param array $translations: an associative array of translations with language code as key and translation id as value
268 */
269 public function save_translations($type, $id, $translations) {
270 if (($lang = call_user_func(array(&$this, 'get_'.$type.'_language'), $id)) && isset($translations) && is_array($translations)) {
271
272 $translations = array_merge(array($lang->slug => $id), $translations); // make sure this object is in translations
273 $translations = array_diff($translations, array(0)); // don't keep non translated languages
274
275 // unlink removed translations
276 $old_translations = $this->get_translations($type, $id);
277 foreach (array_diff_assoc($old_translations, $translations) as $object_id)
278 $this->delete_translation($type, $object_id);
279
280 // don't create a translation group for untranslated posts as it is useless
281 // but we need one for terms to allow relationships remap when importing from a WXR file
282 if ('term' == $type || count($translations) > 1) {
283 $terms = wp_get_object_terms($translations, $type . '_translations');
284 $term = reset($terms);
285
286 // create a new term if necessary
287 if (empty($term)) {
288 wp_insert_term($group = uniqid('pll_'), $type . '_translations', array('description' => serialize($translations)));
289 }
290 else {
291 // take care not to overwrite extra data stored in description field, if any
292 $d = unserialize($term->description);
293 $d = is_array($d) ? array_diff_key($d, $old_translations) : array(); // remove old translations
294 $d = array_merge($d, $translations); // add new one
295 wp_update_term($group = (int) $term->term_id, $type . '_translations', array('description' => serialize($d)));
296 }
297
298 // link all translations to the new term
299 foreach($translations as $p)
300 wp_set_object_terms($p, $group, $type . '_translations');
301
302 // clean unused terms to avoid orphans in db
303 $this->clean_translations_terms($type);
304 }
305 }
306 }
307
308 /*
309 * deletes a translation of a post or term
310 *
311 * @since 0.5
312 *
313 * @param string $type either 'post' or 'term'
314 * @param int $id post id or term id
315 */
316 public function delete_translation($type, $id) {
317 $term = $this->get_object_term($id, $type . '_translations');
318
319 if (!empty($term)) {
320 $d = unserialize($term->description);
321 $slug = array_search($id, $this->get_translations($type, $id)); // in case some plugin stores the same value with different key
322 unset($d[$slug]);
323
324 wp_update_term((int) $term->term_id, $type . '_translations', array('description' => serialize($d)));
325
326 if ('post' == $type)
327 wp_set_object_terms($id, null, $type . '_translations');
328 else {
329 // always keep a group for terms to allow relationships remap when importing from a WXR file
330 $translations[$slug] = $id;
331 wp_insert_term($group = uniqid('pll_'), $type . '_translations', array('description' => serialize($translations)));
332 wp_set_object_terms($id, $group, $type . '_translations');
333 }
334
335 // clean unused terms to avoid orphans in db
336 $this->clean_translations_terms($type);
337 }
338 }
339
340 /*
341 * returns the id of the translation of a post or term
342 *
343 * @since 0.5
344 *
345 * @param string $type either 'post' or 'term'
346 * @param int $id post id or term id
347 * @param object|string $lang object or slug
348 * @return bool|int post id or term id of the translation, flase if there is none
349 */
350 public function get_translation($type, $id, $lang) {
351 if (!$lang = $this->get_language($lang))
352 return false;
353
354 $translations = $this->get_translations($type, $id);
355
356 return isset($translations[$lang->slug]) ? (int) $translations[$lang->slug] : false;
357 }
358
359 /*
360 * returns an array of translations of a post or term
361 *
362 * @since 0.5
363 *
364 * @param string $type either 'post' or 'term'
365 * @param int $id post id or term id
366 * @return array an associative array of translations with language code as key and translation id as value
367 */
368 public function get_translations($type, $id) {
369 $type = ($type == 'post' || $this->is_translated_post_type($type)) ? 'post' : (($type == 'term' || $this->is_translated_taxonomy($type)) ? 'term' : false);
370 $translations = $type && ($term = $this->get_object_term($id, $type . '_translations')) && !empty($term) ? unserialize($term->description) : array();
371
372 // make sure we return only translations (thus we allow plugins to store other informations in the array)
373 return array_intersect_key($translations, array_flip($this->get_languages_list(array('fields' => 'slug'))));
374 }
375
376 /*
377 * store the post language in the database
378 *
379 * @since 0.6
380 *
381 * @param int $post_id post id
382 * @param int|string|object language (term_id or slug or object)
383 */
384 public function set_post_language($post_id, $lang) {
385 wp_set_post_terms($post_id, $lang ? $this->get_language($lang)->slug : '', 'language' );
386 }
387
388 /*
389 * returns the language of a post
390 *
391 * @since 0.1
392 *
393 * @param int $post_id post id
394 * @return bool|object PLL_Language object, false if no language is associated to that post
395 */
396 public function get_post_language($post_id) {
397 $lang = $this->get_object_term($post_id, 'language' );
398 return ($lang) ? $this->get_language($lang) : false;
399 }
400
401 /*
402 * among the post and its translations, returns the id of the post which is in $lang
403 *
404 * @since 0.1
405 *
406 * @param int $post_id post id
407 * @param int|string|object language (term_id or slug or object)
408 * @return bool|int the translation post id if exists, otherwise the post id, false if the post has no language
409 */
410 public function get_post($post_id, $lang) {
411 $post_lang = $this->get_post_language($post_id); // FIXME is this necessary?
412 if (!$lang || !$post_lang)
413 return false;
414
415 $lang = $this->get_language($lang);
416 return $post_lang->term_id == $lang->term_id ? $post_id : $this->get_translation('post', $post_id, $lang);
417 }
418
419 /*
420 * stores the term language in the database
421 *
422 * @since 0.6
423 *
424 * @param int $term_id term id
425 * @param int|string|object language (term_id or slug or object)
426 */
427 public function set_term_language($term_id, $lang) {
428 wp_set_object_terms($term_id, $lang ? $this->get_language($lang)->tl_term_id : '', 'term_language');
429 }
430
431 /*
432 * removes the term language in database
433 *
434 * @since 0.5
435 *
436 * @param int $term_id term id
437 */
438 public function delete_term_language($term_id) {
439 wp_delete_object_term_relationships($term_id, 'term_language');
440 }
441
442 /*
443 * returns the language of a term
444 *
445 * @since 0.1
446 *
447 * @param int|string $value term id or term slug
448 * @param string $taxonomy optional taxonomy needed when the term slug is passed as first parameter
449 * @return bool|object PLL_Language object, false if no language is associated to that term
450 */
451 public function get_term_language($value, $taxonomy = '') {
452 if (is_numeric($value))
453 $term_id = $value;
454
455 // get_term_by still not cached in WP 3.5.1 but internally, the function is always called by term_id
456 elseif (is_string($value) && $taxonomy)
457 $term_id = get_term_by('slug', $value , $taxonomy)->term_id;
458
459 $lang = $this->get_object_term($term_id, 'term_language');
460
461 // switch to PLL_Language
462 return ($lang) ? $this->get_language($lang->term_id) : false;
463 }
464
465 /*
466 * among the term and its translations, returns the id of the term which is in $lang
467 *
468 * @since 0.1
469 *
470 * @param int $term_id term id
471 * @param int|string|object language (term_id or slug or object)
472 * @return bool|int the translation term id if exists, otherwise the term id, false if the term has no language
473 */
474 public function get_term($term_id, $lang) {
475 $lg = $this->get_term_language($term_id); // FIXME is this necessary?
476 if (!$lang || !$lg)
477 return false;
478
479 $lang = $this->get_language($lang);
480 return $lg->term_id == $lang->term_id ? $term_id : $this->get_translation('term', $term_id, $lang);
481 }
482
483 /*
484 * a join clause to add to sql queries when filtering by language is needed directly in query
485 *
486 * @since 1.2
487 *
488 * @param string $type either 'post' or 'term'
489 * @return string join clause
490 */
491 public function join_clause($type) {
492 global $wpdb;
493 return " INNER JOIN $wpdb->term_relationships AS pll_tr ON pll_tr.object_id = " . ('term' == $type ? "t.term_id" : "ID");
494 }
495
496 /*
497 * a where clause to add to sql queries when filtering by language is needed directly in query
498 *
499 * @since 1.2
500 *
501 * @param object|array|string $lang a PLL_Language object or a comma separated list of languag slug or an array of language slugs
502 * @param string $type either 'post' or 'term'
503 * @return string where clause
504 */
505 public function where_clause($lang, $type) {
506 global $wpdb;
507 $tt_id = 'term' == $type ? 'tl_term_taxonomy_id' : 'term_taxonomy_id';
508
509 // $lang is an object
510 // generally the case if the query is coming from Polylang
511 if (is_object($lang))
512 return $wpdb->prepare(" AND pll_tr.term_taxonomy_id = %d", $lang->$tt_id);
513
514 // $lang is a comma separated list of slugs (or an array of slugs)
515 // generally the case is the query is coming from outside with 'lang' parameter
516 $slugs = is_array($lang) ? $lang : explode(',', $lang);
517 foreach ($slugs as $slug)
518 $languages[] = (int) $this->get_language($slug)->$tt_id;
519
520 return " AND pll_tr.term_taxonomy_id IN (" . implode(',', $languages) . ")";
521 }
522
523 /*
524 * adds terms clauses to get_terms to filter them by languages - used in both frontend and admin
525 *
526 * @since 1.2
527 *
528 * @param array $clauses the list of sql clauses in terms query
529 * @param object $lang PLL_Language object
530 * @return array modifed list of clauses
531 */
532 public function terms_clauses($clauses, $lang) {
533 if (!empty($lang)) {
534 $clauses['join'] .= $this->join_clause('term');
535 $clauses['where'] .= $this->where_clause($lang, 'term');
536 }
537 return $clauses;
538 }
539
540 /*
541 * register the language taxonomy
542 *
543 * @since 1.2
544 */
545 public function register_taxonomy() {
546 // registers the language taxonomy
547 register_taxonomy('language', $this->get_translated_post_types(), array(
548 'labels' => array(
549 'name' => __('Languages', 'polylang'),
550 'singular_name' => __('Language', 'polylang'),
551 'all_items' => __('All languages', 'polylang'),
552 ),
553 'public' => false, // avoid displaying the 'like post tags text box' in the quick edit
554 'query_var' => 'lang',
555 '_pll' => true // polylang taxonomy
556 ));
557 }
558
559 /*
560 * returns post types that need to be translated
561 *
562 * @since 1.2
563 *
564 * @param bool $filter true if we should return only valid registered post types
565 * @return array post type names for which Polylang manages languages and translations
566 */
567 public function get_translated_post_types($filter = true) {
568 static $post_types = array();
569
570 // the post types list is cached for better better performance
571 // wait for 'after_setup_theme' to apply the cache to allow themes adding the filter in functions.php
572 if (!$post_types || !did_action('after_setup_theme')) {
573 $post_types = array('post' => 'post', 'page' => 'page');
574
575 if (!empty($this->options['media_support']))
576 $post_types['attachement'] = 'attachment';
577
578 if (is_array($this->options['post_types']))
579 $post_types = array_merge($post_types, $this->options['post_types']);
580
581 $post_types = apply_filters('pll_get_post_types', $post_types , false);
582 }
583
584 return $filter ? array_intersect($post_types, get_post_types()) : $post_types;
585 }
586
587 /*
588 * check if registered post type must be translated
589 *
590 * @since 1.2
591 *
592 * @param string $post_type post type name
593 */
594 public function registered_post_type($post_type) {
595 if ($this->is_translated_post_type($post_type)) {
596 register_taxonomy_for_object_type('language', $post_type);
597 register_taxonomy_for_object_type('post_translations', $post_type);
598 }
599 }
600
601 /*
602 * returns true if Polylang manages languages and translations for this post type
603 *
604 * @since 1.2
605 *
606 * @param string|array $post_type post type name or array of post type names
607 */
608 public function is_translated_post_type($post_type) {
609 $post_types = $this->get_translated_post_types(false);
610 return (is_array($post_type) && array_intersect($post_type, $post_types) || in_array($post_type, $post_types));
611 }
612
613 /*
614 * return taxonomies that need to be translated
615 *
616 * @since 1.2
617 *
618 * @param bool $filter true if we should return only valid registered taxonmies
619 * @return array array of registered taxonomy names for which Polylang manages languages and translations
620 */
621 public function get_translated_taxonomies($filter = true) {
622 static $taxonomies = array();
623
624 if (!$taxonomies || !did_action('after_setup_theme')) {
625 $taxonomies = array('category' => 'category', 'post_tag' => 'post_tag');
626
627 if (is_array($this->options['taxonomies']))
628 $taxonomies = array_merge($taxonomies, $this->options['taxonomies']);
629
630 $taxonomies = apply_filters('pll_get_taxonomies', $taxonomies, false);
631 }
632
633 return $filter ? array_intersect($taxonomies, get_taxonomies()) : $taxonomies;
634 }
635
636 /*
637 * returns true if Polylang manages languages and translations for this post type
638 *
639 * @since 1.2
640 *
641 * @param string|array $tax taxonomy name or array of taxonomy names
642 */
643 public function is_translated_taxonomy($tax) {
644 $taxonomies = $this->get_translated_taxonomies(false);
645 return (is_array($tax) && array_intersect($tax, $taxonomies) || in_array($tax, $taxonomies));
646 }
647
648 /*
649 * it is possible to have several terms with the same name in the same taxonomy (one per language)
650 * but the native term_exists will return true even if only one exists
651 * so here the function adds the language parameter
652 *
653 * @since 1.4
654 *
655 * @param string $term_name the term name
656 * @param string $taxonomy taxonomy name
657 * @param int $parent parent term id
658 * @param string|object $language the language slug or object
659 * @return null|int the term_id of the found term
660 */
661 public function term_exists($term_name, $taxonomy, $parent, $language) {
662 global $wpdb;
663
664 $select = "SELECT t.term_id FROM $wpdb->terms AS t";
665 $join = " INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id";
666 $join .= $this->join_clause('term');
667 $where = $wpdb->prepare(" WHERE tt.taxonomy = %s AND t.name = %s", $taxonomy, $term_name);
668 $where .= $this->where_clause($this->get_language($language), 'term');
669
670 if ($parent > 0)
671 $where .= $wpdb->prepare(" AND tt.parent = %d", $parent);
672
673 return $wpdb->get_var($select . $join . $where);
674 }
675
676 /*
677 * gets the number of posts per language in a date, author or post type archive
678 *
679 * @since 1.2
680 *
681 * @param object lang
682 * @param array $q WP_Query arguments (accepted: post_type, m, year, monthnum, day, author, author_name, post_format)
683 * @return int
684 */
685 public function count_posts($lang, $q = array()) {
686 global $wpdb;
687
688 if (empty($q['post_type']))
689 $q['post_type'] = array('post'); // we *need* a post type
690
691 if (!is_array($q['post_type']))
692 $q['post_type'] = array($q['post_type']);
693
694 $cache_key = md5(serialize($q));
695 $counts = wp_cache_get($cache_key, 'pll_count_posts');
696
697 if (false === $counts) {
698 $select = "SELECT pll_tr.term_taxonomy_id, COUNT(*) AS num_posts FROM {$wpdb->posts} AS p";
699 $join = $this->join_clause('post');
700 $where = " WHERE post_status = 'publish'";
701 $where .= " AND p.post_type IN ('" . join("', '", $q['post_type'] ) . "')";
702 $where .= $this->where_clause($this->get_languages_list(), 'post');
703 $groupby = " GROUP BY pll_tr.term_taxonomy_id";
704
705 if (!empty($q['m'])) {
706 $q['m'] = '' . preg_replace('|[^0-9]|', '', $q['m']);
707 $where .= $wpdb->prepare(" AND YEAR(p.post_date) = %d", substr($q['m'], 0, 4));
708 if ( strlen($q['m']) > 5 )
709 $where .= $wpdb->prepare(" AND MONTH(p.post_date) = %d", substr($q['m'], 4, 2));
710 if ( strlen($q['m']) > 7 )
711 $where .= $wpdb->prepare(" AND DAYOFMONTH(p.post_date) = %d", substr($q['m'], 6, 2));
712 }
713
714 if (!empty($q['year']))
715 $where .= $wpdb->prepare(" AND YEAR(p.post_date) = %d", $q['year']);
716
717 if (!empty($q['monthnum']))
718 $where .= $wpdb->prepare(" AND MONTH(p.post_date) = %d", $q['monthnum']);
719
720 if (!empty($q['day']))
721 $where .= $wpdb->prepare(" AND DAYOFMONTH(p.post_date) = %d", $q['day']);
722
723 if (!empty($q['author_name'])) {
724 $author = get_user_by('slug', sanitize_title_for_query($q['author_name']));
725 if ($author)
726 $q['author'] = $author->ID;
727 }
728
729 if (!empty($q['author']))
730 $where .= $wpdb->prepare(" AND p.post_author = %d", $q['author']);
731
732 if (!empty($q['post_format'])) {
733 $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON tr.object_id = p.ID";
734 $join .= " INNER JOIN {$wpdb->term_taxonomy} AS tt ON tt.term_taxonomy_id = tr.term_taxonomy_id";
735 $join .= " INNER JOIN {$wpdb->terms} AS t ON t.term_id = tt.term_id";
736 $where .= $wpdb->prepare(" AND t.slug = %s", $q['post_format']);
737 }
738
739 $res = $wpdb->get_results($select . $join . $where . $groupby, ARRAY_A);
740 foreach ((array) $res as $row)
741 $counts[$row['term_taxonomy_id']] = $row['num_posts'];
742
743 wp_cache_set($cache_key, $counts, 'pll_count_posts');
744 }
745
746 return empty($counts[$lang->term_taxonomy_id]) ? 0 : $counts[$lang->term_taxonomy_id];
747 }
748
749 /*
750 * returns ids of objects in a language similarly to get_objects_in_term for a taxonomy
751 * faster than get_objects_in_term as it avoids a JOIN
752 *
753 * @since 1.4
754 *
755 * @param object $lang a PLL_Language object
756 * @param string $type optional, either 'post' or 'term', defaults to 'post'
757 * @return array
758 */
759 public function get_objects_in_language($lang, $type = 'post') {
760 global $wpdb;
761 return $wpdb->get_col($wpdb->prepare("
762 SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d",
763 'term' == $type ? $lang->tl_term_taxonomy_id : $lang->term_taxonomy_id
764 ));
765 }
766
767 /*
768 * setup the links model based on options
769 *
770 * @since 1.2
771 *
772 * @return object implementing "links_model interface"
773 */
774 public function get_links_model() {
775 $c = array('Directory', 'Directory', 'Subdomain', 'Domain');
776 $class = get_option('permalink_structure') ? 'PLL_Links_' .$c[$this->options['force_lang']] : 'PLL_Links_Default';
777 return new $class($this);
778 }
779 }
780