PluginProbe
Polylang / 1.1.6
Polylang v1.1.6
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 / base.php

base.php in Polylang 1.1.6, at include/base.php

385 lines 14.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // setups basic functions used for admin and frontend
4 abstract class Polylang_Base {
5 public $post_types; // post types to filter by language
6 public $taxonomies; // taxonomies to filter by language
7 public $is_ajax_on_front;
8 public $is_admin;
9
10 protected $options;
11 protected $home;
12 protected $using_permalinks;
13 protected $strings = array(); // strings to translate
14
15 // used to cache results
16 // FIXME use wp_cache ?
17 private $languages_list = array();
18 private $language = array();
19 private $links = array();
20
21 function __construct() {
22 // avoid loading polylang admin filters for frontend ajax requests if 'pll_load_front' is set (thanks to g100g)
23 // FIXME should I permit plugins to overwrite this?
24 $this->is_ajax_on_front = defined('DOING_AJAX') && isset($_REQUEST['pll_load_front']);
25 $this->is_admin = defined('DOING_CRON') || (is_admin() && !$this->is_ajax_on_front);
26
27 // init options often needed
28 $this->options = get_option('polylang');
29 $this->home = get_option('home');
30 $this->using_permalinks = (bool) get_option('permalink_structure');
31
32 add_action('wp_loaded', array(&$this, 'add_post_types_taxonomies'), 1); // must come after 'init' to be sure that all post types and taxonomies are registered
33 }
34
35 // init post types and taxonomies to filter by language
36 function add_post_types_taxonomies() {
37 $post_types = array('post', 'page');
38 if (!empty($this->options['media_support']))
39 $post_types[] = 'attachment';
40 if (!empty($this->options['post_types']))
41 $post_types = array_merge($post_types, $this->options['post_types']);
42 $post_types = apply_filters('pll_get_post_types', array_combine($post_types, $post_types), false);
43 $this->post_types = array_intersect($post_types, get_post_types()); // only valid registered post types
44
45 $taxonomies = array('category', 'post_tag', 'nav_menu');
46 if (!empty($this->options['taxonomies']))
47 $taxonomies = array_merge($taxonomies, $this->options['taxonomies']);
48 $taxonomies = apply_filters('pll_get_taxonomies', array_combine($taxonomies, $taxonomies), false);
49 $this->taxonomies = array_intersect($taxonomies, get_taxonomies()); // only valid registered taxonomies
50 }
51
52 // returns the list of available languages
53 function get_languages_list($args = array()) {
54 // although get_terms is cached, it is efficient to add our own cache
55 // FIXME don't use on admin in case we modify the list of languages!
56 if (!$this->is_admin && isset($this->languages_list[$cache_key = md5(serialize($args))]))
57 return $this->languages_list[$cache_key];
58
59 $args = wp_parse_args($args, array('hide_empty' => false, 'orderby'=> 'term_group'));
60 $languages = get_terms('language', $args);
61 $languages = empty($languages) || is_wp_error($languages) ? array() : $languages;
62 return isset($cache_key) ? ($this->languages_list[$cache_key] = $languages) : $languages;
63 }
64
65 // retrieves the dropdown list of the languages
66 function dropdown_languages($args = array()) {
67 $args = apply_filters('pll_dropdown_language_args', $args);
68 $defaults = array('name' => 'lang_choice', 'class' => '', 'add_options' => array(), 'hide_empty' => false, 'value' => 'slug', 'selected' => '');
69 extract(wp_parse_args($args, $defaults));
70
71 // sort $add_options by value
72 uasort($add_options, create_function('$a, $b', "return \$a['value'] > \$b['value'];" ));
73
74 $out = sprintf(
75 '<select name="%1$s" id="%1$s"%2$s>'."\n",
76 esc_attr($name),
77 $class ? ' class="'.esc_attr($class).'"' : ''
78 );
79
80 foreach ($add_options as $option)
81 $out .= sprintf(
82 '<option value="%s">%s</option>'."\n",
83 esc_attr($option['value']),
84 esc_html($option['text'])
85 );
86
87 foreach ($this->get_languages_list($args) as $language) {
88 $out .= sprintf(
89 '<option value="%s"%s>%s</option>'."\n",
90 esc_attr($language->$value),
91 $language->$value == $selected ? ' selected="selected"' : '',
92 esc_html($language->name)
93 );
94 }
95 $out .= '</select>'."\n";
96 return $out;
97 }
98
99 // returns the language by its id or its slug
100 // Note: it seems that get_term_by slug is not cached (3.2.1)
101 function get_language($value) {
102 if (is_object($value))
103 return $value;
104
105 if (isset($this->language[$value]))
106 return $this->language[$value];
107
108 $lang = (is_numeric($value) || (int) $value) ? get_term((int) $value, 'language') :
109 (is_string($value) ? get_term_by('slug', $value , 'language') : false);
110
111 return !empty($lang) && !is_wp_error($lang) ? ($this->language[$value] = $lang) : false;
112 }
113
114 // saves translations for posts or terms
115 // the underscore in '_lang' hides the post meta in the Custom Fields metabox in the Edit Post screen
116 // $type: either 'post' or 'term'
117 // $id: post id or term id
118 // $translations: an associative array of translations with language code as key and translation id as value
119 function save_translations($type, $id, $translations) {
120 $lang = call_user_func(array(&$this, 'get_'.$type.'_language'), $id);
121 if (!$lang)
122 return;
123
124 if (isset($translations) && is_array($translations)) {
125 $translations = array_merge(array($lang->slug => $id), $translations);
126 foreach($translations as $p)
127 update_metadata($type, (int) $p, '_translations', $translations);
128 }
129 }
130
131 // deletes a translation of a post or term
132 function delete_translation($type, $id) {
133 $translations = $this->get_translations($type, $id);
134 if (is_array($translations)) {
135 $slug = array_search($id, $translations);
136 unset($translations[$slug]);
137 foreach($translations as $p)
138 update_metadata($type, (int) $p, '_translations', $translations);
139 delete_metadata($type, $id, '_translations');
140 }
141 }
142
143 // returns the id of the translation of a post or term
144 // $type: either 'post' or 'term'
145 // $id: post id or term id
146 // $lang: object or slug (in the order of preference latest to avoid)
147 function get_translation($type, $id, $lang) {
148 $translations = $this->get_translations($type, $id);
149 $slug = $this->get_language($lang)->slug;
150 return isset($translations[$slug]) ? (int) $translations[$slug] : false;
151 }
152
153 // returns an array of translations of a post or term
154 function get_translations($type, $id) {
155 $type = ($type == 'post' || in_array($type, $this->post_types)) ? 'post' : (($type == 'term' || in_array($type, $this->taxonomies)) ? 'term' : false);
156 // maybe_unserialize due to useless serialization in versions < 0.9
157 return $type && ($meta = get_metadata($type, $id, '_translations', true)) ? maybe_unserialize($meta) : array();
158 }
159
160 // store the post language in the database
161 function set_post_language($post_id, $lang) {
162 wp_set_post_terms($post_id, $lang ? $this->get_language($lang)->slug : '', 'language' );
163 }
164
165 // returns the language of a post
166 function get_post_language($post_id) {
167 $lang = get_the_terms($post_id, 'language' );
168 return ($lang) ? reset($lang) : false; // there's only one language per post : first element of the array returned
169 }
170
171 // among the post and its translations, returns the id of the post which is in $lang
172 function get_post($post_id, $lang) {
173 $post_lang = $this->get_post_language($post_id);
174 if (!$lang || !$post_lang)
175 return false;
176
177 $lang = $this->get_language($lang);
178 return $post_lang->term_id == $lang->term_id ? $post_id : $this->get_translation('post', $post_id, $lang);
179 }
180
181 // store the term language in the database
182 function set_term_language($term_id, $lang) {
183 update_metadata('term', $term_id, '_language', $lang ? $this->get_language($lang)->term_id : 0);
184 }
185
186 // remove the term language in the database
187 function delete_term_language($term_id) {
188 delete_metadata('term', $term_id, '_language');
189 }
190
191 // returns the language of a term
192 function get_term_language($value, $taxonomy = '') {
193 if (is_numeric($value))
194 $term_id = $value;
195
196 elseif (is_string($value) && $taxonomy)
197 $term_id = get_term_by('slug', $value , $taxonomy)->term_id;
198
199 return empty($term_id) ? false : $this->get_language(get_metadata('term', $term_id, '_language', true));
200 }
201
202 // among the term and its translations, returns the id of the term which is in $lang
203 function get_term($term_id, $lang) {
204 $lg = $this->get_term_language($term_id);
205 if (!$lang || !$lg)
206 return false;
207
208 $lang = $this->get_language($lang);
209 return $lg->term_id == $lang->term_id ? $term_id : $this->get_translation('term', $term_id, $lang);
210 }
211
212 // adds language information to a link when using pretty permalinks
213 function add_language_to_link($url, $lang) {
214 if (empty($lang))
215 return $url;
216
217 global $wp_rewrite;
218 if ($this->using_permalinks) {
219 $base = $this->options['rewrite'] ? '' : 'language/';
220 $slug = $this->options['default_lang'] == $lang->slug && $this->options['hide_default'] ? '' : $base.$lang->slug.'/';
221 return esc_url(str_replace($this->home.'/'.$wp_rewrite->root, $this->home.'/'.$wp_rewrite->root.$slug, $url));
222 }
223
224 // special case for pages which do not accept adding the lang parameter
225 elseif ('_get_page_link' != current_filter())
226 return esc_url(add_query_arg( 'lang', $lang->slug, $url ));
227
228 return $url;
229 }
230
231 // optionally rewrite posts, pages links to filter them by language
232 // rewrite post format (and optionally categories and post tags) archives links to filter them by language
233 function add_post_term_link_filters() {
234 if ($this->options['force_lang'] && $this->using_permalinks) {
235 foreach (array('post_link', '_get_page_link', 'post_type_link') as $filter)
236 add_filter($filter, array(&$this, 'post_link'), 10, 2);
237 }
238
239 add_filter('term_link', array(&$this, 'term_link'), 10, 3);
240 }
241
242 // modifies post & page links
243 function post_link($link, $post) {
244 if (isset($this->links[$link]))
245 return $this->links[$link];
246
247 if ('post_type_link' == current_filter() && !in_array($post->post_type, $this->post_types))
248 return $this->links[$link] = $link;
249
250 if ('_get_page_link' == current_filter()) // this filter uses the ID instead of the post object
251 $post = get_post($post);
252
253 // /!\ when post_status in not "publish", WP does not use pretty permalinks
254 return $this->links[$link] = $post->post_status != 'publish' ? $link : $this->add_language_to_link($link, $this->get_post_language($post->ID));
255 }
256
257 // modifies term link
258 function term_link($link, $term, $tax) {
259 if (isset($this->links[$link]))
260 return $this->links[$link];
261
262 return $this->links[$link] = ($tax == 'post_format' || ($this->options['force_lang'] && $this->using_permalinks && in_array($tax, $this->taxonomies))) ?
263 $this->add_language_to_link($link, $this->get_term_language($term->term_id)) : $link;
264 }
265
266 // returns the html link to the flag if exists
267 // $lang: object
268 function get_flag($lang, $url_only = false) {
269 if (file_exists(POLYLANG_DIR.($file = '/flags/'.$lang->description.'.png')))
270 $url = POLYLANG_URL.$file;
271
272 // overwrite with custom flags
273 // never use custom flags on admin side
274 if (!$this->is_admin && ( file_exists(PLL_LOCAL_DIR.($file = '/'.$lang->description.'.png')) || file_exists(PLL_LOCAL_DIR.($file = '/'.$lang->description.'.jpg')) ))
275 $url = PLL_LOCAL_URL.$file;
276
277 return apply_filters('pll_get_flag', empty($url) ? '' :
278 ($url_only ? $url :
279 sprintf(
280 '<img src="%s" title="%s" alt="%s" />',
281 esc_url($url),
282 esc_attr(apply_filters('pll_flag_title', $lang->name, $lang->slug, $lang->description)),
283 esc_attr($lang->name)
284 )));
285 }
286
287 // get languages term_ids or term_taxonomy_ids for use in sql queries
288 function _get_languages_for_sql($lang, $field) {
289 global $wpdb;
290 // the query is coming from Polylang and the $lang is an object
291 if (is_object($lang))
292 return $field == 'term_id' ? "'" . $lang->$field . "'" : $lang->$field;
293
294 // the query is coming from outside with 'lang' parameter and $lang is a comma separated list of slugs (or an array of slugs)
295 $languages = is_array($lang) ? $lang : explode(',', $lang);
296 $languages = "'" . implode("','", array_map( 'sanitize_title_for_query', $languages)) . "'";
297 $languages = $wpdb->get_col("
298 SELECT $wpdb->term_taxonomy.$field FROM $wpdb->term_taxonomy
299 INNER JOIN $wpdb->terms USING (term_id)
300 WHERE taxonomy = 'language' AND $wpdb->terms.slug IN ($languages)
301 "); // get ids from slugs
302
303 if ($field == 'term_id')
304 $languages = array_map(create_function('$v', 'return "\'" . $v . "\'";'), $languages);
305
306 return implode(',', $languages);
307 }
308
309 // adds clauses to comments query - used in both frontend and admin
310 function _comments_clauses($clauses, $lang) {
311 global $wpdb;
312 if (!empty($lang)) {
313 // if this clause is not already added by WP
314 if (!strpos($clauses['join'], '.ID'))
315 $clauses['join'] .= " JOIN $wpdb->posts ON $wpdb->posts.ID = $wpdb->comments.comment_post_ID";
316
317 $clauses['join'] .= " INNER JOIN $wpdb->term_relationships AS pll_tr ON pll_tr.object_id = ID";
318 $clauses['where'] .= " AND pll_tr.term_taxonomy_id IN (" . $this->_get_languages_for_sql($lang, 'term_taxonomy_id') . ")";
319 }
320 return $clauses;
321 }
322
323 // adds terms clauses to get_terms - used in both frontend and admin
324 function _terms_clauses($clauses, $lang) {
325 global $wpdb;
326 if (!empty($lang)) {
327 $clauses['join'] .= " LEFT JOIN $wpdb->termmeta AS pll_tm ON t.term_id = pll_tm.term_id";
328 $clauses['where'] .= " AND pll_tm.meta_key = '_language' AND pll_tm.meta_value IN (" . $this->_get_languages_for_sql($lang, 'term_id') . ")";
329 }
330 return $clauses;
331 }
332
333 // returns all page ids *not in* language defined by $lang_id
334 function exclude_pages($lang_id) {
335 return get_posts(array(
336 'lang' => 0, // so this query is not filtered by our pre_get_post filter in core.php
337 'numberposts' => -1,
338 'nopaging' => true,
339 'post_type' => array_intersect(get_post_types(array('hierarchical' => 1)), $this->post_types),
340 'fields' => 'ids',
341 'tax_query' => array(array(
342 'taxonomy' => 'language',
343 'terms' => $lang_id,
344 'operator' => 'NOT IN'
345 ))
346 ));
347 }
348
349 // export a mo object in options
350 function mo_export($mo, $lang) {
351 $strings = array();
352 foreach ($mo->entries as $entry)
353 $strings[] = array($entry->singular, $mo->translate($entry->singular));
354 update_option('polylang_mo'.$lang->term_id, $strings);
355 }
356
357 // import a mo object from options
358 function mo_import($lang) {
359 $mo = new MO();
360 if ($strings = get_option('polylang_mo'.$lang->term_id)) {
361 foreach ($strings as $msg)
362 $mo->add_entry($mo->make_entry($msg[0], $msg[1]));
363 }
364 return $mo;
365 }
366
367 // list the post metas to synchronize
368 static function list_metas_to_sync() {
369 return array(
370 'taxonomies' => __('Taxonomies', 'polylang'),
371 'post_meta' => __('Custom fields', 'polylang'),
372 'comment_status' => __('Comment status', 'polylang'),
373 'ping_status' => __('Ping status', 'polylang'),
374 'sticky_posts' => __('Sticky posts', 'polylang'),
375 'post_date' => __('Published date', 'polylang'),
376 'post_format' => __('Post format', 'polylang'),
377 'post_parent' => __('Page parent', 'polylang'),
378 '_wp_page_template' => __('Page template', 'polylang'),
379 'menu_order' => __('Page order', 'polylang'),
380 '_thumbnail_id' => __('Featured image', 'polylang'),
381 );
382 }
383
384 } //class Polylang_Base
385