parent_id = add_submenu_page( 'tptn_dashboard', esc_html__( 'Top 10 Tools', 'top-10' ), esc_html__( 'Tools', 'top-10' ), 'manage_options', 'tptn_tools_page', array( $this, 'render_page' ) ); add_action( 'load-' . $this->parent_id, array( $this, 'help_tabs' ) ); } /** * Admin Menu. * * @since 3.3.0 */ public function network_admin_menu() { $this->parent_id = add_submenu_page( 'tptn_dashboard', esc_html__( 'Top 10 Tools', 'top-10' ), esc_html__( 'Tools', 'top-10' ), 'manage_network_options', 'tptn_network_tools_page', array( $this, 'render_page' ) ); add_action( 'load-' . $this->parent_id, array( $this, 'help_tabs' ) ); } /** * Enqueue scripts in admin area. * * @since 3.3.0 * * @param string $hook The current admin page. */ public function admin_enqueue_scripts( $hook ) { if ( $hook === $this->parent_id ) { wp_enqueue_script( 'top-ten-admin-js' ); wp_enqueue_style( 'top-ten-admin-css' ); wp_enqueue_style( 'wp-spinner' ); wp_localize_script( 'top-ten-admin-js', 'top_ten_admin_data', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'security' => wp_create_nonce( 'tptn-admin' ), 'strings' => array( 'confirm_message' => esc_html__( 'Are you sure you want to clear the cache?', 'top-10' ), 'clearing_text' => esc_html__( 'Clearing...', 'top-10' ), 'fail_message' => esc_html__( 'Failed to clear cache. Please try again.', 'top-10' ), 'request_fail_message' => esc_html__( 'Request failed: ', 'top-10' ), ), ) ); } } /** * Render the tools settings page. * * @since 2.5.0 * * @return void */ public function render_page() { $screen = get_current_screen(); $network_wide = false; if ( $screen->id === $this->parent_id . '-network' ) { $network_wide = true; } /* Truncate overall posts table */ if ( isset( $_POST['tptn_recreate_primary_key'] ) && check_admin_referer( 'tptn-tools-settings' ) ) { self::recreate_primary_key(); add_settings_error( 'tptn-notices', '', esc_html__( 'Primary Key has been recreated', 'top-10' ), 'updated' ); } /* Truncate overall posts table */ if ( isset( $_POST['tptn_reset_overall'] ) && check_admin_referer( 'tptn-tools-settings' ) ) { if ( ! is_multisite() || $network_wide ) { Database::truncate_table( Database::get_table( false ) ); } else { Counter::delete_counts( array( 'daily' => false ) ); } add_settings_error( 'tptn-notices', '', esc_html__( 'Top 10 popular posts reset', 'top-10' ), 'updated' ); } /* Truncate daily posts table */ if ( isset( $_POST['tptn_reset_daily'] ) && check_admin_referer( 'tptn-tools-settings' ) ) { if ( ! is_multisite() || $network_wide ) { Database::truncate_table( Database::get_table( true ) ); } else { Counter::delete_counts( array( 'daily' => true ) ); } add_settings_error( 'tptn-notices', '', esc_html__( 'Top 10 daily popular posts reset', 'top-10' ), 'updated' ); } /* Recreate tables */ if ( isset( $_POST['tptn_recreate_tables'] ) && check_admin_referer( 'tptn-tools-settings' ) ) { $result = self::recreate_tables(); if ( is_wp_error( $result ) ) { add_settings_error( 'tptn-notices', '', $result->get_error_message(), 'error' ); } else { add_settings_error( 'tptn-notices', '', esc_html__( 'Top 10 tables have been recreated', 'top-10' ), 'updated' ); } } /* Sync funnel (run aggregation now) */ if ( isset( $_POST['tptn_sync_funnel'] ) && check_admin_referer( 'tptn-tools-settings' ) ) { $result = Database::aggregate_visit_log(); if ( true === $result ) { add_settings_error( 'tptn-notices', '', esc_html__( 'Funnel has been synced. Buffered visits have been aggregated.', 'top-10' ), 'updated' ); } elseif ( 0 === $result ) { add_settings_error( 'tptn-notices', '', esc_html__( 'Nothing to sync. The funnel is empty.', 'top-10' ), 'updated' ); } elseif ( false === $result ) { add_settings_error( 'tptn-notices', '', esc_html__( 'Sync skipped: another aggregation is already in progress. Please try again in a moment.', 'top-10' ), 'updated' ); } elseif ( is_wp_error( $result ) ) { add_settings_error( 'tptn-notices', '', sprintf( /* translators: %s: error message from the database */ esc_html__( 'Sync failed: %s', 'top-10' ), esc_html( $result->get_error_message() ) ), 'error' ); } } /* Fix cron schedules */ if ( isset( $_POST['tptn_fix_crons'] ) && check_admin_referer( 'tptn-tools-settings' ) ) { $result = self::fix_crons(); if ( is_wp_error( $result ) ) { add_settings_error( 'tptn-notices', '', implode( ' ', array_map( 'esc_html', $result->get_error_messages() ) ), 'error' ); } else { add_settings_error( 'tptn-notices', '', esc_html( $result ), 'updated' ); } } ob_start(); ?>

%1$s', esc_html__( 'Clear cache', 'top-10' ) ); ?>

%s', esc_attr__( 'Are you sure you want to reset the popular posts?', 'top-10' ), esc_attr__( 'Reset Popular Posts', 'top-10' ) ); ?>

%s', esc_attr__( 'Are you sure you want to reset the daily popular posts?', 'top-10' ), esc_attr__( 'Reset Daily Popular Posts', 'top-10' ) ); ?>


hide_errors(); 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 $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 } 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 $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 } 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 $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 } $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 $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 $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 $wpdb->show_errors(); } /** * Retrieves the SQL code to recreate the PRIMARY KEY. * * @since 2.5.7 */ public static function recreate_primary_key_html() { $table_name = Database::get_table( false ); $table_name_daily = Database::get_table( true ); $sql = 'ALTER TABLE ' . $table_name . ' DROP PRIMARY KEY; '; $sql .= '
'; $sql .= 'ALTER TABLE ' . $table_name_daily . ' DROP PRIMARY KEY; '; $sql .= '
'; $sql .= 'ALTER TABLE ' . $table_name_daily . ' DROP INDEX IF EXISTS blog_date; '; $sql .= '
'; $sql .= 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY(postnumber, blog_id); '; $sql .= '
'; $sql .= 'ALTER TABLE ' . $table_name_daily . ' ADD PRIMARY KEY(postnumber, dp_date, blog_id); '; $sql .= '
'; $sql .= 'ALTER TABLE ' . $table_name_daily . ' ADD INDEX blog_date(blog_id, dp_date, postnumber); '; /** * Filters the SQL code to recreate the PRIMARY KEY. * * @since 2.5.7 * @param string $sql SQL code to recreate PRIMARY KEY. */ return apply_filters( 'tptn_recreate_primary_key_html', $sql ); } /** * Retrieves the SQL code to recreate the PRIMARY KEY. * * @since 2.7.0 */ public static function recreate_tables() { $errors = new \WP_Error(); $result = Database::recreate_overall_table( false ); if ( is_wp_error( $result ) ) { $errors->merge_from( $result ); } $result = Database::recreate_daily_table( false ); if ( is_wp_error( $result ) ) { $errors->merge_from( $result ); } $result = Database::recreate_funnel_table( false ); if ( is_wp_error( $result ) ) { $errors->merge_from( $result ); } $result = Database::recreate_log_table( false ); if ( is_wp_error( $result ) ) { $errors->merge_from( $result ); } return $errors->has_errors() ? $errors : true; } /** * Clear and reschedule the Top 10 cron jobs. * * @since 4.4.0 * * @return string|\WP_Error Success message or WP_Error if a job could not be rescheduled. */ public static function fix_crons() { $errors = new \WP_Error(); $messages = array(); $success = true; if ( self::clear_scheduled_hook_or_error( 'tptn_cron_hook', $errors ) ) { if ( tptn_get_option( 'cron_on' ) ) { Cron::enable_run( (int) tptn_get_option( 'cron_hour' ), (int) tptn_get_option( 'cron_min' ), tptn_get_option( 'cron_recurrence' ) ); if ( wp_next_scheduled( 'tptn_cron_hook' ) ) { $messages[] = __( 'Maintenance cron has been rescheduled.', 'top-10' ); } else { $errors->add( 'tptn_cron_hook', __( 'The maintenance cron could not be rescheduled as the cron event list could not be saved.', 'top-10' ) ); $success = false; } } else { $messages[] = __( 'Maintenance cron is disabled in the settings and was left unscheduled.', 'top-10' ); } } else { $success = false; } if ( self::clear_scheduled_hook_or_error( 'tptn_aggregation_cron_hook', $errors ) ) { Cron::enable_aggregation_run(); if ( wp_next_scheduled( 'tptn_aggregation_cron_hook' ) ) { $messages[] = __( 'Aggregation cron has been rescheduled.', 'top-10' ); } else { $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' ) ); $success = false; } } else { $success = false; } // Only drop the previously recorded WP-Cron errors once both jobs are confirmed rebuilt (or intentionally left off). if ( $success ) { Cron::clear_reschedule_errors(); } return $errors->has_errors() ? $errors : implode( ' ', $messages ); } /** * Clear a scheduled hook, surfacing any WP_Error from WordPress core instead of silently ignoring it. * * If the existing event cannot be cleared, the caller must not attempt to reschedule it: the stale * event would still satisfy wp_next_scheduled(), making a broken cron write look like a successful repair. * * @since 4.4.0 * * @param string $hook Hook name to clear. * @param \WP_Error $errors Error collector to append to on failure. * @return bool True if the hook was cleared (or had nothing scheduled), false on failure. */ private static function clear_scheduled_hook_or_error( $hook, \WP_Error $errors ) { $result = wp_clear_scheduled_hook( $hook, array(), true ); if ( is_wp_error( $result ) ) { $errors->add( $hook, sprintf( /* translators: 1: Hook name, 2: Error message from WordPress core. */ __( 'Could not clear the existing schedule for %1$s: %2$s', 'top-10' ), $hook, $result->get_error_message() ) ); return false; } return true; } /** * Generates the Tools help page. * * @since 3.3.0 */ public static function help_tabs() { $screen = get_current_screen(); $screen->set_help_sidebar( /* translators: 1: Support link. */ '

