PluginProbe
WebberZone Top 10 — Popular Posts / 4.5.2
WebberZone Top 10 — Popular Posts v4.5.2
4.5.2 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 All 118 releases
← All changes | includes/admin/class-tools-page.php +329 -52 4.3.1 → 4.5.2 View file →
@@ -106,12 +106,13 @@
106 106 array(
107 107 'ajax_url' => admin_url( 'admin-ajax.php' ),
108 108 'security' => wp_create_nonce( 'tptn-admin' ),
109 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' ),
110 + 'confirm_message' => esc_html__( 'Are you sure you want to clear the cache?', 'top-10' ),
111 + 'network_confirm_message' => esc_html__( 'Are you sure you want to clear the Top 10 cache across all active sites?', 'top-10' ),
112 + 'clearing_text' => esc_html__( 'Clearing...', 'top-10' ),
113 + 'fail_message' => esc_html__( 'Failed to clear cache. Please try again.', 'top-10' ),
114 + 'request_fail_message' => esc_html__( 'Request failed: ', 'top-10' ),
114 115 ),
115 116 )
116 117 );
117 118 }
@@ -125,16 +126,18 @@
125 126 * @return void
126 127 */
127 128 public function render_page() {
128 129 $screen = get_current_screen();
129 - $network_wide = false;
130 + $network_wide = is_multisite() && is_network_admin();
130 131
131 - if ( $screen->id === $this->parent_id . '-network' ) {
132 + // Keep the screen check as a fallback for admin screen implementations that
133 + // do not expose is_network_admin() reliably.
134 + if ( $screen && $screen->id === $this->parent_id . '-network' ) {
132 135 $network_wide = true;
133 136 }
134 137
135 - /* Truncate overall posts table */
136 - if ( isset( $_POST['tptn_recreate_primary_key'] ) && check_admin_referer( 'tptn-tools-settings' ) ) {
138 + /* Recreate the shared table indexes. */
139 + if ( ( ! is_multisite() || $network_wide ) && isset( $_POST['tptn_recreate_primary_key'] ) && check_admin_referer( 'tptn-tools-settings' ) ) {
137 140 self::recreate_primary_key();
138 141 add_settings_error( 'tptn-notices', '', esc_html__( 'Primary Key has been recreated', 'top-10' ), 'updated' );
139 142 }
140 143
@@ -144,8 +147,9 @@
144 147 Database::truncate_table( Database::get_table( false ) );
145 148 } else {
146 149 Counter::delete_counts( array( 'daily' => false ) );
147 150 }
151 + Dashboard_Widgets::clear_network_dashboard_cache();
148 152 add_settings_error( 'tptn-notices', '', esc_html__( 'Top 10 popular posts reset', 'top-10' ), 'updated' );
149 153 }
150 154
151 155 /* Truncate daily posts table */
@@ -154,13 +158,14 @@
154 158 Database::truncate_table( Database::get_table( true ) );
155 159 } else {
156 160 Counter::delete_counts( array( 'daily' => true ) );
157 161 }
162 + Dashboard_Widgets::clear_network_dashboard_cache();
158 163 add_settings_error( 'tptn-notices', '', esc_html__( 'Top 10 daily popular posts reset', 'top-10' ), 'updated' );
159 164 }
160 165
161 - /* Recreate tables */
162 - if ( isset( $_POST['tptn_recreate_tables'] ) && check_admin_referer( 'tptn-tools-settings' ) ) {
166 + /* Recreate the shared tables. */
167 + if ( ( ! is_multisite() || $network_wide ) && isset( $_POST['tptn_recreate_tables'] ) && check_admin_referer( 'tptn-tools-settings' ) ) {
163 168 $result = self::recreate_tables();
164 169 if ( is_wp_error( $result ) ) {
165 170 add_settings_error( 'tptn-notices', '', $result->get_error_message(), 'error' );
166 171 } else {
@@ -169,13 +174,20 @@
169 174 }
170 175
171 176 /* Sync funnel (run aggregation now) */
172 177 if ( isset( $_POST['tptn_sync_funnel'] ) && check_admin_referer( 'tptn-tools-settings' ) ) {
173 - $result = Database::aggregate_visit_log();
178 + $blog_id = ( is_multisite() && ! $network_wide ) ? get_current_blog_id() : null;
179 + $result = Database::aggregate_visit_log( 10000, $blog_id );
174 180 if ( true === $result ) {
175 - add_settings_error( 'tptn-notices', '', esc_html__( 'Funnel has been synced. Buffered visits have been aggregated.', 'top-10' ), 'updated' );
181 + $message = $network_wide
182 + ? __( 'The network funnel has been synced. Buffered visits from all sites have been aggregated.', 'top-10' )
183 + : __( 'The site funnel has been synced. Buffered visits for this site have been aggregated.', 'top-10' );
184 + add_settings_error( 'tptn-notices', '', esc_html( $message ), 'updated' );
176 185 } elseif ( 0 === $result ) {
177 - add_settings_error( 'tptn-notices', '', esc_html__( 'Nothing to sync. The funnel is empty.', 'top-10' ), 'updated' );
186 + $message = $network_wide
187 + ? __( 'Nothing to sync. The network funnel is empty.', 'top-10' )
188 + : __( 'Nothing to sync. This site funnel is empty.', 'top-10' );
189 + add_settings_error( 'tptn-notices', '', esc_html( $message ), 'updated' );
178 190 } elseif ( false === $result ) {
179 191 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 192 } elseif ( is_wp_error( $result ) ) {
181 193 add_settings_error(
@@ -190,13 +202,26 @@
190 202 );
191 203 }
192 204 }
193 205
206 + /* Fix cron schedules */
207 + if ( isset( $_POST['tptn_fix_crons'] ) && check_admin_referer( 'tptn-tools-settings' ) ) {
208 + $result = self::fix_crons( $network_wide );
209 + if ( is_wp_error( $result ) ) {
210 + add_settings_error( 'tptn-notices', '', implode( ' ', array_map( 'esc_html', $result->get_error_messages() ) ), 'error' );
211 + } else {
212 + add_settings_error( 'tptn-notices', '', esc_html( $result ), 'updated' );
213 + }
214 + }
215 +
194 216 ob_start();
195 217 ?>
196 218 <div class="wrap">
197 219 <h1><?php esc_html_e( 'Top 10 Tools', 'top-10' ); ?></h1>
198 - <?php do_action( 'tptn_settings_page_header' ); ?>
220 + <?php
221 + /** This action is documented in includes/admin/class-dashboard.php */
222 + do_action( 'tptn_settings_page_header' );
223 + ?>
199 224
200 225 <?php settings_errors(); ?>
201 226
202 227 <div id="poststuff">
@@ -219,58 +244,110 @@
219 244 <div class="inside">
220 245 <p>
221 246 <?php
222 247 printf(
223 - '<button type="button" name="tptn_cache_clear" class="button button-secondary tptn_cache_clear" aria-label="%1$s">%1$s</button>',
224 - esc_html__( 'Clear cache', 'top-10' )
248 + '<button type="button" name="tptn_cache_clear" class="button button-secondary tptn_cache_clear" data-network-wide="%1$s" aria-label="%2$s">%2$s</button>',
249 + $network_wide ? '1' : '0',
250 + esc_html( $network_wide ? __( 'Clear Cache Across Network', 'top-10' ) : __( 'Clear cache', 'top-10' ) )
225 251 );
226 252 ?>
227 253 </p>
228 254 <p class="description">
229 - <?php esc_html_e( 'Clear the Top 10 cache. This will also be cleared automatically when you save the settings page.', 'top-10' ); ?>
255 + <?php
256 + if ( $network_wide ) {
257 + esc_html_e( 'Clear the Top 10 cache for all active sites in the network.', 'top-10' );
258 + } else {
259 + esc_html_e( 'Clear the Top 10 cache. This will also be cleared automatically when you save the settings page.', 'top-10' );
260 + }
261 + ?>
230 262 </p>
231 263 </div>
232 264 </div>
233 265
234 266 <div class="postbox">
235 - <h2><span><?php esc_html_e( 'Sync Funnel', 'top-10' ); ?></span></h2>
267 + <h2><span><?php echo esc_html( $network_wide ? __( 'Sync Network Funnel', 'top-10' ) : __( 'Sync Funnel', 'top-10' ) ); ?></span></h2>
236 268 <div class="inside">
237 269 <p>
238 - <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>
270 + <button name="tptn_sync_funnel" type="submit" id="tptn_sync_funnel" class="button button-secondary"><?php echo esc_html( $network_wide ? __( 'Sync Network Funnel Now', 'top-10' ) : __( 'Sync Funnel Now', 'top-10' ) ); ?></button>
239 271 </p>
240 272 <p class="description">
241 - <?php esc_html_e( 'Drain the visits funnel into the count tables immediately. This is equivalent to running the 5-minute aggregation cron job.', 'top-10' ); ?>
273 + <?php
274 + if ( $network_wide ) {
275 + esc_html_e( 'Drain buffered visits from all sites into the count tables immediately. This is equivalent to running the aggregation cron job.', 'top-10' );
276 + } else {
277 + esc_html_e( 'Drain this site\'s buffered visits into the count tables immediately. Other sites\' buffered visits are not affected.', 'top-10' );
278 + }
279 + ?>
242 280 </p>
243 281 </div>
244 282 </div>
245 283
284 + <?php
285 + /**
286 + * Fires between the action boxes on the Tools page.
287 + *
288 + * @since 4.5.0
289 + *
290 + * @param bool $network_wide Whether the Tools page is being rendered in network admin.
291 + */
292 + do_action( 'tptn_tools_page_actions', $network_wide );
293 + ?>
294 +
246 295 <div class="postbox">
247 - <h2><span><?php esc_html_e( 'Recreate Primary Key', 'top-10' ); ?></span></h2>
296 + <h2><span><?php echo esc_html( $network_wide ? __( 'Fix Network Cron Schedules', 'top-10' ) : __( 'Fix Cron Schedules', 'top-10' ) ); ?></span></h2>
248 297 <div class="inside">
249 298 <p>
250 - <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>
299 + <button name="tptn_fix_crons" type="submit" id="tptn_fix_crons" class="button button-secondary"><?php echo esc_html( $network_wide ? __( 'Fix Network Cron Schedules', 'top-10' ) : __( 'Fix Cron Schedules', 'top-10' ) ); ?></button>
251 300 </p>
252 301 <p class="description">
253 - <?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' ); ?>
302 + <?php
303 + if ( $network_wide ) {
304 + esc_html_e( 'Clears and reschedules the Top 10 maintenance and aggregation jobs for all active sites. Use this if a site\'s job is not scheduled or if your error log reports cron reschedule errors.', 'top-10' );
305 + } else {
306 + esc_html_e( 'Clears and reschedules the Top 10 cron jobs for this site. 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' );
307 + }
308 + ?>
254 309 </p>
255 - <p>
256 - <code style="display:block;"><?php echo self::recreate_primary_key_html(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></code>
257 - </p>
258 310 </div>
259 311 </div>
260 312
313 + <?php if ( ! is_multisite() || $network_wide ) : ?>
314 + <div class="postbox">
315 + <h2><span><?php esc_html_e( 'Recreate Primary Key', 'top-10' ); ?></span></h2>
316 + <div class="inside">
317 + <p>
318 + <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>
319 + </p>
320 + <p class="description">
321 + <?php esc_html_e( 'Deletes and reinitializes the primary key in the shared database tables. Run this from Network Admin on multisite installations. Remember to back up your database first!', 'top-10' ); ?>
322 + </p>
323 + <p>
324 + <code style="display:block;"><?php echo self::recreate_primary_key_html(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></code>
325 + </p>
326 + </div>
327 + </div>
328 + <?php endif; ?>
329 +
261 330 <div class="postbox">
262 331 <h2><span><?php esc_html_e( 'Reset database', 'top-10' ); ?></span></h2>
263 332 <div class="inside">
264 333 <p class="description">
265 - <?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' ); ?>
334 + <?php
335 + if ( $network_wide ) {
336 + esc_html_e( 'This will reset the Top 10 popular posts across all sites in the network. This cannot be reversed. Make sure that your database has been backed up before proceeding.', 'top-10' );
337 + } elseif ( is_multisite() ) {
338 + esc_html_e( 'This will reset the Top 10 popular posts for the current site only. Other sites in the network will not be affected. This cannot be reversed. Make sure that your database has been backed up before proceeding.', 'top-10' );
339 + } else {
340 + esc_html_e( 'This will reset the Top 10 popular posts for this site. This cannot be reversed. Make sure that your database has been backed up before proceeding.', 'top-10' );
341 + }
342 + ?>
266 343 </p>
267 344 <p>
268 345 <?php
269 346 printf(
270 347 '<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>',
271 - esc_attr__( 'Are you sure you want to reset the popular posts?', 'top-10' ),
272 - esc_attr__( 'Reset Popular Posts', 'top-10' )
348 + esc_attr__( $network_wide ? 'Are you sure you want to reset popular posts across the network?' : 'Are you sure you want to reset the popular posts for this site?', 'top-10' ),
349 + esc_attr__( $network_wide ? 'Reset Popular Posts Across Network' : 'Reset Popular Posts', 'top-10' )
273 350 );
274 351 ?>
275 352 </p>
276 353 <p>
@@ -276,10 +353,10 @@
276 353 <p>
277 354 <?php
278 355 printf(
279 356 '<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>',
280 - esc_attr__( 'Are you sure you want to reset the daily popular posts?', 'top-10' ),
281 - esc_attr__( 'Reset Daily Popular Posts', 'top-10' )
357 + esc_attr__( $network_wide ? 'Are you sure you want to reset daily popular posts across the network?' : 'Are you sure you want to reset the daily popular posts for this site?', 'top-10' ),
358 + esc_attr__( $network_wide ? 'Reset Daily Popular Posts Across Network' : 'Reset Daily Popular Posts', 'top-10' )
282 359 );
283 360 ?>
284 361 </p>
285 362 </div>
@@ -284,9 +361,9 @@
284 361 </p>
285 362 </div>
286 363 </div>
287 364
288 - <?php if ( ! is_multisite() || is_network_admin() ) : ?>
365 + <?php if ( ! is_multisite() || $network_wide ) : ?>
289 366 <div class="postbox">
290 367 <h2><span><?php esc_html_e( 'Recreate Database Tables', 'top-10' ); ?></span></h2>
291 368 <div class="inside">
292 369 <p class="description">
@@ -331,9 +408,9 @@
331 408
332 409 $table_name = Database::get_table( false );
333 410 $table_name_daily = Database::get_table( true );
334 411
335 - $wpdb->hide_errors();
412 + $show_errors = $wpdb->hide_errors();
336 413
337 414 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
338 415 $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
339 416 }
@@ -347,9 +424,9 @@
347 424 $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
348 425 $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
349 426 $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
350 427
351 - $wpdb->show_errors();
428 + $wpdb->show_errors( $show_errors );
352 429 }
353 430
354 431 /**
355 432 * Retrieves the SQL code to recreate the PRIMARY KEY.
@@ -409,12 +486,168 @@
409 486 if ( is_wp_error( $result ) ) {
410 487 $errors->merge_from( $result );
411 488 }
412 489
490 + Database::clear_table_installation_cache();
491 + Dashboard_Widgets::clear_network_dashboard_cache();
492 +
413 493 return $errors->has_errors() ? $errors : true;
414 494 }
415 495
416 496 /**
497 + * Clear and reschedule the Top 10 cron jobs.
498 + *
499 + * @since 4.4.0
500 + *
501 + * @param bool $network_wide Whether to repair cron schedules for all active sites.
502 + * @return string|\WP_Error Success message or WP_Error if a job could not be rescheduled.
503 + */
504 + public static function fix_crons( $network_wide = false ) {
505 + if ( $network_wide && is_multisite() ) {
506 + $site_ids = wp_list_pluck(
507 + get_sites(
508 + array(
509 + 'archived' => 0,
510 + 'spam' => 0,
511 + 'deleted' => 0,
512 + 'number' => 0,
513 + )
514 + ),
515 + 'blog_id'
516 + );
517 + $errors = new \WP_Error();
518 + $sites_processed = 0;
519 + $current_blog_id = get_current_blog_id();
520 +
521 + foreach ( $site_ids as $site_id ) {
522 + $site_id = absint( $site_id );
523 + $switched = $site_id !== $current_blog_id;
524 + if ( $switched ) {
525 + switch_to_blog( $site_id );
526 + }
527 +
528 + try {
529 + $result = self::fix_crons_for_site();
530 + if ( is_wp_error( $result ) ) {
531 + foreach ( $result->get_error_messages() as $message ) {
532 + $errors->add(
533 + 'tptn-cron-site-' . $site_id,
534 + sprintf(
535 + /* translators: 1: Site ID, 2: Error message. */
536 + __( 'Site %1$d: %2$s', 'top-10' ),
537 + $site_id,
538 + $message
539 + )
540 + );
541 + }
542 + } else {
543 + ++$sites_processed;
544 + }
545 + } finally {
546 + if ( $switched ) {
547 + restore_current_blog();
548 + }
549 + }
550 + }
551 +
552 + if ( $errors->has_errors() ) {
553 + return $errors;
554 + }
555 +
556 + return sprintf(
557 + /* translators: %s: Number of sites. */
558 + __( 'Cron schedules have been repaired for %s active site(s).', 'top-10' ),
559 + number_format_i18n( $sites_processed )
560 + );
561 + }
562 +
563 + return self::fix_crons_for_site();
564 + }
565 +
566 + /**
567 + * Clear and reschedule the Top 10 cron jobs for the current site.
568 + *
569 + * @since 4.5.0
570 + *
571 + * @return string|\WP_Error Success message or WP_Error if a job could not be rescheduled.
572 + */
573 + private static function fix_crons_for_site() {
574 + $errors = new \WP_Error();
575 + $messages = array();
576 + $success = true;
577 +
578 + if ( self::clear_scheduled_hook_or_error( 'tptn_cron_hook', $errors ) ) {
579 + if ( tptn_get_option( 'cron_on' ) ) {
580 + Cron::enable_run(
581 + (int) tptn_get_option( 'cron_hour' ),
582 + (int) tptn_get_option( 'cron_min' ),
583 + tptn_get_option( 'cron_recurrence' )
584 + );
585 + if ( wp_next_scheduled( 'tptn_cron_hook' ) ) {
586 + $messages[] = __( 'Maintenance cron has been rescheduled.', 'top-10' );
587 + } else {
588 + $errors->add( 'tptn_cron_hook', __( 'The maintenance cron could not be rescheduled as the cron event list could not be saved.', 'top-10' ) );
589 + $success = false;
590 + }
591 + } else {
592 + $messages[] = __( 'Maintenance cron is disabled in the settings and was left unscheduled.', 'top-10' );
593 + }
594 + } else {
595 + $success = false;
596 + }
597 +
598 + if ( self::clear_scheduled_hook_or_error( 'tptn_aggregation_cron_hook', $errors ) ) {
599 + Cron::enable_aggregation_run();
600 + if ( wp_next_scheduled( 'tptn_aggregation_cron_hook' ) ) {
601 + $messages[] = __( 'Aggregation cron has been rescheduled.', 'top-10' );
602 + } else {
603 + $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' ) );
604 + $success = false;
605 + }
606 + } else {
607 + $success = false;
608 + }
609 +
610 + // Only drop the previously recorded WP-Cron errors once both jobs are confirmed rebuilt (or intentionally left off).
611 + if ( $success ) {
612 + Cron::clear_reschedule_errors();
613 + }
614 +
615 + return $errors->has_errors() ? $errors : implode( ' ', $messages );
616 + }
617 +
618 + /**
619 + * Clear a scheduled hook, surfacing any WP_Error from WordPress core instead of silently ignoring it.
620 + *
621 + * If the existing event cannot be cleared, the caller must not attempt to reschedule it: the stale
622 + * event would still satisfy wp_next_scheduled(), making a broken cron write look like a successful repair.
623 + *
624 + * @since 4.4.0
625 + *
626 + * @param string $hook Hook name to clear.
627 + * @param \WP_Error $errors Error collector to append to on failure.
628 + * @return bool True if the hook was cleared (or had nothing scheduled), false on failure.
629 + */
630 + private static function clear_scheduled_hook_or_error( $hook, \WP_Error $errors ) {
631 + $result = wp_clear_scheduled_hook( $hook, array(), true );
632 +
633 + if ( is_wp_error( $result ) ) {
634 + $errors->add(
635 + $hook,
636 + sprintf(
637 + /* translators: 1: Hook name, 2: Error message from WordPress core. */
638 + __( 'Could not clear the existing schedule for %1$s: %2$s', 'top-10' ),
639 + $hook,
640 + $result->get_error_message()
641 + )
642 + );
643 + return false;
644 + }
645 +
646 + return true;
647 + }
648 +
649 + /**
417 650 * Generates the Tools help page.
418 651 *
419 652 * @since 3.3.0
420 653 */
@@ -443,8 +676,15 @@
443 676 '<p>' . __( 'Clear the cache, reset the popular posts tables plus some miscellaneous fixes for older versions of Top 10.', 'top-10' ) . '</p>',
444 677 )
445 678 );
446 679
680 + /**
681 + * Fires after the Tools page help tabs have been registered.
682 + *
683 + * @since 2.5.0
684 + *
685 + * @param \WP_Screen $screen The current screen object.
686 + */
447 687 do_action( 'tptn_settings_tools_help', $screen );
448 688 }
449 689
450 690 /**
@@ -465,22 +705,24 @@
465 705 $statuses = array();
466 706
467 707 $installed_label = '<span style="color: #006400;">' . __( 'Installed', 'top-10' ) . '</span>';
468 708 $not_installed_label = '<span style="color: #8B0000;">' . __( 'Not Installed', 'top-10' ) . '</span>';
709 + $table_statuses = Database::get_table_installation_status();
469 710
470 711 foreach ( $tables as $key => $table_name ) {
471 - $statuses[ $key ] = Database::is_table_installed( $table_name ) ? $installed_label : $not_installed_label;
712 + $statuses[ $key ] = ! empty( $table_statuses[ $table_name ] ) ? $installed_label : $not_installed_label;
472 713 }
473 714
474 715 // Create tables if they don't exist.
475 - if ( ! Database::are_tables_installed() ) {
716 + if ( empty( $table_statuses[ Database::get_table( false ) ] ) || empty( $table_statuses[ Database::get_table( true ) ] ) ) {
476 717 // Use Activator to create tables.
477 718 Activator::create_tables();
478 719 Database::clear_table_statistics_cache();
720 + $table_statuses = Database::get_table_installation_status( true );
479 721
480 722 // Refresh statuses after creating tables.
481 723 foreach ( $tables as $key => $table_name ) {
482 - $statuses[ $key ] = Database::is_table_installed( $table_name ) ? $installed_label : $not_installed_label;
724 + $statuses[ $key ] = ! empty( $table_statuses[ $table_name ] ) ? $installed_label : $not_installed_label;
483 725 }
484 726 }
485 727
486 728 /**
@@ -520,9 +762,14 @@
520 762 </td>
521 763 </tr>
522 764
523 765 <?php
524 - $table_keys = array( 'top_ten', 'top_ten_daily', 'top_ten_visits_funnel', 'top_ten_visits_log' );
766 + $table_keys = array(
767 + 'top_ten',
768 + 'top_ten_daily',
769 + 'top_ten_visits_funnel',
770 + 'top_ten_visits_log',
771 + );
525 772 foreach ( $table_keys as $table_key ) :
526 773 if ( ! isset( $statuses[ $table_key ] ) ) {
527 774 continue;
528 775 }
@@ -533,13 +780,11 @@
533 780 <?php
534 781 echo $statuses[ $table_key ]; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
535 782
536 783 if ( isset( $table_stats[ $table_key ] ) ) {
537 - $format = ( is_multisite() && ! is_network_admin() )
538 - /* translators: 1: Number of entries, 2: Estimated table size */
539 - ? __( 'Entries: %1$s | Est. Size: %2$s', 'top-10' )
540 - /* translators: 1: Number of entries, 2: Table size */
541 - : __( 'Entries: %1$s | Size: %2$s', 'top-10' );
784 + $format = is_multisite()
785 + ? __( 'Network-wide estimated entries: %1$s | Network-wide estimated size: %2$s', 'top-10' )
786 + : __( 'Estimated entries: %1$s | Estimated size: %2$s', 'top-10' );
542 787
543 788 echo '<br><span class="description">';
544 789 printf(
545 790 esc_html( $format ),
@@ -592,17 +837,32 @@
592 837 </span>
593 838 <?php else : ?>
594 839 <span style="color: #8B0000;"><?php esc_html_e( 'Not scheduled', 'top-10' ); ?></span>
595 840 <?php endif; ?>
841 + <?php
842 + $reschedule_error = Cron::get_reschedule_error( $hook );
843 + if ( $reschedule_error ) :
844 + ?>
845 + <br><span class="description" style="color: #8B0000;">
846 + <?php
847 + printf(
848 + /* translators: 1: Error message from WP-Cron, 2: Human-readable time difference. */
849 + esc_html__( 'WP-Cron reported an error rescheduling this job %2$s ago: %1$s', 'top-10' ),
850 + esc_html( $reschedule_error['message'] ),
851 + esc_html( human_time_diff( $reschedule_error['time'] ) )
852 + );
853 + ?>
854 + </span>
855 + <?php endif; ?>
596 856 </td>
597 857 </tr>
598 858 <?php endforeach; ?>
599 859
600 - <?php if ( ! Database::are_tables_installed() ) : ?>
860 + <?php if ( ( ! is_multisite() || is_network_admin() ) && ! Database::are_tables_installed() ) : ?>
601 861 <tr>
602 862 <th scope="row"><?php esc_html_e( 'Repair database', 'top-10' ); ?></th>
603 863 <td>
604 - <a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?page=tptn_dashboard&action=recreate_tables' ), 'tptn-recreate-tables' ) ); ?>" class="button">
864 + <a href="<?php echo esc_url( wp_nonce_url( ( is_multisite() && is_network_admin() ? network_admin_url( 'admin.php?page=tptn_network_tools_page&action=recreate_tables' ) : admin_url( 'admin.php?page=tptn_tools_page&action=recreate_tables' ) ), 'tptn-recreate-tables' ) ); ?>" class="button">
605 865 <?php esc_html_e( 'Recreate tables', 'top-10' ); ?>
606 866 </a>
607 867 </td>
608 868 </tr>
@@ -631,18 +891,25 @@
631 891 if ( ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'tptn-create-missing-tables' ) ) {
632 892 wp_die( esc_html__( 'Security check failed', 'top-10' ) );
633 893 }
634 894
635 - if ( ! current_user_can( 'manage_options' ) ) {
895 + if ( is_multisite() && ! is_network_admin() ) {
896 + wp_die( esc_html__( 'Database table repair must be run from Network Admin.', 'top-10' ) );
897 + }
898 +
899 + $capability = is_network_admin() ? 'manage_network_options' : 'manage_options';
900 + if ( ! current_user_can( $capability ) ) {
636 901 wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'top-10' ) );
637 902 }
638 903
639 904 Activator::create_tables();
640 905 Database::clear_table_statistics_cache();
906 + Dashboard_Widgets::clear_network_dashboard_cache();
907 + $table_statuses = Database::get_table_installation_status( true );
641 908
642 909 $still_missing = false;
643 - foreach ( array( Database::get_table( false ), Database::get_table( true ), Database::get_log_table(), Database::get_funnel_table() ) as $table ) {
644 - if ( ! Database::is_table_installed( $table ) ) {
910 + foreach ( $table_statuses as $installed ) {
911 + if ( ! $installed ) {
645 912 $still_missing = true;
646 913 break;
647 914 }
648 915 }
@@ -648,9 +915,9 @@
648 915 }
649 916
650 917 $status = $still_missing ? 'failed' : 'created';
651 918 $referer = wp_get_referer();
652 - $fallback = admin_url( 'admin.php?page=' . ( is_network_admin() ? 'tptn_network_tools_page' : 'tptn_tools_page' ) );
919 + $fallback = is_network_admin() ? network_admin_url( 'admin.php?page=tptn_network_tools_page' ) : admin_url( 'admin.php?page=tptn_tools_page' );
653 920 $redirect = add_query_arg( 'tptn_tables_created', $status, $referer ? $referer : $fallback );
654 921
655 922 wp_safe_redirect( $redirect );
656 923 exit;
@@ -669,9 +936,14 @@
669 936 if ( ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'tptn-recreate-tables' ) ) {
670 937 wp_die( esc_html__( 'Security check failed', 'top-10' ) );
671 938 }
672 939
673 - if ( ! current_user_can( 'manage_options' ) ) {
940 + if ( is_multisite() && ! is_network_admin() ) {
941 + wp_die( esc_html__( 'Database table repair must be run from Network Admin.', 'top-10' ) );
942 + }
943 +
944 + $capability = is_network_admin() ? 'manage_network_options' : 'manage_options';
945 + if ( ! current_user_can( $capability ) ) {
674 946 wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'top-10' ) );
675 947 }
676 948
677 949 // Recreate tables.
@@ -678,8 +950,10 @@
678 950 $result_overall = Database::recreate_overall_table( false );
679 951 $result_daily = Database::recreate_daily_table( false );
680 952 $result_funnel = Database::recreate_funnel_table( false );
681 953 $result_log = Database::recreate_log_table( false );
954 + Database::clear_table_installation_cache();
955 + Dashboard_Widgets::clear_network_dashboard_cache();
682 956
683 957 // Check for errors.
684 958 if ( is_wp_error( $result_overall ) ) {
685 959 add_settings_error(
@@ -730,9 +1004,12 @@
730 1004 Database::clear_table_statistics_cache();
731 1005 }
732 1006
733 1007 // Redirect back to the tools page.
734 - $page = is_network_admin() ? 'tptn_network_tools_page' : 'tptn_tools_page';
735 - wp_safe_redirect( admin_url( 'admin.php?page=' . $page . '&settings-updated=true' ) );
1008 + if ( is_network_admin() ) {
1009 + wp_safe_redirect( network_admin_url( 'admin.php?page=tptn_network_tools_page&settings-updated=true' ) );
1010 + } else {
1011 + wp_safe_redirect( admin_url( 'admin.php?page=tptn_tools_page&settings-updated=true' ) );
1012 + }
736 1013 exit;
737 1014 }
738 1015 }