PluginProbe
Polylang / 1.5
Polylang v1.5
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
← All changes | admin/admin-filters-term.php +385 -445 2.71.5 View file →
@@ -1,327 +1,218 @@
1 1 <?php
2 2
3 -/**
4 - * Manages filters and actions related to terms on admin side
3 +/*
4 + * manages filters and actions related to terms on admin side
5 5 *
6 6 * @since 1.2
7 7 */
8 8 class PLL_Admin_Filters_Term {
9 - public $links, $model, $options, $pref_lang;
10 - protected $pre_term_name; // Used to store the term name before creating a slug if needed
11 - protected $post_id; // Used to store the current post_id when bulk editing posts
9 + public $links, $model, $options, $curlang, $pref_lang;
10 + protected $pre_term_name; // used to store the term name before creating a slug if needed
12 11
13 - /**
14 - * Constructor: setups filters and actions
12 + /*
13 + * constructor: setups filters and actions
15 14 *
16 15 * @param object $polylang
17 16 */
18 - public function __construct( &$polylang ) {
17 + public function __construct(&$polylang) {
19 18 $this->links = &$polylang->links;
20 19 $this->model = &$polylang->model;
21 20 $this->options = &$polylang->options;
21 + $this->curlang = &$polylang->curlang;
22 22 $this->pref_lang = &$polylang->pref_lang;
23 23
24 - foreach ( $this->model->get_translated_taxonomies() as $tax ) {
25 - // Adds the language field in the 'Categories' and 'Post Tags' panels
26 - add_action( $tax . '_add_form_fields', array( $this, 'add_term_form' ) );
24 + foreach ($this->model->get_translated_taxonomies() as $tax) {
25 + // adds the language field in the 'Categories' and 'Post Tags' panels
26 + add_action($tax.'_add_form_fields', array(&$this, 'add_term_form'));
27 27
28 - // Adds the language field and translations tables in the 'Edit Category' and 'Edit Tag' panels
29 - add_action( $tax . '_edit_form_fields', array( $this, 'edit_term_form' ) );
28 + // adds the language field and translations tables in the 'Edit Category' and 'Edit Tag' panels
29 + add_action($tax.'_edit_form_fields', array(&$this, 'edit_term_form'));
30 +
31 + // adds action related to languages when deleting categories and post tags
32 + add_action('delete_'.$tax, array(&$this, 'delete_term'));
30 33 }
31 34
32 - // Adds actions related to languages when creating or saving categories and post tags
33 - add_filter( 'wp_dropdown_cats', array( $this, 'wp_dropdown_cats' ) );
34 - add_action( 'create_term', array( $this, 'save_term' ), 900, 3 );
35 - add_action( 'edit_term', array( $this, 'save_term' ), 900, 3 ); // Late as it may conflict with other plugins, see http://wordpress.org/support/topic/polylang-and-wordpress-seo-by-yoast
36 - add_action( 'pre_post_update', array( $this, 'pre_post_update' ) );
37 - add_filter( 'pre_term_name', array( $this, 'pre_term_name' ) );
38 - add_filter( 'pre_term_slug', array( $this, 'pre_term_slug' ), 10, 2 );
35 + // adds actions related to languages when creating or saving categories and post tags
36 + add_filter('wp_dropdown_cats', array(&$this, 'wp_dropdown_cats'));
37 + add_filter('pre_insert_term', array(&$this, 'pre_insert_term'), 10, 2);
38 + add_action('create_term', array(&$this, 'save_term'), 10, 3);
39 + add_action('edit_term', array(&$this, 'save_term'), 10, 3);
40 + add_filter('pre_term_name', array(&$this, 'pre_term_name'));
41 + add_filter('pre_term_slug', array(&$this, 'pre_term_slug'), 10, 2);
39 42
40 - // Ajax response for edit term form
41 - add_action( 'wp_ajax_term_lang_choice', array( $this, 'term_lang_choice' ) );
42 - add_action( 'wp_ajax_pll_terms_not_translated', array( $this, 'ajax_terms_not_translated' ) );
43 + // ajax response for edit term form
44 + add_action('wp_ajax_term_lang_choice', array(&$this,'term_lang_choice'));
45 + add_action('wp_ajax_pll_terms_not_translated', array(&$this,'ajax_terms_not_translated'));
43 46
44 - // Allows to get the default categories in all languages
45 - add_filter( 'option_default_category', array( $this, 'option_default_category' ) );
46 - add_action( 'update_option_default_category', array( $this, 'update_option_default_category' ), 10, 2 );
47 + // filters categories and post tags by language
48 + add_filter('terms_clauses', array(&$this, 'terms_clauses'), 10, 3);
47 49
48 - // Updates the translations term ids when splitting a shared term
49 - add_action( 'split_shared_term', array( $this, 'split_shared_term' ), 10, 4 ); // WP 4.2
50 + // backward compatibility WP < 3.7
51 + version_compare($GLOBALS['wp_version'], '3.7', '<') ?
52 + add_action('wp_ajax_polylang-ajax-tag-search', array(&$this,'ajax_tag_search')) :
53 + add_action('wp_ajax_polylang-ajax-tag-search', 'wp_ajax_ajax_tag_search'); // take profit of new filter, cache...
54 +
55 + add_filter('option_default_category', array(&$this, 'option_default_category'));
50 56 }
51 57
52 - /**
53 - * Adds the language field in the 'Categories' and 'Post Tags' panels
58 + /*
59 + * adds the language field in the 'Categories' and 'Post Tags' panels
54 60 *
55 61 * @since 0.1
56 62 */
57 63 public function add_term_form() {
58 - if ( isset( $_GET['taxonomy'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
59 - $taxonomy = sanitize_key( $_GET['taxonomy'] ); // phpcs:ignore WordPress.Security.NonceVerification
60 - }
61 -
62 - if ( isset( $_REQUEST['post_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
63 - $post_type = sanitize_key( $_REQUEST['post_type'] ); // phpcs:ignore WordPress.Security.NonceVerification
64 - }
65 -
66 - if ( isset( $GLOBALS['post_type'] ) ) {
67 - $post_type = $GLOBALS['post_type'];
68 - }
69 -
70 - if ( empty( $taxonomy ) || ! taxonomy_exists( $taxonomy ) || ! post_type_exists( $post_type ) ) {
71 - return;
72 - }
73 -
74 - $from_term_id = isset( $_GET['from_tag'] ) ? (int) $_GET['from_tag'] : 0; // phpcs:ignore WordPress.Security.NonceVerification
75 -
76 - $lang = isset( $_GET['new_lang'] ) ? $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ) : $this->pref_lang; // phpcs:ignore WordPress.Security.NonceVerification
77 -
64 + $taxonomy = $_GET['taxonomy'];
65 + $post_type = isset($GLOBALS['post_type']) ? $GLOBALS['post_type'] : $_REQUEST['post_type'];
66 + $lang = isset($_GET['new_lang']) ? $this->model->get_language($_GET['new_lang']) : $this->pref_lang;
78 67 $dropdown = new PLL_Walker_Dropdown();
79 68
80 - $dropdown_html = $dropdown->walk(
81 - $this->model->get_languages_list(),
82 - -1,
83 - array(
84 - 'name' => 'term_lang_choice',
85 - 'value' => 'term_id',
86 - 'selected' => $lang ? $lang->term_id : '',
87 - 'flag' => true,
88 - )
89 - );
69 + wp_nonce_field('pll_language', '_pll_nonce');
90 70
91 - wp_nonce_field( 'pll_language', '_pll_nonce' );
92 -
93 - printf(
94 - '<div class="form-field">
71 + printf('
72 + <div class="form-field">
95 73 <label for="term_lang_choice">%s</label>
96 - <div id="select-add-term-language">%s</div>
74 + %s
97 75 <p>%s</p>
98 76 </div>',
99 - esc_html__( 'Language', 'polylang' ),
100 - $dropdown_html, // phpcs:ignore
101 - esc_html__( 'Sets the language', 'polylang' )
77 + __('Language', 'polylang'),
78 + $dropdown->walk($this->model->get_languages_list(), array('name' => 'term_lang_choice', 'value' => 'term_id', 'selected' => $lang ? $lang->term_id : '')),
79 + __('Sets the language', 'polylang')
102 80 );
103 81
104 - if ( ! empty( $from_term_id ) ) {
105 - printf( '<input type="hidden" name="from_tag" value="%d" />', (int) $from_term_id );
106 - }
82 + if (!empty($_GET['from_tag']))
83 + printf('<input type="hidden" name="from_tag" value="%d" />', $_GET['from_tag']);
107 84
108 - // Adds translation fields
85 + // adds translation fields
109 86 echo '<div id="term-translations" class="form-field">';
110 - if ( $lang ) {
111 - include PLL_ADMIN_INC . '/view-translations-term.php';
112 - }
113 - echo '</div>' . "\n";
87 + if ($lang)
88 + include(PLL_ADMIN_INC.'/view-translations-term.php');
89 + echo '</div>'."\n";
114 90 }
115 91
116 - /**
117 - * Adds the language field and translations tables in the 'Edit Category' and 'Edit Tag' panels
92 + /*
93 + * adds the language field and translations tables in the 'Edit Category' and 'Edit Tag' panels
118 94 *
119 95 * @since 0.1
120 - *
121 - * @param object $tag
122 96 */
123 - public function edit_term_form( $tag ) {
124 - if ( isset( $_REQUEST['post_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
125 - $post_type = sanitize_key( $_REQUEST['post_type'] ); // phpcs:ignore WordPress.Security.NonceVerification
126 - }
127 -
128 - if ( isset( $GLOBALS['post_type'] ) ) {
129 - $post_type = $GLOBALS['post_type'];
130 - }
131 -
132 - if ( ! post_type_exists( $post_type ) ) {
133 - return;
134 - }
135 -
136 - $term_id = $tag->term_id;
137 - $taxonomy = $tag->taxonomy; // phpcs:ignore WordPressVIPMinimum.Variables.VariableAnalysis.UnusedVariable
138 -
139 - $lang = $this->model->term->get_language( $term_id );
140 - $lang = empty( $lang ) ? $this->pref_lang : $lang;
141 -
142 - // Disable the language dropdown and the translations input fields for default categories to prevent removal
143 - $disabled = in_array( get_option( 'default_category' ), $this->model->term->get_translations( $term_id ) );
144 -
97 + public function edit_term_form($tag) {
98 + $term_id = $tag->term_id;
99 + $lang = $this->model->get_term_language($term_id);
100 + $taxonomy = $tag->taxonomy;
101 + $post_type = isset($GLOBALS['post_type']) ? $GLOBALS['post_type'] : $_REQUEST['post_type'];
145 102 $dropdown = new PLL_Walker_Dropdown();
146 103
147 - $dropdown_html = $dropdown->walk(
148 - $this->model->get_languages_list(),
149 - -1,
150 - array(
151 - 'name' => 'term_lang_choice',
152 - 'value' => 'term_id',
153 - 'selected' => $lang ? $lang->term_id : '',
154 - 'disabled' => $disabled,
155 - 'flag' => true,
156 - )
157 - );
104 + wp_nonce_field('pll_language', '_pll_nonce');
158 105
159 - wp_nonce_field( 'pll_language', '_pll_nonce' );
160 -
161 - printf(
162 - '<tr class="form-field">
106 + printf('
107 + <tr class="form-field">
163 108 <th scope="row">
164 109 <label for="term_lang_choice">%s</label>
165 110 </th>
166 - <td id="select-edit-term-language">
111 + <td>
167 112 %s<br />
168 - <p class="description">%s</p>
113 + <span class="description">%s</span>
169 114 </td>
170 115 </tr>',
171 - esc_html__( 'Language', 'polylang' ),
172 - $dropdown_html, // phpcs:ignore
173 - esc_html__( 'Sets the language', 'polylang' )
116 + __('Language', 'polylang'),
117 + $dropdown->walk($this->model->get_languages_list(), array('name' => 'term_lang_choice', 'value' => 'term_id', 'selected' => $lang ? $lang->term_id : '')),
118 + __('Sets the language', 'polylang')
174 119 );
175 120
176 121 echo '<tr id="term-translations" class="form-field">';
177 - if ( $lang ) {
178 - include PLL_ADMIN_INC . '/view-translations-term.php';
179 - }
180 - echo '</tr>' . "\n";
122 + if ($lang)
123 + include(PLL_ADMIN_INC.'/view-translations-term.php');
124 + echo '</tr>'."\n";
181 125 }
182 126
183 - /**
184 - * Translates term parent if exists when using "Add new" ( translation )
127 + /*
128 + * translates term parent if exists when using "Add new" (translation)
185 129 *
186 130 * @since 0.7
187 131 *
188 - * @param string $output html markup for dropdown list of categories
132 + * @param string html markup for dropdown list of categories
189 133 * @return string modified html
190 134 */
191 - public function wp_dropdown_cats( $output ) {
192 - if ( isset( $_GET['taxonomy'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
193 - $taxonomy = sanitize_key( $_GET['taxonomy'] ); // phpcs:ignore WordPress.Security.NonceVerification
135 + public function wp_dropdown_cats($output) {
136 + if (isset($_GET['taxonomy'], $_GET['from_tag'], $_GET['new_lang']) && $id = get_term($_GET['from_tag'], $_GET['taxonomy'])->parent) {
137 + if ($parent = $this->model->get_translation('term', $id, $_GET['new_lang']))
138 + return str_replace('"'.$parent.'"', '"'.$parent.'" selected="selected"', $output);
194 139 }
195 -
196 - if ( isset( $taxonomy, $_GET['from_tag'], $_GET['new_lang'] ) && taxonomy_exists( $taxonomy ) ) { // phpcs:ignore WordPress.Security.NonceVerification
197 - $term = get_term( (int) $_GET['from_tag'], $taxonomy ); // phpcs:ignore WordPress.Security.NonceVerification
198 -
199 - if ( $term && $id = $term->parent ) {
200 - $lang = $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
201 - if ( $parent = $this->model->term->get_translation( $id, $lang ) ) {
202 - return str_replace( '"' . $parent . '"', '"' . $parent . '" selected="selected"', $output );
203 - }
204 - }
205 - }
206 140 return $output;
207 141 }
208 142
209 - /**
210 - * Stores the current post_id when bulk editing posts for use in save_language and pre_term_slug
143 + /*
144 + * prevents duplicating a term translation
211 145 *
212 - * @since 1.7
146 + * @since 1.3
213 147 *
214 - * @param int $post_id
148 + * @param string $term The term to add or update.
149 + * @param string $taxonomy The taxonomy to which to add the term
150 + *
151 + * @return object|string WP_Error object if the translation already exits, unmodified $term otherwise
215 152 */
216 - public function pre_post_update( $post_id ) {
217 - if ( isset( $_GET['bulk_edit'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
218 - $this->post_id = $post_id;
153 + public function pre_insert_term($term, $taxonomy) {
154 + if (isset($_POST['action'], $_POST['from_tag'], $_POST['term_lang_choice']) && 'add-tag' == $_POST['action'] && $this->model->get_translation('term', $_POST['from_tag'], $_POST['term_lang_choice'])) {
155 + $from_term = get_term($_POST['from_tag'], $taxonomy);
156 + return new WP_Error('term_translation_exists', sprintf(
157 + __('A translation does already exist for %s', 'polylang'),
158 + $from_term->name
159 + ));
219 160 }
161 + return $term;
220 162 }
221 163
222 - /**
223 - * Saves language
164 + /*
165 + * saves language
224 166 *
225 167 * @since 1.5
226 168 *
227 - * @param int $term_id
169 + * @param int $term_id
228 170 * @param string $taxonomy
229 171 */
230 - protected function save_language( $term_id, $taxonomy ) {
231 - global $wpdb;
232 - // Security checks are necessary to accept language modifications
172 + protected function save_language($term_id, $taxonomy) {
173 + // security checks are necessary to accept language modifications
233 174 // as 'wp_update_term' can be called from outside WP admin
234 175
235 - // Edit tags
236 - if ( isset( $_POST['term_lang_choice'] ) ) {
237 - if ( isset( $_POST['action'] ) && sanitize_key( $_POST['action'] ) === 'add-' . $taxonomy ) { // phpcs:ignore WordPress.Security.NonceVerification
238 - check_ajax_referer( 'add-' . $taxonomy, '_ajax_nonce-add-' . $taxonomy ); // Category metabox
239 - } else {
240 - check_admin_referer( 'pll_language', '_pll_nonce' ); // Edit tags or tags metabox
241 - }
176 + // edit tags
177 + if (isset($_POST['term_lang_choice'])) {
178 + if ('add-' . $taxonomy == $_POST['action'])
179 + check_ajax_referer($_POST['action'], '_ajax_nonce-add-' . $taxonomy); // category metabox
242 180
243 - $this->model->term->set_language( $term_id, $this->model->get_language( sanitize_key( $_POST['term_lang_choice'] ) ) );
181 + else
182 + check_admin_referer('pll_language', '_pll_nonce'); // edit tags or tags metabox
183 +
184 + $this->model->set_term_language($term_id, $_POST['term_lang_choice']);
244 185 }
245 186
246 - // *Post* bulk edit, in case a new term is created
247 - elseif ( isset( $_GET['bulk_edit'], $_GET['inline_lang_choice'] ) ) {
248 - check_admin_referer( 'bulk-posts' );
187 + // quick edit
188 + elseif (isset($_POST['inline_lang_choice'])) {
189 + check_ajax_referer('taxinlineeditnonce', '_inline_edit');
249 190
250 - // Bulk edit does not modify the language
251 - // So we possibly create a tag in several languages
252 - if ( -1 == $_GET['inline_lang_choice'] ) {
253 - // The language of the current term is set a according to the language of the current post
254 - $this->model->term->set_language( $term_id, $this->model->post->get_language( $this->post_id ) );
255 - $term = get_term( $term_id, $taxonomy );
191 + if (isset($_POST['inline-save-tax']) && $this->model->get_term_language($term_id)->slug != $_POST['inline_lang_choice'])
192 + $this->model->delete_translation('term', $term_id);
193 + $this->model->set_term_language($term_id, $_POST['inline_lang_choice']);
194 + }
256 195
257 - // Get all terms with the same name
258 - // FIXME backward compatibility WP < 4.2
259 - // No WP function to get all terms with the exact same name so let's use a custom query
260 - // $terms = get_terms( $taxonomy, array( 'name' => $term->name, 'hide_empty' => false, 'fields' => 'ids' ) ); should be OK in 4.2
261 - // I may need to rework the loop below
262 - $terms = $wpdb->get_results(
263 - $wpdb->prepare(
264 - "SELECT t.term_id FROM $wpdb->terms AS t
265 - INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id
266 - WHERE tt.taxonomy = %s AND t.name = %s",
267 - $taxonomy,
268 - $term->name
269 - )
270 - );
271 -
272 - // If we have several terms with the same name, they are translations of each other
273 - if ( count( $terms ) > 1 ) {
274 - $translations = array();
275 -
276 - foreach ( $terms as $term ) {
277 - $translations[ $this->model->term->get_language( $term->term_id )->slug ] = $term->term_id;
278 - }
279 -
280 - $this->model->term->save_translations( $term_id, $translations );
281 - }
282 - }
283 -
284 - else {
285 - $this->model->term->set_language( $term_id, $this->model->get_language( sanitize_key( $_GET['inline_lang_choice'] ) ) );
286 - }
196 + // edit post
197 + elseif (isset($_POST['post_lang_choice'])) {// FIXME should be useless now
198 + check_admin_referer('pll_language', '_pll_nonce');
199 + $this->model->set_term_language($term_id, $_POST['post_lang_choice']);
287 200 }
288 201
289 - // Quick edit
290 - elseif ( isset( $_POST['inline_lang_choice'] ) ) {
291 - check_ajax_referer(
292 - isset( $_POST['action'] ) && 'inline-save' == $_POST['action'] ? 'inlineeditnonce' : 'taxinlineeditnonce', // Post quick edit or tag quick edit ?
293 - '_inline_edit'
294 - );
202 + elseif ($this->model->get_term_language($term_id))
203 + {} // avoids breaking the language if the term is updated outside the edit post or edit tag pages
295 204
296 - $old_lang = $this->model->term->get_language( $term_id ); // Stores the old language
297 - $lang = $this->model->get_language( sanitize_key( $_POST['inline_lang_choice'] ) ); // New language
298 - $translations = $this->model->term->get_translations( $term_id );
205 + // sets language from term parent if exists thanks to Scott Kingsley Clark
206 + elseif (($term = get_term($term_id, $taxonomy)) && !empty($term->parent) && $parent_lang = $this->model->get_term_language($term->parent))
207 + $this->model->set_term_language($term_id, $parent_lang);
299 208
300 - // Checks if the new language already exists in the translation group
301 - if ( $old_lang && $old_lang->slug != $lang->slug ) {
302 - if ( array_key_exists( $lang->slug, $translations ) ) {
303 - $this->model->term->delete_translation( $term_id );
304 - }
305 -
306 - elseif ( array_key_exists( $old_lang->slug, $translations ) ) {
307 - unset( $translations[ $old_lang->slug ] );
308 - $this->model->term->save_translations( $term_id, $translations );
309 - }
310 - }
311 -
312 - $this->model->term->set_language( $term_id, $lang ); // Set new language
313 - }
314 -
315 - // Edit post
316 - elseif ( isset( $_POST['post_lang_choice'] ) ) { // FIXME should be useless now
317 - check_admin_referer( 'pll_language', '_pll_nonce' );
318 - $this->model->term->set_language( $term_id, $this->model->get_language( sanitize_key( $_POST['post_lang_choice'] ) ) );
319 - }
209 + else
210 + $this->model->set_term_language($term_id, $this->pref_lang);
320 211 }
321 212
322 - /**
323 - * Save translations from our form
213 + /*
214 + * save translations from our form
324 215 *
325 216 * @since 1.5
326 217 *
327 218 * @param int $term_id
@@ -326,58 +217,55 @@
326 217 *
327 218 * @param int $term_id
328 219 * @return array
329 220 */
330 - protected function save_translations( $term_id ) {
331 - // Security check as 'wp_update_term' can be called from outside WP admin
332 - check_admin_referer( 'pll_language', '_pll_nonce' );
221 + protected function save_translations($term_id) {
222 + // security check
223 + // as 'wp_update_term' can be called from outside WP admin
224 + check_admin_referer('pll_language', '_pll_nonce');
333 225
334 - $translations = array();
335 -
336 - // Save translations after checking the translated term is in the right language ( as well as cast id to int )
337 - if ( isset( $_POST['term_tr_lang'] ) ) {
338 - foreach ( array_map( 'absint', $_POST['term_tr_lang'] ) as $lang => $tr_id ) {
339 - $tr_lang = $this->model->term->get_language( $tr_id );
340 - $translations[ $lang ] = $tr_lang && $tr_lang->slug == $lang ? $tr_id : 0;
341 - }
226 + // save translations after checking the translated term is in the right language (as well as cast id to int)
227 + foreach ($_POST['term_tr_lang'] as $lang => $tr_id) {
228 + $tr_lang = $this->model->get_term_language((int) $tr_id);
229 + $translations[$lang] = $tr_lang && $tr_lang->slug == $lang ? (int) $tr_id : 0;
342 230 }
343 231
344 - $this->model->term->save_translations( $term_id, $translations );
232 + $this->model->save_translations('term', $term_id, $translations);
345 233
346 234 return $translations;
347 235 }
348 236
349 - /**
350 - * Called when a category or post tag is created or edited
351 - * Saves language and translations
237 + /*
238 + * called when a category or post tag is created or edited
239 + * saves language and translations
352 240 *
353 241 * @since 0.1
354 242 *
355 - * @param int $term_id
356 - * @param int $tt_id term taxonomy id
243 + * @param int $term_id
244 + * @param int $tt_id term taxononomy id
357 245 * @param string $taxonomy
358 246 */
359 - public function save_term( $term_id, $tt_id, $taxonomy ) {
360 - // Does nothing except on taxonomies which are filterable
361 - if ( ! $this->model->is_translated_taxonomy( $taxonomy ) ) {
247 + public function save_term($term_id, $tt_id, $taxonomy) {
248 + // does nothing except on taxonomies which are filterable
249 + if (!$this->model->is_translated_taxonomy($taxonomy))
362 250 return;
363 - }
364 251
365 - // Capability check
366 - // As 'wp_update_term' can be called from outside WP admin
367 - // 2nd test for creating tags when creating / editing a post
368 - $tax = get_taxonomy( $taxonomy );
369 - if ( current_user_can( $tax->cap->edit_terms ) || ( isset( $_POST['tax_input'][ $taxonomy ] ) && current_user_can( $tax->cap->assign_terms ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification
370 - $this->save_language( $term_id, $taxonomy );
252 + // capability check
253 + // as 'wp_update_term' can be called from outside WP admin
254 + $tax = get_taxonomy($taxonomy);
255 + if (!current_user_can($tax->cap->edit_terms))
256 + wp_die( __( 'Cheatin&#8217; uh?' ) );
371 257
372 - if ( isset( $_POST['term_tr_lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
373 - $this->save_translations( $term_id );
374 - }
375 - }
258 + $this->save_language($term_id, $taxonomy);
259 +
260 + if (isset($_POST['term_tr_lang']))
261 + $translations = $this->save_translations($term_id);
262 +
263 + do_action('pll_save_term', $term_id, $taxonomy, empty($translations) ? $this->model->get_translations('term', $term_id) : $translations);
376 264 }
377 265
378 - /**
379 - * Stores the term name for use in pre_term_slug
266 + /*
267 + * stores the term name for use in pre_term_slug
380 268 *
381 269 * @since 0.9.5
382 270 *
383 271 * @param string $name term name
@@ -382,14 +270,14 @@
382 270 *
383 271 * @param string $name term name
384 272 * @return string unmodified term name
385 273 */
386 - public function pre_term_name( $name ) {
274 + public function pre_term_name($name) {
387 275 return $this->pre_term_name = $name;
388 276 }
389 277
390 - /**
391 - * Creates the term slug in case the term already exists in another language
278 + /*
279 + * creates the term slug in case the term already exists in another language
392 280 *
393 281 * @since 0.9.5
394 282 *
395 283 * @param string $slug
@@ -395,247 +283,299 @@
395 283 * @param string $slug
396 284 * @param string $taxonomy
397 285 * @return string
398 286 */
399 - public function pre_term_slug( $slug, $taxonomy ) {
400 - $name = sanitize_title( $this->pre_term_name );
287 + public function pre_term_slug($slug, $taxonomy) {
288 + $name = sanitize_title($this->pre_term_name);
401 289
402 - // If the term already exists in another language
403 - if ( ! $slug && $this->model->is_translated_taxonomy( $taxonomy ) && term_exists( $name, $taxonomy ) ) {
404 - if ( isset( $_POST['term_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
405 - $slug = $name . '-' . $this->model->get_language( sanitize_key( $_POST['term_lang_choice'] ) )->slug; // phpcs:ignore WordPress.Security.NonceVerification
406 - }
290 + // if the new term has the same name as a language, we *need* to differentiate the term
291 + // see http://core.trac.wordpress.org/ticket/23199
292 + if (term_exists($name, 'language') && !term_exists($name, $taxonomy) && (!$slug || $slug == $name))
293 + $slug = $name . '-' . $taxonomy; // a convenient slug which may be modified later by the user
407 294
408 - elseif ( isset( $_POST['inline_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
409 - $slug = $name . '-' . $this->model->get_language( sanitize_key( $_POST['inline_lang_choice'] ) )->slug; // phpcs:ignore WordPress.Security.NonceVerification
410 - }
295 + // if the term already exists in another language
296 + if (!$slug && $this->model->is_translated_taxonomy($taxonomy) && term_exists($name, $taxonomy)) {
297 + if (isset($_POST['term_lang_choice']))
298 + $slug = $name . '-' . $this->model->get_language($_POST['term_lang_choice'])->slug;
411 299
412 - // *Post* bulk edit, in case a new term is created
413 - elseif ( isset( $_GET['bulk_edit'], $_GET['inline_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
414 - // Bulk edit does not modify the language
415 - if ( -1 == $_GET['inline_lang_choice'] ) { // phpcs:ignore WordPress.Security.NonceVerification
416 - $slug = $name . '-' . $this->model->post->get_language( $this->post_id )->slug;
417 - } else {
418 - $slug = $name . '-' . $this->model->get_language( sanitize_key( $_GET['inline_lang_choice'] ) )->slug; // phpcs:ignore WordPress.Security.NonceVerification
419 - }
420 - }
300 + elseif (isset($_POST['inline_lang_choice']))
301 + $slug = $name . '-' . $this->model->get_language($_POST['inline_lang_choice'])->slug;
421 302 }
422 303
423 304 return $slug;
424 305 }
425 306
426 - /**
427 - * Ajax response for edit term form
307 + /*
308 + * called when a category or post tag is deleted
309 + * deletes language and translations
428 310 *
311 + * @since 0.1
312 + *
313 + * @param int $term_id
314 + */
315 + public function delete_term($term_id) {
316 + $this->model->delete_translation('term', $term_id);
317 + $this->model->delete_term_language($term_id);
318 + }
319 +
320 + /*
321 + * ajax response for edit term form
322 + *
429 323 * @since 0.2
430 324 */
431 325 public function term_lang_choice() {
432 - check_ajax_referer( 'pll_language', '_pll_nonce' );
326 + check_ajax_referer('pll_language', '_pll_nonce');
433 327
434 - if ( ! isset( $_POST['taxonomy'], $_POST['post_type'], $_POST['lang'] ) ) {
435 - wp_die( 0 );
436 - }
328 + $lang = $this->model->get_language($_POST['lang']);
329 + $term_id = isset($_POST['term_id']) ? $_POST['term_id'] : null;
330 + $taxonomy = $_POST['taxonomy'];
331 + $post_type = $_POST['post_type'];
437 332
438 - $lang = $this->model->get_language( sanitize_key( $_POST['lang'] ) );
439 - $term_id = isset( $_POST['term_id'] ) ? (int) $_POST['term_id'] : null; // phpcs:ignore WordPressVIPMinimum.Variables.VariableAnalysis.UnusedVariable
440 - $taxonomy = sanitize_key( $_POST['taxonomy'] ); // phpcs:ignore WordPressVIPMinimum.Variables.VariableAnalysis.UnusedVariable
441 - $post_type = sanitize_key( $_POST['post_type'] );
442 -
443 - if ( ! post_type_exists( $post_type ) || ! taxonomy_exists( $taxonomy ) ) {
444 - wp_die( 0 );
445 - }
446 -
447 333 ob_start();
448 - if ( $lang ) {
449 - include PLL_ADMIN_INC . '/view-translations-term.php';
450 - }
451 - $x = new WP_Ajax_Response( array( 'what' => 'translations', 'data' => ob_get_contents() ) );
334 + if ($lang)
335 + include(PLL_ADMIN_INC.'/view-translations-term.php');
336 + $x = new WP_Ajax_Response(array('what' => 'translations', 'data' => ob_get_contents()));
452 337 ob_end_clean();
453 338
454 - // Parent dropdown list ( only for hierarchical taxonomies )
339 + // parent dropdown list (only for hierarchical taxonomies)
455 340 // $args copied from edit_tags.php except echo
456 - if ( is_taxonomy_hierarchical( $taxonomy ) ) {
341 + if (is_taxonomy_hierarchical($taxonomy)) {
457 342 $args = array(
458 - 'hide_empty' => 0,
459 - 'hide_if_empty' => false,
460 - 'taxonomy' => $taxonomy,
461 - 'name' => 'parent',
462 - 'orderby' => 'name',
463 - 'hierarchical' => true,
464 - 'show_option_none' => __( 'None', 'polylang' ),
465 - 'echo' => 0,
343 + 'hide_empty' => 0,
344 + 'hide_if_empty' => false,
345 + 'taxonomy' => $taxonomy,
346 + 'name' => 'parent',
347 + 'orderby' => 'name',
348 + 'hierarchical' => true,
349 + 'show_option_none' => __('None'),
350 + 'echo' => 0,
466 351 );
467 - $x->Add( array( 'what' => 'parent', 'data' => wp_dropdown_categories( $args ) ) );
352 + $x->Add(array('what' => 'parent', 'data' => wp_dropdown_categories($args)));
468 353 }
469 354
470 - // Tag cloud
471 - // Tests copied from edit_tags.php
355 + // tag cloud
356 + // tests copied from edit_tags.php
472 357 else {
473 - $tax = get_taxonomy( $taxonomy );
474 - if ( ! is_null( $tax->labels->popular_items ) ) {
475 - $args = array( 'taxonomy' => $taxonomy, 'echo' => false );
476 - if ( current_user_can( $tax->cap->edit_terms ) ) {
477 - $args = array_merge( $args, array( 'link' => 'edit' ) );
478 - }
358 + $tax = get_taxonomy($taxonomy);
359 + if (!is_null($tax->labels->popular_items)) {
360 + $args = array('taxonomy' => $taxonomy, 'echo' => false);
361 + if (current_user_can($tax->cap->edit_terms))
362 + $args = array_merge($args, array('link' => 'edit'));
479 363
480 - if ( $tag_cloud = wp_tag_cloud( $args ) ) {
481 - $html = sprintf( '<div class="tagcloud"><h2>%1$s</h2>%2$s</div>', esc_html( $tax->labels->popular_items ), $tag_cloud );
482 - $x->Add( array( 'what' => 'tag_cloud', 'data' => $html ) );
483 - }
364 + if ($tag_cloud = wp_tag_cloud($args))
365 + $x->Add(array('what' => 'tag_cloud', 'data' => '<h3>'.$tax->labels->popular_items.'</h3>'.$tag_cloud));
484 366 }
485 367 }
486 368
487 - // Flag
488 - $x->Add( array( 'what' => 'flag', 'data' => empty( $lang->flag ) ? esc_html( $lang->slug ) : $lang->flag ) );
489 -
490 369 $x->send();
491 370 }
492 371
493 - /**
494 - * Ajax response for input in translation autocomplete input box
372 + /*
373 + * ajax response for input in translation autocomplete input box
495 374 *
496 375 * @since 1.5
497 376 */
498 377 public function ajax_terms_not_translated() {
499 - check_ajax_referer( 'pll_language', '_pll_nonce' );
378 + check_ajax_referer('pll_language', '_pll_nonce');
500 379
501 - if ( ! isset( $_GET['term'], $_GET['post_type'], $_GET['taxonomy'], $_GET['term_language'], $_GET['translation_language'] ) ) {
502 - wp_die( 0 );
380 + $return = array();
381 +
382 + // it is more efficient to use one common query for all languages as soon as there are more than 2
383 + // pll_get_terms_not_translated arg to identify this query in terms_clauses filter
384 + foreach (get_terms($_REQUEST['taxonomy'], 'hide_empty=0&pll_get_terms_not_translated=1&name__like=' . $_REQUEST['term']) as $term) {
385 + $lang = $this->model->get_term_language($term->term_id);
386 +
387 + if ($lang && $lang->slug == $_REQUEST['translation_language'] && !$this->model->get_translation('term', $term->term_id, $_REQUEST['term_language']))
388 + $return[] = array(
389 + 'id' => $term->term_id,
390 + 'value' => $term->name,
391 + 'link' => $this->edit_translation_link($term->term_id, $_REQUEST['taxonomy'], $_REQUEST['post_type'])
392 + );
503 393 }
504 394
505 - $s = wp_unslash( $_GET['term'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
506 - $post_type = sanitize_key( $_GET['post_type'] );
507 - $taxonomy = sanitize_key( $_GET['taxonomy'] );
395 + // add current translation in list
396 + // not in add term for as term_id is not set
397 + if ('undefined' !== $_REQUEST['term_id'] && $term_id = $this->model->get_translation('term', $_REQUEST['term_id'], $_REQUEST['translation_language'])) {
398 + $term = get_term($term_id, $_REQUEST['taxonomy']);
399 + array_unshift($return, array(
400 + 'id' => $term_id,
401 + 'value' => $term->name,
402 + 'link' => $this->edit_translation_link($term->term_id, $_REQUEST['taxonomy'], $_REQUEST['post_type'])
403 + ));
404 + }
508 405
509 - if ( ! post_type_exists( $post_type ) || ! taxonomy_exists( $taxonomy ) ) {
510 - wp_die( 0 );
511 - }
406 + wp_die(json_encode($return));
407 + }
512 408
513 - $term_language = $this->model->get_language( sanitize_key( $_GET['term_language'] ) );
514 - $translation_language = $this->model->get_language( sanitize_key( $_GET['translation_language'] ) );
409 + /*
410 + * filters categories and post tags by language when needed on admin side
411 + *
412 + * @since 0.5
413 + *
414 + * @param array $clauses list of sql clauses
415 + * @param array $taxonomies list of taxonomies
416 + * @param array $args get_terms arguments
417 + * @return array modified sql clauses
418 + */
419 + public function terms_clauses($clauses, $taxonomies, $args) {
420 + // does nothing except on taxonomies which are filterable
421 + foreach ($taxonomies as $tax)
422 + if (!$this->model->is_translated_taxonomy($tax))
423 + return $clauses;
515 424
516 - $terms = array();
517 - $return = array();
425 + if (function_exists('get_current_screen'))
426 + $screen = get_current_screen(); // since WP 3.1, may not be available the first time(s) get_terms is called
518 427
519 - // Add current translation in list.
520 - // Not in add term as term_id is not set.
521 - if ( isset( $_GET['term_id'] ) && 'undefined' !== $_GET['term_id'] && $term_id = $this->model->term->get_translation( (int) $_GET['term_id'], $translation_language ) ) {
522 - $terms = array( get_term( $term_id, $taxonomy ) );
428 + // don't filter nav menus on nav menus screen
429 + if (isset($screen) && 'nav-menus' == $screen->base && in_array('nav_menu', $taxonomies))
430 + return $clauses;
431 +
432 + // if get_terms is queried with a 'lang' parameter
433 + if (!empty($args['lang']))
434 + return $this->model->terms_clauses($clauses, $args['lang']);
435 +
436 + // does nothing in Languages and dasboard admin panels
437 + if (isset($screen) && in_array($screen->base, array('toplevel_page_mlang', 'dashboard')))
438 + return $clauses;
439 +
440 + // do not filter 'get_terms_not_translated'
441 + if (!empty($args['pll_get_terms_not_translated']))
442 + return $clauses;
443 +
444 + // The only ajax response I want to deal with is when changing the language in post metabox
445 + if (isset($_POST['action']) && !in_array($_POST['action'], array('post_lang_choice', 'term_lang_choice', 'get-tagcloud')))
446 + return $clauses;
447 +
448 + // I only want to filter the parent dropdown list when editing a term in a hierarchical taxonomy
449 + if (isset($_POST['action']) && $_POST['action'] == 'term_lang_choice' && !(isset($args['class']) || isset($args['unit'])))
450 + return $clauses;
451 +
452 + // ajax response for changing the language in the post metabox (or in the edit-tags panels)
453 + if (isset($_POST['lang']))
454 + $lang = $this->model->get_language($_POST['lang']);
455 +
456 + // ajax tag search since WP 3.7
457 + elseif (!empty($_GET['lang']) && isset($_GET['action']) && 'polylang-ajax-tag-search' == $_GET['action'])
458 + $lang = $this->model->get_language($_GET['lang']);
459 +
460 + // the post (or term) is created with the 'add new' (translation) link
461 + // test of $args['page'] to avoid filtering the terms list table in edit-tags panel
462 + elseif (!empty($_GET['new_lang']) && empty($args['page']))
463 + $lang = $this->model->get_language($_GET['new_lang']);
464 +
465 + // FIXME can we simplify how we deal with the admin language filter?
466 + // the language filter selection has just changed
467 + // test $screen->base to avoid interference between the language filter and the post language selection and the category parent dropdown list
468 + elseif (!empty($_GET['lang']) && !(isset($screen) && in_array($screen->base, array('post', 'edit-tags')))) {
469 + if ($_GET['lang'] != 'all')
470 + $lang = $this->model->get_language($_GET['lang']);
471 + elseif ($screen->base == 'edit-tags' && isset($args['class']))
472 + $lang = $this->pref_lang; // parent dropdown
523 473 }
524 474
525 - // It is more efficient to use one common query for all languages as soon as there are more than 2.
526 - foreach ( get_terms( $taxonomy, 'hide_empty=0&lang=0&name__like=' . $s ) as $term ) {
527 - $lang = $this->model->term->get_language( $term->term_id );
475 + // again the language filter
476 + elseif (!empty($this->curlang) && (isset($screen) && $screen->base != 'post' && !($screen->base == 'edit-tags' && isset($args['class'])))) // don't apply to post edit and the category parent dropdown list
477 + $lang = $this->curlang;
528 478
529 - if ( $lang && $lang->slug == $translation_language->slug && ! $this->model->term->get_translation( $term->term_id, $term_language ) ) {
530 - $terms[] = $term;
531 - }
532 - }
479 + elseif (isset($_GET['post']))
480 + $lang = $this->model->get_post_language($_GET['post']);
533 481
534 - // Format the ajax response.
535 - foreach ( $terms as $term ) {
536 - $return[] = array(
537 - 'id' => $term->term_id,
538 - 'value' => rtrim( // Trim the seperator added at the end by WP.
539 - get_term_parents_list(
540 - $term->term_id,
541 - $term->taxonomy,
542 - array(
543 - 'separator' => ' > ',
544 - 'link' => false,
545 - )
546 - ),
547 - ' >'
548 - ),
549 - 'link' => $this->links->edit_term_translation_link( $term->term_id, $term->taxonomy, $post_type ),
550 - );
551 - }
482 + // for the parent dropdown list in edit term
483 + elseif (isset($_GET['tag_ID']))
484 + $lang = $this->model->get_term_language($_GET['tag_ID']);
552 485
553 - wp_die( wp_json_encode( $return ) );
486 + // when a new category is created in the edit post panel
487 + elseif (isset($_POST['term_lang_choice']))
488 + $lang = $this->model->get_language($_POST['term_lang_choice']);
489 +
490 + // for a new post (or the parent dropdown list of a new term)
491 + elseif (isset($screen) && ($screen->base == 'post' || ($screen->base == 'edit-tags' && isset($args['class']))))
492 + $lang = $this->pref_lang;
493 +
494 + // adds our clauses to filter by current language
495 + return !empty($lang) ? $this->model->terms_clauses($clauses, $lang) : $clauses;
554 496 }
555 497
556 - /**
557 - * Filters the default category in note below the category list table and in settings->writing dropdown
498 + /*
499 + * replaces ajax tag search of WP to filter tags by language
500 + * backward compatibility WP < 3.7
501 + * see http://core.trac.wordpress.org/ticket/25231
558 502 *
559 - * @since 1.2
560 - *
561 - * @param int $value
562 - * @return int
503 + * @since 0.7
563 504 */
564 - public function option_default_category( $value ) {
565 - if ( isset( $this->pref_lang ) && $tr = $this->model->term->get( $value, $this->pref_lang ) ) {
566 - $value = $tr;
505 + public function ajax_tag_search() {
506 + global $wpdb;
507 +
508 + if ( isset( $_GET['tax'] ) ) {
509 + $taxonomy = sanitize_key( $_GET['tax'] );
510 + $tax = get_taxonomy( $taxonomy );
511 + if ( ! $tax )
512 + die( '0' );
513 + if ( ! current_user_can( $tax->cap->assign_terms ) )
514 + die( '-1' );
515 + } else {
516 + die('0');
567 517 }
568 - return $value;
518 +
519 + $s = stripslashes( $_GET['q'] );
520 +
521 + if ( false !== strpos( $s, ',' ) ) {
522 + $s = explode( ',', $s );
523 + $s = $s[count( $s ) - 1];
524 + }
525 + $s = trim( $s );
526 + if ( strlen( $s ) < 2 )
527 + die; // require 2 chars for matching
528 +
529 + $lang = $this->model->get_language($_GET['lang']);
530 +
531 + $results = $wpdb->get_col( $wpdb->prepare(
532 + "SELECT t.name FROM $wpdb->term_taxonomy AS tt
533 + INNER JOIN $wpdb->terms AS t ON tt.term_id = t.term_id
534 + INNER JOIN $wpdb->term_relationships AS pll_tr ON pll_tr.object_id = t.term_id
535 + WHERE tt.taxonomy = %s AND t.name LIKE (%s) AND pll_tr.term_taxonomy_id = %d",
536 + $taxonomy, '%' . like_escape( $s ) . '%', $lang->tl_term_taxonomy_id ) );
537 +
538 + echo join( $results, "\n" );
539 + die;
569 540 }
570 541
571 - /**
572 - * Checks if the new default category is translated in all languages
573 - * If not, create the translations
542 + /*
543 + * hack to avoid displaying delete link for the default category in all languages
544 + * also returns the default category in the right language when called from wp_delete_term
574 545 *
575 - * @since 1.7
546 + * @since 1.2
576 547 *
577 - * @param int $old_value
578 548 * @param int $value
549 + * @return int
579 550 */
580 - public function update_option_default_category( $old_value, $value ) {
581 - $default_cat_lang = $this->model->term->get_language( $value );
551 + public function option_default_category($value) {
552 + $traces = debug_backtrace();
582 553
583 - // Assign a default language to default category
584 - if ( ! $default_cat_lang ) {
585 - $default_cat_lang = $this->model->get_language( $this->options['default_lang'] );
586 - $this->model->term->set_language( (int) $value, $default_cat_lang );
587 - }
554 + if (isset($traces[4])) {
555 + if (in_array($traces[4]['function'], array('column_cb', 'column_name')) && in_array($traces[4]['args'][0]->term_id, $this->model->get_translations('term', $value)))
556 + return $traces[4]['args'][0]->term_id;
588 557
589 - foreach ( $this->model->get_languages_list() as $language ) {
590 - if ( $language->slug != $default_cat_lang->slug && ! $this->model->term->get_translation( $value, $language ) ) {
591 - $this->model->create_default_category( $language );
592 - }
558 + if ('wp_delete_term' == $traces[4]['function'])
559 + return $this->model->get_term($value, $this->model->get_term_language($traces[4]['args'][0]));
593 560 }
561 + return $value;
594 562 }
595 563
596 - /**
597 - * Updates the translations term ids when splitting a shared term
598 - * Splits translations if these are shared terms too
564 + /*
565 + * returns html markup for a translation link
599 566 *
600 - * @since 1.7
567 + * @since 1.4
601 568 *
602 - * @param int $term_id Shared term_id
603 - * @param int $new_term_id
604 - * @param int $term_taxonomy_id
569 + * @param object $term_id translation term id
605 570 * @param string $taxonomy
571 + * @param string $post_type
572 + * @return string
606 573 */
607 - public function split_shared_term( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
608 - if ( ! $this->model->is_translated_taxonomy( $taxonomy ) ) {
609 - return;
610 - }
611 -
612 - // Avoid recursion
613 - static $avoid_recursion = false;
614 - if ( $avoid_recursion ) {
615 - return;
616 - }
617 -
618 - $avoid_recursion = true;
619 - $lang = $this->model->term->get_language( $term_id );
620 - $translations = array();
621 -
622 - foreach ( $this->model->term->get_translations( $term_id ) as $key => $tr_id ) {
623 - if ( $lang->slug == $key ) {
624 - $translations[ $key ] = $new_term_id;
625 - }
626 - else {
627 - $tr_term = get_term( $tr_id, $taxonomy );
628 - $translations[ $key ] = _split_shared_term( $tr_id, $tr_term->term_taxonomy_id );
629 -
630 - // Hack translation ids sent by the form to avoid overwrite in PLL_Admin_Filters_Term::save_translations
631 - if ( isset( $_POST['term_tr_lang'][ $key ] ) && $_POST['term_tr_lang'][ $key ] == $tr_id ) { // phpcs:ignore WordPress.Security.NonceVerification
632 - $_POST['term_tr_lang'][ $key ] = $translations[ $key ];
633 - }
634 - }
635 - $this->model->term->set_language( $translations[ $key ], $key );
636 - }
637 -
638 - $this->model->term->save_translations( $new_term_id, $translations );
639 - $avoid_recursion = false;
574 + public function edit_translation_link($term_id, $taxonomy, $post_type) {
575 + return sprintf(
576 + '<a href="%1$s" class="pll_icon_edit title="%2$s"></a></td>',
577 + esc_url(get_edit_term_link($term_id, $taxonomy, $post_type)),
578 + __('Edit','polylang')
579 + );
640 580 }
641 581 }