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();
?>
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();
?>
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;
}
}