PluginProbe
WebberZone Top 10 — Popular Posts / 4.4.0
WebberZone Top 10 — Popular Posts v4.4.0
4.5.1 4.5.0 4.4.3 4.4.2 4.4.1 4.4.0 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 trunk 1.0 1.0.1 1.1 1.2 1.3 1.4 1.4.1 1.5 1.5.1 1.5.2 1.5.3 1.6 1.6.1 All 117 releases
top-10 / includes / admin / class-tools-page.php

class-tools-page.php in WebberZone Top 10 — Popular Posts 4.4.0, at includes/admin/class-tools-page.php

859 lines 30.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Tools Page class.
4 *
5 * @package WebberZone\Top_Ten\Admin
6 */
7
8 namespace WebberZone\Top_Ten\Admin;
9
10 use WebberZone\Top_Ten\Database;
11 use WebberZone\Top_Ten\Counter;
12 use WebberZone\Top_Ten\Admin\Activator;
13 use WebberZone\Top_Ten\Util\Hook_Registry;
14
15 if ( ! defined( 'WPINC' ) ) {
16 die;
17 }
18
19 /**
20 * Generates the Tools page.
21 *
22 * @since 3.3.0
23 */
24 class Tools_Page {
25
26 /**
27 * Parent Menu ID.
28 *
29 * @since 3.3.0
30 *
31 * @var string Parent Menu ID.
32 */
33 public $parent_id;
34
35 /**
36 * Constructor class.
37 *
38 * @since 3.3.0
39 */
40 public function __construct() {
41 Hook_Registry::add_action( 'admin_menu', array( $this, 'admin_menu' ) );
42 Hook_Registry::add_action( 'network_admin_menu', array( $this, 'network_admin_menu' ), 11 );
43 Hook_Registry::add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
44 Hook_Registry::add_action( 'admin_init', array( $this, 'handle_recreate_tables_action' ) );
45 Hook_Registry::add_action( 'admin_init', array( $this, 'handle_create_missing_tables_action' ) );
46
47 // Clear table statistics cache when counts are updated.
48 Hook_Registry::add_action( 'tptn_count_updated', array( 'WebberZone\Top_Ten\Database', 'clear_table_statistics_cache' ) );
49 Hook_Registry::add_action( 'tptn_delete_counts', array( 'WebberZone\Top_Ten\Database', 'clear_table_statistics_cache' ) );
50 Hook_Registry::add_action( 'tptn_set_count', array( 'WebberZone\Top_Ten\Database', 'clear_table_statistics_cache' ) );
51 }
52
53 /**
54 * Admin Menu.
55 *
56 * @since 3.3.0
57 */
58 public function admin_menu() {
59
60 $this->parent_id = add_submenu_page(
61 'tptn_dashboard',
62 esc_html__( 'Top 10 Tools', 'top-10' ),
63 esc_html__( 'Tools', 'top-10' ),
64 'manage_options',
65 'tptn_tools_page',
66 array( $this, 'render_page' )
67 );
68
69 add_action( 'load-' . $this->parent_id, array( $this, 'help_tabs' ) );
70 }
71
72 /**
73 * Admin Menu.
74 *
75 * @since 3.3.0
76 */
77 public function network_admin_menu() {
78
79 $this->parent_id = add_submenu_page(
80 'tptn_dashboard',
81 esc_html__( 'Top 10 Tools', 'top-10' ),
82 esc_html__( 'Tools', 'top-10' ),
83 'manage_network_options',
84 'tptn_network_tools_page',
85 array( $this, 'render_page' )
86 );
87
88 add_action( 'load-' . $this->parent_id, array( $this, 'help_tabs' ) );
89 }
90
91 /**
92 * Enqueue scripts in admin area.
93 *
94 * @since 3.3.0
95 *
96 * @param string $hook The current admin page.
97 */
98 public function admin_enqueue_scripts( $hook ) {
99 if ( $hook === $this->parent_id ) {
100 wp_enqueue_script( 'top-ten-admin-js' );
101 wp_enqueue_style( 'top-ten-admin-css' );
102 wp_enqueue_style( 'wp-spinner' );
103 wp_localize_script(
104 'top-ten-admin-js',
105 'top_ten_admin_data',
106 array(
107 'ajax_url' => admin_url( 'admin-ajax.php' ),
108 'security' => wp_create_nonce( 'tptn-admin' ),
109 'strings' => array(
110 'confirm_message' => esc_html__( 'Are you sure you want to clear the cache?', 'top-10' ),
111 'clearing_text' => esc_html__( 'Clearing...', 'top-10' ),
112 'fail_message' => esc_html__( 'Failed to clear cache. Please try again.', 'top-10' ),
113 'request_fail_message' => esc_html__( 'Request failed: ', 'top-10' ),
114 ),
115 )
116 );
117 }
118 }
119
120 /**
121 * Render the tools settings page.
122 *
123 * @since 2.5.0
124 *
125 * @return void
126 */
127 public function render_page() {
128 $screen = get_current_screen();
129 $network_wide = false;
130
131 if ( $screen->id === $this->parent_id . '-network' ) {
132 $network_wide = true;
133 }
134
135 /* Truncate overall posts table */
136 if ( isset( $_POST['tptn_recreate_primary_key'] ) && check_admin_referer( 'tptn-tools-settings' ) ) {
137 self::recreate_primary_key();
138 add_settings_error( 'tptn-notices', '', esc_html__( 'Primary Key has been recreated', 'top-10' ), 'updated' );
139 }
140
141 /* Truncate overall posts table */
142 if ( isset( $_POST['tptn_reset_overall'] ) && check_admin_referer( 'tptn-tools-settings' ) ) {
143 if ( ! is_multisite() || $network_wide ) {
144 Database::truncate_table( Database::get_table( false ) );
145 } else {
146 Counter::delete_counts( array( 'daily' => false ) );
147 }
148 add_settings_error( 'tptn-notices', '', esc_html__( 'Top 10 popular posts reset', 'top-10' ), 'updated' );
149 }
150
151 /* Truncate daily posts table */
152 if ( isset( $_POST['tptn_reset_daily'] ) && check_admin_referer( 'tptn-tools-settings' ) ) {
153 if ( ! is_multisite() || $network_wide ) {
154 Database::truncate_table( Database::get_table( true ) );
155 } else {
156 Counter::delete_counts( array( 'daily' => true ) );
157 }
158 add_settings_error( 'tptn-notices', '', esc_html__( 'Top 10 daily popular posts reset', 'top-10' ), 'updated' );
159 }
160
161 /* Recreate tables */
162 if ( isset( $_POST['tptn_recreate_tables'] ) && check_admin_referer( 'tptn-tools-settings' ) ) {
163 $result = self::recreate_tables();
164 if ( is_wp_error( $result ) ) {
165 add_settings_error( 'tptn-notices', '', $result->get_error_message(), 'error' );
166 } else {
167 add_settings_error( 'tptn-notices', '', esc_html__( 'Top 10 tables have been recreated', 'top-10' ), 'updated' );
168 }
169 }
170
171 /* Sync funnel (run aggregation now) */
172 if ( isset( $_POST['tptn_sync_funnel'] ) && check_admin_referer( 'tptn-tools-settings' ) ) {
173 $result = Database::aggregate_visit_log();
174 if ( true === $result ) {
175 add_settings_error( 'tptn-notices', '', esc_html__( 'Funnel has been synced. Buffered visits have been aggregated.', 'top-10' ), 'updated' );
176 } elseif ( 0 === $result ) {
177 add_settings_error( 'tptn-notices', '', esc_html__( 'Nothing to sync. The funnel is empty.', 'top-10' ), 'updated' );
178 } elseif ( false === $result ) {
179 add_settings_error( 'tptn-notices', '', esc_html__( 'Sync skipped: another aggregation is already in progress. Please try again in a moment.', 'top-10' ), 'updated' );
180 } elseif ( is_wp_error( $result ) ) {
181 add_settings_error(
182 'tptn-notices',
183 '',
184 sprintf(
185 /* translators: %s: error message from the database */
186 esc_html__( 'Sync failed: %s', 'top-10' ),
187 esc_html( $result->get_error_message() )
188 ),
189 'error'
190 );
191 }
192 }
193
194 /* Fix cron schedules */
195 if ( isset( $_POST['tptn_fix_crons'] ) && check_admin_referer( 'tptn-tools-settings' ) ) {
196 $result = self::fix_crons();
197 if ( is_wp_error( $result ) ) {
198 add_settings_error( 'tptn-notices', '', implode( ' ', array_map( 'esc_html', $result->get_error_messages() ) ), 'error' );
199 } else {
200 add_settings_error( 'tptn-notices', '', esc_html( $result ), 'updated' );
201 }
202 }
203
204 ob_start();
205 ?>
206 <div class="wrap">
207 <h1><?php esc_html_e( 'Top 10 Tools', 'top-10' ); ?></h1>
208 <?php do_action( 'tptn_settings_page_header' ); ?>
209
210 <?php settings_errors(); ?>
211
212 <div id="poststuff">
213 <div id="post-body" class="metabox-holder columns-2">
214 <div id="post-body-content">
215
216 <form method="post" >
217
218 <div class="postbox">
219 <h2><span><?php esc_html_e( 'Status', 'top-10' ); ?></span></h2>
220 <div class="inside">
221 <div class="tptn-db-status">
222 <?php echo self::get_db_status_report(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
223 </div>
224 </div>
225 </div>
226
227 <div class="postbox">
228 <h2><span><?php esc_html_e( 'Clear cache', 'top-10' ); ?></span></h2>
229 <div class="inside">
230 <p>
231 <?php
232 printf(
233 '<button type="button" name="tptn_cache_clear" class="button button-secondary tptn_cache_clear" aria-label="%1$s">%1$s</button>',
234 esc_html__( 'Clear cache', 'top-10' )
235 );
236 ?>
237 </p>
238 <p class="description">
239 <?php esc_html_e( 'Clear the Top 10 cache. This will also be cleared automatically when you save the settings page.', 'top-10' ); ?>
240 </p>
241 </div>
242 </div>
243
244 <div class="postbox">
245 <h2><span><?php esc_html_e( 'Sync Funnel', 'top-10' ); ?></span></h2>
246 <div class="inside">
247 <p>
248 <button name="tptn_sync_funnel" type="submit" id="tptn_sync_funnel" class="button button-secondary"><?php esc_attr_e( 'Sync Funnel Now', 'top-10' ); ?></button>
249 </p>
250 <p class="description">
251 <?php esc_html_e( 'Drain the visits funnel into the count tables immediately. This is equivalent to running the aggregation cron job.', 'top-10' ); ?>
252 </p>
253 </div>
254 </div>
255
256 <div class="postbox">
257 <h2><span><?php esc_html_e( 'Fix Cron Schedules', 'top-10' ); ?></span></h2>
258 <div class="inside">
259 <p>
260 <button name="tptn_fix_crons" type="submit" id="tptn_fix_crons" class="button button-secondary"><?php esc_attr_e( 'Fix Cron Schedules', 'top-10' ); ?></button>
261 </p>
262 <p class="description">
263 <?php esc_html_e( 'Clears and reschedules the Top 10 cron jobs (maintenance and aggregation). Use this if the status above shows a job as not scheduled or if your error log reports cron reschedule errors for the Top 10 hooks.', 'top-10' ); ?>
264 </p>
265 </div>
266 </div>
267
268 <div class="postbox">
269 <h2><span><?php esc_html_e( 'Recreate Primary Key', 'top-10' ); ?></span></h2>
270 <div class="inside">
271 <p>
272 <button name="tptn_recreate_primary_key" type="submit" id="tptn_recreate_primary_key" class="button button-secondary"><?php esc_attr_e( 'Recreate Primary Key', 'top-10' ); ?></button>
273 </p>
274 <p class="description">
275 <?php esc_html_e( 'Deletes and reinitializes the primary key in the database tables. If the above function gives an error, then you can run the below code in phpMyAdmin or Adminer. Remember to backup your database first!', 'top-10' ); ?>
276 </p>
277 <p>
278 <code style="display:block;"><?php echo self::recreate_primary_key_html(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></code>
279 </p>
280 </div>
281 </div>
282
283 <div class="postbox">
284 <h2><span><?php esc_html_e( 'Reset database', 'top-10' ); ?></span></h2>
285 <div class="inside">
286 <p class="description">
287 <?php esc_html_e( 'This will reset the Top 10 tables. If this is a multisite install, this will reset the popular posts for the current site. If this is the Network Admin screen, then it will reset the popular posts across all sites. This cannot be reversed. Make sure that your database has been backed up before proceeding', 'top-10' ); ?>
288 </p>
289 <p>
290 <?php
291 printf(
292 '<button name="tptn_reset_overall" type="submit" id="tptn_reset_overall" class="button button-secondary" style="color:#fff;background-color: #a00;border-color: #900;" onclick="if (!confirm(\'%s\')) return false;">%s</button>',
293 esc_attr__( 'Are you sure you want to reset the popular posts?', 'top-10' ),
294 esc_attr__( 'Reset Popular Posts', 'top-10' )
295 );
296 ?>
297 </p>
298 <p>
299 <?php
300 printf(
301 '<button name="tptn_reset_daily" type="submit" id="tptn_reset_daily" class="button button-secondary" style="color:#fff;background-color: #a00;border-color: #900;" onclick="if (!confirm(\'%s\')) return false;">%s</button>',
302 esc_attr__( 'Are you sure you want to reset the daily popular posts?', 'top-10' ),
303 esc_attr__( 'Reset Daily Popular Posts', 'top-10' )
304 );
305 ?>
306 </p>
307 </div>
308 </div>
309
310 <?php if ( ! is_multisite() || is_network_admin() ) : ?>
311 <div class="postbox">
312 <h2><span><?php esc_html_e( 'Recreate Database Tables', 'top-10' ); ?></span></h2>
313 <div class="inside">
314 <p class="description">
315 <?php esc_html_e( 'Only click the button below after performing a full backup of the database. You can use any of the popular backup plugins or phpMyAdmin to achieve this. The authors of this plugin do not guarantee that everything will go smoothly as it depends on your site environment and volume of data. If you are not comfortable, please do not proceed.', 'top-10' ); ?>
316 </p>
317 <p>
318 <button name="tptn_recreate_tables" type="submit" id="tptn_recreate_tables" style="color:#fff;background-color: #a00;border-color: #900;" onclick="if (!confirm('<?php esc_attr_e( 'Hit Cancel if you have not backed up your database', 'top-10' ); ?>')) return false;" class="button button-secondary"><?php esc_attr_e( 'Recreate Database Tables', 'top-10' ); ?></button>
319 </p>
320 </div>
321 </div>
322 <?php endif; ?>
323
324 <?php wp_nonce_field( 'tptn-tools-settings' ); ?>
325 </form>
326
327 </div><!-- /#post-body-content -->
328
329 <div id="postbox-container-1" class="postbox-container">
330
331 <div id="side-sortables" class="meta-box-sortables ui-sortable">
332 <?php include_once 'sidebar.php'; ?>
333 </div><!-- /#side-sortables -->
334
335 </div><!-- /#postbox-container-1 -->
336 </div><!-- /#post-body -->
337 <br class="clear" />
338 </div><!-- /#poststuff -->
339
340 </div><!-- /.wrap -->
341
342 <?php
343 echo ob_get_clean(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
344 }
345
346 /**
347 * Function to delete and create the primary keys in the database table.
348 *
349 * @since 2.5.6
350 */
351 public static function recreate_primary_key() {
352 global $wpdb;
353
354 $table_name = Database::get_table( false );
355 $table_name_daily = Database::get_table( true );
356
357 $wpdb->hide_errors();
358
359 if ( $wpdb->query( $wpdb->prepare( "SHOW INDEXES FROM {$table_name} WHERE Key_name = %s", 'PRIMARY' ) ) ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
360 $wpdb->query( 'ALTER TABLE ' . $table_name . ' DROP PRIMARY KEY ' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.SchemaChange
361 }
362 if ( $wpdb->query( $wpdb->prepare( "SHOW INDEXES FROM {$table_name_daily} WHERE Key_name = %s", 'PRIMARY' ) ) ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
363 $wpdb->query( 'ALTER TABLE ' . $table_name_daily . ' DROP PRIMARY KEY ' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.SchemaChange
364 }
365 if ( $wpdb->query( $wpdb->prepare( "SHOW INDEXES FROM {$table_name_daily} WHERE Key_name = %s", 'blog_date' ) ) ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
366 $wpdb->query( 'ALTER TABLE ' . $table_name_daily . ' DROP INDEX blog_date' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.SchemaChange
367 }
368
369 $wpdb->query( 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY(postnumber, blog_id) ' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.SchemaChange
370 $wpdb->query( 'ALTER TABLE ' . $table_name_daily . ' ADD PRIMARY KEY(postnumber, dp_date, blog_id) ' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.SchemaChange
371 $wpdb->query( 'ALTER TABLE ' . $table_name_daily . ' ADD INDEX blog_date(blog_id, dp_date, postnumber)' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.SchemaChange
372
373 $wpdb->show_errors();
374 }
375
376 /**
377 * Retrieves the SQL code to recreate the PRIMARY KEY.
378 *
379 * @since 2.5.7
380 */
381 public static function recreate_primary_key_html() {
382
383 $table_name = Database::get_table( false );
384 $table_name_daily = Database::get_table( true );
385
386 $sql = 'ALTER TABLE ' . $table_name . ' DROP PRIMARY KEY; ';
387 $sql .= '<br />';
388 $sql .= 'ALTER TABLE ' . $table_name_daily . ' DROP PRIMARY KEY; ';
389 $sql .= '<br />';
390 $sql .= 'ALTER TABLE ' . $table_name_daily . ' DROP INDEX IF EXISTS blog_date; ';
391 $sql .= '<br />';
392 $sql .= 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY(postnumber, blog_id); ';
393 $sql .= '<br />';
394 $sql .= 'ALTER TABLE ' . $table_name_daily . ' ADD PRIMARY KEY(postnumber, dp_date, blog_id); ';
395 $sql .= '<br />';
396 $sql .= 'ALTER TABLE ' . $table_name_daily . ' ADD INDEX blog_date(blog_id, dp_date, postnumber); ';
397
398 /**
399 * Filters the SQL code to recreate the PRIMARY KEY.
400 *
401 * @since 2.5.7
402 * @param string $sql SQL code to recreate PRIMARY KEY.
403 */
404 return apply_filters( 'tptn_recreate_primary_key_html', $sql );
405 }
406
407 /**
408 * Retrieves the SQL code to recreate the PRIMARY KEY.
409 *
410 * @since 2.7.0
411 */
412 public static function recreate_tables() {
413 $errors = new \WP_Error();
414
415 $result = Database::recreate_overall_table( false );
416 if ( is_wp_error( $result ) ) {
417 $errors->merge_from( $result );
418 }
419
420 $result = Database::recreate_daily_table( false );
421 if ( is_wp_error( $result ) ) {
422 $errors->merge_from( $result );
423 }
424
425 $result = Database::recreate_funnel_table( false );
426 if ( is_wp_error( $result ) ) {
427 $errors->merge_from( $result );
428 }
429
430 $result = Database::recreate_log_table( false );
431 if ( is_wp_error( $result ) ) {
432 $errors->merge_from( $result );
433 }
434
435 return $errors->has_errors() ? $errors : true;
436 }
437
438 /**
439 * Clear and reschedule the Top 10 cron jobs.
440 *
441 * @since 4.4.0
442 *
443 * @return string|\WP_Error Success message or WP_Error if a job could not be rescheduled.
444 */
445 public static function fix_crons() {
446 $errors = new \WP_Error();
447 $messages = array();
448 $success = true;
449
450 if ( self::clear_scheduled_hook_or_error( 'tptn_cron_hook', $errors ) ) {
451 if ( tptn_get_option( 'cron_on' ) ) {
452 Cron::enable_run(
453 (int) tptn_get_option( 'cron_hour' ),
454 (int) tptn_get_option( 'cron_min' ),
455 tptn_get_option( 'cron_recurrence' )
456 );
457 if ( wp_next_scheduled( 'tptn_cron_hook' ) ) {
458 $messages[] = __( 'Maintenance cron has been rescheduled.', 'top-10' );
459 } else {
460 $errors->add( 'tptn_cron_hook', __( 'The maintenance cron could not be rescheduled as the cron event list could not be saved.', 'top-10' ) );
461 $success = false;
462 }
463 } else {
464 $messages[] = __( 'Maintenance cron is disabled in the settings and was left unscheduled.', 'top-10' );
465 }
466 } else {
467 $success = false;
468 }
469
470 if ( self::clear_scheduled_hook_or_error( 'tptn_aggregation_cron_hook', $errors ) ) {
471 Cron::enable_aggregation_run();
472 if ( wp_next_scheduled( 'tptn_aggregation_cron_hook' ) ) {
473 $messages[] = __( 'Aggregation cron has been rescheduled.', 'top-10' );
474 } else {
475 $errors->add( 'tptn_aggregation_cron_hook', __( 'The aggregation cron could not be rescheduled as the cron event list could not be saved. Check for object cache or database issues.', 'top-10' ) );
476 $success = false;
477 }
478 } else {
479 $success = false;
480 }
481
482 // Only drop the previously recorded WP-Cron errors once both jobs are confirmed rebuilt (or intentionally left off).
483 if ( $success ) {
484 Cron::clear_reschedule_errors();
485 }
486
487 return $errors->has_errors() ? $errors : implode( ' ', $messages );
488 }
489
490 /**
491 * Clear a scheduled hook, surfacing any WP_Error from WordPress core instead of silently ignoring it.
492 *
493 * If the existing event cannot be cleared, the caller must not attempt to reschedule it: the stale
494 * event would still satisfy wp_next_scheduled(), making a broken cron write look like a successful repair.
495 *
496 * @since 4.4.0
497 *
498 * @param string $hook Hook name to clear.
499 * @param \WP_Error $errors Error collector to append to on failure.
500 * @return bool True if the hook was cleared (or had nothing scheduled), false on failure.
501 */
502 private static function clear_scheduled_hook_or_error( $hook, \WP_Error $errors ) {
503 $result = wp_clear_scheduled_hook( $hook, array(), true );
504
505 if ( is_wp_error( $result ) ) {
506 $errors->add(
507 $hook,
508 sprintf(
509 /* translators: 1: Hook name, 2: Error message from WordPress core. */
510 __( 'Could not clear the existing schedule for %1$s: %2$s', 'top-10' ),
511 $hook,
512 $result->get_error_message()
513 )
514 );
515 return false;
516 }
517
518 return true;
519 }
520
521 /**
522 * Generates the Tools help page.
523 *
524 * @since 3.3.0
525 */
526 public static function help_tabs() {
527 $screen = get_current_screen();
528
529 $screen->set_help_sidebar(
530 /* translators: 1: Support link. */
531 '<p>' . sprintf( __( 'For more information or how to get support visit the <a href="%1$s">WebberZone support site</a>.', 'top-10' ), esc_url( 'https://webberzone.com/support/' ) ) . '</p>' .
532 /* translators: 1: Forum link. */
533 '<p>' . sprintf( __( 'Support queries should be posted in the <a href="%1$s">WordPress.org support forums</a>.', 'top-10' ), esc_url( 'https://wordpress.org/support/plugin/top-10' ) ) . '</p>' .
534 '<p>' . sprintf(
535 /* translators: 1: Github Issues link, 2: Github page. */
536 __( '<a href="%1$s">Post an issue</a> on <a href="%2$s">GitHub</a> (bug reports only).', 'top-10' ),
537 esc_url( 'https://github.com/WebberZone/top-10/issues' ),
538 esc_url( 'https://github.com/WebberZone/top-10' )
539 ) . '</p>'
540 );
541
542 $screen->add_help_tab(
543 array(
544 'id' => 'tptn-settings-general',
545 'title' => __( 'General', 'top-10' ),
546 'content' =>
547 '<p>' . __( 'This screen provides some tools that help maintain certain features of Top 10.', 'top-10' ) . '</p>' .
548 '<p>' . __( 'Clear the cache, reset the popular posts tables plus some miscellaneous fixes for older versions of Top 10.', 'top-10' ) . '</p>',
549 )
550 );
551
552 do_action( 'tptn_settings_tools_help', $screen );
553 }
554
555 /**
556 * Check if tables exist and create them if they don't.
557 *
558 * @since 4.2.0
559 *
560 * @return array Array of table statuses indicating whether they are installed.
561 */
562 public static function check_table_status() {
563 $tables = array(
564 'top_ten' => Database::get_table( false ),
565 'top_ten_daily' => Database::get_table( true ),
566 'top_ten_visits_funnel' => Database::get_funnel_table(),
567 'top_ten_visits_log' => Database::get_log_table(),
568 );
569
570 $statuses = array();
571
572 $installed_label = '<span style="color: #006400;">' . __( 'Installed', 'top-10' ) . '</span>';
573 $not_installed_label = '<span style="color: #8B0000;">' . __( 'Not Installed', 'top-10' ) . '</span>';
574
575 foreach ( $tables as $key => $table_name ) {
576 $statuses[ $key ] = Database::is_table_installed( $table_name ) ? $installed_label : $not_installed_label;
577 }
578
579 // Create tables if they don't exist.
580 if ( ! Database::are_tables_installed() ) {
581 // Use Activator to create tables.
582 Activator::create_tables();
583 Database::clear_table_statistics_cache();
584
585 // Refresh statuses after creating tables.
586 foreach ( $tables as $key => $table_name ) {
587 $statuses[ $key ] = Database::is_table_installed( $table_name ) ? $installed_label : $not_installed_label;
588 }
589 }
590
591 /**
592 * Filter the table statuses report.
593 *
594 * @since 4.1.0
595 *
596 * @param array $statuses Array of table statuses.
597 */
598 return apply_filters( 'tptn_table_statuses', $statuses );
599 }
600
601 /**
602 * Get database status report.
603 *
604 * @since 4.2.0
605 *
606 * @return string HTML output for the database status report.
607 */
608 public static function get_db_status_report() {
609 global $tptn_db_version;
610
611 // Get table statuses.
612 $statuses = self::check_table_status();
613
614 // Get table statistics from Database class.
615 $table_stats = Database::get_table_statistics();
616
617 ob_start();
618 ?>
619 <table class="form-table">
620 <tr>
621 <th scope="row"><?php esc_html_e( 'Database version', 'top-10' ); ?></th>
622 <td>
623 <?php esc_html_e( 'Installed version', 'top-10' ); ?> <?php echo esc_html( get_site_option( 'tptn_db_version', '0' ) ); ?> /
624 <?php esc_html_e( 'Current version', 'top-10' ); ?> <?php echo esc_html( $tptn_db_version ); ?>
625 </td>
626 </tr>
627
628 <?php
629 $table_keys = array( 'top_ten', 'top_ten_daily', 'top_ten_visits_funnel', 'top_ten_visits_log' );
630 foreach ( $table_keys as $table_key ) :
631 if ( ! isset( $statuses[ $table_key ] ) ) {
632 continue;
633 }
634 ?>
635 <tr>
636 <th scope="row"><?php printf( /* translators: %s: Table name */ esc_html__( '%s table', 'top-10' ), esc_html( $table_key ) ); ?></th>
637 <td>
638 <?php
639 echo $statuses[ $table_key ]; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
640
641 if ( isset( $table_stats[ $table_key ] ) ) {
642 $format = ( is_multisite() && ! is_network_admin() )
643 /* translators: 1: Number of entries, 2: Estimated table size */
644 ? __( 'Entries: %1$s | Est. Size: %2$s', 'top-10' )
645 /* translators: 1: Number of entries, 2: Table size */
646 : __( 'Entries: %1$s | Size: %2$s', 'top-10' );
647
648 echo '<br><span class="description">';
649 printf(
650 esc_html( $format ),
651 '<strong>' . esc_html( number_format_i18n( $table_stats[ $table_key ]['entries'] ) ) . '</strong>',
652 '<strong>' . esc_html( size_format( $table_stats[ $table_key ]['size'] ) ) . '</strong>'
653 );
654 echo '</span>';
655 }
656 ?>
657 </td>
658 </tr>
659 <?php endforeach; ?>
660
661 <?php
662 $cron_jobs = array(
663 'tptn_cron_hook' => __( 'Maintenance cron', 'top-10' ),
664 'tptn_aggregation_cron_hook' => __( 'Aggregation cron', 'top-10' ),
665 );
666 foreach ( $cron_jobs as $hook => $label ) :
667 $next_run = wp_next_scheduled( $hook );
668 $recurrence = $next_run ? wp_get_schedule( $hook ) : false;
669 $schedules = wp_get_schedules();
670 $interval_display = '';
671 if ( $recurrence && isset( $schedules[ $recurrence ]['display'] ) ) {
672 $interval_display = $schedules[ $recurrence ]['display'];
673 }
674 ?>
675 <tr>
676 <th scope="row"><?php echo esc_html( $label ); ?></th>
677 <td>
678 <?php if ( $next_run ) : ?>
679 <span style="color: #006400;"><?php esc_html_e( 'Scheduled', 'top-10' ); ?></span>
680 <br><span class="description">
681 <?php
682 if ( $interval_display ) {
683 printf(
684 /* translators: %s: schedule display name e.g. "Every two minutes" */
685 esc_html__( 'Runs: %s', 'top-10' ),
686 esc_html( $interval_display )
687 );
688 echo '<br>';
689 }
690 printf(
691 /* translators: %s: human-readable time difference */
692 esc_html__( 'Next run: %s', 'top-10' ),
693 /* translators: %s: human-readable time difference */
694 esc_html( sprintf( __( 'in %s', 'top-10' ), human_time_diff( time(), $next_run ) ) )
695 );
696 ?>
697 </span>
698 <?php else : ?>
699 <span style="color: #8B0000;"><?php esc_html_e( 'Not scheduled', 'top-10' ); ?></span>
700 <?php endif; ?>
701 <?php
702 $reschedule_error = Cron::get_reschedule_error( $hook );
703 if ( $reschedule_error ) :
704 ?>
705 <br><span class="description" style="color: #8B0000;">
706 <?php
707 printf(
708 /* translators: 1: Error message from WP-Cron, 2: Human-readable time difference. */
709 esc_html__( 'WP-Cron reported an error rescheduling this job %2$s ago: %1$s', 'top-10' ),
710 esc_html( $reschedule_error['message'] ),
711 esc_html( human_time_diff( $reschedule_error['time'] ) )
712 );
713 ?>
714 </span>
715 <?php endif; ?>
716 </td>
717 </tr>
718 <?php endforeach; ?>
719
720 <?php if ( ! Database::are_tables_installed() ) : ?>
721 <tr>
722 <th scope="row"><?php esc_html_e( 'Repair database', 'top-10' ); ?></th>
723 <td>
724 <a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?page=tptn_dashboard&action=recreate_tables' ), 'tptn-recreate-tables' ) ); ?>" class="button">
725 <?php esc_html_e( 'Recreate tables', 'top-10' ); ?>
726 </a>
727 </td>
728 </tr>
729 <?php endif; ?>
730 </table>
731 <?php
732
733 return ob_get_clean();
734 }
735
736 /**
737 * Handle recreate tables action from admin area.
738 *
739 * @since 4.2.0
740 */
741 /**
742 * Handle the create-missing-tables GET action triggered from the admin notice.
743 *
744 * @since 4.3.0
745 */
746 public static function handle_create_missing_tables_action() {
747 if ( ! isset( $_GET['action'] ) || 'tptn_create_missing_tables' !== $_GET['action'] || ! isset( $_GET['_wpnonce'] ) ) {
748 return;
749 }
750
751 if ( ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'tptn-create-missing-tables' ) ) {
752 wp_die( esc_html__( 'Security check failed', 'top-10' ) );
753 }
754
755 if ( ! current_user_can( 'manage_options' ) ) {
756 wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'top-10' ) );
757 }
758
759 Activator::create_tables();
760 Database::clear_table_statistics_cache();
761
762 $still_missing = false;
763 foreach ( array( Database::get_table( false ), Database::get_table( true ), Database::get_log_table(), Database::get_funnel_table() ) as $table ) {
764 if ( ! Database::is_table_installed( $table ) ) {
765 $still_missing = true;
766 break;
767 }
768 }
769
770 $status = $still_missing ? 'failed' : 'created';
771 $referer = wp_get_referer();
772 $fallback = admin_url( 'admin.php?page=' . ( is_network_admin() ? 'tptn_network_tools_page' : 'tptn_tools_page' ) );
773 $redirect = add_query_arg( 'tptn_tables_created', $status, $referer ? $referer : $fallback );
774
775 wp_safe_redirect( $redirect );
776 exit;
777 }
778
779 /**
780 * Handle the recreate-tables GET action from the tools page.
781 *
782 * @since 4.1.0
783 */
784 public static function handle_recreate_tables_action() {
785 if ( ! isset( $_GET['action'] ) || 'recreate_tables' !== $_GET['action'] || ! isset( $_GET['_wpnonce'] ) ) {
786 return;
787 }
788
789 if ( ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'tptn-recreate-tables' ) ) {
790 wp_die( esc_html__( 'Security check failed', 'top-10' ) );
791 }
792
793 if ( ! current_user_can( 'manage_options' ) ) {
794 wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'top-10' ) );
795 }
796
797 // Recreate tables.
798 $result_overall = Database::recreate_overall_table( false );
799 $result_daily = Database::recreate_daily_table( false );
800 $result_funnel = Database::recreate_funnel_table( false );
801 $result_log = Database::recreate_log_table( false );
802
803 // Check for errors.
804 if ( is_wp_error( $result_overall ) ) {
805 add_settings_error(
806 'tptn-notices',
807 'tptn-recreate-overall-error',
808 $result_overall->get_error_message(),
809 'error'
810 );
811 }
812
813 if ( is_wp_error( $result_daily ) ) {
814 add_settings_error(
815 'tptn-notices',
816 'tptn-recreate-daily-error',
817 $result_daily->get_error_message(),
818 'error'
819 );
820 }
821
822 if ( is_wp_error( $result_funnel ) ) {
823 add_settings_error(
824 'tptn-notices',
825 'tptn-recreate-funnel-error',
826 $result_funnel->get_error_message(),
827 'error'
828 );
829 }
830
831 if ( is_wp_error( $result_log ) ) {
832 add_settings_error(
833 'tptn-notices',
834 'tptn-recreate-log-error',
835 $result_log->get_error_message(),
836 'error'
837 );
838 }
839
840 // If no errors, add success message.
841 if ( ! is_wp_error( $result_overall ) && ! is_wp_error( $result_daily ) && ! is_wp_error( $result_funnel ) && ! is_wp_error( $result_log ) ) {
842 add_settings_error(
843 'tptn-notices',
844 'tptn-recreate-success',
845 __( 'Tables have been recreated successfully.', 'top-10' ),
846 'success'
847 );
848
849 // Clear table statistics cache since tables were recreated.
850 Database::clear_table_statistics_cache();
851 }
852
853 // Redirect back to the tools page.
854 $page = is_network_admin() ? 'tptn_network_tools_page' : 'tptn_tools_page';
855 wp_safe_redirect( admin_url( 'admin.php?page=' . $page . '&settings-updated=true' ) );
856 exit;
857 }
858 }
859