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

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