PluginProbe
Polylang / 1.1
Polylang v1.1
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, at include/base.php

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