' . sprintf( __( 'For more information or how to get support visit the WebberZone support site.', 'top-10' ), esc_url( 'https://webberzone.com/support/' ) ) . '

' . /* translators: 1: Forum link. */ '

' . sprintf( __( 'Support queries should be posted in the WordPress.org support forums.', 'top-10' ), esc_url( 'https://wordpress.org/support/plugin/top-10' ) ) . '

' . '

' . sprintf( /* translators: 1: Github Issues link, 2: Github page. */ __( 'Post an issue on GitHub (bug reports only).', 'top-10' ), esc_url( 'https://github.com/WebberZone/top-10/issues' ), esc_url( 'https://github.com/WebberZone/top-10' ) ) . '

' ); $screen->add_help_tab( array( 'id' => 'tptn-settings-general', 'title' => __( 'General', 'top-10' ), 'content' => '

' . __( 'This screen provides some tools that help maintain certain features of Top 10.', 'top-10' ) . '

' . '

' . __( 'Clear the cache, reset the popular posts tables plus some miscellaneous fixes for older versions of Top 10.', 'top-10' ) . '

', ) ); do_action( 'tptn_settings_tools_help', $screen ); } /** * Check if tables exist and create them if they don't. * * @since 4.2.0 * * @return array Array of table statuses indicating whether they are installed. */ public static function check_table_status() { $tables = array( 'top_ten' => Database::get_table( false ), 'top_ten_daily' => Database::get_table( true ), 'top_ten_visits_funnel' => Database::get_funnel_table(), 'top_ten_visits_log' => Database::get_log_table(), ); $statuses = array(); $installed_label = '' . __( 'Installed', 'top-10' ) . ''; $not_installed_label = '' . __( 'Not Installed', 'top-10' ) . ''; foreach ( $tables as $key => $table_name ) { $statuses[ $key ] = Database::is_table_installed( $table_name ) ? $installed_label : $not_installed_label; } // Create tables if they don't exist. if ( ! Database::are_tables_installed() ) { // Use Activator to create tables. Activator::create_tables(); Database::clear_table_statistics_cache(); // Refresh statuses after creating tables. foreach ( $tables as $key => $table_name ) { $statuses[ $key ] = Database::is_table_installed( $table_name ) ? $installed_label : $not_installed_label; } } /** * Filter the table statuses report. * * @since 4.1.0 * * @param array $statuses Array of table statuses. */ return apply_filters( 'tptn_table_statuses', $statuses ); } /** * Get database status report. * * @since 4.2.0 * * @return string HTML output for the database status report. */ public static function get_db_status_report() { global $tptn_db_version; // Get table statuses. $statuses = self::check_table_status(); // Get table statistics from Database class. $table_stats = Database::get_table_statistics(); ob_start(); ?> __( 'Maintenance cron', 'top-10' ), 'tptn_aggregation_cron_hook' => __( 'Aggregation cron', 'top-10' ), ); foreach ( $cron_jobs as $hook => $label ) : $next_run = wp_next_scheduled( $hook ); $recurrence = $next_run ? wp_get_schedule( $hook ) : false; $schedules = wp_get_schedules(); $interval_display = ''; if ( $recurrence && isset( $schedules[ $recurrence ]['display'] ) ) { $interval_display = $schedules[ $recurrence ]['display']; } ?>
/
'; printf( esc_html( $format ), '' . esc_html( number_format_i18n( $table_stats[ $table_key ]['entries'] ) ) . '', '' . esc_html( size_format( $table_stats[ $table_key ]['size'] ) ) . '' ); echo ''; } ?>

'; } printf( /* translators: %s: human-readable time difference */ esc_html__( 'Next run: %s', 'top-10' ), /* translators: %s: human-readable time difference */ esc_html( sprintf( __( 'in %s', 'top-10' ), human_time_diff( time(), $next_run ) ) ) ); ?>
get_error_message(), 'error' ); } if ( is_wp_error( $result_daily ) ) { add_settings_error( 'tptn-notices', 'tptn-recreate-daily-error', $result_daily->get_error_message(), 'error' ); } if ( is_wp_error( $result_funnel ) ) { add_settings_error( 'tptn-notices', 'tptn-recreate-funnel-error', $result_funnel->get_error_message(), 'error' ); } if ( is_wp_error( $result_log ) ) { add_settings_error( 'tptn-notices', 'tptn-recreate-log-error', $result_log->get_error_message(), 'error' ); } // If no errors, add success message. if ( ! is_wp_error( $result_overall ) && ! is_wp_error( $result_daily ) && ! is_wp_error( $result_funnel ) && ! is_wp_error( $result_log ) ) { add_settings_error( 'tptn-notices', 'tptn-recreate-success', __( 'Tables have been recreated successfully.', 'top-10' ), 'success' ); // Clear table statistics cache since tables were recreated. Database::clear_table_statistics_cache(); } // Redirect back to the tools page. $page = is_network_admin() ? 'tptn_network_tools_page' : 'tptn_tools_page'; wp_safe_redirect( admin_url( 'admin.php?page=' . $page . '&settings-updated=true' ) ); exit; } }