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' ) ), ) ); } } }