PluginProbe
WPGlobus / trunk
WPGlobus vtrunk
3.0.5 3.0.4 trunk 2.1.15 2.10.10 2.12.2 2.2.35 2.3.12 2.4.17 2.5.23 2.6.8 2.7.14 2.8.11 3.0.0 3.0.1 3.0.2 3.0.3
wpglobus / includes / admin / class-wpglobus-clean.php

class-wpglobus-clean.php in WPGlobus trunk, at includes/admin/class-wpglobus-clean.php

668 lines 19.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WPGlobus_Clean
4 *
5 * @since 1.4.3
6 * @package WPGlobus/Admin
7 */
8
9 use WPGLIB\Txt;
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit; // Exit if accessed directly
13 }
14
15 if ( ! class_exists( 'WPGlobus_Clean' ) ) :
16
17 /**
18 * Class WPGlobus_Clean
19 */
20 class WPGlobus_Clean {
21
22 protected const LOG_BASENAME = 'wpglobus-clean';
23
24 protected static $tables = array();
25
26 protected static $log_file = '';
27
28 protected static $dry_run = false;
29
30 /**
31 * Controller
32 */
33 public static function controller() {
34
35 if ( defined( 'WPGLOBUS_CLEAN_DRY_RUN' ) ) {
36 self::$dry_run = (bool) WPGLOBUS_CLEAN_DRY_RUN;
37 }
38
39 self::set_log_file( true );
40
41 self::get_table();
42
43 self::screen();
44
45 add_action(
46 'admin_footer',
47 array(
48 'WPGlobus_Clean',
49 'action__admin_print_scripts',
50 ),
51 99
52 );
53 }
54
55 /**
56 * Initialize the class variable `log_file`.
57 * Note: 'wp-content' can be set to a different path, so we are using the standard WP method.
58 *
59 * @todo Check if the folder exists and file is writeable.
60 */
61 protected static function set_log_file( bool $init = false ): void {
62
63 $upload_dir = wp_upload_dir();
64 $wpglobus_logs_dir = $upload_dir['basedir'] . '/wpglobus-logs';
65
66 wp_mkdir_p( $wpglobus_logs_dir );
67
68 self::$log_file = $wpglobus_logs_dir . '/' . self::LOG_BASENAME . '.log';
69
70 // Protect the folder from reading via URL
71
72 /**
73 * WP_Filesystem
74 *
75 * @global WP_Filesystem_Direct $wp_filesystem
76 */
77 global $wp_filesystem;
78 if ( ! $wp_filesystem ) {
79 require_once ABSPATH . 'wp-admin/includes/file.php';
80 if ( ! WP_Filesystem() ) {
81 return;
82 }
83 }
84
85 if ( ! $wp_filesystem->exists( $wpglobus_logs_dir . '/.htaccess' ) ) {
86 $wp_filesystem->put_contents( $wpglobus_logs_dir . '/.htaccess', 'deny from all' );
87 }
88 if ( ! $wp_filesystem->exists( $wpglobus_logs_dir . '/index.php' ) ) {
89 $wp_filesystem->put_contents( $wpglobus_logs_dir . '/index.php', '' );
90 }
91
92 if ( $init ) {
93 // Create or empty the log file.
94 $wp_filesystem->put_contents( self::$log_file, '' );
95 }
96 }
97
98 /**
99 * Logger
100 *
101 * @param string $table
102 * @param string $id
103 * @param string $initial
104 * @param string $converted
105 */
106 protected static function log( $table = '', $id = '', $initial = '', $converted = '' ): void {
107 static $fn_log = 'error_log';
108
109 $after = empty( $converted ) ? '(empty)' : maybe_serialize( $converted );
110
111 $fn_log(
112 implode( '|', array(
113 gmdate( DATE_ATOM ),
114 'INFO',
115 'TABLE: ' . $table,
116 'ID: ' . $id,
117 'BEFORE: ' . maybe_serialize( $initial ),
118 'AFTER: ' . $after,
119 ) )
120 . "\n", 3, self::$log_file
121 );
122 }
123
124 protected static function log_debug( string $title, string $message = '' ): void {
125 static $fn_log = 'error_log';
126
127 $fn_log(
128 implode( '|', array(
129 gmdate( DATE_ATOM ),
130 'DEBUG',
131 maybe_serialize( $title ),
132 maybe_serialize( $message ),
133 ) )
134 . "\n", 3, self::$log_file
135 );
136 }
137
138 /**
139 * Get table
140 *
141 * @param string $table Table name to set data.
142 *
143 * @return void
144 */
145 public static function get_table( $table = '' ) {
146
147 $get_all = false;
148 if ( '' === $table ) {
149 $get_all = true;
150 }
151
152 if ( 'posts' === $table || $get_all ) {
153
154 // Table posts
155 $posts = new stdClass();
156 $posts->include_fields = array(
157 'post_content',
158 'post_title',
159 'post_excerpt',
160 'post_content_filtered',
161 );
162 $posts->id_field = 'ID';
163 $posts->post_status = array(
164 'publish',
165 'draft',
166 );
167
168 self::$tables['posts'] = $posts;
169
170 }
171
172 if ( 'postmeta' === $table || $get_all ) {
173
174 // Table postmeta
175 $postmeta = new stdClass();
176 $postmeta->include_fields = array(
177 'meta_value',
178 );
179 $postmeta->id_field = 'meta_id';
180
181 self::$tables['postmeta'] = $postmeta;
182
183 }
184
185 if ( 'options' === $table || $get_all ) {
186
187 // Table options
188 $options = new stdClass();
189 $options->include_fields = array(
190 'option_value',
191 );
192 $options->id_field = 'option_id';
193
194 self::$tables['options'] = $options;
195
196 }
197
198 if ( 'terms' === $table || $get_all ) {
199
200 // Table terms
201 $terms = new stdClass();
202 $terms->include_fields = array(
203 'name',
204 );
205 $terms->id_field = 'term_id';
206
207 self::$tables['terms'] = $terms;
208
209 }
210
211 if ( 'term_taxonomy' === $table || $get_all ) {
212
213 // Table term_taxonomy
214 $term_taxonomy = new stdClass();
215 $term_taxonomy->include_fields = array(
216 'description',
217 );
218 $term_taxonomy->id_field = 'term_taxonomy_id';
219
220 self::$tables['term_taxonomy'] = $term_taxonomy;
221
222 }
223
224 if ( 'usermeta' === $table || $get_all ) {
225
226 // Table usermeta
227 $usermeta = new stdClass();
228 $usermeta->include_fields = array(
229 'meta_value',
230 );
231
232 $usermeta->id_field = 'umeta_id';
233
234 self::$tables['usermeta'] = $usermeta;
235
236 }
237
238 if ( class_exists( 'WooCommerce' ) ) :
239
240 /**
241 * WooCommerce tables
242 */
243 if ( 'woocommerce_attribute_taxonomies' === $table || $get_all ) {
244
245 // Table woocommerce_attribute_taxonomies
246 $woocommerce_attribute_taxonomies = new stdClass();
247 $woocommerce_attribute_taxonomies->include_fields = array(
248 'attribute_label',
249 );
250 $woocommerce_attribute_taxonomies->id_field = 'attribute_id';
251
252 self::$tables['woocommerce_attribute_taxonomies'] = $woocommerce_attribute_taxonomies;
253 }
254
255 if ( 'woocommerce_order_items' === $table || $get_all ) {
256 // Table woocommerce_order_items
257 $woocommerce_order_items = new stdClass();
258 $woocommerce_order_items->include_fields = array(
259 'order_item_name',
260 );
261 $woocommerce_order_items->id_field = 'order_item_id';
262
263 self::$tables['woocommerce_order_items'] = $woocommerce_order_items;
264 }
265
266 endif;
267 }
268
269 /**
270 * Get table list
271 */
272 public static function get_table_list() {
273
274 $spinner = WPGlobus::$PLUGIN_DIR_URL . 'includes/css/images/spinner.gif';
275
276 $list = '<ul>';
277 foreach ( self::$tables as $table => $data ) {
278
279 $list .= '<li id="' . $table . '">';
280 $list .= '<span class="wpglobus-spinner" style="float:left;margin-right:10px;"><img src="' . $spinner . '" alt=""/></span>';
281 $list .= '<span class="wpglobus-result" style="float:left;width:20px;height:20px;"></span>';
282 $list .= '<span class=""><input type="checkbox" id="cb-' . $table . '" checked disabled /></span>';
283 $list .= $table;
284 $list .= '</li>';
285
286 }
287 $list .= '<li id="wpglobus_options">';
288 $list .= '<span class="wpglobus-spinner" style="float:left;margin-right:10px;"><img src="' . $spinner . '" alt=""/></span>';
289 $list .= '<span class="wpglobus-result" style="float:left;width:20px;height:20px;"></span>';
290 $list .= '<span class=""><input type="checkbox" id="cb-wpglobus_options" name="cb-wpglobus_options" /></span>';
291 $list .= esc_html( __( 'Remove the WPGlobus settings (not recommended)', 'wpglobus' ) );
292 $list .= ' - ';
293 $list .= esc_html( __( 'note that WPGlobus extensions settings will be removed also!', 'wpglobus' ) );
294 $list .= '</li>';
295 $list .= '</ul>';
296
297 return $list;
298 }
299
300 /**
301 * Ajax action handler
302 *
303 * @param string[] $order
304 */
305 public static function process_ajax( $order ) {
306
307 if ( defined( 'WPGLOBUS_CLEAN_DRY_RUN' ) ) {
308 self::$dry_run = (bool) WPGLOBUS_CLEAN_DRY_RUN;
309 }
310
311 self::set_log_file();
312
313 $_log = false;
314
315 if ( 'true' === $order['log'] ) {
316 $_log = true;
317 }
318
319 if ( 'die' === $order['action'] ) {
320 wp_send_json_success( $order );
321 }
322
323 if ( 'wpglobus-reset' === $order['action'] ) {
324
325 global $wpdb;
326
327 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
328 $option_records = $wpdb->get_results( "SELECT `option_name`, `option_value` FROM $wpdb->options WHERE `option_name` REGEXP 'wpglobus'", ARRAY_A );
329 if ( ! empty( $option_records ) ) {
330 foreach ( $option_records as $option ) {
331 self::log_debug( 'delete_option', implode( '|',
332 array(
333 $option['option_name'],
334 $option['option_value'],
335 )
336 ) );
337 if ( ! self::$dry_run ) {
338 delete_option( $option['option_name'] );
339 }
340 }
341 }
342
343 wp_send_json_success( $order );
344 }
345
346 if ( 'clean' !== $order['action'] ) {
347 wp_send_json_error( $order );
348 }
349
350 self::get_table( $order['table'] );
351
352 global $wpdb;
353
354 /**
355 * Make table with prefix
356 */
357 $table = esc_sql( $wpdb->prefix . $order['table'] );
358
359 /** @var StdClass $_o_table */
360 $_o_table = self::$tables[ $order['table'] ];
361
362 /**
363 * Set record ID
364 */
365 if ( ! empty( $_o_table->id_field ) ) {
366 $id = esc_sql( $_o_table->id_field );
367 } else {
368 wp_send_json_error( $order );
369 }
370
371 /** @var array $_include_fields */
372 $_include_fields = $_o_table->include_fields;
373 if ( count( $_include_fields ) === 0 ) {
374 // Impossible: no fields to process.
375 wp_send_json_error( $order );
376 }
377
378 /**
379 * Comma-separated list of include fields, prepared for SQL.
380 *
381 * @var string $sql_list_include_fields
382 */
383 $sql_list_include_fields = $wpdb->prepare( implode( ', ', array_fill( 0, count( $_include_fields ), '%i' ) ), ...$_include_fields );
384 self::log_debug( '$sql_list_include_fields', $sql_list_include_fields );
385
386 /**
387 * Set condition
388 *
389 * @note REGEXP is not a variable. Hardcoded because each `prepare` adds slashes.
390 */
391
392 if ( count( $_include_fields ) === 1 ) {
393 // One field
394 $sql_select_records_with_language_marks = $wpdb->prepare( 'SELECT %i FROM %i WHERE %i REGEXP %s', $id, $table, $_include_fields[0], '(\\{|\\[|<!--):[a-z][a-z]' );
395
396 } else {
397 // Multiple fields
398 $condition = esc_sql( 'AND (0=1' );
399 foreach ( $_include_fields as $field ) {
400 $condition .= $wpdb->prepare( ' OR %i REGEXP %s', esc_sql( $field ), '(\{|\[|<!--):[a-z][a-z]' );
401 }
402 $condition .= ')';
403 // FALSE POSITIVE: $condition is prepared above!
404 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
405 $sql_select_records_with_language_marks = $wpdb->prepare( "SELECT %i FROM %i WHERE 1=1 $condition", $id, $table );
406 }
407 self::log_debug( '$sql_select_records_with_language_marks', $sql_select_records_with_language_marks );
408
409 // FALSE POSITIVE: $sql_select_records_with_language_marks is prepared above!
410 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
411 $ids = $wpdb->get_results( $sql_select_records_with_language_marks, ARRAY_A );
412
413 $result = true;
414
415 foreach ( $ids as $data ) {
416
417 foreach ( $data as $field_id => $record_id ) {
418
419 // FALSE POSITIVE: $sql_list_include_fields is prepared above!
420 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
421 $sql_select_get_one_row = $wpdb->prepare( "SELECT $sql_list_include_fields FROM %i WHERE %i = %s", $table, $field_id, $record_id );
422
423 // FALSE POSITIVE: $sql_select_get_one_row is prepared above!
424 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
425 $record = $wpdb->get_results( $sql_select_get_one_row );
426
427 foreach ( $_include_fields as $include_field ) {
428
429 $value = $record[0]->$include_field;
430
431 $value = trim( $value );
432 if ( empty( $value ) ) {
433 continue;
434 }
435
436 $serialized = false;
437 if ( is_serialized( $value ) ) {
438 $serialized = true;
439 $value = maybe_unserialize( $value );
440 }
441
442 $converted = WPGlobus_Core::text_filter( $value, WPGlobus::Config()->default_language, WPGlobus::RETURN_EMPTY );
443
444 if ( $_log ) {
445 self::log( $table, $record_id, $value, $converted );
446 }
447
448 if ( $serialized ) {
449 $converted = maybe_serialize( $converted );
450 }
451
452 $sql_update = $wpdb->prepare( "UPDATE %i SET %i = %s WHERE %i = %d", $table, $include_field, $converted, $field_id, $record_id );
453 self::log_debug( '$sql_update', $sql_update );
454
455 if ( self::$dry_run ) {
456 continue;
457 }
458
459 // FALSE POSITIVE: $sql_update is prepared above!
460 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
461 $_update_successful = $wpdb->query( $sql_update );
462 if ( ! $_update_successful ) {
463 $result = false;
464 break;
465 }
466 } // endforeach;
467 } // endforeach
468 } // endforeach
469
470 if ( false === $result ) {
471 wp_send_json_error( $order );
472 }
473
474 wp_send_json_success( $order );
475 }
476
477 /**
478 * Enqueue js
479 *
480 * @return void
481 */
482 public static function action__admin_print_scripts() {
483
484 wp_enqueue_script(
485 'wpglobus-clean',
486 WPGlobus::$PLUGIN_DIR_URL . 'includes/js/wpglobus-clean' . WPGlobus::SCRIPT_SUFFIX() . '.js',
487 array( 'jquery' ),
488 WPGLOBUS_VERSION,
489 true
490 );
491
492 wp_localize_script(
493 'wpglobus-clean',
494 'WPGlobusClean',
495 array(
496 'version' => WPGLOBUS_VERSION,
497 'icons' => array(
498 'success' => WPGlobus::$PLUGIN_DIR_URL . 'includes/css/images/success.png',
499 'error' => WPGlobus::$PLUGIN_DIR_URL . 'includes/css/images/error.png',
500 ),
501 'data' => array_merge( self::$tables, array( 'wpglobus_options' => new stdClass() ) ),
502 )
503 );
504 }
505
506 /**
507 * Output the clean screen.
508 *
509 * @return void
510 */
511 public static function screen() {
512
513 /**
514 * WP anti-hacks.
515 *
516 * @since 2.12.1
517 */
518 if ( ! current_user_can( 'manage_options' ) ) {
519 ?>
520 <div class="wrap about-wrap wpglobus-about-wrap clean-wrap wpglobus-clean">
521 <h1 class="wpglobus"><span class="wpglobus-wp">WP</span>Globus
522 <span class="wpglobus-version"><?php echo esc_html( WPGLOBUS_VERSION ); ?></span>
523 </h1>
524 <h4>Unauthorized user</h4>
525 </div>
526 <?php
527 return;
528 }
529
530 /**
531 * For Google Analytics
532 */
533 /**
534 * $ga_campaign = '?utm_source=wpglobus-admin-clean&utm_medium=link&utm_campaign=clean-up-tool';
535 * $url_wpglobus_site = WPGlobus_Utils::url_wpglobus_site();
536 * $url_wpglobus_site_contact = $url_wpglobus_site . 'pg/contact-us/' . $ga_campaign;
537 * $url_wpglobus_site_quick_start = $url_wpglobus_site . 'quick-start/' . $ga_campaign;
538 * // $url_wpglobus_site_home = $url_wpglobus_site . $ga_campaign;
539 * // $url_wpglobus_site_faq = $url_wpglobus_site . 'faq/' . $ga_campaign;
540 * // $url_wpglobus_site_pro_support = $url_wpglobus_site . 'professional-support/' . $ga_campaign;
541 */
542 $url_wpglobus_logo = WPGlobus::$PLUGIN_DIR_URL . 'includes/css/images/wpglobus-logo-180x180.png';
543
544 // @formatter:off
545 ?>
546 <style>
547 .wp-badge.wpglobus-badge {
548 background: #ffffff url(<?php echo esc_url( $url_wpglobus_logo ); ?>) no-repeat;
549 background-size: contain;
550 }
551 </style>
552 <?php
553 // @formatter:on
554 ?>
555 <div class="wrap about-wrap wpglobus-about-wrap clean-wrap wpglobus-clean">
556 <h1 class="wpglobus"><span class="wpglobus-wp">WP</span>Globus
557 <span class="wpglobus-version"><?php echo esc_html( WPGLOBUS_VERSION ); ?></span>
558 </h1>
559
560 <div class="wpglobus-motto"><?php esc_html_e( 'Multilingual WordPress', 'wpglobus' ); ?></div>
561
562 <div class="about-text">
563 <?php esc_html_e( 'WPGlobus is a family of WordPress plugins assisting you in making multilingual WordPress blogs and sites.', 'wpglobus' ); ?>
564 </div>
565
566 <div class="wp-badge wpglobus-badge"></div>
567
568 <h2 class="nav-tab-wrapper">
569 <a href="#" class="nav-tab nav-tab-active">
570 <?php
571 esc_html_e( 'Clean-up Tool', 'wpglobus' );
572 if ( self::$dry_run ) {
573 echo ' ( DRY RUN )';
574 }
575 ?>
576 </a>
577 </h2>
578
579 <div class="wp-ui-notification" style="padding: .5em; margin-top: 1em;">
580 <?php esc_html_e( 'WARNING: this operation is non-reversible. It is strongly recommended that you backup your database before proceeding.', 'wpglobus' ); ?>
581 </div>
582
583 <?php if ( ! self::$dry_run ) { ?>
584 <div class="wp-ui-notification" style="padding: .5em;">
585 Put <code>define( 'WPGLOBUS_CLEAN_DRY_RUN', true );</code>
586 in <code>wp-config.php</code>
587 to check what will be done without performing the actual database changes.
588 All actions will be recorded in the log file.
589 </div>
590 <?php } ?>
591
592 <div style="padding: .5em">
593 <p><strong>
594 1. <?php esc_html_e( 'This tool should be used only if you plan to completely uninstall WPGlobus. By running it, you will remove ALL translations you have entered to your post, pages, etc., keeping only the MAIN language texts. Please make sure that all entries have some content in the main language. Otherwise, you might end up with empty titles, no content, no excerpts, blank comments and so on.', 'wpglobus' ); ?>
595 </strong></p>
596 <p><strong>
597 2. <?php esc_html_e( 'Make sure that your active theme does not have any code related to WPGlobus. Such code could be added by you or by a 3rd party developer. If that code runs without first verifying that WPGlobus is active, WordPress may die with a fatal error.', 'wpglobus' ); ?>
598 </strong></p>
599 </div>
600
601 <div class="wp-ui-notification" style="padding: .5em;">
602 <?php
603 $_message = esc_html(
604 sprintf( // translators: %1$s - language name, %2$s - language code. Do not remove.
605 __( 'The main language is currently set to %1$s (%2$s). ALL TEXTS THAT ARE NOT IN %1$s WILL BE DELETED! To change the main language, please go to {{settings}}.', 'wpglobus' ),
606 WPGlobus::Config()->en_language_name[ WPGlobus::Config()->default_language ],
607 WPGlobus::Config()->default_language
608 )
609 );
610
611 $_settings_link = '<a href="' . esc_url( WPGlobus_Admin_Page::url_settings( 'languages' ) ) . '">' . esc_html( Txt::t( 'Settings' ) ) . '</a>';
612 echo wp_kses_post( str_replace( '{{settings}}', $_settings_link, $_message ) );
613 ?>
614 </div>
615
616 <hr/>
617 <h3 id="about-to-clean">
618 <?php esc_html_e( 'You are about to clean the content of the following database tables:', 'wpglobus' ); ?>
619 </h3>
620
621 <?php echo wp_kses( self::get_table_list(), WPGlobus_WP::allowed_post_tags_extended() ); ?>
622
623 <hr/>
624
625 <h3>
626 <?php esc_html_e( 'The operations log', 'wpglobus' ); ?>
627 </h3>
628 <div>
629 <?php esc_html_e( 'We are going to write a detailed log of all the database changes performed. It should help in the case you need to restore something important. The log will be written to the file:', 'wpglobus' ); ?>
630 </div>
631 <br/>
632 <code>
633 <?php echo esc_html( self::$log_file ); ?>
634 </code>
635 <br/>
636 <br/>
637 <label>
638 <input type="checkbox" name="wpglobus-clean-log" id="wpglobus-clean-log" checked="checked"/>
639 <?php esc_html_e( 'Uncheck if you do not want to write the operations log (we recommend to keep it checked)', 'wpglobus' ); ?>
640
641 </label>
642 <hr/>
643 <h3>
644 <?php esc_html_e( 'You have been warned...', 'wpglobus' ); ?>
645 </h3>
646 <?php esc_html_e( 'Please confirm by checking the box below:', 'wpglobus' ); ?>
647 <div class="wp-ui-notification" style="padding: .5em;">
648 <?php esc_html_e( 'I have read and understood everything written on this page. I am aware that by using this tool I may loose some content of my website. I have made a database backup and know how to restore it if necessary. I am fully responsible for the results.', 'wpglobus' ); ?>
649 </div>
650
651 <label><input type="checkbox" name="wpglobus-clean-activate"
652 id="wpglobus-clean-activate"/><?php esc_html_e( 'YES, I CONFIRM', 'wpglobus' ); ?>
653 </label>
654 <div class="return-to-dashboard">
655 <a id="wpglobus-clean-button" class="button button-primary hidden" href="#about-to-clean">
656 <?php esc_html_e( 'Process with the Clean-up', 'wpglobus' ); ?>
657 </a>
658 </div>
659
660 </div>
661
662 <?php
663 }
664
665 }
666
667 endif;
668