PLL_Admin_Model

Extends the PLL_Model class with methods needed only in Polylang settings pages.

Defined (1)

The class is defined in the following location(s).

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