PluginProbe
Polylang / 3.7
Polylang v3.7
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 / settings / table-string.php

table-string.php in Polylang 3.7, at settings/table-string.php

454 lines 13.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package Polylang
4 */
5
6 if ( ! class_exists( 'WP_List_Table' ) ) {
7 require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php'; // since WP 3.1
8 }
9
10 /**
11 * A class to create the strings translations table
12 * Thanks to Matt Van Andel ( http://www.mattvanandel.com ) for its plugin "Custom List Table Example" !
13 *
14 * @since 0.6
15 */
16 class PLL_Table_String extends WP_List_Table {
17 /**
18 * The list of languages.
19 *
20 * @var PLL_Language[]
21 */
22 protected $languages;
23
24 /**
25 * Registered strings.
26 *
27 * @var array
28 */
29 protected $strings;
30
31 /**
32 * The string groups.
33 *
34 * @var string[]
35 */
36 protected $groups;
37
38 /**
39 * The selected string group or -1 if none is selected.
40 *
41 * @var string|int
42 */
43 protected $selected_group;
44
45 /**
46 * Constructor.
47 *
48 * @since 0.6
49 *
50 * @param PLL_Language[] $languages List of languages.
51 */
52 public function __construct( $languages ) {
53 parent::__construct(
54 array(
55 'plural' => 'Strings translations', // Do not translate ( used for css class )
56 'ajax' => false,
57 )
58 );
59
60 $this->languages = $languages;
61 $this->strings = PLL_Admin_Strings::get_strings();
62 $this->groups = array_unique( wp_list_pluck( $this->strings, 'context' ) );
63
64 $this->selected_group = -1;
65
66 if ( ! empty( $_GET['group'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
67 $group = sanitize_text_field( wp_unslash( $_GET['group'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
68 if ( in_array( $group, $this->groups ) ) {
69 $this->selected_group = $group;
70 }
71 }
72
73 add_action( 'mlang_action_string-translation', array( $this, 'save_translations' ) );
74 }
75
76 /**
77 * Displays the item information in a column (default case).
78 *
79 * @since 0.6
80 *
81 * @param array $item Data related to the current string.
82 * @param string $column_name The current column name.
83 * @return string
84 */
85 public function column_default( $item, $column_name ) {
86 return $item[ $column_name ];
87 }
88
89 /**
90 * Displays the checkbox in first column.
91 *
92 * @since 1.1
93 *
94 * @param array $item Data related to the current string.
95 * @return string
96 */
97 public function column_cb( $item ) {
98 return sprintf(
99 '<label class="screen-reader-text" for="cb-select-%1$s">%2$s</label><input id="cb-select-%1$s" type="checkbox" name="strings[]" value="%1$s" %3$s />',
100 esc_attr( $item['row'] ),
101 /* translators: accessibility text, %s is a string potentially in any language */
102 sprintf( __( 'Select %s', 'polylang' ), format_to_edit( $item['string'] ) ),
103 empty( $item['icl'] ) ? 'disabled' : '' // Only strings registered with WPML API can be removed.
104 );
105 }
106
107 /**
108 * Displays the string to translate.
109 *
110 * @since 1.0
111 *
112 * @param array $item Data related to the current string.
113 * @return string
114 */
115 public function column_string( $item ) {
116 return format_to_edit( $item['string'] ); // Don't interpret special chars for the string column.
117 }
118
119 /**
120 * Displays the translations to edit.
121 *
122 * @since 0.6
123 *
124 * @param array $item Data related to the current string.
125 * @return string
126 */
127 public function column_translations( $item ) {
128 $out = '';
129 $languages = array();
130
131 foreach ( $this->languages as $language ) {
132 $languages[ $language->slug ] = $language->name;
133 }
134
135 foreach ( $item['translations'] as $key => $translation ) {
136 $input_type = $item['multiline'] ?
137 '<textarea name="translation[%1$s][%2$s]" id="%1$s-%2$s">%4$s</textarea>' :
138 '<input type="text" name="translation[%1$s][%2$s]" id="%1$s-%2$s" value="%4$s" />';
139 $out .= sprintf(
140 '<div class="translation"><label for="%1$s-%2$s">%3$s</label>' . $input_type . '</div>' . "\n",
141 esc_attr( $key ),
142 esc_attr( $item['row'] ),
143 esc_html( $languages[ $key ] ),
144 format_to_edit( $translation ) // Don't interpret special chars.
145 );
146 }
147
148 return $out;
149 }
150
151 /**
152 * Gets the list of columns.
153 *
154 * @since 0.6
155 *
156 * @return string[] The list of column titles.
157 */
158 public function get_columns() {
159 return array(
160 'cb' => '<input type="checkbox" />', // Checkbox.
161 'string' => esc_html__( 'String', 'polylang' ),
162 'name' => esc_html__( 'Name', 'polylang' ),
163 'context' => esc_html__( 'Group', 'polylang' ),
164 'translations' => esc_html__( 'Translations', 'polylang' ),
165 );
166 }
167
168 /**
169 * Gets the list of sortable columns
170 *
171 * @since 0.6
172 *
173 * @return array
174 */
175 public function get_sortable_columns() {
176 return array(
177 'string' => array( 'string', false ),
178 'name' => array( 'name', false ),
179 'context' => array( 'context', false ),
180 );
181 }
182
183 /**
184 * Gets the name of the default primary column.
185 *
186 * @since 2.1
187 *
188 * @return string Name of the default primary column, in this case, 'string'.
189 */
190 protected function get_default_primary_column_name() {
191 return 'string';
192 }
193
194 /**
195 * Search for a string in translations. Case insensitive.
196 *
197 * @since 2.6
198 *
199 * @param PLL_Language[] $languages An array of language objects.
200 * @param string $s Searched string.
201 * @return string[] Found strings.
202 */
203 protected function search_in_translations( $languages, $s ) {
204 $founds = array();
205
206 foreach ( $languages as $language ) {
207 $mo = new PLL_MO();
208 $mo->import_from_db( $language );
209 foreach ( wp_list_pluck( $mo->entries, 'translations' ) as $string => $translation ) {
210 if ( false !== stripos( $translation[0], $s ) ) {
211 $founds[] = $string;
212 }
213 }
214 }
215
216 return array_unique( $founds );
217 }
218
219 /**
220 * Sorts registered string items.
221 *
222 * @since 0.6
223 *
224 * @param array $a The first item to compare.
225 * @param array $b The second item to compare.
226 * @return int -1 or 1 if $a is considered to be respectively less than or greater than $b.
227 */
228 protected function usort_reorder( $a, $b ) {
229 if ( ! empty( $_GET['orderby'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
230 $orderby = sanitize_key( $_GET['orderby'] ); // phpcs:ignore WordPress.Security.NonceVerification
231 if ( isset( $a[ $orderby ], $b[ $orderby ] ) ) {
232 $result = strcmp( $a[ $orderby ], $b[ $orderby ] ); // Determine sort order
233 return ( empty( $_GET['order'] ) || 'asc' === $_GET['order'] ) ? $result : -$result; // phpcs:ignore WordPress.Security.NonceVerification
234 }
235 }
236
237 return 0;
238 }
239
240 /**
241 * Prepares the list of registered strings for display.
242 *
243 * @since 0.6
244 *
245 * @return void
246 */
247 public function prepare_items() {
248 // Is admin language filter active?
249 if ( $lg = get_user_meta( get_current_user_id(), 'pll_filter_content', true ) ) {
250 $languages = wp_list_filter( $this->languages, array( 'slug' => $lg ) );
251 } else {
252 $languages = $this->languages;
253 }
254
255 $data = $this->strings;
256
257 // Filter by selected group
258 if ( -1 !== $this->selected_group ) {
259 $data = wp_list_filter( $data, array( 'context' => $this->selected_group ) );
260 }
261
262 // Filter by searched string
263 $s = empty( $_GET['s'] ) ? '' : wp_unslash( $_GET['s'] ); // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
264
265 if ( ! empty( $s ) ) {
266 // Search in translations
267 $in_translations = $this->search_in_translations( $languages, $s );
268
269 foreach ( $data as $key => $row ) {
270 if ( stripos( $row['name'], $s ) === false && stripos( $row['string'], $s ) === false && ! in_array( $row['string'], $in_translations ) ) {
271 unset( $data[ $key ] );
272 }
273 }
274 }
275
276 // Sorting
277 uasort( $data, array( $this, 'usort_reorder' ) );
278
279 // Paging
280 $per_page = $this->get_items_per_page( 'pll_strings_per_page' );
281 $this->_column_headers = array( $this->get_columns(), array(), $this->get_sortable_columns() );
282
283 $total_items = count( $data );
284 $this->items = array_slice( $data, ( $this->get_pagenum() - 1 ) * $per_page, $per_page, true );
285
286 $this->set_pagination_args(
287 array(
288 'total_items' => $total_items,
289 'per_page' => $per_page,
290 'total_pages' => (int) ceil( $total_items / $per_page ),
291 )
292 );
293
294 // Translate strings
295 // Kept for the end as it is a slow process
296 foreach ( $languages as $language ) {
297 $mo = new PLL_MO();
298 $mo->import_from_db( $language );
299 foreach ( $this->items as $key => $row ) {
300 $this->items[ $key ]['translations'][ $language->slug ] = $mo->translate_if_any( $row['string'] );
301 $this->items[ $key ]['row'] = $key; // Store the row number for convenience
302 }
303 }
304 }
305
306 /**
307 * Get the list of possible bulk actions.
308 *
309 * @since 1.1
310 *
311 * @return string[] Array of bulk actions.
312 */
313 public function get_bulk_actions() {
314 return array( 'delete' => __( 'Delete', 'polylang' ) );
315 }
316
317 /**
318 * Get the current action selected from the bulk actions dropdown.
319 * overrides parent function to avoid submit button to trigger bulk actions
320 *
321 * @since 1.8
322 *
323 * @return string|false The action name or False if no action was selected
324 */
325 public function current_action() {
326 return empty( $_POST['submit'] ) ? parent::current_action() : false; // phpcs:ignore WordPress.Security.NonceVerification
327 }
328
329 /**
330 * Displays the dropdown list to filter strings per group
331 *
332 * @since 1.1
333 *
334 * @param string $which only 'top' is supported
335 * @return void
336 */
337 public function extra_tablenav( $which ) {
338 if ( 'top' !== $which ) {
339 return;
340 }
341
342 echo '<div class="alignleft actions">';
343 printf(
344 '<label class="screen-reader-text" for="select-group" >%s</label>',
345 /* translators: accessibility text */
346 esc_html__( 'Filter by group', 'polylang' )
347 );
348 echo '<select id="select-group" name="group">' . "\n";
349 printf(
350 '<option value="-1"%s>%s</option>' . "\n",
351 selected( $this->selected_group, -1, false ),
352 esc_html__( 'View all groups', 'polylang' )
353 );
354
355 foreach ( $this->groups as $group ) {
356 printf(
357 '<option value="%s"%s>%s</option>' . "\n",
358 esc_attr( urlencode( $group ) ),
359 selected( $this->selected_group, $group, false ),
360 esc_html( $group )
361 );
362 }
363 echo '</select>' . "\n";
364
365 submit_button( __( 'Filter', 'polylang' ), 'button', 'filter_action', false, array( 'id' => 'post-query-submit' ) );
366 echo '</div>';
367 }
368
369 /**
370 * Saves the strings translations in DB
371 * Optionally clean the DB
372 *
373 * @since 1.9
374 *
375 * @return void
376 */
377 public function save_translations() {
378 check_admin_referer( 'string-translation', '_wpnonce_string-translation' );
379
380 if ( ! empty( $_POST['submit'] ) ) {
381 foreach ( $this->languages as $language ) {
382 if ( empty( $_POST['translation'][ $language->slug ] ) || ! is_array( $_POST['translation'][ $language->slug ] ) ) { // In case the language filter is active ( thanks to John P. Bloch )
383 continue;
384 }
385
386 $translations = array_map( 'trim', (array) wp_unslash( $_POST['translation'][ $language->slug ] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
387
388 $mo = new PLL_MO();
389 $mo->import_from_db( $language );
390
391 foreach ( $translations as $key => $translation ) {
392 /**
393 * Filters the string translation before it is saved in DB.
394 * Allows to sanitize strings registered with pll_register_string().
395 *
396 * @since 1.6
397 * @since 2.7 The translation passed to the filter is unslashed.
398 * @since 3.7 Add original string as 4th parameter.
399 *
400 * @param string $translation The string translation.
401 * @param string $name The name as defined in pll_register_string.
402 * @param string $context The context as defined in pll_register_string.
403 * @param string $original The original string to translate.
404 */
405 $translation = apply_filters( 'pll_sanitize_string_translation', $translation, $this->strings[ $key ]['name'], $this->strings[ $key ]['context'], $this->strings[ $key ]['string'] );
406 $mo->add_entry(
407 $mo->make_entry(
408 $this->strings[ $key ]['string'],
409 $translation
410 )
411 );
412 }
413
414 // Clean database ( removes all strings which were registered some day but are no more )
415 if ( ! empty( $_POST['clean'] ) ) {
416 $new_mo = new PLL_MO();
417
418 foreach ( $this->strings as $string ) {
419 $new_mo->add_entry( $mo->make_entry( $string['string'], $mo->translate( $string['string'] ) ) );
420 }
421 }
422
423 isset( $new_mo ) ? $new_mo->export_to_db( $language ) : $mo->export_to_db( $language );
424 }
425
426 pll_add_notice( new WP_Error( 'pll_strings_translations_updated', __( 'Translations updated.', 'polylang' ), 'success' ) );
427
428 /**
429 * Fires after the strings translations are saved in DB
430 *
431 * @since 1.2
432 */
433 do_action( 'pll_save_strings_translations' );
434 }
435
436 // Unregisters strings registered through WPML API
437 if ( $this->current_action() === 'delete' && ! empty( $_POST['strings'] ) && function_exists( 'icl_unregister_string' ) ) {
438 foreach ( array_map( 'sanitize_key', $_POST['strings'] ) as $key ) {
439 icl_unregister_string( $this->strings[ $key ]['context'], $this->strings[ $key ]['name'] );
440 }
441 }
442
443 // To refresh the page ( possible thanks to the $_GET['noheader']=true )
444 $args = array_intersect_key( $_REQUEST, array_flip( array( 's', 'paged', 'group' ) ) );
445 if ( ! empty( $_GET['paged'] ) && ! empty( $_POST['submit'] ) ) {
446 $args['paged'] = (int) $_GET['paged']; // Don't rely on $_REQUEST['paged'] or $_POST['paged']. See #14
447 }
448 if ( ! empty( $args['s'] ) ) {
449 $args['s'] = urlencode( $args['s'] ); // Searched string needs to be encoded as it comes from $_POST
450 }
451 PLL_Settings::redirect( $args );
452 }
453 }
454