PluginProbe
Polylang / 1.7.3
Polylang v1.7.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 / admin / admin-model.php

admin-model.php in Polylang 1.7.3, at admin/admin-model.php

434 lines 15.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * extends the PLL_Model class with methods needed only in Polylang settings pages
5 *
6 * @since 1.2
7 */
8 class PLL_Admin_Model extends PLL_Model {
9
10 /*
11 * adds a new language
12 * creates a default category for this language
13 *
14 * list of arguments that $args must contain:
15 * name -> language name (used only for display)
16 * slug -> language code (ideally 2-letters ISO 639-1 language code
17 * locale -> WordPress locale. If something wrong is used for the locale, the .mo files will not be loaded...
18 * rtl -> 1 if rtl language, 0 otherwise
19 * term_group -> language order when displayed
20 *
21 * optional arguments that $args can contain:
22 * no_default_cat -> if set, no default category will be created for this language
23 *
24 * @since 1.2
25 *
26 * @param array $args
27 * @return bool true if success / false if failed
28 */
29 public function add_language($args) {
30 if (!$this->validate_lang($args))
31 return false;
32
33 // first the language taxonomy
34 $description = serialize(array('locale' => $args['locale'], 'rtl' => (int) $args['rtl']));
35 $r = wp_insert_term($args['name'], 'language', array('slug' => $args['slug'], 'description' => $description));
36 if (is_wp_error($r)) {
37 // avoid an ugly fatal error if something went wrong (reported once in the forum)
38 add_settings_error('general', 'pll_add_language', __('Impossible to add the language.', 'polylang'));
39 return false;
40 }
41 wp_update_term((int) $r['term_id'], 'language', array('term_group' => (int) $args['term_group'])); // can't set the term group directly in wp_insert_term
42
43 // the term_language taxonomy
44 // don't want shared terms so use a different slug
45 wp_insert_term($args['name'], 'term_language', array('slug' => 'pll_' . $args['slug']));
46
47 // init a mo_id for this language
48 $mo = new PLL_MO();
49 $mo->export_to_db($this->get_language($args['slug']));
50
51 $this->clean_languages_cache(); // udpate the languages list now !
52
53 if (!isset($this->options['default_lang'])) {
54 // if this is the first language created, set it as default language
55 $this->options['default_lang'] = $args['slug'];
56 update_option('polylang', $this->options);
57
58 // and assign default language to default category
59 $this->set_term_language((int) get_option('default_category'), (int) $r['term_id']);
60 }
61 elseif (empty($args['no_default_cat']))
62 $this->create_default_category($args['slug']);
63
64 flush_rewrite_rules(); // refresh rewrite rules
65
66 add_settings_error('general', 'pll_languages_created', __('Language added.', 'polylang'), 'updated');
67 return true;
68 }
69
70 /*
71 * delete a language
72 *
73 * @since 1.2
74 *
75 * @param int $lang_id language term_id
76 */
77 public function delete_language($lang_id) {
78 $lang = $this->get_language((int) $lang_id);
79 $default_cats = $this->get_translations('term', get_option('default_category'));
80
81 // delete the translations
82 $this->update_translations($lang->slug);
83
84 // delete language option in widgets
85 foreach ($GLOBALS['wp_registered_widgets'] as $widget) {
86 if (!empty($widget['callback'][0]) && !empty($widget['params'][0]['number'])) {
87 $obj = $widget['callback'][0];
88 $number = $widget['params'][0]['number'];
89 if (is_object($obj) && method_exists($obj, 'get_settings') && method_exists($obj, 'save_settings')) {
90 $settings = $obj->get_settings();
91 if (isset($settings[$number]['pll_lang']) && $settings[$number]['pll_lang'] == $lang->slug) {
92 unset($settings[$number]['pll_lang']);
93 $obj->save_settings($settings);
94 }
95 }
96 }
97 }
98
99 // delete menus locations
100 if (!empty($this->options['nav_menus'])) {
101 foreach ($this->options['nav_menus'] as $theme => $locations) {
102 foreach ($locations as $location => $languages) {
103 unset($this->options['nav_menus'][$theme][$location][$lang->slug]);
104 }
105 }
106 }
107
108 // delete users options
109 foreach (get_users(array('fields' => 'ID')) as $user_id) {
110 delete_user_meta($user_id, 'user_lang', $lang->locale);
111 delete_user_meta($user_id, 'pll_filter_content', $lang->slug);
112 delete_user_meta($user_id, 'description_'.$lang->slug);
113 }
114
115 // delete the string translations
116 $post = get_page_by_title('polylang_mo_' . $lang->term_id, OBJECT, 'polylang_mo');
117 if (!empty($post))
118 wp_delete_post($post->ID);
119
120 // delete domain
121 unset($this->options['domains'][$lang->slug]);
122
123 // delete the language itself
124 wp_delete_term($lang->term_id, 'language');
125 wp_delete_term($lang->tl_term_id, 'term_language');
126
127 // update languages list
128 $this->clean_languages_cache();
129
130 // oops ! we deleted the default language...
131 if ($this->options['default_lang'] == $lang->slug) {
132 if ($slugs = $this->get_languages_list(array('fields' => 'slug'))) {
133 $this->options['default_lang'] = $slug = reset($slugs); // arbitrary choice...
134
135 // the default category should be in the default language
136 if (isset($default_cats[$slug]))
137 update_option('default_category', $default_cats[$slug]);
138 }
139 else
140 unset($this->options['default_lang']);
141 }
142
143 update_option('polylang', $this->options);
144 flush_rewrite_rules(); // refresh rewrite rules
145 add_settings_error('general', 'pll_languages_deleted', __('Language deleted.', 'polylang'), 'updated');
146 }
147
148 /*
149 * update language properties
150 *
151 * list of arguments that $args must contain:
152 * lang_id -> term_id of the language to modify
153 * name -> language name (used only for display)
154 * slug -> language code (ideally 2-letters ISO 639-1 language code
155 * locale -> WordPress locale. If something wrong is used for the locale, the .mo files will not be loaded...
156 * rtl -> 1 if rtl language, 0 otherwise
157 * term_group -> language order when displayed
158 *
159 * @since 1.2
160 *
161 * @param array $args
162 * @return bool true if success / false if failed
163 */
164 public function update_language($args) {
165 $lang = $this->get_language((int) $args['lang_id']);
166 if (!$this->validate_lang($args, $lang))
167 return false;
168
169 // Update links to this language in posts and terms in case the slug has been modified
170 $slug = $args['slug'];
171 $old_slug = $lang->slug;
172
173 if ($old_slug != $slug) {
174 // update the language slug in translations
175 $this->update_translations($old_slug, $slug);
176
177 // update language option in widgets
178 foreach ($GLOBALS['wp_registered_widgets'] as $widget) {
179 if (!empty($widget['callback'][0]) && !empty($widget['params'][0]['number'])) {
180 $obj = $widget['callback'][0];
181 $number = $widget['params'][0]['number'];
182 if (is_object($obj) && method_exists($obj, 'get_settings') && method_exists($obj, 'save_settings')) {
183 $settings = $obj->get_settings();
184 if (isset($settings[$number]['pll_lang']) && $settings[$number]['pll_lang'] == $old_slug) {
185 $settings[$number]['pll_lang'] = $slug;
186 $obj->save_settings($settings);
187 }
188 }
189 }
190 }
191
192 // update menus locations
193 if (!empty($this->options['nav_menus'])) {
194 foreach ($this->options['nav_menus'] as $theme => $locations) {
195 foreach ($locations as $location => $languages) {
196 if (!empty($this->options['nav_menus'][$theme][$location][$old_slug])) {
197 $this->options['nav_menus'][$theme][$location][$slug] = $this->options['nav_menus'][$theme][$location][$old_slug];
198 unset($this->options['nav_menus'][$theme][$location][$old_slug]);
199 }
200 }
201 }
202 }
203
204 // update domains
205 if (!empty($this->options['domains'][$old_slug])) {
206 $this->options['domains'][$slug] = $this->options['domains'][$old_slug];
207 unset($this->options['domains'][$old_slug]);
208 }
209
210 // update the default language option if necessary
211 if ($this->options['default_lang'] == $old_slug)
212 $this->options['default_lang'] = $slug;
213 }
214
215 update_option('polylang', $this->options);
216
217 // and finally update the language itself
218 $description = serialize(array('locale' => $args['locale'], 'rtl' => (int) $args['rtl']));
219 wp_update_term((int) $lang->term_id, 'language', array('slug' => $slug, 'name' => $args['name'], 'description' => $description, 'term_group' => (int) $args['term_group']));
220 wp_update_term((int) $lang->tl_term_id, 'term_language', array('slug' => 'pll_' . $slug, 'name' => $args['name']));
221
222 $this->clean_languages_cache();
223 flush_rewrite_rules(); // refresh rewrite rules
224 add_settings_error('general', 'pll_languages_updated', __('Language updated.', 'polylang'), 'updated');
225 return true;
226 }
227
228 /*
229 * validates data entered when creating or updating a language
230 *
231 * @since 0.4
232 *
233 * @param array $args
234 * @param object $lang optional the language currently updated, the language is created if not set
235 * @return bool true if success / false if failed
236 * @see PLL_Admin_Model::add_language
237 */
238 protected function validate_lang($args, $lang = null) {
239 // validate locale
240 if ( !preg_match('#^[A-Za-z_]+$#', $args['locale']))
241 add_settings_error('general', 'pll_invalid_locale', __('Enter a valid WordPress locale', 'polylang'));
242
243 // validate slug characters
244 if (!preg_match('#^[a-z_-]+$#', $args['slug']))
245 add_settings_error('general', 'pll_invalid_slug', __('The language code contains invalid characters', 'polylang'));
246
247 // validate slug is unique
248 if ($this->get_language($args['slug']) && ($lang === null || (isset($lang) && $lang->slug != $args['slug'])))
249 add_settings_error('general', 'pll_non_unique_slug', __('The language code must be unique', 'polylang'));
250
251 // validate name
252 // no need to sanitize it as wp_insert_term will do it for us
253 if (empty($args['name']))
254 add_settings_error('general', 'pll_invalid_name', __('The language must have a name', 'polylang'));
255
256 return get_settings_errors() ? false : true;
257 }
258
259 /*
260 * used to set the language of posts or terms in mass
261 *
262 * @since 1.2
263 *
264 * @param string $type either 'post' or 'term'
265 * @param array $ids array of post ids or term ids
266 * @param object|string $lang object or slug
267 */
268 public function set_language_in_mass($type, $ids, $lang) {
269 global $wpdb;
270
271 $ids = array_map('intval', $ids);
272 $lang = $this->get_language($lang);
273 $tt_id = 'term' == $type ? $lang->tl_term_taxonomy_id : $lang->term_taxonomy_id;
274
275 foreach ($ids as $id)
276 $values[] = $wpdb->prepare("(%d, %d)", $id, $tt_id);
277
278 if (!empty($values)) {
279 $values = array_unique($values);
280 $wpdb->query("INSERT INTO $wpdb->term_relationships (object_id, term_taxonomy_id) VALUES " . implode(',', $values));
281 $lang->update_count(); // updating term count is mandatory (thanks to AndyDeGroo)
282 }
283
284 if ('term' == $type) {
285 foreach ($ids as $id)
286 $translations[] = array($lang->slug => $id);
287
288 if (!empty($translations))
289 $this->set_translation_in_mass('term', $translations);
290 }
291 }
292
293 /*
294 * used to create a translations groups in mass
295 *
296 * @since 1.6.3
297 *
298 * @param string $type either 'post' or 'term'
299 * @param array $translations array of translations arrays
300 */
301 public function set_translation_in_mass($type, $translations) {
302 global $wpdb;
303
304 foreach ($translations as $t) {
305 $term = uniqid('pll_'); // the term name
306 $terms[] = $wpdb->prepare('("%1$s", "%1$s")', $term);
307 $slugs[] = $wpdb->prepare('"%s"', $term);
308 $description[$term] = serialize($t);
309 $count[$term] = count($t);
310 }
311
312 // insert terms
313 if (!empty($terms)) {
314 $terms = array_unique($terms);
315 $wpdb->query("INSERT INTO $wpdb->terms (slug, name) VALUES " . implode(',', $terms));
316 }
317
318 // get all terms with their term_id
319 $terms = $wpdb->get_results("SELECT term_id, slug FROM $wpdb->terms WHERE slug IN (" . implode(',', $slugs) . ")");
320
321 // prepare terms taxonomy relationship
322 foreach ($terms as $term)
323 $tts[] = $wpdb->prepare('(%d, "%s", "%s", %d)', $term->term_id, $type . '_translations', $description[$term->slug], $count[$term->slug]);
324
325 // insert term_taxonomy
326 if (!empty($tts)) {
327 $tts = array_unique($tts);
328 $wpdb->query("INSERT INTO $wpdb->term_taxonomy (term_id, taxonomy, description, count) VALUES " . implode(',', $tts));
329 }
330
331 // get all terms with term_taxonomy_id
332 $terms = get_terms($type . '_translations', array('hide_empty' => false));
333
334 // prepare objects relationships
335 foreach ($terms as $term) {
336 $t = unserialize($term->description);
337 if (in_array($t, $translations)) {
338 foreach ($t as $object_id)
339 if (!empty($object_id))
340 $trs[] = $wpdb->prepare('(%d, %d)', $object_id, $term->term_taxonomy_id);
341 }
342 }
343
344 // insert term_relationships
345 if (!empty($trs)) {
346 $wpdb->query("INSERT INTO $wpdb->term_relationships (object_id, term_taxonomy_id) VALUES " . implode(',', $trs));
347 $trs = array_unique($trs);
348 }
349 }
350
351 /*
352 * returns unstranslated posts and terms ids (used in settings)
353 *
354 * @since 0.9
355 *
356 * @return array array made of an array of post ids and an array of term ids
357 */
358 public function get_objects_with_no_lang() {
359 $posts = get_posts(array(
360 'numberposts' => -1,
361 'nopaging' => true,
362 'post_type' => $this->get_translated_post_types(),
363 'post_status' => 'any',
364 'fields' => 'ids',
365 'tax_query' => array(array(
366 'taxonomy' => 'language',
367 'terms' => $this->get_languages_list(array('fields' => 'term_id')),
368 'operator' => 'NOT IN'
369 ))
370 ));
371
372 $terms = get_terms($this->get_translated_taxonomies(), array('get'=>'all', 'fields'=>'ids'));
373 $groups = $this->get_languages_list(array('fields' => 'tl_term_id'));
374 $tr_terms = get_objects_in_term($groups, 'term_language');
375 $terms = array_unique(array_diff($terms, $tr_terms)); // array_unique to avoid duplicates if a term is in more than one taxonomy
376
377 return apply_filters('pll_get_objects_with_no_lang', empty($posts) && empty($terms) ? false : array('posts' => $posts, 'terms' => $terms));
378 }
379
380 /*
381 * used to delete translations or update the translations when a language slug has been modified in settings
382 *
383 * @since 0.5
384 *
385 * @param string $old_slug the old language slug
386 * @param string $new_slug optional, the new language slug, if not set it means the correspondant has been deleted
387 */
388 public function update_translations($old_slug, $new_slug = '') {
389 global $wpdb;
390
391 $terms = get_terms(array('post_translations', 'term_translations'));
392
393 foreach ($terms as $term) {
394 $tr = unserialize($term->description);
395 if (!empty($tr[$old_slug])) {
396 if ($new_slug)
397 $tr[$new_slug] = $tr[$old_slug]; // suppress this for delete
398 else {
399 $dr['id'][] = (int) $tr[$old_slug];
400 $dr['tt'][] = (int) $term->term_taxonomy_id;
401 }
402 unset($tr[$old_slug]);
403
404 if (empty($tr) || 1 == count($tr)) {
405 $dt['t'][] = (int) $term->term_id;
406 $dt['tt'][] = (int) $term->term_taxonomy_id;
407 }
408 else {
409 $ut['case'][] = $wpdb->prepare('WHEN %d THEN %s', $term->term_id, serialize($tr));
410 $ut['in'][] = (int) $term->term_id;
411 }
412 }
413 }
414
415 // delete relationships
416 if (!empty($dr))
417 $wpdb->query("DELETE FROM $wpdb->term_relationships
418 WHERE object_id IN ( " . implode(',', $dr['id']) . " )
419 AND term_taxonomy_id IN ( " . implode(',', $dr['tt']) . " )");
420
421 // delete terms
422 if (!empty($dt)) {
423 $wpdb->query("DELETE FROM $wpdb->terms WHERE term_id IN ( " . implode(',', $dt['t']) . " ) ");
424 $wpdb->query("DELETE FROM $wpdb->term_taxonomy WHERE term_taxonomy_id IN ( " . implode(',', $dt['tt']) . " ) ");
425 }
426
427 // update terms
428 if (!empty($ut))
429 $wpdb->query("UPDATE $wpdb->term_taxonomy
430 SET description = ( CASE term_id " . implode(' ', $ut['case']) . " END )
431 WHERE term_id IN ( " . implode(',', $ut['in']) . " )");
432 }
433 }
434