Traffic_Aggregator::get_realtime_count() ) ); } /** * Truncates the error log file. * * @return void * * @since 1.1.0 */ public static function truncate_log_file() { WP_Helper::verify_admin_nonce( 'advan-plugin-data', 'advanced-analytics-security' ); Error_Log::clear( Error_Log::autodetect() ); Log_Line_Parser::delete_last_parsed_timestamp(); \wp_send_json_success( 2 ); } /** * Truncates the error log file, but keeps the last records. * * @return void * * @since 1.9.2 */ public static function truncate_and_keep_log_file() { WP_Helper::verify_admin_nonce( 'advan-plugin-data', 'advanced-analytics-security' ); // Suppress the output which plugin generates during internal operations. \ob_start(); Error_Log::truncate_and_keep_errors(); \ob_clean(); \wp_send_json_success( 2 ); } /** * Downloads the error log file. * * @return void * * @since 1.1.0 */ public static function download_log_file() { WP_Helper::verify_admin_nonce( 'advan-plugin-data', 'advanced-analytics-security' ); File_Helper::download( Error_Log::autodetect() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped \wp_send_json_success( 2 ); } /** * Method responsible for AJAX data saving * * @return void * * @since 1.1.0 */ public static function save_settings_ajax() { WP_Helper::verify_admin_nonce( 'aadvana-plugin-data', 'aadvana-security' ); if ( isset( $_POST[ \ADVAN_SETTINGS_NAME ] ) && ! empty( $_POST[ \ADVAN_SETTINGS_NAME ] ) && \is_array( $_POST[ \ADVAN_SETTINGS_NAME ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing $data = array_map( 'sanitize_text_field', \stripslashes_deep( $_POST[ \ADVAN_SETTINGS_NAME ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing \update_option( \ADVAN_SETTINGS_NAME, Settings::collect_and_sanitize_options( $data ) ); \wp_send_json_success( 2 ); } \wp_send_json_error( __( 'Something went wrong.', '0-day-analytics' ), 400 ); } /** * Executes cron deletion sequence. * * @return void * * @since 1.3.0 */ public static function delete_cron() { WP_Helper::verify_admin_nonce( 'bulk-custom-delete' ); $hash = self::validate_hash_param(); $result = Crons_Helper::delete_event( $hash ); if ( $result && ! \is_wp_error( $result ) ) { \wp_send_json_success( 2 ); } elseif ( \is_wp_error( $result ) ) { \wp_send_json_error( $result->get_error_message(), 500 ); } else { \wp_send_json_error( __( 'Unable to delete cron (no longer exists maybe ? Try refreshing the page).', '0-day-analytics' ), 500 ); } } /** * Executes transient deletion sequence. * * @return void * * @since 1.7.0 */ public static function delete_transient() { WP_Helper::verify_admin_nonce( 'bulk-custom-delete' ); $id = self::validate_id_param(); $result = Transients_Helper::delete_transient( $id ); if ( $result && ! \is_wp_error( $result ) ) { \wp_send_json_success( 2 ); } elseif ( \is_wp_error( $result ) ) { \wp_send_json_error( $result->get_error_message(), 500 ); } else { \wp_send_json_error( __( 'Unable to delete transient.', '0-day-analytics' ), 500 ); } } /** * Executes cron run sequence. * * @return void * * @since 1.3.0 */ public static function run_cron() { WP_Helper::verify_admin_nonce( 'bulk-custom-delete' ); $hash = self::validate_hash_param(); if ( ! defined( 'DOING_CRON' ) ) { define( 'DOING_CRON', true ); } $result = Crons_Helper::execute_event( $hash ); if ( $result && ! \is_wp_error( $result ) ) { \wp_send_json_success( 2 ); } elseif ( \is_wp_error( $result ) ) { \wp_send_json_error( $result->get_error_message(), 500 ); } elseif ( false === $result ) { \wp_send_json_error( __( 'Cron does not exists. Try to refresh the page.', '0-day-analytics' ), 500 ); } else { \wp_send_json_error( __( 'Unable to run cron.', '0-day-analytics' ), 500 ); } } /** * Validates and retrieves the hash parameter from the request. * * @return string The sanitized hash. * * @since 1.4.0 */ private static function validate_hash_param(): string { if ( ! isset( $_REQUEST['hash'] ) || empty( $_REQUEST['hash'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended \wp_send_json_error( __( 'Missing or empty hash parameter.', '0-day-analytics' ), 400 ); } return \sanitize_text_field( \wp_unslash( $_REQUEST['hash'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended } /** * Validates and retrieves the id parameter from the request. * * @return int The sanitized id. * * @since 1.7.0 */ private static function validate_id_param(): int { if ( ! isset( $_REQUEST['id'] ) || empty( $_REQUEST['id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended \wp_send_json_error( 'Missing or empty id parameter.', 400 ); } return (int) \sanitize_text_field( \wp_unslash( $_REQUEST['id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended } /** * Sends test message to Telegram channel * * @return void * * @since 1.9.5 */ public static function telegram_test_message_ajax() { WP_Helper::verify_admin_nonce( Telegram::NONCE_NAME ); if ( Telegram::is_set() ) { Telegram_API::send_telegram_message_via_api( null, null, __( 'TEST MESSAGE', '0-day-analytics' ) ); \wp_send_json_success(); } \wp_send_json_error( __( 'TELEGRAM: Something is wrong with your configuration.', '0-day-analytics' ) . Slack_API::get_slack_error() ); } /** * Sends test message to Slack channel * * @return void * * @since 1.9.5 */ public static function slack_test_message_ajax() { WP_Helper::verify_admin_nonce( Slack::NONCE_NAME ); if ( Slack::is_set() ) { Slack_API::send_slack_message_via_api( null, null, __( 'TEST MESSAGE', '0-day-analytics' ) ); \wp_send_json_success(); } \wp_send_json_error( __( 'SLACK: Something is wrong with your configuration.', '0-day-analytics' ) . Slack_API::get_slack_error() ); } /** * Stores the Slack Credentials key via AJAX request * * @return void * * @since 1.8.0 */ public static function store_slack_api_key_ajax() { WP_Helper::verify_admin_nonce( Slack::NONCE_NAME ); if ( isset( $_REQUEST['slack_auth'] ) && ! empty( $_REQUEST['slack_auth'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $slack_valid = Slack_API::verify_slack_token( (string) \sanitize_text_field( \wp_unslash( $_REQUEST['slack_auth'] ) ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended ); if ( $slack_valid ) { $options = Slack::get_settings(); $options['auth_token'] = \sanitize_text_field( \wp_unslash( $_REQUEST['slack_auth'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended Slack::set_settings( $options ); \wp_send_json_success(); } \wp_send_json_error( __( 'SLACK: No token provided or the provided one is invalid. Please check and provide the details again.', '0-day-analytics' ) . Slack_API::get_slack_error() ); } } /** * Stores the Slack Credentials key via AJAX request * * @return void * * @since 1.8.5 */ public static function store_telegram_api_key_ajax() { WP_Helper::verify_admin_nonce( Telegram::NONCE_NAME ); if ( isset( $_REQUEST['telegram_auth'] ) && ! empty( $_REQUEST['telegram_auth'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $telegram_valid = Telegram_API::verify_telegram_token( (string) \sanitize_text_field( \wp_unslash( $_REQUEST['telegram_auth'] ) ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended ); if ( $telegram_valid ) { $options = Telegram::get_settings(); $options['auth_token'] = \sanitize_text_field( \wp_unslash( $_REQUEST['telegram_auth'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended Telegram::set_settings( $options ); \wp_send_json_success(); } \wp_send_json_error( __( 'TELEGRAM: No token provided or the provided one is invalid. Please check and provide the details again.', '0-day-analytics' ) . Telegram_API::get_telegram_error() ); } } /** * Returns the data used to show notification (if any) * * @return void * * @since 1.9.3 */ public static function get_notification_data() { WP_Helper::verify_admin_nonce( 'advan-plugin-data', 'advanced-analytics-security' ); $data = Logs_List::get_notification_data(); if ( null !== Error_Log::get_last_error() ) { // Some error with error log file - do not bother the user with endless notifications. \wp_send_json_success(); \wp_die(); } if ( empty( $data ) ) { \wp_send_json_success(); } else { \wp_send_json_success( $data ); } } /** * Extracts the plugin versions. * * @return void * * @since 1.9.7 */ public static function extract_plugin_versions() { WP_Helper::verify_admin_nonce( 'advan-plugin-data', 'advanced-analytics-security' ); if ( ! isset( $_REQUEST['plugin_slug'] ) || empty( $_REQUEST['plugin_slug'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended \wp_send_json_error( \esc_html__( 'Plugin slug is not provided.', '0-day-analytics' ), 404 ); } $plugin_versions = Upgrade_Notice::extract_plugin_versions( \sanitize_title( \wp_unslash( $_REQUEST['plugin_slug'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( \is_wp_error( $plugin_versions ) ) { \wp_send_json_error( $plugin_versions->get_error_message(), 500 ); } \wp_send_json_success( $plugin_versions ); } /** * Switch the plugin to desired version. * * @return void * * @since 1.9.7 */ public static function switch_plugin_version() { WP_Helper::verify_admin_nonce( 'advan-plugin-data', 'advanced-analytics-security' ); if ( ! isset( $_REQUEST['plugin_slug'] ) || empty( $_REQUEST['plugin_slug'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended \wp_send_json_error( \esc_html__( 'Plugin slug is not provided.', '0-day-analytics' ), 404 ); } if ( ! isset( $_REQUEST['version'] ) || empty( $_REQUEST['version'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended \wp_send_json_error( \esc_html__( 'Plugin slug is not provided.', '0-day-analytics' ), 404 ); } $switch_versions = Upgrade_Notice::version_switch( \sanitize_title( \wp_unslash( $_REQUEST['plugin_slug'] ) ), \sanitize_text_field( \wp_unslash( $_REQUEST['version'] ) ) ); if ( \is_wp_error( $switch_versions ) ) { \wp_send_json_error( $switch_versions->get_error_message(), 500 ); } \wp_send_json_success(); } /** * Shows the source code. * * @return void * * @since 1.8.2 */ public static function ajax_view_source() { WP_Helper::verify_admin_nonce( 'source-view' ); if ( ! isset( $_REQUEST['error_file'] ) || empty( $_REQUEST['error_file'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended \wp_send_json_error( \esc_html__( 'File not found.', '0-day-analytics' ), 404 ); } $file_name = \sanitize_text_field( \wp_unslash( $_REQUEST['error_file'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! File_Helper::is_file_valid_php( $file_name ) ) { \wp_send_json_error( \esc_html__( 'File does not exists or it is not valid PHP file.', '0-day-analytics' ), 404 ); } // Validate against allowed base directory using realpath to prevent traversal. $real_file = realpath( $file_name ); $real_base = realpath( ABSPATH ); if ( false === $real_file || false === $real_base || 0 !== strpos( $real_file, $real_base ) || is_link( $file_name ) ) { \wp_send_json_error( \esc_html__( 'Invalid file path.', '0-day-analytics' ), 403 ); } // Use the resolved real path from here on. $file_name = $real_file; // Don't show any configurational files for security reasons. if ( Settings::get_option( 'protected_config_source' ) && ( strpos( \basename( $file_name ), 'config' ) !== false || strpos( \basename( $file_name ), 'settings' ) !== false || strpos( \basename( $file_name ), 'wp-load' ) !== false ) ) { \wp_send_json_error( \esc_html__( 'File source view is protected. You can change this in Advanced Settings - Do not show the source of the config and settings files', '0-day-analytics' ), 404 ); } // Parse requested highlight lines. $lines_raw = isset( $_REQUEST['error_line'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['error_line'] ) ) : '11'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $highlight_lines = array(); if ( \strpos( (string) $lines_raw, '-' ) ) { $highlight_lines = array_map( 'absint', explode( '-', $lines_raw ) ); } else { $highlight_lines = array( \absint( $lines_raw ) ); } $scroll_to = max( 1, $highlight_lines[0] - 5 ); $highlight_map = array_flip( $highlight_lines ); // Set Night Owl token colours for PHP's highlight_string(). $orig_ini = array( 'highlight.keyword' => ini_get( 'highlight.keyword' ), 'highlight.default' => ini_get( 'highlight.default' ), 'highlight.comment' => ini_get( 'highlight.comment' ), 'highlight.string' => ini_get( 'highlight.string' ), 'highlight.html' => ini_get( 'highlight.html' ), ); ini_set( 'highlight.keyword', '#c792ea' ); // phpcs:ignore WordPress.PHP.IniSet.Risky ini_set( 'highlight.default', '#d6deeb' ); // phpcs:ignore WordPress.PHP.IniSet.Risky ini_set( 'highlight.comment', '#637777' ); // phpcs:ignore WordPress.PHP.IniSet.Risky ini_set( 'highlight.string', '#ecc48d' ); // phpcs:ignore WordPress.PHP.IniSet.Risky ini_set( 'highlight.html', '#7fdbca' ); // phpcs:ignore WordPress.PHP.IniSet.Risky $raw_source = @file_get_contents( $file_name ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents $highlighted = highlight_string( $raw_source, true ); // Restore original ini values. foreach ( $orig_ini as $key => $val ) { ini_set( $key, $val ); // phpcs:ignore WordPress.PHP.IniSet.Risky } // Strip the
wrapper that highlight_string() adds.
$highlighted = preg_replace( '#^]*>#s', '', $highlighted );
$highlighted = preg_replace( '#\s*
$#s', '', $highlighted );
// Split into lines while preserving span context across line breaks.
// highlight_string() creates tags that can span multiple lines,
// so a naive explode("\n") produces broken HTML. We track open spans
// and re-open them on each new line.
$raw_lines = explode( "\n", $highlighted );
$code_lines = array();
$open_spans = array(); // stack of currently open tags.
foreach ( $raw_lines as $raw_line ) {
// Re-open any spans that were open from the previous line.
$prefix = implode( '', $open_spans );
// Find all span opens and closes in this line to track the stack.
$working = $raw_line;
// Match all and in order.
preg_match_all( '#]*>|#', $working, $tags, PREG_OFFSET_CAPTURE );
if ( ! empty( $tags[0] ) ) {
foreach ( $tags[0] as $tag_match ) {
$tag = $tag_match[0];
if ( 0 === strpos( $tag, '' === $tag && ! empty( $open_spans ) ) {
array_pop( $open_spans );
}
}
}
// Close any spans that are still open so this line is valid HTML.
$suffix = str_repeat( '', count( $open_spans ) );
$code_lines[] = $prefix . $raw_line . $suffix;
}
// Post-process: enrich token colours beyond highlight_string()'s 5 types.
// Night Owl palette: variables #addb67, functions #82aaff, constants #ff5874,
// numbers #f78c6c, types #7fdbca, operators #c792ea.
$type_keywords = 'int|float|string|bool|array|object|callable|iterable|void|mixed|never|null|true|false|self|static|parent';
// Default color used by highlight_string() for identifiers.
$default_color = '#d6deeb';
foreach ( $code_lines as $idx => $cl ) {
// 1. Function calls: highlight_string() puts the function name in a
// default-colored span and the "(" in a keyword-colored span, e.g.:
// func_name(
// We detect this cross-span pattern and recolor the name span.
$cl = preg_replace(
'#()([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)(\s*\()#',
'$1$2$3',
$cl
);
// 2. Apply remaining replacements to text content (between > and <).
$code_lines[ $idx ] = preg_replace_callback(
'#(?<=>)([^<]+)#',
function ( $m ) use ( $type_keywords ) {
$text = $m[1];
// Variables: $name, $this.
$text = preg_replace(
'/(\$[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)/',
'$1',
$text
);
// Constants: sequences of 2+ uppercase letters/underscores (min 2 chars).
$text = preg_replace(
'/(?$1',
$text
);
// Numbers: integer and float literals.
$text = preg_replace(
'/\b(\d+\.?\d*(?:e[+-]?\d+)?)\b/i',
'$1',
$text
);
// Type hints: standalone type keywords.
$text = preg_replace(
'/\b(' . $type_keywords . ')\b/i',
'$1',
$text
);
// Arrow / double-colon / double-arrow operators.
// highlight_string() HTML-encodes > so -> becomes ->
$text = preg_replace(
'/(->|::|=>)/',
'$1',
$text
);
return $text;
},
$cl
);
}
$total_lines = count( $code_lines );
$css_url = \esc_url( ADVAN_PLUGIN_ROOT_URL . 'css/source-viewer.css' );
$js_url = \esc_url( ADVAN_PLUGIN_ROOT_URL . 'js/source-viewer.js' );
?>
\esc_html__( 'Hey... are you getting this?', '0-day-analytics' ),
'body' => '' . \esc_html__( 'Looks like you did!', '0-day-analytics' ) . '
',
),
array(
'title' => \esc_html__( 'There\'s a message for you...', '0-day-analytics' ),
'body' => '' . \esc_html__( 'Here it is:', '0-day-analytics' ) . '
',
),
array(
'title' => \esc_html__( 'Is it working?', '0-day-analytics' ),
'body' => '' . \esc_html__( 'Yes, it\'s working!', '0-day-analytics' ) . '
',
),
array(
'title' => \esc_html__( 'Hope you\'re getting this...', '0-day-analytics' ),
'body' => '' . \esc_html__( 'Looks like this was sent out just fine and you got it.', '0-day-analytics' ) . '
',
),
array(
'title' => \esc_html__( 'Testing delivery configuration...', '0-day-analytics' ),
'body' => '' . \esc_html__( 'Everything looks good!', '0-day-analytics' ) . '
',
),
array(
'title' => \esc_html__( 'Testing email delivery', '0-day-analytics' ),
'body' => '' . \esc_html__( 'Looks good!', '0-day-analytics' ) . '
',
),
array(
'title' => \esc_html__( 'Config is looking good', '0-day-analytics' ),
'body' => '' . \esc_html__( 'Seems like everything has been set up properly!', '0-day-analytics' ) . '
',
),
array(
'title' => \esc_html__( 'All set up', '0-day-analytics' ),
'body' => '' . \esc_html__( 'Your configuration is working properly.', '0-day-analytics' ) . '
',
),
array(
'title' => \esc_html__( 'Good to go', '0-day-analytics' ),
'body' => '' . \esc_html__( 'Config is working great.', '0-day-analytics' ) . '
',
),
array(
'title' => \esc_html__( 'Good job', '0-day-analytics' ),
'body' => '' . \esc_html__( 'Everything is set.', '0-day-analytics' ) . '
',
),
);
$email = $_REQUEST['email'] ?? ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$random_number = rand( 0, count( $content ) - 1 );
$to = \sanitize_email( $email );
$title = $content[ $random_number ]['title'];
$body = $content[ $random_number ]['body'] . 'This message was sent from ' . \get_bloginfo( 'url' ) . ' on ' . \wp_date( 'F j, Y' ) . ' at ' . \wp_date( 'H:i:s' ) . ' via.' . ADVAN_NAME . '
';
$headers = array( 'Content-Type: text/html; charset=UTF-8' );
$success = \wp_mail(
$to,
$title,
$body,
$headers
);
if ( $success ) {
\wp_send_json_success();
} else {
\wp_send_json_error( __( 'Mail: Something is wrong with your configuration.', '0-day-analytics' ) );
}
}
/**
* Dismisses the error filtering notice for the given user
*
* @return void
*
* @since 3.8.0
*/
public static function error_log_filtering_dismiss_notice() {
$user_id = \get_current_user_id();
\update_user_meta( $user_id, '_aadvana_filter_error_log_notice_dismissed', 1 );
\wp_die();
}
/**
* Export Large CSV in Batches
*
* @return void
*
* @since 3.8.0
*/
public static function export_large_csv() {
WP_Helper::verify_admin_nonce( 'export_large_csv_nonce', 'security' );
$batch = isset( $_POST['batch'] ) ? intval( $_POST['batch'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$batch_size = isset( $_POST['batch_size'] ) ? intval( $_POST['batch_size'] ) : 500; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$offset = $batch * $batch_size;
[$export_dir, $export_url] = Miscellaneous::get_export_dir();
$csv_path = $export_dir . 'export_temp.csv';
$csv_url = $export_url . 'export_temp.csv';
$rows = array();
$total = 0;
$extra_file_name = '';
if ( isset( $_POST['typeExport'] ) && ! empty( $_POST['typeExport'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
if ( 'cron' === $_POST['typeExport'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
$plugin_filter = ( isset( $_POST['plugin_filter'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin_filter'] ) ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
$site_filter = ( isset( $_POST['site_filter'] ) ? sanitize_text_field( wp_unslash( $_POST['site_filter'] ) ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
$rows = array();
if ( '' !== $site_filter && -1 !== (int) $site_filter && function_exists( 'is_multisite' ) && is_multisite() ) {
$rows = Crons_Helper::get_events_for_site( (int) $site_filter );
} else {
$rows = Crons_List::get_cron_items();
}
if ( '' !== $plugin_filter && -1 !== (int) $plugin_filter ) {
$rows = array_filter(
$rows,
function ( $row ) use ( $plugin_filter ) {
if ( isset( $row['plugin_slugs'] ) && is_array( $row['plugin_slugs'] ) ) {
return in_array( $plugin_filter, $row['plugin_slugs'], true );
}
return ( isset( $row['plugin_slug'] ) && '' !== $row['plugin_slug'] && $row['plugin_slug'] === $plugin_filter );
}
);
}
$total = count( $rows );
$rows = \array_slice( $rows, $offset, $batch_size, true );
}
if ( 'logs' === $_POST['typeExport'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
$plugin_filter = ( ( isset( $_POST['plugin_filter'] ) ) ? \sanitize_text_field( \wp_unslash( $_POST['plugin_filter'] ) ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
$search = isset( $_POST['search'] ) ? \sanitize_text_field( wp_unslash( $_POST['search'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$extra_file_name = '_' . $plugin_filter . '_';
$list_logs = new Logs_List( array( 'plugin_filter' => $plugin_filter ) );
$rows = $list_logs::get_error_items();
$rows = \array_slice( $rows, $offset, $batch_size, true );
foreach ( $rows as $key => $row ) {
if ( isset( $row['sub_items'] ) ) {
if ( \is_array( $row['sub_items'] ) && ! empty( $row['sub_items'] ) ) {
$subs = array();
foreach ( $row['sub_items'] as $sub_key => $sub_item ) {
$subs[] = \json_encode( $sub_item );
}
$rows[ $key ]['sub_items'] = $subs;
}
} else {
$rows[ $key ]['sub_items'] = '';
}
if ( ! isset( $row['plugin'] ) ) {
$rows[ $key ]['plugin'] = '';
}
}
$total = count( $rows );
}
if ( 'table' === $_POST['typeExport'] && isset( $_POST['tableName'] ) && ! empty( $_POST['tableName'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
$table = \sanitize_text_field( \wp_unslash( $_POST['tableName'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
$search = isset( $_POST['search'] ) ? \sanitize_text_field( \wp_unslash( $_POST['search'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$extra_file_name = '_' . str_replace( ' ', '_', $table ) . '_';
if ( Common_Table::check_table_exists( $table ) ) {
$list_table = new Table_List( $table );
$rows = $list_table->fetch_table_data(
array(
'per_page' => $batch_size,
'offset' => $offset,
'order' => 'ASC',
'search_string' => $search,
)
);
$total = $list_table->get_count();
}
}
if ( 'mail' === $_POST['typeExport'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
$list_table = new WP_Mail_List();
$search = isset( $_POST['search'] ) ? sanitize_text_field( wp_unslash( $_POST['search'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$plugin = isset( $_POST['plugin'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$extra_file_name = '_' . str_replace( ' ', '_', $list_table->get_table_name() ) . '_';
if ( ! empty( $plugin ) && -1 !== (int) $plugin ) {
$extra_file_name .= $plugin . '_';
}
$rows = $list_table->fetch_table_data(
array(
'per_page' => $batch_size,
'offset' => $offset,
'order' => 'ASC',
'site_id' => '',
'search' => $search,
'plugin' => $plugin,
)
);
$total = $list_table->get_count();
}
if ( 'requests' === $_POST['typeExport'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
$search = isset( $_POST['search'] ) ? \sanitize_text_field( \wp_unslash( $_POST['search'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$plugin = isset( $_POST['plugin'] ) ? \sanitize_text_field( \wp_unslash( $_POST['plugin'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$date_from = isset( $_POST['dateFrom'] ) ? \sanitize_text_field( \wp_unslash( $_POST['dateFrom'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$date_to = isset( $_POST['dateTo'] ) ? \sanitize_text_field( \wp_unslash( $_POST['dateTo'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$status_filter = isset( $_POST['statusFilter'] ) ? \sanitize_text_field( \wp_unslash( $_POST['statusFilter'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$type_filter = isset( $_POST['typeFilter'] ) ? \sanitize_text_field( \wp_unslash( $_POST['typeFilter'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$domain_filter = isset( $_POST['domainFilter'] ) ? \sanitize_text_field( \wp_unslash( $_POST['domainFilter'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$runtime_min = isset( $_POST['runtimeMin'] ) ? \sanitize_text_field( \wp_unslash( $_POST['runtimeMin'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$runtime_max = isset( $_POST['runtimeMax'] ) ? \sanitize_text_field( \wp_unslash( $_POST['runtimeMax'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
// Use the list class to compute totals and fetch rows, keeping filters/formatting consistent.
$list_table = new Requests_List();
$extra_file_name = '_' . str_replace( ' ', '_', $list_table->get_table_name() ) . '_';
// Get total using the list's counting behaviour.
$list_table->fetch_table_data(
array(
'count' => true,
'search_string' => $search,
'plugin' => $plugin,
'date_from' => $date_from,
'date_to' => $date_to,
'status_filter' => $status_filter,
'type_filter' => $type_filter,
'domain_filter' => $domain_filter,
'runtime_min' => $runtime_min,
'runtime_max' => $runtime_max,
)
);
$total = $list_table->get_count();
// Fetch the actual rows for this batch.
$rows = $list_table->fetch_table_data(
array(
'offset' => $offset,
'per_page' => $batch_size,
'order' => 'ASC',
'search_string' => $search,
'plugin' => $plugin,
'date_from' => $date_from,
'date_to' => $date_to,
'status_filter' => $status_filter,
'type_filter' => $type_filter,
'domain_filter' => $domain_filter,
'runtime_min' => $runtime_min,
'runtime_max' => $runtime_max,
)
);
}
if ( 'hooks_capture' === $_POST['typeExport'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
$search = isset( $_POST['search'] ) ? sanitize_text_field( wp_unslash( $_POST['search'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$hook_type = isset( $_POST['hook_type'] ) ? sanitize_text_field( wp_unslash( $_POST['hook_type'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$trigger_source = isset( $_POST['trigger_source'] ) ? sanitize_text_field( wp_unslash( $_POST['trigger_source'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$date_from = isset( $_POST['date_from'] ) ? sanitize_text_field( wp_unslash( $_POST['date_from'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$date_to = isset( $_POST['date_to'] ) ? sanitize_text_field( wp_unslash( $_POST['date_to'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$group_filter = isset( $_POST['group_filter'] ) ? absint( $_POST['group_filter'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
// Ask the list class to compute the total and fetch rows so formatting and filters stay consistent.
$list = new \ADVAN\Lists\Hooks_Capture_List( '' );
// Get total using the list's counting behaviour.
$list->fetch_table_data(
array(
'count' => true,
'search_string' => $search,
'hook_type' => $hook_type,
'trigger_source' => $trigger_source,
'date_from' => $date_from,
'date_to' => $date_to,
'group_filter' => $group_filter,
)
);
$total = $list->get_count();
// Fetch the actual rows for this batch.
$rows = $list->fetch_table_data(
array(
'offset' => $offset,
'per_page' => $batch_size,
'search_string' => $search,
'hook_type' => $hook_type,
'trigger_source' => $trigger_source,
'date_from' => $date_from,
'date_to' => $date_to,
'group_filter' => $group_filter,
)
);
}
if ( 'hooks_management' === $_POST['typeExport'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
$search = isset( $_POST['search'] ) ? sanitize_text_field( wp_unslash( $_POST['search'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$category = isset( $_POST['category'] ) ? sanitize_text_field( wp_unslash( $_POST['category'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$enabled = isset( $_POST['enabled'] ) ? sanitize_text_field( wp_unslash( $_POST['enabled'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
// Use the list class to compute totals and fetch rows, keeping filters/formatting consistent.
$list = new \ADVAN\Lists\Hooks_Management_List( '' );
// Count first using the list class.
$list->fetch_table_data(
array(
'count' => true,
'search_string' => $search,
'category' => $category,
'enabled' => $enabled,
)
);
$total = $list->get_count();
// Fetch rows for this batch.
$rows = $list->fetch_table_data(
array(
'offset' => $offset,
'per_page' => $batch_size,
'search_string' => $search,
'category' => $category,
'enabled' => $enabled,
)
);
}
if ( 'fatals' === $_POST['typeExport'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
$list_table = new Fatals_List();
$search = isset( $_POST['search'] ) ? \sanitize_text_field( \wp_unslash( $_POST['search'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$plugin = isset( $_POST['plugin'] ) ? \sanitize_text_field( \wp_unslash( $_POST['plugin'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$extra_file_name = '_' . str_replace( ' ', '_', $list_table->get_table_name() ) . '_';
$rows = $list_table->fetch_table_data(
array(
'per_page' => $batch_size,
'offset' => $offset,
'order' => 'ASC',
'search_string' => $search,
'plugin' => $plugin,
)
);
$total = $list_table->get_count();
}
if ( 'transients' === $_POST['typeExport'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
$list_table = new Transients_List( array() );
$type = isset( $_POST['transientType'] ) ? \sanitize_text_field( \wp_unslash( $_POST['transientType'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$extra_file_name = '_' . str_replace( ' ', '_', $list_table->get_table_name() ) . '_' . $type . '_';
$search = isset( $_POST['search'] ) ? \sanitize_text_field( \wp_unslash( $_POST['search'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$rows = $list_table->fetch_table_data(
array(
'per_page' => $batch_size,
'offset' => $offset,
'order' => 'ASC',
'type' => $type,
'search' => $search,
)
);
$total = $list_table->get_count();
}
if ( 'snippets' === $_POST['typeExport'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
$search = isset( $_POST['search'] ) ? \sanitize_text_field( wp_unslash( $_POST['search'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$snippet_status = isset( $_POST['snippet_status'] ) ? \sanitize_text_field( wp_unslash( $_POST['snippet_status'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$snippet_type = isset( $_POST['snippet_type'] ) ? \sanitize_text_field( wp_unslash( $_POST['snippet_type'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$filters = array();
if ( '' !== $search ) {
$filters['search'] = $search;
}
if ( '' !== $snippet_status ) {
$filters['status'] = $snippet_status;
}
if ( '' !== $snippet_type ) {
$filters['type'] = $snippet_type;
}
$total = Snippet_Entity::get_snippets_count_with_filters( $filters );
$rows = Snippet_Entity::get_snippets_with_filters( $filters, $offset, $batch_size );
}
if ( 'rest_api' === $_POST['typeExport'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
$search = isset( $_POST['search'] ) ? \sanitize_text_field( \wp_unslash( $_POST['search'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$namespace_filter = isset( $_POST['namespace_filter'] ) ? \sanitize_text_field( \wp_unslash( $_POST['namespace_filter'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$status_filter = isset( $_POST['status_filter'] ) ? \sanitize_key( \wp_unslash( $_POST['status_filter'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$method_filter = isset( $_POST['method_filter'] ) ? \sanitize_text_field( \wp_unslash( $_POST['method_filter'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$access_filter = isset( $_POST['access_filter'] ) ? \sanitize_key( \wp_unslash( $_POST['access_filter'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$extra_file_name = '_rest_api_endpoints_';
$result = Rest_Api_List::get_filtered_endpoints_for_export(
array(
'offset' => $offset,
'per_page' => $batch_size,
'search' => $search,
'namespace_filter' => $namespace_filter,
'status_filter' => $status_filter,
'method_filter' => $method_filter,
'access_filter' => $access_filter,
)
);
$rows = $result['rows'];
$total = $result['total'];
}
if ( 'security_events' === $_POST['typeExport'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
$search = isset( $_POST['search'] ) ? \sanitize_text_field( \wp_unslash( $_POST['search'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$event_type = isset( $_POST['event_type'] ) ? \sanitize_text_field( \wp_unslash( $_POST['event_type'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$severity = isset( $_POST['severity'] ) ? \sanitize_text_field( \wp_unslash( $_POST['severity'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$extra_file_name = '_security_events_';
$list = new Security_List();
// Get total.
$list->fetch_table_data(
array(
'per_page' => 1,
'offset' => 0,
'search_string' => $search,
'event_type' => $event_type,
'severity' => $severity,
)
);
$total = $list->get_count();
// Fetch rows for this batch.
$rows = $list->fetch_table_data(
array(
'offset' => $offset,
'per_page' => $batch_size,
'order' => 'ASC',
'search_string' => $search,
'event_type' => $event_type,
'severity' => $severity,
)
);
}
} else {
\wp_send_json_error(
array(
'message' => __( 'Export type is not specified.', '0-day-analytics' ),
),
400
);
}
$is_first_batch = ( 0 === $batch );
if ( empty( $rows ) ) {
// Open CSV for writing/appending.
$handle = @fopen( $csv_path, $is_first_batch ? 'w' : 'a' );
if ( ! $handle ) {
\wp_send_json_error(
array(
'message' => __( 'Failed to write to CSV file. Check folder permissions.', '0-day-analytics' ),
),
500
);
}
// Get date and time formats from WordPress settings.
$date_format = get_option( 'date_format' ); // e.g., 'F j, Y'.
$time_format = get_option( 'time_format' ); // e.g., 'g:i a'.
// Provide defaults if options are not set.
if ( empty( $date_format ) ) {
$date_format = 'F j, Y';
}
if ( empty( $time_format ) ) {
$time_format = 'H:i:s';
}
// Combine date and time if needed.
$formatted_datetime = date_i18n( $date_format . ' ' . $time_format );
// Sanitize filename: replace spaces and colons with dashes.
$safe_datetime = preg_replace( '/[^A-Za-z0-9\-]/', '-', $formatted_datetime );
// Create filename.
$filename = 'export_' . \sanitize_text_field( \wp_unslash( $_POST['typeExport'] ) ) . '_' . $extra_file_name . $safe_datetime . '.csv'; // phpcs:ignore WordPress.Security.NonceVerification.Missing
rename( $export_dir . 'export_temp.csv', $export_dir . $filename ); // phpcs:ignore WordPress.WP.AlternativeFunctions.rename_rename
\wp_send_json_success(
array(
'done' => true,
'download_url' => $export_url . $filename,
)
);
}
// Open CSV for writing/appending.
$handle = @fopen( $csv_path, $is_first_batch ? 'w' : 'a' );
if ( ! $handle ) {
\wp_send_json_error(
array(
'message' => __( 'Failed to write to CSV file. Check folder permissions.', '0-day-analytics' ),
),
500
);
}
if ( $is_first_batch && ! empty( $rows ) ) {
fputcsv(
$handle,
array_keys( reset( $rows ) ),
"\t",
'"',
'"',
);
}
foreach ( $rows as $row ) {
\fputcsv(
$handle,
array_map(
function ( $value ): string {
// Convert to string.
if ( is_array( $value ) ) {
$value = \implode( ', ', $value );
}
if ( is_object( $value ) ) {
$value = json_encode( $value );
}
$value = (string) $value;
// Remove control characters (non-printable).
$value = preg_replace( '/[\x00-\x1F\x7F]/u', '', $value );
return (string) $value;
},
$row
),
"\t",
'"',
'"',
);
}
fclose( $handle );
\wp_send_json_success(
array(
'done' => false,
'next_batch' => $batch + 1,
'processed' => ( $batch + 1 ) * $batch_size,
'total' => $total,
)
);
}
/**
* Cleanup Temporary CSV
*
* @return void
*
* @since 3.8.0
*/
public static function export_large_csv_cleanup() {
WP_Helper::verify_admin_nonce( 'export_large_csv_nonce', 'security' );
[$export_dir] = Miscellaneous::get_export_dir();
$csv_path = $export_dir . 'export_temp.csv';
if ( file_exists( $csv_path ) ) {
unlink( $csv_path );
\wp_send_json_success( array( 'deleted' => true ) );
} else {
\wp_send_json_success( array( 'deleted' => false ) );
}
}
/**
* Save hook group (create or update).
*
* @return void
*
* @since 4.5.0
*/
public static function save_hook_group() {
WP_Helper::verify_admin_nonce( 'advan_hook_groups', 'nonce' );
if ( ! class_exists( 'ADVAN\Entities\Hook_Groups_Entity' ) ) {
\wp_send_json_error( 'Hook groups not available' );
}
$group_id = isset( $_POST['group_id'] ) ? (int) $_POST['group_id'] : 0;
$name = isset( $_POST['name'] ) ? \sanitize_text_field( \wp_unslash( $_POST['name'] ) ) : '';
$color = isset( $_POST['color'] ) ? \sanitize_hex_color( $_POST['color'] ) : '#007cba';
$description = isset( $_POST['description'] ) ? \sanitize_textarea_field( \wp_unslash( $_POST['description'] ) ) : '';
if ( empty( $name ) ) {
\wp_send_json_error( 'Group name is required' );
}
if ( $group_id > 0 ) {
// Update existing group.
$success = Hook_Groups_Entity::update_group( $group_id, $name, $color, $description );
} else {
// Create new group.
$success = Hook_Groups_Entity::create_group( $name, $color, $description );
}
if ( $success ) {
\wp_send_json_success();
} else {
\wp_send_json_error( 'Failed to save group' );
}
}
/**
* Delete hook group.
*
* @return void
*
* @since 4.5.0
*/
public static function delete_hook_group() {
WP_Helper::verify_admin_nonce( 'advan_hook_groups', 'nonce' );
if ( ! class_exists( 'ADVAN\Entities\Hook_Groups_Entity' ) ) {
\wp_send_json_error( 'Hook groups not available' );
}
$group_id = isset( $_POST['group_id'] ) ? (int) $_POST['group_id'] : 0;
if ( $group_id <= 0 ) {
\wp_send_json_error( 'Invalid group ID' );
}
$success = Hook_Groups_Entity::delete_group( $group_id );
if ( $success ) {
\wp_send_json_success();
} else {
\wp_send_json_error( 'Failed to delete group' );
}
}
/**
* Get detailed table information including structure, indexes, foreign keys, and health.
*
* @return void
*
* @since 4.7.0
*/
public static function get_table_info() {
WP_Helper::verify_admin_nonce( 'get_table_info', 'security' );
$table_name = isset( $_POST['table_name'] ) ? \sanitize_text_field( \wp_unslash( $_POST['table_name'] ) ) : '';
if ( empty( $table_name ) ) {
\wp_send_json_error( 'Invalid table name' );
}
$table_info = Common_Table::get_table_info( $table_name );
if ( \is_wp_error( $table_info ) ) {
\wp_send_json_error( $table_info->get_error_message() );
}
\wp_send_json_success( $table_info );
}
/**
* Optimize a database table.
*
* @return void
*
* @since 5.1.0
*/
public static function optimize_table() {
WP_Helper::verify_admin_nonce( 'optimize_table', 'security' );
$table_name = isset( $_POST['table_name'] ) ? \sanitize_text_field( \wp_unslash( $_POST['table_name'] ) ) : '';
if ( empty( $table_name ) ) {
\wp_send_json_error( \__( 'Invalid table name.', '0-day-analytics' ) );
}
$result = Common_Table::optimize_table( $table_name );
if ( \is_wp_error( $result ) ) {
\wp_send_json_error( $result->get_error_message() );
}
\wp_send_json_success( $result );
}
/**
* AJAX handler for toggling a module on/off from the dashboard.
*
* @return void
*
* @since 5.3.0
*/
public static function toggle_module() {
// Verify nonce.
WP_Helper::verify_admin_nonce( 'advan_dashboard_toggle_module', 'nonce' );
$option_key = isset( $_POST['option_key'] ) ? \sanitize_text_field( \wp_unslash( $_POST['option_key'] ) ) : '';
$enabled = isset( $_POST['enabled'] ) ? \filter_var( \wp_unslash( $_POST['enabled'] ), FILTER_VALIDATE_BOOLEAN ) : false;
if ( empty( $option_key ) ) {
\wp_send_json_error( array( 'message' => \__( 'Invalid module option key.', '0-day-analytics' ) ) );
}
// Validate the option key is a known module option.
$valid_keys = array();
$modules = Module_Registry::get_modules();
foreach ( $modules as $module ) {
if ( ! empty( $module['option_key'] ) ) {
$valid_keys[] = $module['option_key'];
}
}
if ( ! in_array( $option_key, $valid_keys, true ) ) {
\wp_send_json_error( array( 'message' => \__( 'Unknown module option.', '0-day-analytics' ) ) );
}
// Get current options, update, and store.
$options = Settings::get_current_options();
$options[ $option_key ] = $enabled;
Settings::store_options( $options );
$module_label = '';
foreach ( $modules as $module ) {
if ( ( $module['option_key'] ?? '' ) === $option_key ) {
$module_label = $module['label'];
break;
}
}
\wp_send_json_success(
array(
'message' => sprintf(
/* translators: 1: module name, 2: enabled/disabled */
\__( '%1$s module %2$s.', '0-day-analytics' ),
$module_label,
$enabled ? \__( 'enabled', '0-day-analytics' ) : \__( 'disabled', '0-day-analytics' )
),
)
);
}
}
}