PluginProbe
Polylang / 1.5.2
Polylang v1.5.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.5.2, at include/model.php

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