Plugin Directory

Changeset 3398360


Ignore:
Timestamp:
11/18/2025 08:26:28 PM (3 months ago)
Author:
awesomefootnotes
Message:

Adding the first version of my plugin

Location:
0-day-analytics
Files:
3 deleted
50 edited
1 copied

Legend:

Unmodified
Added
Removed
  • 0-day-analytics/tags/4.2.0/advanced-analytics.php

    r3393178 r3398360  
    1111 * Plugin Name:     0 Day Analytics
    1212 * Description:     Take full control of error log, crons, transients, plugins, requests, mails and DB tables.
    13  * Version:         4.1.1
     13 * Version:         4.2.0
    1414 * Author:          Stoil Dobrev
    1515 * Author URI:      https://github.com/sdobreff/
     
    3737// Constants.
    3838if ( ! defined( 'ADVAN_VERSION' ) ) {
    39     define( 'ADVAN_VERSION', '4.1.1' );
     39    define( 'ADVAN_VERSION', '4.2.0' );
    4040    define( 'ADVAN_TEXTDOMAIN', '0-day-analytics' );
    4141    define( 'ADVAN_NAME', '0 Day Analytics' );
     
    6767                    sprintf(
    6868                        // translators: the minimum version of the PHP required by the plugin.
    69                         __(
     69                        \__(
    7070                            '"%1$s" requires PHP %2$s or newer. Plugin is automatically deactivated.',
    7171                            '0-day-analytics'
  • 0-day-analytics/tags/4.2.0/classes/migration/class-migration.php

    r3384847 r3398360  
    216216            }
    217217        }
     218
     219        /**
     220         * Migrates the plugin up-to version 4.2.0 (adds plugin_slug column to mail log).
     221         *
     222         * @return void
     223         *
     224         * @since 4.2.0
     225         */
     226        public static function migrate_up_to_420() {
     227            if ( \class_exists( '\\ADVAN\\Entities\\WP_Mail_Entity' ) ) {
     228                if ( Common_Table::check_table_exists( WP_Mail_Entity::get_table_name() ) && ! Common_Table::check_column( 'plugin_slug', 'varchar(255)', WP_Mail_Entity::get_table_name() ) ) {
     229                    \ADVAN\Entities\WP_Mail_Entity::alter_table_411();
     230                }
     231            }
     232        }
    218233    }
    219234}
  • 0-day-analytics/tags/4.2.0/classes/vendor/controllers/class-wp-mail-log.php

    r3393178 r3398360  
    1313
    1414use ADVAN\Entities\WP_Mail_Entity;
     15use ADVAN\Helpers\Plugin_Theme_Helper;
    1516use ADVAN\Helpers\Settings;
    1617
     
    123124                    $message = $email_class->get( 'content_plaintext', 'replace-tokens' );
    124125                }
     126                $bt_segment = self::get_backtrace();
     127                $plugin_slug = '';
     128                if ( isset( $bt_segment['file'] ) ) {
     129                    $plugin_base = Plugin_Theme_Helper::get_plugin_from_file_path( $bt_segment['file'] );
     130                    if ( $plugin_base ) {
     131                        $plugin_slug = $plugin_base;
     132                    }
     133                }
    125134                self::$bp_mail = array(
    126135                    'time'               => time(),
     
    129138                    'message'            => self::filter_html( $message ),
    130139                    'backtrace_segment'  => \wp_json_encode( self::get_backtrace() ),
     140                    'plugin_slug'        => $plugin_slug,
    131141                    'status'             => 1,
    132142                    'attachments'        => \wp_json_encode( self::get_attachment_locations( array() ) ),
     
    150160
    151161            if ( \is_array( $args ) ) {
     162                $bt_segment = self::get_backtrace();
     163                $plugin_slug = '';
     164                if ( isset( $bt_segment['file'] ) ) {
     165                    $plugin_base = Plugin_Theme_Helper::get_plugin_from_file_path( $bt_segment['file'] );
     166                    if ( $plugin_base ) {
     167                        $plugin_slug = $plugin_base;
     168                    }
     169                }
    152170                $log_entry = array(
    153171                    'time'               => time(),
     
    156174                    'message'            => self::filter_html( $args['message'] ),
    157175                    'backtrace_segment'  => \wp_json_encode( self::get_backtrace() ),
     176                    'plugin_slug'        => $plugin_slug,
    158177                    'status'             => 1,
    159178                    'attachments'        => \wp_json_encode( self::get_attachment_locations( $args['attachments'] ) ),
     
    202221                    $mail_header = $prop->getValue( $phpmailer );
    203222
     223                    $bt_segment = self::get_backtrace();
     224                    $plugin_slug = '';
     225                    if ( isset( $phpmailer->Backtrace, $phpmailer->Backtrace['file'] ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
     226                        $plugin_base = Plugin_Theme_Helper::get_plugin_from_file_path( $phpmailer->Backtrace['file'] ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
     227                        if ( $plugin_base ) {
     228                            $plugin_slug = $plugin_base;
     229                        }
     230                    } elseif ( isset( $bt_segment['file'] ) ) {
     231                        $plugin_base = Plugin_Theme_Helper::get_plugin_from_file_path( $bt_segment['file'] );
     232                        if ( $plugin_base ) {
     233                            $plugin_slug = $plugin_base;
     234                        }
     235                    }
    204236                    $log_entry = array(
    205237                        'time'               => time(),
     
    209241                        'message'            => self::filter_html( $phpmailer->Body ), // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
    210242                        'backtrace_segment'  => \wp_json_encode( self::get_backtrace() ),
     243                        'plugin_slug'        => $plugin_slug,
    211244                        'status'             => 1,
    212245                        'attachments'        => \wp_json_encode( self::get_attachment_locations( $attachment ) ),
  • 0-day-analytics/tags/4.2.0/classes/vendor/entities/class-wp-mail-entity.php

    r3393178 r3398360  
    1313
    1414use ADVAN\Helpers\WP_Helper;
     15use ADVAN\Helpers\Plugin_Theme_Helper;
    1516use ADVAN\Entities_Global\Common_Table;
    1617
     
    5455            'id'                 => 'int',
    5556            'blog_id'            => 'int',
     57            'plugin_slug'        => 'string',
    5658            'time'               => 'string',
    5759            'email_to'           => 'string',
     
    7779            'id'                 => 0,
    7880            'blog_id'            => 0,
     81            'plugin_slug'        => '',
    7982            'time'               => '',
    8083            'email_to'           => '',
     
    115118                id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    116119                blog_id INT NOT NULL,
     120                plugin_slug VARCHAR(255) DEFAULT NULL,
    117121                time DOUBLE NOT NULL DEFAULT 0,
    118122                email_to TEXT DEFAULT NULL,
     
    168172
    169173        /**
     174         * Adds plugin_slug column (version 4.1.1 migration) capturing originating plugin.
     175         *
     176         * @return array|bool
     177         *
     178         * @since 4.1.1
     179         */
     180        public static function alter_table_411() {
     181            $table  = self::get_table_name();
     182            $column = 'plugin_slug';
     183            if ( self::column_exists( $table, $column ) ) {
     184                return true;
     185            }
     186            $sql = 'ALTER TABLE `' . $table . '` ADD `plugin_slug` VARCHAR(255) DEFAULT NULL AFTER `blog_id`;';
     187            return Common_Table::execute_query( $sql );
     188        }
     189
     190        /**
    170191         * Helper to check if a column exists in the given table.
    171192         *
     
    260281            return $output;
    261282        }
     283
     284        /**
     285         * Generates dropdown with all distinct plugin slugs seen in the mail log.
     286         *
     287         * @param string $selected The currently selected plugin slug (or -1 for all).
     288         * @param string $which    Indicates position of the dropdown (top or bottom).
     289         *
     290         * @return string Rendered HTML <select> element, or empty string if none.
     291         *
     292         * @since 4.1.1
     293         */
     294        public static function get_all_plugins_dropdown( $selected = '', $which = '' ): string {
     295            // Restrict visibility to administrators to avoid information disclosure.
     296            if ( function_exists( 'current_user_can' ) && ! \current_user_can( 'manage_options' ) ) {
     297                return '';
     298            }
     299
     300            $which = in_array( $which, array( 'top', 'bottom' ), true ) ? $which : '';
     301
     302            // Build cache key per position and selection (reuse sites cache container for simplicity).
     303            $cache_key = 'plugins|' . $which . '|' . (string) $selected;
     304            if ( isset( self::$drop_down_sites_rendered[ $cache_key ] ) ) {
     305                return self::$drop_down_sites_rendered[ $cache_key ];
     306            }
     307
     308            $sql     = 'SELECT plugin_slug FROM ' . self::get_table_name() . ' GROUP BY plugin_slug ORDER BY plugin_slug DESC';
     309            $results = self::get_results( $sql );
     310            $plugins = array();
     311            $output  = '';
     312
     313            if ( $results ) {
     314                foreach ( $results as $result ) {
     315                    $slug = isset( $result['plugin_slug'] ) ? trim( (string) $result['plugin_slug'] ) : '';
     316                    if ( '' === $slug ) {
     317                        continue;
     318                    }
     319                    $details = Plugin_Theme_Helper::get_plugin_from_path( $slug );
     320                    $name    = ( isset( $details['Name'] ) && ! empty( $details['Name'] ) ) ? $details['Name'] : $slug;
     321                    $plugins[] = array(
     322                        'id'   => $slug,
     323                        'name' => $name,
     324                    );
     325                }
     326            }
     327
     328            if ( ! empty( $plugins ) ) {
     329                $output  = '<select class="plugin_filter" name="plugin_' . \esc_attr( $which ) . '" id="plugin_' . \esc_attr( $which ) . '">';
     330                $output .= '<option value="-1">' . __( 'All plugins', '0-day-analytics' ) . '</option>';
     331                foreach ( $plugins as $plugin_info ) {
     332                    $selected_attr = ( isset( $selected ) && '' !== trim( (string) $selected ) && (string) $selected === (string) $plugin_info['id'] ) ? ' selected' : '';
     333                    $output       .= '<option value="' . \esc_attr( $plugin_info['id'] ) . '"' . $selected_attr . '>' . \esc_html( $plugin_info['name'] ) . '</option>';
     334                }
     335                $output .= '</select>';
     336            }
     337
     338            self::$drop_down_sites_rendered[ $cache_key ] = $output;
     339            return $output;
     340        }
    262341    }
    263342}
  • 0-day-analytics/tags/4.2.0/classes/vendor/helpers/class-ajax-helper.php

    r3393178 r3398360  
    171171                    \add_action( 'wp_ajax_advan_file_editor_compare_backup', array( File_Editor::class, 'ajax_compare_backup' ) );
    172172                    \add_action( 'wp_ajax_advan_file_editor_delete_backup', array( File_Editor::class, 'ajax_delete_backup' ) );
     173                    // Added download action registration (returns 0 previously when unregistered).
     174                    \add_action( 'wp_ajax_advan_file_editor_download_file', array( File_Editor::class, 'ajax_download_file' ) );
     175                    // Rename file or directory.
     176                    \add_action( 'wp_ajax_advan_file_editor_rename', array( File_Editor::class, 'ajax_rename' ) );
     177                    // Duplicate file or directory.
     178                    \add_action( 'wp_ajax_advan_file_editor_duplicate', array( File_Editor::class, 'ajax_duplicate' ) );
    173179                }
    174180            }
     
    708714            if ( isset( $_POST['typeExport'] ) && ! empty( $_POST['typeExport'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
    709715                if ( 'cron' === $_POST['typeExport'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
    710                     $rows  = Crons_List::get_cron_items();
     716                    $plugin_filter = ( isset( $_POST['plugin_filter'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin_filter'] ) ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
     717                    $site_filter   = ( isset( $_POST['site_filter'] ) ? sanitize_text_field( wp_unslash( $_POST['site_filter'] ) ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
     718                    $rows          = array();
     719
     720                    if ( '' !== $site_filter && -1 !== (int) $site_filter && function_exists( 'is_multisite' ) && is_multisite() ) {
     721                        $rows = Crons_Helper::get_events_for_site( (int) $site_filter );
     722                    } else {
     723                        $rows = Crons_List::get_cron_items();
     724                    }
     725
     726                    if ( '' !== $plugin_filter && -1 !== (int) $plugin_filter ) {
     727                        $rows = array_filter(
     728                            $rows,
     729                            function ( $row ) use ( $plugin_filter ) {
     730                                if ( isset( $row['plugin_slugs'] ) && is_array( $row['plugin_slugs'] ) ) {
     731                                    return in_array( $plugin_filter, $row['plugin_slugs'], true );
     732                                }
     733                                return ( isset( $row['plugin_slug'] ) && '' !== $row['plugin_slug'] && $row['plugin_slug'] === $plugin_filter );
     734                            }
     735                        );
     736                    }
     737                    $total = count( $rows );
    711738                    $rows  = \array_slice( $rows, $offset, $batch_size, true );
    712                     $total = count( $rows );
    713739                }
    714740                if ( 'logs' === $_POST['typeExport'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
     
    768794                if ( 'mail' === $_POST['typeExport'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
    769795
    770                     $list_table      = new WP_Mail_List();
    771                     $search          = isset( $_POST['search'] ) ? sanitize_text_field( wp_unslash( $_POST['search'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
     796                    $list_table = new WP_Mail_List();
     797                    $search     = isset( $_POST['search'] ) ? sanitize_text_field( wp_unslash( $_POST['search'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
     798                    $plugin     = isset( $_POST['plugin'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
     799
    772800                    $extra_file_name = '_' . str_replace( ' ', '_', $list_table->get_table_name() ) . '_';
     801                    if ( ! empty( $plugin ) && -1 !== (int) $plugin ) {
     802                        $extra_file_name .= $plugin . '_';
     803                    }
    773804
    774805                    $rows = $list_table->fetch_table_data(
     
    779810                            'site_id'  => '',
    780811                            'search'   => $search,
     812                            'plugin'   => $plugin,
    781813                        )
    782814                    );
  • 0-day-analytics/tags/4.2.0/classes/vendor/helpers/class-crons-helper.php

    r3393178 r3398360  
    1515
    1616use ADVAN\Lists\Crons_List;
     17use ADVAN\Helpers\Plugin_Theme_Helper;
    1718
    1819// Exit if accessed directly.
     
    263264                            continue;
    264265                        }
    265                         foreach ( $cron as $hook => $events ) {
    266                             foreach ( $events as $event ) {
    267 
    268                                 $cron_item = array();
    269 
    270                                 $cron_item['hook']     = \esc_html( $hook );
    271                                 $cron_item['schedule'] = $timestamp;
    272                                 if ( isset( $event['schedule'] ) ) {
    273                                     $cron_item['recurrence'] = \esc_html( $event['schedule'] );
     266                            foreach ( $cron as $hook => $events ) {
     267                                foreach ( $events as $event ) {
     268
     269                                    $cron_item = array();
     270
     271                                    $cron_item['hook']     = \esc_html( $hook );
     272                                    $cron_item['schedule'] = $timestamp;
     273                                    if ( isset( $event['schedule'] ) ) {
     274                                        $cron_item['recurrence'] = \esc_html( $event['schedule'] );
     275                                    }
     276                                    if ( isset( $event['args'] ) ) {
     277                                        $cron_item['args'] = $event['args'];
     278                                    }
     279
     280                                    $cron_item['hash'] = substr( md5( $cron_item['hook'] . $cron_item['recurrence'] . $cron_item['schedule'] . \wp_json_encode( $event['args'] ) ), 0, 8 );
     281
     282                                    // Site metadata for multisite clarity.
     283                                    $cron_item['site_id']   = function_exists( 'get_current_blog_id' ) ? get_current_blog_id() : 1;
     284                                    $cron_item['site_name'] = ( function_exists( 'get_bloginfo' ) ) ? get_bloginfo( 'name' ) : '';
     285
     286                                    // Derive all plugin slugs from callback files (multi-plugin aggregation).
     287                                    $callbacks    = self::get_cron_callbacks( $hook );
     288                                    $plugin_slugs = array();
     289                                    if ( ! empty( $callbacks ) ) {
     290                                        foreach ( $callbacks as $cb ) {
     291                                            if ( isset( $cb['callback']['file'] ) && ! empty( $cb['callback']['file'] ) ) {
     292                                                $slug = Plugin_Theme_Helper::get_plugin_from_file_path( (string) $cb['callback']['file'] );
     293                                                if ( false !== $slug && ! in_array( $slug, $plugin_slugs, true ) ) {
     294                                                    $plugin_slugs[] = (string) $slug;
     295                                                }
     296                                            }
     297                                        }
     298                                    }
     299                                    // Backward compatibility single slug (first) & full list.
     300                                    $cron_item['plugin_slug']  = isset( $plugin_slugs[0] ) ? $plugin_slugs[0] : '';
     301                                    $cron_item['plugin_slugs'] = $plugin_slugs; // empty array signifies core/unknown.
     302
     303                                    self::$events[ $cron_item['hash'] ] = $cron_item;
    274304                                }
    275                                 if ( isset( $event['args'] ) ) {
    276                                     $cron_item['args'] = $event['args'];
    277                                 }
    278 
    279                                 $cron_item['hash'] = substr( md5( $cron_item['hook'] . $cron_item['recurrence'] . $cron_item['schedule'] . \wp_json_encode( $event['args'] ) ), 0, 8 );
    280305                            }
    281                             self::$events[ $cron_item['hash'] ] = $cron_item;
    282                         }
    283306                    }
    284307                }
     
    286309
    287310            return self::$events;
     311        }
     312
     313        /**
     314         * Returns the cron events for a specific site in a multisite installation.
     315         * Safely switches to the blog, collects events, then restores.
     316         *
     317         * @param int $blog_id Site (blog) ID.
     318         * @return array
     319         */
     320        public static function get_events_for_site( int $blog_id ): array {
     321            if ( ! function_exists( 'is_multisite' ) || ! is_multisite() ) {
     322                return self::get_events();
     323            }
     324
     325            $original = get_current_blog_id();
     326            if ( $original === $blog_id ) {
     327                return self::get_events();
     328            }
     329
     330            // Switch, clear cache, collect, restore and clear again to avoid bleed.
     331            switch_to_blog( $blog_id );
     332            self::$events = null;
     333            $events      = self::get_events();
     334            restore_current_blog();
     335            self::$events = null; // ensure subsequent calls on original blog re-fetch.
     336
     337            return $events;
    288338        }
    289339
     
    566616            }
    567617
    568             if ( defined( 'ALTERNATE_WP_CRON' ) && \ALTERNATE_WP_CRON ) {
     618            if ( defined( 'ALTERNATE_WP_CRON' ) && constant( 'ALTERNATE_WP_CRON' ) ) {
    569619                return new \WP_Error(
    570620                    'advana_cron_info',
  • 0-day-analytics/tags/4.2.0/classes/vendor/helpers/class-file-helper.php

    r3393178 r3398360  
    2828
    2929        /**
     30         * Try to initialize and return WP_Filesystem instance.
     31         *
     32         * @return null|\WP_Filesystem_Base Null when unavailable or failed to initialize.
     33         *
     34         * @since 4.1.2
     35         */
     36        private static function get_wp_filesystem() {
     37            global $wp_filesystem;
     38
     39            // Attempt to bootstrap the WP_Filesystem API.
     40            if ( ! function_exists( '\\WP_Filesystem' ) ) {
     41                require_once ABSPATH . 'wp-admin/includes/file.php';
     42            }
     43
     44            try {
     45                // Initialize if not already.
     46                if ( ! isset( $wp_filesystem ) || ! is_object( $wp_filesystem ) ) {
     47                    \WP_Filesystem();
     48                }
     49            } catch ( \Throwable $e ) {
     50                // Initialization failed, fall back to native PHP.
     51                return null;
     52            }
     53
     54            return ( isset( $wp_filesystem ) && is_object( $wp_filesystem ) ) ? $wp_filesystem : null;
     55        }
     56
     57        /**
     58         * Native PHP file writer with append support and locking.
     59         *
     60         * @param string $file_path Absolute path to file.
     61         * @param string $content   Content to write.
     62         * @param bool   $append    Append instead of overwrite.
     63         *
     64         * @return bool True on success.
     65         *
     66         * @since 4.1.2
     67         */
     68        private static function native_put_contents( string $file_path, string $content, bool $append = false ): bool {
     69            $flags = LOCK_EX;
     70            if ( $append ) {
     71                $flags |= FILE_APPEND;
     72            }
     73            $bytes = @file_put_contents( $file_path, $content, $flags ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
     74            return ( false !== $bytes );
     75        }
     76
     77        /**
    3078         * Normalize a filesystem path in a cross-platform way.
    3179         *
     
    117165         */
    118166        public static function write_to_file( string $filename, string $content, bool $append = false ): bool {
    119             global $wp_filesystem;
    120 
    121             require_once ABSPATH . 'wp-admin/includes/file.php';
    122             WP_Filesystem();
    123 
    124167            $logging_dir = dirname( $filename );
    125168
     
    132175            }
    133176
    134             $result = false;
    135 
     177            // Ensure destination directory exists, try WP first then native.
    136178            if ( ! is_dir( $logging_dir ) ) {
    137                 if ( false === \wp_mkdir_p( $logging_dir ) ) {
     179                if ( ! function_exists( '\\wp_mkdir_p' ) ) {
     180                    require_once ABSPATH . 'wp-admin/includes/file.php';
     181                }
     182                $made_dir = false;
     183                try {
     184                    $made_dir = \wp_mkdir_p( $logging_dir );
     185                } catch ( \Throwable $e ) {
     186                    $made_dir = false;
     187                }
     188                if ( ! $made_dir ) {
     189                    $made_dir = @mkdir( $logging_dir, 0755, true ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.mkdir_directoryPermissions
     190                }
     191                if ( ! $made_dir ) {
    138192                    self::$last_error = new \WP_Error(
    139193                        'mkdir_failed',
     
    144198                        )
    145199                    );
    146 
    147                     return $result;
     200                    return false;
    148201                }
    149202            }
     
    157210            }
    158211
    159             // Fix append logic: only append if requested and file exists.
    160             if ( $append && $wp_filesystem->exists( $file_path ) ) {
    161                 $existing_content = $wp_filesystem->get_contents( $file_path );
    162                 $result           = $wp_filesystem->put_contents( $file_path, $existing_content . $content );
    163             } else {
    164                 $result = $wp_filesystem->put_contents( $file_path, $content );
     212            // Try WP_Filesystem first.
     213            $result = false;
     214            $fs     = self::get_wp_filesystem();
     215            if ( $fs ) {
     216                try {
     217                    if ( $append && $fs->exists( $file_path ) ) {
     218                        $existing_content = $fs->get_contents( $file_path );
     219                        if ( false === $existing_content ) {
     220                            $existing_content = '';
     221                        }
     222                        $result = (bool) $fs->put_contents( $file_path, $existing_content . $content );
     223                    } else {
     224                        $result = (bool) $fs->put_contents( $file_path, $content );
     225                    }
     226                } catch ( \Throwable $e ) {
     227                    $result = false; // Will fall back below.
     228                }
     229            }
     230
     231            // Fall back to native PHP functions if WP_Filesystem path failed or unavailable.
     232            if ( false === $result ) {
     233                $result = self::native_put_contents( $file_path, $content, $append );
    165234            }
    166235
     
    174243                    )
    175244                );
    176             }
    177 
    178             // Best-effort permission hardening (may not work on all FS abstractions).
    179             if ( $result ) {
    180                 // Best effort tighten file perms; ignore if FS abstraction disallows.
    181                 @chmod( $file_path, 0640 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.file_system_operations_chmod
    182             }
    183 
    184             return (bool) $result;
     245                return false;
     246            }
     247
     248            // Best-effort permission hardening.
     249            @chmod( $file_path, 0640 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.file_system_operations_chmod
     250
     251            return true;
    185252        }
    186253
     
    211278                $size = filesize( $filename );
    212279
    213                 return self::show_size( $size );
    214             }
    215 
    216             return '0 B';
    217         }
    218 
    219         /**
    220          * Shows formatted (human readable) size
    221          *
    222          * @param int $size - The size (in bytes) to format.
    223          *
    224          * @return string
    225          *
    226          * @since 2.1.2
    227          */
    228         public static function show_size( $size ) {
    229 
    230             if ( 0 <= $size ) {
    231 
    232                 $units          = array( 'B', 'KB', 'MB', 'GB', 'TB' );
    233                 $formatted_size = $size;
    234                 $i              = 0; // Initialize to avoid undefined index when size < 1024.
    235 
    236                 $units_length = count( $units ) - 1;
    237 
    238                 for ( $i = 0; $size >= 1024 && $i < $units_length; $i++ ) {
    239                     $size          /= 1024;
    240                     $formatted_size = round( $size, 2 );
    241                 }
    242 
    243                 return $formatted_size . ' ' . $units[ $i ];
     280                return \size_format( $size );
    244281            }
    245282
     
    287324
    288325            // Resolve and validate path against allowed base directory.
    289             $allowed_base      = apply_filters( ADVAN_TEXTDOMAIN . 'download_base_dir', WP_CONTENT_DIR );
     326            $allowed_base      = \apply_filters( ADVAN_TEXTDOMAIN . 'download_base_dir', WP_CONTENT_DIR );
    290327            $real_allowed_base = realpath( $allowed_base );
    291328            $real_requested    = realpath( $file_path );
     
    713750            \WP_Filesystem();
    714751            if ( isset( $wp_filesystem ) && is_object( $wp_filesystem ) ) {
    715                 $move = $wp_filesystem->move( $temp_path, $file_path, true );
     752                $move = false;
     753                try {
     754                    $move = $wp_filesystem->move( $temp_path, $file_path, true );
     755                } catch ( \Throwable $e ) {
     756                    $move = false;
     757                }
     758                if ( ! $move ) {
     759                    // Try native rename as a fallback when WP_Filesystem move fails.
     760                    $move = @rename( $temp_path, $file_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.rename_rename
     761                }
    716762                if ( ! $move ) {
    717763                    // Cleanup temp file on failure.
    718                     $wp_filesystem->delete( $temp_path );
     764                    if ( isset( $wp_filesystem ) && is_object( $wp_filesystem ) ) {
     765                        @$wp_filesystem->delete( $temp_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
     766                    } else {
     767                        @unlink( $temp_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.unlink_unlink
     768                    }
    719769                }
    720770                return (bool) $move;
     
    722772
    723773            // Fallback if filesystem is unavailable.
    724             return rename( $temp_path, $file_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.rename_rename
     774            return @rename( $temp_path, $file_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.rename_rename
    725775        }
    726776    }
  • 0-day-analytics/tags/4.2.0/classes/vendor/helpers/class-settings.php

    r3393178 r3398360  
    824824                                $_FILES[ self::SETTINGS_FILE_FIELD ] = array();
    825825                            } else {
    826                                 global $wp_filesystem;
    827                                 if ( null === $wp_filesystem ) {
    828                                     \WP_Filesystem(); }
    829                                 $path = \sanitize_text_field( \wp_unslash( $_FILES[ self::SETTINGS_FILE_FIELD ]['tmp_name'] ) );
    830                                 if ( $wp_filesystem->exists( $path ) ) {
    831                                     $options = json_decode( $wp_filesystem->get_contents( $path ), true );
     826                                $path    = \sanitize_text_field( \wp_unslash( $_FILES[ self::SETTINGS_FILE_FIELD ]['tmp_name'] ) );
     827                                $content = '';
     828
     829                                // Try WP_Filesystem first; fall back to native PHP on failure.
     830                                try {
     831                                    global $wp_filesystem;
     832                                    if ( ! isset( $wp_filesystem ) || ! is_object( $wp_filesystem ) ) {
     833                                        \WP_Filesystem();
     834                                    }
     835                                    if ( isset( $wp_filesystem ) && is_object( $wp_filesystem ) && $wp_filesystem->exists( $path ) ) {
     836                                        $tmp = $wp_filesystem->get_contents( $path );
     837                                        if ( false !== $tmp && '' !== $tmp ) {
     838                                            $content = (string) $tmp;
     839                                        }
     840                                    }
     841                                } catch ( \Throwable $e ) {
     842                                    // Ignore and fall back below.
     843                                }
     844
     845                                if ( '' === $content && \is_readable( $path ) ) {
     846                                    $content = @file_get_contents( $path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
     847                                }
     848
     849                                $options = array();
     850                                if ( is_string( $content ) && '' !== $content ) {
     851                                    $options = json_decode( $content, true );
    832852                                }
    833853                                if ( ! is_array( $options ) ) {
    834                                     $options = array(); }
     854                                    $options = array();
     855                                }
    835856                                if ( ! empty( $options ) ) {
    836857                                    \remove_filter( 'sanitize_option_' . ADVAN_SETTINGS_NAME, array( self::class, 'collect_and_sanitize_options' ) );
  • 0-day-analytics/tags/4.2.0/classes/vendor/helpers/class-system-analytics.php

    r3391413 r3398360  
    327327
    328328        /**
     329         * Check if a path is allowed by open_basedir restrictions.
     330         *
     331         * @param string $path The path to check.
     332         *
     333         * @return bool
     334         *
     335         * @since 4.2.0
     336         */
     337        public static function is_allowed_by_open_base_dir( string $path ): bool {
     338            $ob = ini_get( 'open_basedir' );
     339            if ( ! $ob ) {
     340                return true; // no restrictions.
     341            }
     342
     343            $allowed = explode( PATH_SEPARATOR, $ob );
     344
     345            $real = @realpath( $path );
     346            if ( false === $real ) {
     347                return false;
     348            }
     349
     350            foreach ( $allowed as $dir ) {
     351                $dir = rtrim( $dir, '/' );
     352                if ( strpos( $real, $dir ) === 0 ) {
     353                    return true;
     354                }
     355            }
     356            return false;
     357        }
     358
     359        /**
    329360         * Returns the disk usage
    330361         *
     
    335366        public static function get_disk_usage() {
    336367            $total = @disk_total_space( '/' );
    337             $free  = @disk_free_space( '/' );
     368            if ( self::is_allowed_by_open_base_dir( '/' ) ) {
     369                $free = @disk_free_space( '/' );
     370            }
    338371            if ( $total && $free ) {
    339372                $used = 100 - ( ( $free / $total ) * 100 );
  • 0-day-analytics/tags/4.2.0/classes/vendor/helpers/class-wp-error-handler.php

    r3393178 r3398360  
    259259         */
    260260        public static function trigger_error( $status, string $function_name, $errstr, $version, $errno = E_USER_NOTICE ) {
    261 
    262261            if ( false === $status ) {
    263262                return $status;
  • 0-day-analytics/tags/4.2.0/classes/vendor/lists/class-crons-list.php

    r3393178 r3398360  
    1919use ADVAN\Helpers\WP_Helper;
    2020use ADVAN\Helpers\Crons_Helper;
     21use ADVAN\Helpers\Plugin_Theme_Helper;
    2122use ADVAN\Lists\Views\Crons_View;
    2223use ADVAN\Lists\Traits\List_Trait;
     
    5758        public const CRON_MENU_SLUG = 'advan_cron_jobs';
    5859
     60        public const PLUGIN_FILTER_ACTION = self::PAGE_SLUG . '_filter_plugin';
     61
     62        public const SITE_FILTER_ACTION = self::PAGE_SLUG . '_filter_site';
     63
    5964        /**
    6065         * Format for the file link.
     
    166171            \add_action( 'admin_post_' . self::UPDATE_ACTION, array( Crons_View::class, 'update_cron' ) );
    167172            \add_action( 'admin_post_' . self::NEW_ACTION, array( Crons_View::class, 'new_cron' ) );
     173            \add_action( 'admin_post_' . self::PLUGIN_FILTER_ACTION, array( Crons_View::class, 'plugin_filter_action' ) );
     174            \add_action( 'admin_post_' . self::SITE_FILTER_ACTION, array( Crons_View::class, 'site_filter_action' ) );
    168175        }
    169176
     
    193200
    194201            \add_action( 'load-' . $cron_hook, array( Settings::class, 'aadvana_common_help' ) );
     202            // Process actions early to avoid header warnings on redirects.
     203            \add_action( 'load-' . $cron_hook, array( self::class, 'process_actions_load' ) );
    195204
    196205            /* Crons end */
     206        }
     207
     208        /**
     209         * Handle cron table actions on the early page load hook.
     210         *
     211         * @return void
     212         *
     213         * @since 4.2.0
     214         */
     215        public static function process_actions_load() {
     216            if ( ! \current_user_can( 'manage_options' ) ) {
     217                return;
     218            }
     219            $table = new self( array() );
     220            $table->handle_table_actions();
    197221        }
    198222
     
    247271            );
    248272
     273            // Insert site column for multisite network admins for clarity.
     274            if ( function_exists( 'is_multisite' ) && is_multisite() && current_user_can( 'manage_network' ) ) {
     275                $admin_fields = array(
     276                    'cb'         => $admin_fields['cb'],
     277                    'hook'       => $admin_fields['hook'],
     278                    'site'       => __( 'Site', '0-day-analytics' ),
     279                    'schedule'   => $admin_fields['schedule'],
     280                    'recurrence' => $admin_fields['recurrence'],
     281                    'args'       => $admin_fields['args'],
     282                    'actions'    => $admin_fields['actions'],
     283                );
     284            }
     285
    249286            $screen_options = $admin_fields;
    250287
     
    273310            $sortable              = $this->get_sortable_columns();
    274311            $this->_column_headers = array( $columns, $hidden, $sortable );
    275 
    276             $this->handle_table_actions();
    277312
    278313            $this->fetch_table_data();
     
    394429            }
    395430
     431            // Plugin filtering (only when not requesting unfiltered set for counts).
     432            $site_filter = ( isset( $_REQUEST['site'] ) && '' !== trim( (string) $_REQUEST['site'] ) && -1 !== (int) $_REQUEST['site'] ) ? (int) sanitize_text_field( wp_unslash( $_REQUEST['site'] ) ) : -1;
     433            if ( -1 !== $site_filter && function_exists( 'is_multisite' ) && is_multisite() ) {
     434                self::$read_items = Crons_Helper::get_events_for_site( $site_filter );
     435            }
     436
     437            if ( isset( $_REQUEST['plugin'] ) && '' !== trim( (string) $_REQUEST['plugin'] ) && -1 !== (int) $_REQUEST['plugin'] ) {
     438                $plugin_filter    = sanitize_text_field( wp_unslash( $_REQUEST['plugin'] ) );
     439                self::$read_items = array_filter(
     440                    self::$read_items,
     441                    function ( $event ) use ( $plugin_filter ) {
     442                        if ( isset( $event['plugin_slugs'] ) && is_array( $event['plugin_slugs'] ) ) {
     443                            return in_array( $plugin_filter, $event['plugin_slugs'], true );
     444                        }
     445                        return ( isset( $event['plugin_slug'] ) && '' !== $event['plugin_slug'] && $event['plugin_slug'] === $plugin_filter );
     446                    }
     447                );
     448            }
     449
    396450            if ( ! $no_type_filtering ) {
    397451                if ( ! empty( $_REQUEST['event_type'] ) && is_string( $_REQUEST['event_type'] ) ) {
     
    442496        public static function format_column_value( $item, $column_name ) {
    443497            switch ( $column_name ) {
     498                case 'site':
     499                    if ( isset( $item['site_id'] ) ) {
     500                        $site_id   = (int) $item['site_id'];
     501                        $site_name = isset( $item['site_name'] ) ? $item['site_name'] : '';
     502                        return '<code>' . esc_html( $site_id ) . '</code> ' . ( ! empty( $site_name ) ? esc_html( $site_name ) : '' );
     503                    }
     504                    return esc_html__( 'N/A', '0-day-analytics' );
    444505                case 'hook':
    445506                    $query_args_view_data             = array();
     
    902963                ?>
    903964                <div class="alignleft actions">
     965                    <?php
     966                    // Plugin dropdown.
     967                    $selected_plugin    = ( isset( $_REQUEST['plugin'] ) && '' !== trim( (string) $_REQUEST['plugin'] ) ) ? ( ( -1 === (int) $_REQUEST['plugin'] ) ? -1 : sanitize_text_field( wp_unslash( $_REQUEST['plugin'] ) ) ) : -1;
     968                    $selected_site      = ( isset( $_REQUEST['site'] ) && '' !== trim( (string) $_REQUEST['site'] ) ) ? ( ( -1 === (int) $_REQUEST['site'] ) ? -1 : (int) sanitize_text_field( wp_unslash( $_REQUEST['site'] ) ) ) : -1;
     969                    $plugins_dropdown   = self::get_plugins_dropdown( $selected_plugin, $which );
     970                        $sites_dropdown = self::get_sites_dropdown( $selected_site, $which );
     971                    if ( ! empty( $plugins_dropdown ) ) {
     972                        echo $plugins_dropdown; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
     973                        if ( ! empty( $sites_dropdown ) ) {
     974                            echo $sites_dropdown; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
     975                            ?>
     976                            <script>
     977                            jQuery(document).ready(function(){
     978                                jQuery('form .site_filter').on('change', function(){
     979                                    jQuery('form .site_filter').val(jQuery(this).val());
     980                                    jQuery(this).closest('form')
     981                                        .attr('action','<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>')
     982                                        .append('<input type="hidden" name="action" value="<?php echo esc_attr( self::SITE_FILTER_ACTION ); ?>">')
     983                                        .append('<?php wp_nonce_field( self::SITE_FILTER_ACTION, self::SITE_FILTER_ACTION . 'nonce' ); ?>')
     984                                        .submit();
     985                                });
     986                            });
     987                            </script>
     988                            <?php
     989                        }
     990                        ?>
     991
     992                        <script>
     993                        jQuery(document).ready(function(){
     994                            jQuery('form .plugin_filter').on('change', function(){
     995                                jQuery('form .plugin_filter').val(jQuery(this).val());
     996                                jQuery(this).closest('form')
     997                                    .attr('action','<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>')
     998                                    .append('<input type="hidden" name="action" value="<?php echo esc_attr( self::PLUGIN_FILTER_ACTION ); ?>">')
     999                                    .append('<?php wp_nonce_field( self::PLUGIN_FILTER_ACTION, self::PLUGIN_FILTER_ACTION . 'nonce' ); ?>')
     1000                                    .submit();
     1001                            });
     1002                        });
     1003                        </script>
     1004                        <?php
     1005                    }
     1006                    ?>
    9041007                    <select class="schedules-filter" name="schedules_filter">
    9051008                    <?php
     
    9271030                <div id="export-form">
    9281031                    <div>
    929                         <button id="start-export" class="button" data-type-export="cron">
     1032                        <button id="start-export" class="button" data-type-export="cron" data-plugin_filter="<?php echo esc_attr( $selected_plugin ); ?>" data-site_filter="<?php echo esc_attr( $selected_site ); ?>">
    9301033                            <?php echo esc_html__( 'CSV Export', '0-day-analytics' ); ?>
    9311034                        </button>
     
    13691472            return $filtered;
    13701473        }
     1474
     1475        /**
     1476         * Builds sites dropdown (multisite only) listing all sites (optionally only those having cron events).
     1477         *
     1478         * @param int    $selected Selected site id or -1.
     1479         * @param string $which  Position top|bottom.
     1480         * @return string
     1481         */
     1482        public static function get_sites_dropdown( $selected = -1, string $which = 'top' ): string {
     1483            if ( ! function_exists( 'is_multisite' ) || ! is_multisite() ) {
     1484                return '';
     1485            }
     1486            if ( ! current_user_can( 'manage_network' ) ) {
     1487                return '';
     1488            }
     1489            $which = in_array( $which, array( 'top', 'bottom' ), true ) ? $which : 'top';
     1490            $sites = get_sites( array( 'number' => 0 ) );
     1491            if ( empty( $sites ) ) {
     1492                return '';
     1493            }
     1494            $output  = '<select class="site_filter" name="site_' . esc_attr( $which ) . '" id="site_' . esc_attr( $which ) . '">';
     1495            $output .= '<option value="-1">' . __( 'All sites', '0-day-analytics' ) . '</option>';
     1496            foreach ( $sites as $site ) {
     1497                $blog_id = (int) $site->blog_id;
     1498                $details = get_blog_details( $blog_id );
     1499                $name    = ( $details && isset( $details->blogname ) ) ? $details->blogname : 'Site ' . $blog_id;
     1500                $sel     = ( (int) $selected === $blog_id ) ? ' selected' : '';
     1501                $output .= '<option value="' . esc_attr( $blog_id ) . '"' . $sel . '>' . esc_html( $name ) . '</option>';
     1502            }
     1503            $output .= '</select>';
     1504            return $output;
     1505        }
     1506
     1507        /**
     1508         * Builds plugins dropdown based on detected plugin slugs from cron callbacks.
     1509         *
     1510         * @param string|int $selected Currently selected plugin slug or -1 for all.
     1511         * @param string     $which    Position (top|bottom) for unique element naming.
     1512         *
     1513         * @return string HTML select or empty string if no plugins detected.
     1514         */
     1515        public static function get_plugins_dropdown( $selected = -1, $which = 'top' ): string {
     1516            $which = in_array( $which, array( 'top', 'bottom' ), true ) ? $which : 'top';
     1517
     1518            $all_events = self::get_cron_items( true );
     1519            $plugins    = array();
     1520            foreach ( $all_events as $event ) {
     1521                if ( isset( $event['plugin_slugs'] ) && is_array( $event['plugin_slugs'] ) ) {
     1522                    foreach ( $event['plugin_slugs'] as $slug ) {
     1523                        if ( ! empty( $slug ) ) {
     1524                            $plugins[ $slug ] = $slug;
     1525                        }
     1526                    }
     1527                } elseif ( isset( $event['plugin_slug'] ) && '' !== $event['plugin_slug'] ) {
     1528                    $plugins[ $event['plugin_slug'] ] = $event['plugin_slug'];
     1529                }
     1530            }
     1531
     1532            if ( empty( $plugins ) ) {
     1533                return '';
     1534            }
     1535
     1536            $output  = '<select class="plugin_filter" name="plugin_' . esc_attr( $which ) . '" id="plugin_' . esc_attr( $which ) . '">';
     1537            $output .= '<option value="-1">' . __( 'All plugins', '0-day-analytics' ) . '</option>';
     1538            foreach ( $plugins as $slug ) {
     1539                $details  = Plugin_Theme_Helper::get_plugin_from_path( $slug );
     1540                $name     = ( isset( $details['Name'] ) && ! empty( $details['Name'] ) ) ? $details['Name'] : $slug;
     1541                $sel_attr = ( (string) $selected === (string) $slug ) ? ' selected' : '';
     1542                $output  .= '<option value="' . esc_attr( $slug ) . '"' . $sel_attr . '>' . esc_html( $name ) . '</option>';
     1543            }
     1544            $output .= '</select>';
     1545
     1546            return $output;
     1547        }
    13711548    }
    13721549}
  • 0-day-analytics/tags/4.2.0/classes/vendor/lists/class-fatals-list.php

    r3393178 r3398360  
    1717use ADVAN\Helpers\Settings;
    1818use ADVAN\Helpers\WP_Helper;
    19 use ADVAN\Helpers\File_Helper;
    2019use ADVAN\Helpers\Miscellaneous;
    2120use ADVAN\Lists\Traits\List_Trait;
     
    184183
    185184            \add_action( 'load-' . $fatals_hook, array( Settings::class, 'aadvana_common_help' ) );
     185            // Process actions early to allow safe redirects before any output.
     186            \add_action( 'load-' . $fatals_hook, array( self::class, 'process_actions_load' ) );
     187        }
     188
     189        /**
     190         * Handle bulk and row actions during the early page load hook.
     191         *
     192         * @return void
     193         *
     194         * @since 4.2.0
     195         */
     196        public static function process_actions_load() {
     197            if ( ! \current_user_can( 'manage_options' ) ) {
     198                return;
     199            }
     200            $table = new self( '' );
     201            $table->handle_table_actions();
    186202        }
    187203
     
    205221         */
    206222        public function prepare_items() {
    207             $this->handle_table_actions();
     223            // Actions are processed during load-<hook> to avoid header warnings.
    208224
    209225            $per_page = self::get_screen_option_per_page();
     
    855871                        <?php } ?>
    856872                <div class="flex flex-row grow-0 p-2 w-full border-0 border-t border-solid justify-between">
    857                     <div class=""> <?php \esc_html_e( 'Size: ', '0-day-analytics' ); ?> <?php echo \esc_attr( File_Helper::show_size( Common_Table::get_table_size() ) ); ?>
     873                    <div class=""> <?php \esc_html_e( 'Size: ', '0-day-analytics' ); ?> <?php echo \esc_attr( \size_format( Common_Table::get_table_size() ) ); ?>
    858874
    859875                        <?php
  • 0-day-analytics/tags/4.2.0/classes/vendor/lists/class-requests-list.php

    r3393178 r3398360  
    1717use ADVAN\Helpers\Settings;
    1818use ADVAN\Helpers\WP_Helper;
    19 use ADVAN\Helpers\File_Helper;
    2019use ADVAN\Helpers\Miscellaneous;
    2120use ADVAN\Lists\Traits\List_Trait;
     
    203202
    204203            \add_action( 'load-' . $requests_hook, array( Settings::class, 'aadvana_common_help' ) );
     204            // Process any actions early to allow safe redirects before output.
     205            \add_action( 'load-' . $requests_hook, array( self::class, 'process_actions_load' ) );
     206        }
     207
     208        /**
     209         * Handle actions on the early page load hook to avoid header issues.
     210         *
     211         * @return void
     212         *
     213         * @since 4.2.0
     214         */
     215        public static function process_actions_load() {
     216            if ( ! \current_user_can( 'manage_options' ) ) {
     217                return;
     218            }
     219            $table = new self( '' );
     220            $table->handle_table_actions();
    205221        }
    206222
     
    224240         */
    225241        public function prepare_items() {
    226             $this->handle_table_actions();
     242            // Actions are processed during load-<hook> to avoid late redirects.
    227243            $per_page = self::get_screen_option_per_page();
    228244
     
    957973                        <?php } ?>
    958974                <div class="flex flex-row grow-0 p-2 w-full border-0 border-t border-solid justify-between">
    959                     <div class=""> <?php \esc_html_e( 'Size: ', '0-day-analytics' ); ?> <?php echo \esc_attr( File_Helper::show_size( Common_Table::get_table_size() ) ); ?>
     975                    <div class=""> <?php \esc_html_e( 'Size: ', '0-day-analytics' ); ?> <?php echo \esc_attr( \size_format( Common_Table::get_table_size() ) ); ?>
    960976
    961977                        <?php
  • 0-day-analytics/tags/4.2.0/classes/vendor/lists/class-table-list.php

    r3393178 r3398360  
    1717use ADVAN\Helpers\Settings;
    1818use ADVAN\Helpers\WP_Helper;
    19 use ADVAN\Helpers\File_Helper;
    2019use ADVAN\Helpers\Miscellaneous;
    2120use ADVAN\Lists\Views\Table_View;
     
    164163            // \add_filter( 'manage_' . $table_hook . '_columns', array( Table_List::class, 'manage_columns' ) );
    165164
     165            // Process bulk/table actions early on page load before any output.
     166            \add_action( 'load-' . $table_hook, array( __CLASS__, 'process_actions_load' ), 5 );
     167
    166168            \add_action( 'load-' . $table_hook, array( Settings::class, 'aadvana_common_help' ) );
     169        }
     170
     171        /**
     172         * Runs table actions during the load-<hook> phase to avoid premature output.
     173         *
     174         * @return void
     175         *
     176         * @since 4.2.0
     177         */
     178        public static function process_actions_load() {
     179            if ( ! \is_user_logged_in() || ! \current_user_can( 'manage_options' ) ) {
     180                return;
     181            }
     182
     183            $table_name      = Common_Table::get_default_table();
     184            $requested_table = isset( $_REQUEST['show_table'] ) ? \sanitize_key( \wp_unslash( $_REQUEST['show_table'] ) ) : '';
     185            if ( $requested_table && \in_array( $requested_table, Common_Table::get_tables(), true ) ) {
     186                $table_name = $requested_table;
     187            }
     188
     189            // Instantiate and process actions. Redirects will exit before any output.
     190            $table = new self( $table_name );
     191            $table->handle_table_actions();
    167192        }
    168193
     
    186211         */
    187212        public function prepare_items() {
    188             $this->handle_table_actions();
    189213
    190214            $per_page = self::get_screen_option_per_page();
     
    932956                        <?php } ?>
    933957                <div class="flex flex-row grow-0 p-2 w-full border-0 border-t border-solid justify-between">
    934                     <div class=""> <?php \esc_html_e( 'Size: ', '0-day-analytics' ); ?> <?php echo \esc_attr( File_Helper::show_size( Common_Table::get_table_size() ) ); ?>
     958                    <div class=""> <?php \esc_html_e( 'Size: ', '0-day-analytics' ); ?> <?php echo \esc_attr( \size_format( Common_Table::get_table_size() ) ); ?>
    935959
    936960                    <?php
     
    971995                    </div>
    972996                    <div>
    973                         <b><?php \esc_html_e( 'Schema: ', '0-day-analytics' ); ?></b> <span class="italic"><?php echo \esc_attr( $wpdb->dbname ); ?></span> | <b><?php \esc_html_e( 'Tables: ', '0-day-analytics' ); ?></b><span class="italic"><?php echo \esc_attr( count( Common_Table::get_tables() ) ); ?></span>
     997                        <b><?php \esc_html_e( 'Schema: ', '0-day-analytics' ); ?></b> <span class="italic"><?php echo \esc_attr( defined( 'DB_NAME' ) ? DB_NAME : '' ); ?></span> | <b><?php \esc_html_e( 'Tables: ', '0-day-analytics' ); ?></b><span class="italic"><?php echo \esc_attr( count( Common_Table::get_tables() ) ); ?></span>
    974998                    </div>
    975999                </div>
  • 0-day-analytics/tags/4.2.0/classes/vendor/lists/class-transients-list.php

    r3392179 r3398360  
    187187            \add_filter( 'manage_' . $transients_hook . '_columns', array( self::class, 'manage_columns' ) );
    188188
    189             \add_action( 'load-' . $transients_hook, array( Settings::class, 'aadvana_common_help' ) );
     189                    \add_action( 'load-' . $transients_hook, array( Settings::class, 'aadvana_common_help' ) );
     190                    // Process bulk/table actions early before any output.
     191                    \add_action( 'load-' . $transients_hook, array( self::class, 'process_actions_load' ) );
     192        }
     193
     194        /**
     195         * Handle any table actions during the early page load hook to allow safe redirects.
     196         *
     197         * @return void
     198         *
     199         * @since 4.2.0
     200         */
     201        public static function process_actions_load() {
     202            if ( ! \current_user_can( 'manage_options' ) ) {
     203                return;
     204            }
     205            $table = new self( array() );
     206            $table->handle_table_actions();
    190207        }
    191208
     
    303320                )
    304321            );
    305 
    306322            $hidden = \get_user_option( 'manage' . WP_Helper::get_wp_screen()->id . 'columnshidden', false );
    307323            if ( ! $hidden ) {
     
    611627
    612628                    \wp_safe_redirect( $redirect );
     629                    exit;
    613630                }
    614631            }
  • 0-day-analytics/tags/4.2.0/classes/vendor/lists/class-wp-mail-list.php

    r3393178 r3398360  
    1717use ADVAN\Helpers\Settings;
    1818use ADVAN\Helpers\WP_Helper;
    19 use ADVAN\Helpers\File_Helper;
    2019use ADVAN\Entities_Global\Common_Table;
    2120use ADVAN\Controllers\WP_Mail_Log;
     
    6160        public const SITE_ID_FILTER_ACTION = 'filter_site_id';
    6261
     62        public const PLUGIN_FILTER_ACTION = self::PAGE_SLUG . '_filter_plugin';
     63
    6364        /**
    6465         * The table to show
     
    160161            \add_action( 'admin_post_' . self::NEW_ACTION, array( WP_Mail_View::class, 'new_mail' ) );
    161162            \add_action( 'admin_post_' . self::SITE_ID_FILTER_ACTION, array( WP_Mail_View::class, 'site_id_filter_action' ) );
     163            \add_action( 'admin_post_' . self::PLUGIN_FILTER_ACTION, array( WP_Mail_View::class, 'plugin_filter_action' ) );
    162164            \add_filter( 'advan_cron_hooks', array( __CLASS__, 'add_cron_job' ) );
    163165        }
     
    218220
    219221            \add_action( 'load-' . $wp_mail_hook, array( Settings::class, 'aadvana_common_help' ) );
     222            // Process actions early to ensure redirects happen before output.
     223            \add_action( 'load-' . $wp_mail_hook, array( self::class, 'process_actions_load' ) );
     224        }
     225
     226        /**
     227         * Handle actions during the early page load hook to avoid header warnings.
     228         *
     229         * @return void
     230         *
     231         * @since 4.2.0
     232         */
     233        public static function process_actions_load() {
     234            if ( ! \current_user_can( 'manage_options' ) ) {
     235                return;
     236            }
     237            $table = new self( '' );
     238            $table->handle_table_actions();
    220239        }
    221240
     
    239258         */
    240259        public function prepare_items() {
    241             $this->handle_table_actions();
     260            // Actions are processed on load-<hook> to prevent late redirects.
    242261
    243262            // Vars.
     
    252271            $type = ! empty( $_GET['mail_type'] ) ? \sanitize_text_field( \wp_unslash( $_GET['mail_type'] ) ) : '';
    253272
     273            if ( isset( $_REQUEST['plugin'] ) && ! empty( $_REQUEST['plugin'] ) ) {
     274                if ( -1 === (int) $_REQUEST['plugin'] ) {
     275                    $plugin = -1;
     276                } else {
     277                    $plugin = \sanitize_text_field( \wp_unslash( $_REQUEST['plugin'] ) );
     278                }
     279            } else {
     280                $plugin = '';
     281            }
     282
    254283            if ( isset( $_REQUEST['site_id'] ) && ! empty( $_REQUEST['site_id'] ) ) {
    255284                if ( -1 === (int) $_REQUEST['site_id'] ) {
     
    275304                    'type'     => $type,
    276305                    'site_id'  => $site_id,
     306                    'plugin'   => $plugin,
    277307                )
    278308            );
     
    372402                    'count'    => false,
    373403                    'site_id'  => '',
     404                    'plugin'   => '',
    374405                )
    375406            );
     
    394425            $site_id       = \sanitize_text_field( \wp_unslash( (string) $parsed_args['site_id'] ) );
    395426            $type          = \sanitize_text_field( \wp_unslash( $parsed_args['type'] ?? '' ) );
     427            $plugin        = \sanitize_text_field( \wp_unslash( (string) ( $parsed_args['plugin'] ?? '' ) ) );
    396428
    397429            if ( '' !== $search_string ) {
     
    433465                    $where_parts[] = 'AND attachments != "[]"';
    434466                }
     467            }
     468
     469            if ( '' !== $plugin && -1 !== (int) $plugin ) {
     470                $where_parts[] = 'AND plugin_slug = %s';
     471                $where_args[]  = $plugin;
    435472            }
    436473
     
    10351072        public function extra_tablenav( $which ) {
    10361073
     1074            // Plugin filter dropdown (mirrors Requests list behavior).
     1075            if ( isset( $_REQUEST['plugin'] ) && ! empty( $_REQUEST['plugin'] ) ) {
     1076                if ( -1 === (int) $_REQUEST['plugin'] ) {
     1077                    $plugin = -1;
     1078                } else {
     1079                    $plugin = \sanitize_text_field( \wp_unslash( $_REQUEST['plugin'] ) );
     1080                }
     1081            } else {
     1082                $plugin = 0;
     1083            }
     1084            ?>
     1085                <div class="alignleft actions bulkactions">
     1086                    <?php echo WP_Mail_Entity::get_all_plugins_dropdown( $plugin, $which ); ?>
     1087                </div>
     1088                <script>
     1089                    jQuery('form .plugin_filter').on('change', function(e) {
     1090                        jQuery('form .plugin_filter').val(jQuery(this).val());
     1091                        jQuery( this ).closest( 'form' )
     1092                            .attr( 'action', '<?php echo \esc_url( \admin_url( 'admin-post.php' ) ); ?>')
     1093                            .append('<input type="hidden" name="action" value="<?php echo \esc_attr( self::PLUGIN_FILTER_ACTION ); ?>">')
     1094                            .append('<input type="hidden" name="context" value="<?php echo \esc_attr( ( \is_network_admin() ) ? 'network' : 'site' ); ?>">')
     1095                            .append('<?php \wp_nonce_field( self::PLUGIN_FILTER_ACTION, self::PLUGIN_FILTER_ACTION . 'nonce' ); ?>')
     1096                            .submit();
     1097                    });
     1098                </script>
     1099            <?php
     1100
    10371101            if ( WP_Helper::is_multisite() ) {
    10381102                if ( isset( $_REQUEST['site_id'] ) && ! empty( $_REQUEST['site_id'] ) ) {
     
    10571121                ?>
    10581122                <div class="alignleft actions bulkactions">
    1059                    
    10601123                    <?php echo WP_Mail_Entity::get_all_sites_dropdown( $site_id, $which ); ?>
    1061                    
    10621124                </div>
    10631125                <script>
     
    10731135                <div id="export-form">
    10741136                    <div>
    1075                         <button id="start-export" class="button" data-type-export="mail" data-search="<?php echo self::escaped_search_input(); ?>">
     1137                        <button id="start-export" class="button" data-type-export="mail" data-search="<?php echo self::escaped_search_input(); ?>" data-plugin="<?php echo \esc_attr( isset( $plugin ) ? (string) $plugin : '' ); ?>">
    10761138                            <?php echo \esc_html__( 'CSV Export', '0-day-analytics' ); ?>
    10771139                        </button>
     
    11121174            <?php } ?>
    11131175                <div class="flex flex-row grow-0 p-2 w-full border-0 border-t border-solid justify-between">
    1114                     <div class=""> <?php \esc_html_e( 'Size: ', '0-day-analytics' ); ?> <?php echo \esc_attr( File_Helper::show_size( Common_Table::get_table_size() ) ); ?>
     1176                    <div class=""> <?php \esc_html_e( 'Size: ', '0-day-analytics' ); ?> <?php echo \esc_attr( \size_format( Common_Table::get_table_size() ) ); ?>
    11151177
    11161178                        <?php
  • 0-day-analytics/tags/4.2.0/classes/vendor/lists/views/class-crons-view.php

    r3391413 r3398360  
    128128                                '<input type="hidden" name="event_type" value="%s"/>',
    129129                                \esc_attr( isset( $_REQUEST['event_type'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['event_type'] ) ) : '' )
     130                            );
     131                            printf(
     132                                '<input type="hidden" name="plugin" value="%s"/>',
     133                                \esc_attr( isset( $_REQUEST['plugin'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['plugin'] ) ) : '' )
     134                            );
     135                            printf(
     136                                '<input type="hidden" name="site" value="%s"/>',
     137                                \esc_attr( isset( $_REQUEST['site'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['site'] ) ) : '' )
    130138                            );
    131139                            ?>
     
    200208                                \esc_attr( isset( $_REQUEST['event_type'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['event_type'] ) ) : '' )
    201209                            );
     210                            printf(
     211                                '<input type="hidden" name="plugin" value="%s"/>',
     212                                \esc_attr( isset( $_REQUEST['plugin'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['plugin'] ) ) : '' )
     213                            );
    202214                        ?>
    203215                        <?php \wp_nonce_field( Crons_List::NONCE_NAME ); ?>
     
    302314                        \esc_attr( isset( $_REQUEST['event_type'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['event_type'] ) ) : '' )
    303315                    );
     316                    printf(
     317                        '<input type="hidden" name="plugin" value="%s"/>',
     318                        \esc_attr( isset( $_REQUEST['plugin'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['plugin'] ) ) : '' )
     319                    );
     320                    printf(
     321                        '<input type="hidden" name="site" value="%s"/>',
     322                        \esc_attr( isset( $_REQUEST['site'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['site'] ) ) : '' )
     323                    );
     324                    printf(
     325                        '<input type="hidden" name="site" value="%s"/>',
     326                        \esc_attr( isset( $_REQUEST['site'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['site'] ) ) : '' )
     327                    );
    304328
    305329                ?>
     
    399423            exit;
    400424        }
     425
     426        /**
     427         * Handles plugin dropdown filter submission for cron list.
     428         *
     429         * @return void
     430         */
     431        public static function plugin_filter_action() {
     432            if ( isset( $_REQUEST['plugin_top'] ) || isset( $_REQUEST['plugin_bottom'] ) ) {
     433                if ( \check_admin_referer( Crons_List::PLUGIN_FILTER_ACTION, Crons_List::PLUGIN_FILTER_ACTION . 'nonce' ) ) {
     434                    $plugin = isset( $_REQUEST['plugin_top'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['plugin_top'] ) ) : \sanitize_text_field( \wp_unslash( $_REQUEST['plugin_bottom'] ) );
     435
     436                    \wp_safe_redirect(
     437                        \remove_query_arg(
     438                            array( 'deleted' ),
     439                            \add_query_arg(
     440                                array(
     441                                    'page'       => Crons_List::CRON_MENU_SLUG,
     442                                    Crons_List::SEARCH_INPUT => Crons_List::escaped_search_input(),
     443                                    'plugin'     => rawurlencode( $plugin ),
     444                                    'site'       => ( isset( $_REQUEST['site'] ) ? rawurlencode( sanitize_text_field( wp_unslash( $_REQUEST['site'] ) ) ) : '' ),
     445                                    'event_type' => ( isset( $_REQUEST['event_type'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['event_type'] ) ) : '' ),
     446                                    'schedules_filter' => ( isset( $_REQUEST['schedules_filter'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['schedules_filter'] ) ) : '' ),
     447                                ),
     448                                \admin_url( 'admin.php' )
     449                            )
     450                        )
     451                    );
     452                    exit;
     453                }
     454            }
     455        }
     456
     457        /**
     458         * Handles site filter submission for cron list.
     459         *
     460         * @return void
     461         */
     462        public static function site_filter_action() {
     463            // Only network admins can filter by site.
     464            if ( ! ( function_exists( 'is_multisite' ) && is_multisite() && current_user_can( 'manage_network' ) ) ) {
     465                return;
     466            }
     467            if ( isset( $_REQUEST['site_top'] ) || isset( $_REQUEST['site_bottom'] ) ) {
     468                if ( \check_admin_referer( Crons_List::SITE_FILTER_ACTION, Crons_List::SITE_FILTER_ACTION . 'nonce' ) ) {
     469                    $site = isset( $_REQUEST['site_top'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['site_top'] ) ) : \sanitize_text_field( \wp_unslash( $_REQUEST['site_bottom'] ) );
     470                    \wp_safe_redirect(
     471                        \remove_query_arg(
     472                            array( 'deleted' ),
     473                            \add_query_arg(
     474                                array(
     475                                    'page'       => Crons_List::CRON_MENU_SLUG,
     476                                    Crons_List::SEARCH_INPUT => Crons_List::escaped_search_input(),
     477                                    'site'       => rawurlencode( $site ),
     478                                    'plugin'     => ( isset( $_REQUEST['plugin'] ) ? rawurlencode( sanitize_text_field( wp_unslash( $_REQUEST['plugin'] ) ) ) : '' ),
     479                                    'event_type' => ( isset( $_REQUEST['event_type'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['event_type'] ) ) : '' ),
     480                                    'schedules_filter' => ( isset( $_REQUEST['schedules_filter'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['schedules_filter'] ) ) : '' ),
     481                                ),
     482                                \admin_url( 'admin.php' )
     483                            )
     484                        )
     485                    );
     486                    exit;
     487                }
     488            }
     489        }
    401490    }
    402491}
  • 0-day-analytics/tags/4.2.0/classes/vendor/lists/views/class-fatals-view.php

    r3393178 r3398360  
    506506        public static function page_load() {
    507507            if ( ! empty( $_GET['_wp_http_referer'] ) ) {
    508                 \wp_redirect(
     508                \wp_safe_redirect(
    509509                    \remove_query_arg( array( '_wp_http_referer', 'bulk_action' ), \wp_unslash( $_SERVER['REQUEST_URI'] ) )
    510510                );
  • 0-day-analytics/tags/4.2.0/classes/vendor/lists/views/class-requests-view.php

    r3393178 r3398360  
    651651        public static function page_load() {
    652652            if ( ! empty( $_GET['_wp_http_referer'] ) ) {
    653                 \wp_redirect(
     653                \wp_safe_redirect(
    654654                    \remove_query_arg( array( '_wp_http_referer', 'bulk_action' ), \wp_unslash( $_SERVER['REQUEST_URI'] ) )
    655655                );
  • 0-day-analytics/tags/4.2.0/classes/vendor/lists/views/class-table-view.php

    r3393178 r3398360  
    4545                \wp_die( \esc_html__( 'You do not have permission to manage tables.', '0-day-analytics' ) );
    4646            }
     47
     48            // Determine requested table early so we can process actions before any output.
     49            $table_name      = Common_Table::get_default_table();
     50            $requested_table = isset( $_REQUEST['show_table'] ) ? \sanitize_key( \wp_unslash( $_REQUEST['show_table'] ) ) : '';
     51            if ( $requested_table && \in_array( $requested_table, Common_Table::get_tables(), true ) ) {
     52                $table_name = $requested_table;
     53            }
     54
     55            // Instantiate list table and process bulk actions BEFORE any output to avoid header issues.
     56            $table = new Table_List( $table_name );
     57
     58            // Enqueue assets and render after potential redirects are processed.
    4759            \add_thickbox();
    4860            \wp_enqueue_style( 'media-views' );
     
    6072            </script>
    6173            <?php
    62 
    63             $table_name      = Common_Table::get_default_table();
    64             $requested_table = isset( $_REQUEST['show_table'] ) ? \sanitize_key( \wp_unslash( $_REQUEST['show_table'] ) ) : '';
    65             if ( $requested_table && \in_array( $requested_table, Common_Table::get_tables(), true ) ) {
    66                 $table_name = $requested_table;
    67             }
    6874
    6975            $action = ! empty( $_REQUEST['action'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
     
    202208            } else {
    203209
    204                 $table = new Table_List( $table_name );
    205210                $table->prepare_items();
    206211                $core_table = '';
     
    725730        public static function page_load() {
    726731            if ( ! empty( $_GET['_wp_http_referer'] ) ) {
    727                 \wp_redirect(
     732                \wp_safe_redirect(
    728733                    \remove_query_arg( array( '_wp_http_referer' ), \wp_unslash( $_SERVER['REQUEST_URI'] ) )
    729734                );
  • 0-day-analytics/tags/4.2.0/classes/vendor/lists/views/class-wp-mail-view.php

    r3393178 r3398360  
    795795            }
    796796        }
     797
     798        /**
     799         * Responsible for filtering table by plugin slug.
     800         *
     801         * @return void
     802         *
     803         * @since 4.1.1
     804         */
     805        public static function plugin_filter_action() {
     806
     807            if ( isset( $_REQUEST['plugin_top'] ) || isset( $_REQUEST['plugin_filter_bottom'] ) ) {
     808
     809                if ( \check_admin_referer( WP_Mail_List::PLUGIN_FILTER_ACTION, WP_Mail_List::PLUGIN_FILTER_ACTION . 'nonce' ) ) {
     810                    $id = \sanitize_text_field( \wp_unslash( $_REQUEST['plugin_top'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
     811
     812                    $context    = isset( $_REQUEST['context'] ) ? \sanitize_text_field( $_REQUEST['context'] ) : 'site';
     813                    $is_network = ( $context === 'network' && \is_multisite() );
     814
     815                    \wp_safe_redirect(
     816                        \remove_query_arg(
     817                            array( 'deleted' ),
     818                            \add_query_arg(
     819                                array(
     820                                    'page'                          => WP_Mail_List::WP_MAIL_MENU_SLUG,
     821                                    WP_Mail_List::SEARCH_INPUT       => WP_Mail_List::escaped_search_input(),
     822                                    'plugin'                        => rawurlencode( $id ),
     823                                ),
     824                                ( ( $is_network ) ? \network_admin_url( 'admin.php' ) : \admin_url( 'admin.php' ) )
     825                            )
     826                        )
     827                    );
     828                    exit;
     829                }
     830            }
     831        }
    797832    }
    798833}
  • 0-day-analytics/tags/4.2.0/classes/vendor/views/class-file-editor.php

    r3393178 r3398360  
    298298                        </div>
    299299                    </div>
     300                    <div class="wfe-resizer" role="separator" aria-label="Resize sidebar" aria-orientation="vertical" tabindex="0"></div>
    300301
    301302                    <div class="wfe-editor-area">
     
    489490                }
    490491                $path    = $real . \DIRECTORY_SEPARATOR . $file;
     492                $type    = is_dir( $path ) ? 'dir' : 'file';
     493                $display = $file;
     494                if ( 'file' === $type ) {
     495                    $size     = @filesize( $path );
     496                    if ( false !== $size ) {
     497                        $display .= ' (' . \size_format( (int) $size ) . ')';
     498                    }
     499                }
    491500                $items[] = array(
    492                     'name' => $file,
     501                    'name' => $display,
    493502                    'path' => $path,
    494                     'type' => is_dir( $path ) ? 'dir' : 'file',
     503                    'type' => $type,
    495504                );
    496505            }
     
    763772            \wp_send_json_success( array( 'diff' => $diff ) );
    764773        }
     774
     775        /**
     776         * AJAX: Downloads a regular file (not backup)
     777         *
     778         * @return void
     779         *
     780         * @since 4.1.1
     781         */
     782        public static function ajax_download_file() {
     783            WP_Helper::verify_admin_nonce( 'advan_file_editor_nonce', '_ajax_nonce' );
     784
     785            $file = \sanitize_text_field( $_GET['file'] ?? '' );
     786            $real = self::safe_path( $file );
     787            if ( ! $real || ! is_file( $real ) || ! is_readable( $real ) ) {
     788                \wp_die( 'Invalid file.' );
     789            }
     790
     791            $filename = basename( $real );
     792            header( 'Content-Type: application/octet-stream' );
     793            header( 'X-Content-Type-Options: nosniff' );
     794            header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
     795            header( 'Content-Length: ' . filesize( $real ) );
     796            header( 'Cache-Control: no-store, no-cache, must-revalidate, max-age=0' );
     797            header( 'Pragma: no-cache' );
     798            readfile( $real );
     799            exit;
     800        }
     801
     802        /**
     803         * AJAX: Renames a file or directory
     804         *
     805         * @return void
     806         *
     807         * @since 4.1.1
     808         */
     809        public static function ajax_rename() {
     810            WP_Helper::verify_admin_nonce( 'advan_file_editor_nonce', '_ajax_nonce' );
     811
     812            $src      = \sanitize_text_field( $_POST['file'] ?? '' );
     813            $new_name = \sanitize_file_name( $_POST['new_name'] ?? '' );
     814
     815            if ( empty( $src ) || empty( $new_name ) ) {
     816                \wp_send_json_error( 'Missing parameters.' );
     817            }
     818
     819            $real = self::safe_path( $src );
     820            if ( ! $real || ! file_exists( $real ) ) {
     821                \wp_send_json_error( 'Source not found.' );
     822            }
     823
     824            $target = dirname( $real ) . \DIRECTORY_SEPARATOR . $new_name;
     825            if ( file_exists( $target ) ) {
     826                \wp_send_json_error( 'Target exists.' );
     827            }
     828
     829            if ( ! @rename( $real, $target ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
     830                \wp_send_json_error( 'Unable to rename.' );
     831            }
     832
     833            \wp_send_json_success( array( 'new_path' => $target, 'new_name' => basename( $target ) ) );
     834        }
     835
     836        /**
     837         * AJAX: Duplicates a file or directory
     838         *
     839         * @return void
     840         *
     841         * @since 4.1.1
     842         */
     843        public static function ajax_duplicate() {
     844            WP_Helper::verify_admin_nonce( 'advan_file_editor_nonce', '_ajax_nonce' );
     845
     846            $src = \sanitize_text_field( $_POST['file'] ?? '' );
     847            if ( empty( $src ) ) {
     848                \wp_send_json_error( 'Missing parameters.' );
     849            }
     850
     851            $real = self::safe_path( $src );
     852            if ( ! $real || ! file_exists( $real ) ) {
     853                \wp_send_json_error( 'Source not found.' );
     854            }
     855
     856            $is_dir   = is_dir( $real );
     857            $dirname  = dirname( $real );
     858            $basename = basename( $real );
     859
     860            // Determine target name.
     861            $ext      = '';
     862            $name_only = $basename;
     863            if ( ! $is_dir && false !== strpos( $basename, '.' ) ) {
     864                $parts    = explode( '.', $basename );
     865                $ext      = array_pop( $parts );
     866                $name_only = implode( '.', $parts );
     867            }
     868
     869            $base_candidate = $name_only . '-copy';
     870            $counter        = 1;
     871            $target         = '';
     872            while ( true ) {
     873                $suffix = ( $counter > 1 ) ? '-' . $counter : '';
     874                $target_name = $base_candidate . $suffix . ( $is_dir ? '' : ( ( '' !== $ext ) ? '.' . $ext : '' ) );
     875                $target      = $dirname . \DIRECTORY_SEPARATOR . $target_name;
     876                if ( ! file_exists( $target ) ) {
     877                    break;
     878                }
     879                $counter++;
     880            }
     881
     882            // Perform copy.
     883            if ( $is_dir ) {
     884                self::recursive_copy( $real, $target );
     885            } else {
     886                if ( ! @copy( $real, $target ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
     887                    \wp_send_json_error( 'Unable to duplicate file.' );
     888                }
     889            }
     890
     891            \wp_send_json_success( array( 'new_path' => $target, 'new_name' => basename( $target ) ) );
     892        }
     893
     894        /**
     895         * Recursively copies a directory.
     896         *
     897         * @param string $src Source dir.
     898         * @param string $dst Destination dir.
     899         *
     900         * @return void
     901         *
     902         * @since 4.1.1
     903         */
     904        private static function recursive_copy( $src, $dst ) {
     905            if ( ! is_dir( $src ) ) {
     906                return;
     907            }
     908            @wp_mkdir_p( $dst );
     909            $dir_handle = opendir( $src );
     910            if ( false === $dir_handle ) {
     911                return;
     912            }
     913            while ( false !== ( $item = readdir( $dir_handle ) ) ) {
     914                if ( '.' === $item || '..' === $item ) {
     915                    continue;
     916                }
     917                $from = $src . \DIRECTORY_SEPARATOR . $item;
     918                $to   = $dst . \DIRECTORY_SEPARATOR . $item;
     919                if ( is_dir( $from ) ) {
     920                    self::recursive_copy( $from, $to );
     921                } else {
     922                    @copy( $from, $to ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
     923                }
     924            }
     925            closedir( $dir_handle );
     926        }
    765927    }
    766928}
  • 0-day-analytics/tags/4.2.0/css/wfe.css

    r3392179 r3398360  
    77.wfe-sidebar {
    88    width: 280px;
    9     /* border-right: 1px solid #ccc; */
    109    padding-right: 10px;
    11     max-height: 80vh;
    1210    background: transparent;
    1311    display: flex;
    1412    flex-direction: column;
    1513    min-height: 30px;
    16     resize: both;
    1714    overflow: auto;
    18     max-height: fit-content;
    19     max-width: fit-content;
     15    max-width: 100%;
     16    position: relative;
    2017}
    2118
     
    8784    height: 100%;
    8885}
     86.wfe-resizer {
     87    width: 6px;
     88    cursor: col-resize;
     89    background: linear-gradient(to right, #ddd, #bbb);
     90    border-left: 1px solid #aaa;
     91    border-right: 1px solid #aaa;
     92    margin: 0;
     93    flex: 0 0 auto;
     94}
     95.aadvana-darkskin .wfe-resizer {
     96    background: linear-gradient(to right, #233445, #1a2734);
     97    border-left: 1px solid #2f4a60;
     98    border-right: 1px solid #2f4a60;
     99}
     100.wfe-resizer:hover, .wfe-resizer:focus {
     101    background: #92b7dd;
     102    outline: none;
     103}
     104.wfe-resizing {
     105    user-select: none !important;
     106    cursor: col-resize !important;
     107}
     108.wfe-context-menu {
     109    position: absolute;
     110    z-index: 99999;
     111    background: #fff;
     112    border: 1px solid #ccc;
     113    box-shadow: 0 2px 6px rgba(0,0,0,0.15);
     114    padding: 4px 0;
     115    min-width: 160px;
     116    font-size: 13px;
     117    border-radius: 4px;
     118}
     119.wfe-context-menu button {
     120    background: none;
     121    border: none;
     122    width: 100%;
     123    text-align: left;
     124    padding: 6px 12px;
     125    cursor: pointer;
     126    font-size: 13px;
     127}
     128.wfe-context-menu button:hover, .wfe-context-menu button:focus {
     129    background: #f0f6ff;
     130    outline: none;
     131}
     132.aadvana-darkskin .wfe-context-menu {
     133    background: #13273b;
     134    border-color: #2f4a60;
     135    box-shadow: 0 2px 6px rgba(0,0,0,0.4);
     136}
     137.aadvana-darkskin .wfe-context-menu button:hover, .aadvana-darkskin .wfe-context-menu button:focus {
     138    background: #1e3e59;
     139}
    89140.wfe-tree {
    90141    border-left: 1px solid #8d7a7a73;
     
    105156.wfe-item:hover {
    106157    background: #e2f0ff26;
     158}
     159.wfe-item.active {
     160    background: #c7e3ff;
     161    font-weight: 600;
     162}
     163.aadvana-darkskin .wfe-item.active {
     164    background: #1e3e59;
    107165}
    108166.toggle {
  • 0-day-analytics/tags/4.2.0/js/admin/wfe.js

    r3392179 r3398360  
    5858    $('#wfe-tree').on('click', '.wfe-item.file', function (e) {
    5959        e.stopPropagation();
    60         const path = $(this).data('path');
     60        const $li = $(this);
     61        const path = $li.data('path');
    6162        currentDir = path.substring(0, path.lastIndexOf('/'));
    6263        $.post(AFE_Ajax.ajax_url, { action: 'advan_file_editor_get_file', file: path, _ajax_nonce: AFE_Ajax.nonce }, (res) => {
     
    6667                $('#wfe-filename').text(path);
    6768                $('#wfe-diff').hide();
     69                $('#wfe-tree .wfe-item.file.active').removeClass('active');
     70                $li.addClass('active');
    6871                listBackups();
    6972            } else alert(res.data);
     
    139142            $('#wfe-filename').text(__('No file selected', '0-day-analytics'));
    140143            editor.setValue('');
     144            $('#wfe-tree .wfe-item.file.active').removeClass('active');
    141145        });
    142146    });
     
    180184        }, (res) => {
    181185            alert(res.success ? '✅ ' + res.data : '❌ ' + res.data);
     186            if (res.success && file) {
     187                // Reload the file content into the editor to reflect restored version
     188                $.post(AFE_Ajax.ajax_url, {
     189                    action: 'advan_file_editor_get_file',
     190                    file: file,
     191                    _ajax_nonce: AFE_Ajax.nonce
     192                }, (res2) => {
     193                    if (res2.success) {
     194                        editor.setValue(res2.data.content);
     195                        $('#wfe-diff').hide();
     196                    }
     197                });
     198                // Refresh backups list
     199                listBackups();
     200            }
    182201        });
    183202    });
     
    240259        });
    241260    }
     261
     262    // --- Resizable Sidebar ---
     263    const $sidebar = $('.wfe-sidebar');
     264    const $resizer = $('.wfe-resizer');
     265    const $container = $('.wfe-container');
     266    const isReverse = $container.css('flex-direction') === 'row-reverse';
     267    const savedWidth = localStorage.getItem('wfeSidebarWidth');
     268    if (savedWidth && parseInt(savedWidth, 10) > 0) {
     269        $sidebar.css('width', parseInt(savedWidth, 10) + 'px');
     270    }
     271
     272    let startX = 0, startWidth = 0, dragging = false;
     273
     274    function onMouseMove(e) {
     275        if (!dragging) return;
     276        const dx = e.pageX - startX;
     277        const adjust = isReverse ? -dx : dx; // row-reverse flips direction
     278        let newWidth = startWidth + adjust;
     279        const min = 160;
     280        const max = 720;
     281        if (newWidth < min) newWidth = min;
     282        if (newWidth > max) newWidth = max;
     283        $sidebar.css('width', newWidth + 'px');
     284    }
     285
     286    function stopDrag() {
     287        if (!dragging) return;
     288        dragging = false;
     289        $('body').removeClass('wfe-resizing');
     290        $(document).off('mousemove.wfeResize mouseup.wfeResize');
     291        const finalWidth = parseInt($sidebar.width(), 10);
     292        if (finalWidth) {
     293            localStorage.setItem('wfeSidebarWidth', finalWidth);
     294        }
     295    }
     296
     297    $resizer.on('mousedown', function (e) {
     298        e.preventDefault();
     299        startX = e.pageX;
     300        startWidth = parseInt($sidebar.width(), 10) || 280;
     301        dragging = true;
     302        $('body').addClass('wfe-resizing');
     303        $(document).on('mousemove.wfeResize', onMouseMove).on('mouseup.wfeResize', stopDrag);
     304    });
     305
     306    // Keyboard accessibility: left/right arrows
     307    $resizer.on('keydown', function(e){
     308        const key = e.key;
     309        let width = parseInt($sidebar.width(), 10) || 280;
     310        const step = (e.shiftKey ? 40 : 20);
     311        if (key === 'ArrowLeft' || key === 'ArrowRight') {
     312            const dirFactor = (key === 'ArrowRight' ? 1 : -1) * (isReverse ? -1 : 1);
     313            width += step * dirFactor;
     314            if (width < 160) width = 160;
     315            if (width > 720) width = 720;
     316            $sidebar.css('width', width + 'px');
     317            localStorage.setItem('wfeSidebarWidth', width);
     318            e.preventDefault();
     319        }
     320    });
     321
     322    // --- File Context Menu (Download) ---
     323    let $ctxMenu = null;
     324    function hideContextMenu(){
     325        if($ctxMenu){
     326            $ctxMenu.remove();
     327            $ctxMenu = null;
     328        }
     329        // Remove temporary listeners
     330        $(document).off('.wfeCtx');
     331        $(window).off('.wfeCtx');
     332    }
     333    function showContextMenu(e, filePath){
     334        hideContextMenu();
     335        $ctxMenu = $('<div class="wfe-context-menu" role="menu"></div>');
     336        // Download
     337        const $downloadBtn = $('<button type="button" role="menuitem">⬇️ '+__('Download','0-day-analytics')+'</button>');
     338        $downloadBtn.on('click', function(){
     339            const url = `${AFE_Ajax.ajax_url}?action=advan_file_editor_download_file&_ajax_nonce=${AFE_Ajax.nonce}&file=${encodeURIComponent(filePath)}`;
     340            hideContextMenu();
     341            window.location.href = url;
     342        });
     343        // Rename
     344        const $renameBtn = $('<button type="button" role="menuitem">✏️ '+__('Rename','0-day-analytics')+'</button>');
     345        $renameBtn.on('click', function(){
     346            const currentBase = filePath.substring(filePath.lastIndexOf('/'));
     347            const newName = prompt(__('Enter new name:','0-day-analytics'), currentBase.replace('/',''));
     348            if(!newName){ return; }
     349            $.post(AFE_Ajax.ajax_url, { action:'advan_file_editor_rename', file:filePath, new_name:newName, _ajax_nonce: AFE_Ajax.nonce }, function(res){
     350                alert(res.success ? '✅ '+__('Renamed','0-day-analytics') : '❌ '+res.data);
     351                if(res.success){
     352                    if(currentFile === filePath){
     353                        currentFile = res.data.new_path;
     354                        $('#wfe-filename').text(res.data.new_path);
     355                    }
     356                    $('#wfe-tree').empty();
     357                    loadDir(AFE_Ajax.base, $('#wfe-tree'));
     358                }
     359            });
     360            hideContextMenu();
     361        });
     362        // Copy Path
     363        const $copyPathBtn = $('<button type="button" role="menuitem">📋 '+__('Copy Path','0-day-analytics')+'</button>');
     364        $copyPathBtn.on('click', function(){
     365            navigator.clipboard.writeText(filePath).then(()=>{
     366                alert('📋 '+__('Path copied','0-day-analytics'));
     367            }).catch(()=>{ alert('❌ '+__('Unable to copy path','0-day-analytics')); });
     368            hideContextMenu();
     369        });
     370        // Delete
     371        const $deleteBtn = $('<button type="button" role="menuitem">🗑️ '+__('Delete','0-day-analytics')+'</button>');
     372        $deleteBtn.on('click', function(){
     373            if(!confirm(__('Delete file?','0-day-analytics')+'\n'+filePath)) { return; }
     374            $.post(AFE_Ajax.ajax_url, { action:'advan_file_editor_delete', path:filePath, _ajax_nonce:AFE_Ajax.nonce }, function(res){
     375                alert(res.success ? '🗑️ '+__('Deleted','0-day-analytics') : '❌ '+res.data);
     376                if(res.success){
     377                    if(currentFile === filePath){
     378                        currentFile = null;
     379                        editor.setValue('');
     380                        $('#wfe-filename').text(__('No file selected','0-day-analytics'));
     381                    }
     382                    $('#wfe-tree').empty();
     383                    loadDir(AFE_Ajax.base, $('#wfe-tree'));
     384                }
     385            });
     386            hideContextMenu();
     387        });
     388        // Duplicate
     389        const $duplicateBtn = $('<button type="button" role="menuitem">🧬 '+__('Duplicate','0-day-analytics')+'</button>');
     390        $duplicateBtn.on('click', function(){
     391            $.post(AFE_Ajax.ajax_url, { action:'advan_file_editor_duplicate', file:filePath, _ajax_nonce:AFE_Ajax.nonce }, function(res){
     392                alert(res.success ? '🧬 '+__('Duplicated','0-day-analytics')+': '+res.data.new_name : '❌ '+res.data);
     393                if(res.success){
     394                    $('#wfe-tree').empty();
     395                    loadDir(AFE_Ajax.base, $('#wfe-tree'));
     396                }
     397            });
     398            hideContextMenu();
     399        });
     400        $ctxMenu.append($downloadBtn, $renameBtn, $duplicateBtn, $copyPathBtn, $deleteBtn);
     401        $('body').append($ctxMenu);
     402        const x = e.pageX;
     403        const y = e.pageY;
     404        $ctxMenu.css({ top: y + 'px', left: x + 'px' });
     405        // Delay binding to avoid immediate self-close from originating event
     406        setTimeout(function(){
     407            $(document).on('mousedown.wfeCtx', function(ev){
     408                if(!$ctxMenu) return;
     409                if(!$(ev.target).closest('.wfe-context-menu').length){ hideContextMenu(); }
     410            });
     411            $(document).on('contextmenu.wfeCtx', function(ev){
     412                if(!$ctxMenu) return;
     413                if(!$(ev.target).closest('.wfe-context-menu').length){ hideContextMenu(); }
     414            });
     415            $(document).on('keydown.wfeCtx', function(ev){ if(ev.key === 'Escape'){ hideContextMenu(); } });
     416            $(window).on('scroll.wfeCtx resize.wfeCtx', hideContextMenu);
     417        },0);
     418        // Focus first item for accessibility
     419        $downloadBtn.focus();
     420    }
     421    $('#wfe-tree').on('contextmenu', '.wfe-item.file', function(e){
     422        e.preventDefault();
     423        const path = $(this).data('path');
     424        showContextMenu(e, path);
     425    });
     426    // Global listeners now added dynamically per open; fallback in case menu injected elsewhere
     427    // (No always-on handlers needed here.)
    242428});
  • 0-day-analytics/tags/4.2.0/readme.txt

    r3393178 r3398360  
    44Tested up to: 6.8
    55Requires PHP: 7.4
    6 Stable tag: 4.1.1
     6Stable tag: 4.2.0
    77License: GPLv3 or later
    88License URI: http://www.gnu.org/licenses/gpl-3.0.txt
     
    114114== Changelog ==
    115115
     116= 4.2.0 =
     117New filters introduced. Code optimizations and bug fixes. File editor extending.
     118
    116119= 4.1.1 =
    117120Very small maintenance update - optimizations and bug fixes.
  • 0-day-analytics/trunk/advanced-analytics.php

    r3393178 r3398360  
    1111 * Plugin Name:     0 Day Analytics
    1212 * Description:     Take full control of error log, crons, transients, plugins, requests, mails and DB tables.
    13  * Version:         4.1.1
     13 * Version:         4.2.0
    1414 * Author:          Stoil Dobrev
    1515 * Author URI:      https://github.com/sdobreff/
     
    3737// Constants.
    3838if ( ! defined( 'ADVAN_VERSION' ) ) {
    39     define( 'ADVAN_VERSION', '4.1.1' );
     39    define( 'ADVAN_VERSION', '4.2.0' );
    4040    define( 'ADVAN_TEXTDOMAIN', '0-day-analytics' );
    4141    define( 'ADVAN_NAME', '0 Day Analytics' );
     
    6767                    sprintf(
    6868                        // translators: the minimum version of the PHP required by the plugin.
    69                         __(
     69                        \__(
    7070                            '"%1$s" requires PHP %2$s or newer. Plugin is automatically deactivated.',
    7171                            '0-day-analytics'
  • 0-day-analytics/trunk/classes/migration/class-migration.php

    r3384847 r3398360  
    216216            }
    217217        }
     218
     219        /**
     220         * Migrates the plugin up-to version 4.2.0 (adds plugin_slug column to mail log).
     221         *
     222         * @return void
     223         *
     224         * @since 4.2.0
     225         */
     226        public static function migrate_up_to_420() {
     227            if ( \class_exists( '\\ADVAN\\Entities\\WP_Mail_Entity' ) ) {
     228                if ( Common_Table::check_table_exists( WP_Mail_Entity::get_table_name() ) && ! Common_Table::check_column( 'plugin_slug', 'varchar(255)', WP_Mail_Entity::get_table_name() ) ) {
     229                    \ADVAN\Entities\WP_Mail_Entity::alter_table_411();
     230                }
     231            }
     232        }
    218233    }
    219234}
  • 0-day-analytics/trunk/classes/vendor/controllers/class-wp-mail-log.php

    r3393178 r3398360  
    1313
    1414use ADVAN\Entities\WP_Mail_Entity;
     15use ADVAN\Helpers\Plugin_Theme_Helper;
    1516use ADVAN\Helpers\Settings;
    1617
     
    123124                    $message = $email_class->get( 'content_plaintext', 'replace-tokens' );
    124125                }
     126                $bt_segment = self::get_backtrace();
     127                $plugin_slug = '';
     128                if ( isset( $bt_segment['file'] ) ) {
     129                    $plugin_base = Plugin_Theme_Helper::get_plugin_from_file_path( $bt_segment['file'] );
     130                    if ( $plugin_base ) {
     131                        $plugin_slug = $plugin_base;
     132                    }
     133                }
    125134                self::$bp_mail = array(
    126135                    'time'               => time(),
     
    129138                    'message'            => self::filter_html( $message ),
    130139                    'backtrace_segment'  => \wp_json_encode( self::get_backtrace() ),
     140                    'plugin_slug'        => $plugin_slug,
    131141                    'status'             => 1,
    132142                    'attachments'        => \wp_json_encode( self::get_attachment_locations( array() ) ),
     
    150160
    151161            if ( \is_array( $args ) ) {
     162                $bt_segment = self::get_backtrace();
     163                $plugin_slug = '';
     164                if ( isset( $bt_segment['file'] ) ) {
     165                    $plugin_base = Plugin_Theme_Helper::get_plugin_from_file_path( $bt_segment['file'] );
     166                    if ( $plugin_base ) {
     167                        $plugin_slug = $plugin_base;
     168                    }
     169                }
    152170                $log_entry = array(
    153171                    'time'               => time(),
     
    156174                    'message'            => self::filter_html( $args['message'] ),
    157175                    'backtrace_segment'  => \wp_json_encode( self::get_backtrace() ),
     176                    'plugin_slug'        => $plugin_slug,
    158177                    'status'             => 1,
    159178                    'attachments'        => \wp_json_encode( self::get_attachment_locations( $args['attachments'] ) ),
     
    202221                    $mail_header = $prop->getValue( $phpmailer );
    203222
     223                    $bt_segment = self::get_backtrace();
     224                    $plugin_slug = '';
     225                    if ( isset( $phpmailer->Backtrace, $phpmailer->Backtrace['file'] ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
     226                        $plugin_base = Plugin_Theme_Helper::get_plugin_from_file_path( $phpmailer->Backtrace['file'] ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
     227                        if ( $plugin_base ) {
     228                            $plugin_slug = $plugin_base;
     229                        }
     230                    } elseif ( isset( $bt_segment['file'] ) ) {
     231                        $plugin_base = Plugin_Theme_Helper::get_plugin_from_file_path( $bt_segment['file'] );
     232                        if ( $plugin_base ) {
     233                            $plugin_slug = $plugin_base;
     234                        }
     235                    }
    204236                    $log_entry = array(
    205237                        'time'               => time(),
     
    209241                        'message'            => self::filter_html( $phpmailer->Body ), // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
    210242                        'backtrace_segment'  => \wp_json_encode( self::get_backtrace() ),
     243                        'plugin_slug'        => $plugin_slug,
    211244                        'status'             => 1,
    212245                        'attachments'        => \wp_json_encode( self::get_attachment_locations( $attachment ) ),
  • 0-day-analytics/trunk/classes/vendor/entities/class-wp-mail-entity.php

    r3393178 r3398360  
    1313
    1414use ADVAN\Helpers\WP_Helper;
     15use ADVAN\Helpers\Plugin_Theme_Helper;
    1516use ADVAN\Entities_Global\Common_Table;
    1617
     
    5455            'id'                 => 'int',
    5556            'blog_id'            => 'int',
     57            'plugin_slug'        => 'string',
    5658            'time'               => 'string',
    5759            'email_to'           => 'string',
     
    7779            'id'                 => 0,
    7880            'blog_id'            => 0,
     81            'plugin_slug'        => '',
    7982            'time'               => '',
    8083            'email_to'           => '',
     
    115118                id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    116119                blog_id INT NOT NULL,
     120                plugin_slug VARCHAR(255) DEFAULT NULL,
    117121                time DOUBLE NOT NULL DEFAULT 0,
    118122                email_to TEXT DEFAULT NULL,
     
    168172
    169173        /**
     174         * Adds plugin_slug column (version 4.1.1 migration) capturing originating plugin.
     175         *
     176         * @return array|bool
     177         *
     178         * @since 4.1.1
     179         */
     180        public static function alter_table_411() {
     181            $table  = self::get_table_name();
     182            $column = 'plugin_slug';
     183            if ( self::column_exists( $table, $column ) ) {
     184                return true;
     185            }
     186            $sql = 'ALTER TABLE `' . $table . '` ADD `plugin_slug` VARCHAR(255) DEFAULT NULL AFTER `blog_id`;';
     187            return Common_Table::execute_query( $sql );
     188        }
     189
     190        /**
    170191         * Helper to check if a column exists in the given table.
    171192         *
     
    260281            return $output;
    261282        }
     283
     284        /**
     285         * Generates dropdown with all distinct plugin slugs seen in the mail log.
     286         *
     287         * @param string $selected The currently selected plugin slug (or -1 for all).
     288         * @param string $which    Indicates position of the dropdown (top or bottom).
     289         *
     290         * @return string Rendered HTML <select> element, or empty string if none.
     291         *
     292         * @since 4.1.1
     293         */
     294        public static function get_all_plugins_dropdown( $selected = '', $which = '' ): string {
     295            // Restrict visibility to administrators to avoid information disclosure.
     296            if ( function_exists( 'current_user_can' ) && ! \current_user_can( 'manage_options' ) ) {
     297                return '';
     298            }
     299
     300            $which = in_array( $which, array( 'top', 'bottom' ), true ) ? $which : '';
     301
     302            // Build cache key per position and selection (reuse sites cache container for simplicity).
     303            $cache_key = 'plugins|' . $which . '|' . (string) $selected;
     304            if ( isset( self::$drop_down_sites_rendered[ $cache_key ] ) ) {
     305                return self::$drop_down_sites_rendered[ $cache_key ];
     306            }
     307
     308            $sql     = 'SELECT plugin_slug FROM ' . self::get_table_name() . ' GROUP BY plugin_slug ORDER BY plugin_slug DESC';
     309            $results = self::get_results( $sql );
     310            $plugins = array();
     311            $output  = '';
     312
     313            if ( $results ) {
     314                foreach ( $results as $result ) {
     315                    $slug = isset( $result['plugin_slug'] ) ? trim( (string) $result['plugin_slug'] ) : '';
     316                    if ( '' === $slug ) {
     317                        continue;
     318                    }
     319                    $details = Plugin_Theme_Helper::get_plugin_from_path( $slug );
     320                    $name    = ( isset( $details['Name'] ) && ! empty( $details['Name'] ) ) ? $details['Name'] : $slug;
     321                    $plugins[] = array(
     322                        'id'   => $slug,
     323                        'name' => $name,
     324                    );
     325                }
     326            }
     327
     328            if ( ! empty( $plugins ) ) {
     329                $output  = '<select class="plugin_filter" name="plugin_' . \esc_attr( $which ) . '" id="plugin_' . \esc_attr( $which ) . '">';
     330                $output .= '<option value="-1">' . __( 'All plugins', '0-day-analytics' ) . '</option>';
     331                foreach ( $plugins as $plugin_info ) {
     332                    $selected_attr = ( isset( $selected ) && '' !== trim( (string) $selected ) && (string) $selected === (string) $plugin_info['id'] ) ? ' selected' : '';
     333                    $output       .= '<option value="' . \esc_attr( $plugin_info['id'] ) . '"' . $selected_attr . '>' . \esc_html( $plugin_info['name'] ) . '</option>';
     334                }
     335                $output .= '</select>';
     336            }
     337
     338            self::$drop_down_sites_rendered[ $cache_key ] = $output;
     339            return $output;
     340        }
    262341    }
    263342}
  • 0-day-analytics/trunk/classes/vendor/helpers/class-ajax-helper.php

    r3393178 r3398360  
    171171                    \add_action( 'wp_ajax_advan_file_editor_compare_backup', array( File_Editor::class, 'ajax_compare_backup' ) );
    172172                    \add_action( 'wp_ajax_advan_file_editor_delete_backup', array( File_Editor::class, 'ajax_delete_backup' ) );
     173                    // Added download action registration (returns 0 previously when unregistered).
     174                    \add_action( 'wp_ajax_advan_file_editor_download_file', array( File_Editor::class, 'ajax_download_file' ) );
     175                    // Rename file or directory.
     176                    \add_action( 'wp_ajax_advan_file_editor_rename', array( File_Editor::class, 'ajax_rename' ) );
     177                    // Duplicate file or directory.
     178                    \add_action( 'wp_ajax_advan_file_editor_duplicate', array( File_Editor::class, 'ajax_duplicate' ) );
    173179                }
    174180            }
     
    708714            if ( isset( $_POST['typeExport'] ) && ! empty( $_POST['typeExport'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
    709715                if ( 'cron' === $_POST['typeExport'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
    710                     $rows  = Crons_List::get_cron_items();
     716                    $plugin_filter = ( isset( $_POST['plugin_filter'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin_filter'] ) ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
     717                    $site_filter   = ( isset( $_POST['site_filter'] ) ? sanitize_text_field( wp_unslash( $_POST['site_filter'] ) ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
     718                    $rows          = array();
     719
     720                    if ( '' !== $site_filter && -1 !== (int) $site_filter && function_exists( 'is_multisite' ) && is_multisite() ) {
     721                        $rows = Crons_Helper::get_events_for_site( (int) $site_filter );
     722                    } else {
     723                        $rows = Crons_List::get_cron_items();
     724                    }
     725
     726                    if ( '' !== $plugin_filter && -1 !== (int) $plugin_filter ) {
     727                        $rows = array_filter(
     728                            $rows,
     729                            function ( $row ) use ( $plugin_filter ) {
     730                                if ( isset( $row['plugin_slugs'] ) && is_array( $row['plugin_slugs'] ) ) {
     731                                    return in_array( $plugin_filter, $row['plugin_slugs'], true );
     732                                }
     733                                return ( isset( $row['plugin_slug'] ) && '' !== $row['plugin_slug'] && $row['plugin_slug'] === $plugin_filter );
     734                            }
     735                        );
     736                    }
     737                    $total = count( $rows );
    711738                    $rows  = \array_slice( $rows, $offset, $batch_size, true );
    712                     $total = count( $rows );
    713739                }
    714740                if ( 'logs' === $_POST['typeExport'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
     
    768794                if ( 'mail' === $_POST['typeExport'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
    769795
    770                     $list_table      = new WP_Mail_List();
    771                     $search          = isset( $_POST['search'] ) ? sanitize_text_field( wp_unslash( $_POST['search'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
     796                    $list_table = new WP_Mail_List();
     797                    $search     = isset( $_POST['search'] ) ? sanitize_text_field( wp_unslash( $_POST['search'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
     798                    $plugin     = isset( $_POST['plugin'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
     799
    772800                    $extra_file_name = '_' . str_replace( ' ', '_', $list_table->get_table_name() ) . '_';
     801                    if ( ! empty( $plugin ) && -1 !== (int) $plugin ) {
     802                        $extra_file_name .= $plugin . '_';
     803                    }
    773804
    774805                    $rows = $list_table->fetch_table_data(
     
    779810                            'site_id'  => '',
    780811                            'search'   => $search,
     812                            'plugin'   => $plugin,
    781813                        )
    782814                    );
  • 0-day-analytics/trunk/classes/vendor/helpers/class-crons-helper.php

    r3393178 r3398360  
    1515
    1616use ADVAN\Lists\Crons_List;
     17use ADVAN\Helpers\Plugin_Theme_Helper;
    1718
    1819// Exit if accessed directly.
     
    263264                            continue;
    264265                        }
    265                         foreach ( $cron as $hook => $events ) {
    266                             foreach ( $events as $event ) {
    267 
    268                                 $cron_item = array();
    269 
    270                                 $cron_item['hook']     = \esc_html( $hook );
    271                                 $cron_item['schedule'] = $timestamp;
    272                                 if ( isset( $event['schedule'] ) ) {
    273                                     $cron_item['recurrence'] = \esc_html( $event['schedule'] );
     266                            foreach ( $cron as $hook => $events ) {
     267                                foreach ( $events as $event ) {
     268
     269                                    $cron_item = array();
     270
     271                                    $cron_item['hook']     = \esc_html( $hook );
     272                                    $cron_item['schedule'] = $timestamp;
     273                                    if ( isset( $event['schedule'] ) ) {
     274                                        $cron_item['recurrence'] = \esc_html( $event['schedule'] );
     275                                    }
     276                                    if ( isset( $event['args'] ) ) {
     277                                        $cron_item['args'] = $event['args'];
     278                                    }
     279
     280                                    $cron_item['hash'] = substr( md5( $cron_item['hook'] . $cron_item['recurrence'] . $cron_item['schedule'] . \wp_json_encode( $event['args'] ) ), 0, 8 );
     281
     282                                    // Site metadata for multisite clarity.
     283                                    $cron_item['site_id']   = function_exists( 'get_current_blog_id' ) ? get_current_blog_id() : 1;
     284                                    $cron_item['site_name'] = ( function_exists( 'get_bloginfo' ) ) ? get_bloginfo( 'name' ) : '';
     285
     286                                    // Derive all plugin slugs from callback files (multi-plugin aggregation).
     287                                    $callbacks    = self::get_cron_callbacks( $hook );
     288                                    $plugin_slugs = array();
     289                                    if ( ! empty( $callbacks ) ) {
     290                                        foreach ( $callbacks as $cb ) {
     291                                            if ( isset( $cb['callback']['file'] ) && ! empty( $cb['callback']['file'] ) ) {
     292                                                $slug = Plugin_Theme_Helper::get_plugin_from_file_path( (string) $cb['callback']['file'] );
     293                                                if ( false !== $slug && ! in_array( $slug, $plugin_slugs, true ) ) {
     294                                                    $plugin_slugs[] = (string) $slug;
     295                                                }
     296                                            }
     297                                        }
     298                                    }
     299                                    // Backward compatibility single slug (first) & full list.
     300                                    $cron_item['plugin_slug']  = isset( $plugin_slugs[0] ) ? $plugin_slugs[0] : '';
     301                                    $cron_item['plugin_slugs'] = $plugin_slugs; // empty array signifies core/unknown.
     302
     303                                    self::$events[ $cron_item['hash'] ] = $cron_item;
    274304                                }
    275                                 if ( isset( $event['args'] ) ) {
    276                                     $cron_item['args'] = $event['args'];
    277                                 }
    278 
    279                                 $cron_item['hash'] = substr( md5( $cron_item['hook'] . $cron_item['recurrence'] . $cron_item['schedule'] . \wp_json_encode( $event['args'] ) ), 0, 8 );
    280305                            }
    281                             self::$events[ $cron_item['hash'] ] = $cron_item;
    282                         }
    283306                    }
    284307                }
     
    286309
    287310            return self::$events;
     311        }
     312
     313        /**
     314         * Returns the cron events for a specific site in a multisite installation.
     315         * Safely switches to the blog, collects events, then restores.
     316         *
     317         * @param int $blog_id Site (blog) ID.
     318         * @return array
     319         */
     320        public static function get_events_for_site( int $blog_id ): array {
     321            if ( ! function_exists( 'is_multisite' ) || ! is_multisite() ) {
     322                return self::get_events();
     323            }
     324
     325            $original = get_current_blog_id();
     326            if ( $original === $blog_id ) {
     327                return self::get_events();
     328            }
     329
     330            // Switch, clear cache, collect, restore and clear again to avoid bleed.
     331            switch_to_blog( $blog_id );
     332            self::$events = null;
     333            $events      = self::get_events();
     334            restore_current_blog();
     335            self::$events = null; // ensure subsequent calls on original blog re-fetch.
     336
     337            return $events;
    288338        }
    289339
     
    566616            }
    567617
    568             if ( defined( 'ALTERNATE_WP_CRON' ) && \ALTERNATE_WP_CRON ) {
     618            if ( defined( 'ALTERNATE_WP_CRON' ) && constant( 'ALTERNATE_WP_CRON' ) ) {
    569619                return new \WP_Error(
    570620                    'advana_cron_info',
  • 0-day-analytics/trunk/classes/vendor/helpers/class-file-helper.php

    r3393178 r3398360  
    2828
    2929        /**
     30         * Try to initialize and return WP_Filesystem instance.
     31         *
     32         * @return null|\WP_Filesystem_Base Null when unavailable or failed to initialize.
     33         *
     34         * @since 4.1.2
     35         */
     36        private static function get_wp_filesystem() {
     37            global $wp_filesystem;
     38
     39            // Attempt to bootstrap the WP_Filesystem API.
     40            if ( ! function_exists( '\\WP_Filesystem' ) ) {
     41                require_once ABSPATH . 'wp-admin/includes/file.php';
     42            }
     43
     44            try {
     45                // Initialize if not already.
     46                if ( ! isset( $wp_filesystem ) || ! is_object( $wp_filesystem ) ) {
     47                    \WP_Filesystem();
     48                }
     49            } catch ( \Throwable $e ) {
     50                // Initialization failed, fall back to native PHP.
     51                return null;
     52            }
     53
     54            return ( isset( $wp_filesystem ) && is_object( $wp_filesystem ) ) ? $wp_filesystem : null;
     55        }
     56
     57        /**
     58         * Native PHP file writer with append support and locking.
     59         *
     60         * @param string $file_path Absolute path to file.
     61         * @param string $content   Content to write.
     62         * @param bool   $append    Append instead of overwrite.
     63         *
     64         * @return bool True on success.
     65         *
     66         * @since 4.1.2
     67         */
     68        private static function native_put_contents( string $file_path, string $content, bool $append = false ): bool {
     69            $flags = LOCK_EX;
     70            if ( $append ) {
     71                $flags |= FILE_APPEND;
     72            }
     73            $bytes = @file_put_contents( $file_path, $content, $flags ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
     74            return ( false !== $bytes );
     75        }
     76
     77        /**
    3078         * Normalize a filesystem path in a cross-platform way.
    3179         *
     
    117165         */
    118166        public static function write_to_file( string $filename, string $content, bool $append = false ): bool {
    119             global $wp_filesystem;
    120 
    121             require_once ABSPATH . 'wp-admin/includes/file.php';
    122             WP_Filesystem();
    123 
    124167            $logging_dir = dirname( $filename );
    125168
     
    132175            }
    133176
    134             $result = false;
    135 
     177            // Ensure destination directory exists, try WP first then native.
    136178            if ( ! is_dir( $logging_dir ) ) {
    137                 if ( false === \wp_mkdir_p( $logging_dir ) ) {
     179                if ( ! function_exists( '\\wp_mkdir_p' ) ) {
     180                    require_once ABSPATH . 'wp-admin/includes/file.php';
     181                }
     182                $made_dir = false;
     183                try {
     184                    $made_dir = \wp_mkdir_p( $logging_dir );
     185                } catch ( \Throwable $e ) {
     186                    $made_dir = false;
     187                }
     188                if ( ! $made_dir ) {
     189                    $made_dir = @mkdir( $logging_dir, 0755, true ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.mkdir_directoryPermissions
     190                }
     191                if ( ! $made_dir ) {
    138192                    self::$last_error = new \WP_Error(
    139193                        'mkdir_failed',
     
    144198                        )
    145199                    );
    146 
    147                     return $result;
     200                    return false;
    148201                }
    149202            }
     
    157210            }
    158211
    159             // Fix append logic: only append if requested and file exists.
    160             if ( $append && $wp_filesystem->exists( $file_path ) ) {
    161                 $existing_content = $wp_filesystem->get_contents( $file_path );
    162                 $result           = $wp_filesystem->put_contents( $file_path, $existing_content . $content );
    163             } else {
    164                 $result = $wp_filesystem->put_contents( $file_path, $content );
     212            // Try WP_Filesystem first.
     213            $result = false;
     214            $fs     = self::get_wp_filesystem();
     215            if ( $fs ) {
     216                try {
     217                    if ( $append && $fs->exists( $file_path ) ) {
     218                        $existing_content = $fs->get_contents( $file_path );
     219                        if ( false === $existing_content ) {
     220                            $existing_content = '';
     221                        }
     222                        $result = (bool) $fs->put_contents( $file_path, $existing_content . $content );
     223                    } else {
     224                        $result = (bool) $fs->put_contents( $file_path, $content );
     225                    }
     226                } catch ( \Throwable $e ) {
     227                    $result = false; // Will fall back below.
     228                }
     229            }
     230
     231            // Fall back to native PHP functions if WP_Filesystem path failed or unavailable.
     232            if ( false === $result ) {
     233                $result = self::native_put_contents( $file_path, $content, $append );
    165234            }
    166235
     
    174243                    )
    175244                );
    176             }
    177 
    178             // Best-effort permission hardening (may not work on all FS abstractions).
    179             if ( $result ) {
    180                 // Best effort tighten file perms; ignore if FS abstraction disallows.
    181                 @chmod( $file_path, 0640 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.file_system_operations_chmod
    182             }
    183 
    184             return (bool) $result;
     245                return false;
     246            }
     247
     248            // Best-effort permission hardening.
     249            @chmod( $file_path, 0640 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.file_system_operations_chmod
     250
     251            return true;
    185252        }
    186253
     
    211278                $size = filesize( $filename );
    212279
    213                 return self::show_size( $size );
    214             }
    215 
    216             return '0 B';
    217         }
    218 
    219         /**
    220          * Shows formatted (human readable) size
    221          *
    222          * @param int $size - The size (in bytes) to format.
    223          *
    224          * @return string
    225          *
    226          * @since 2.1.2
    227          */
    228         public static function show_size( $size ) {
    229 
    230             if ( 0 <= $size ) {
    231 
    232                 $units          = array( 'B', 'KB', 'MB', 'GB', 'TB' );
    233                 $formatted_size = $size;
    234                 $i              = 0; // Initialize to avoid undefined index when size < 1024.
    235 
    236                 $units_length = count( $units ) - 1;
    237 
    238                 for ( $i = 0; $size >= 1024 && $i < $units_length; $i++ ) {
    239                     $size          /= 1024;
    240                     $formatted_size = round( $size, 2 );
    241                 }
    242 
    243                 return $formatted_size . ' ' . $units[ $i ];
     280                return \size_format( $size );
    244281            }
    245282
     
    287324
    288325            // Resolve and validate path against allowed base directory.
    289             $allowed_base      = apply_filters( ADVAN_TEXTDOMAIN . 'download_base_dir', WP_CONTENT_DIR );
     326            $allowed_base      = \apply_filters( ADVAN_TEXTDOMAIN . 'download_base_dir', WP_CONTENT_DIR );
    290327            $real_allowed_base = realpath( $allowed_base );
    291328            $real_requested    = realpath( $file_path );
     
    713750            \WP_Filesystem();
    714751            if ( isset( $wp_filesystem ) && is_object( $wp_filesystem ) ) {
    715                 $move = $wp_filesystem->move( $temp_path, $file_path, true );
     752                $move = false;
     753                try {
     754                    $move = $wp_filesystem->move( $temp_path, $file_path, true );
     755                } catch ( \Throwable $e ) {
     756                    $move = false;
     757                }
     758                if ( ! $move ) {
     759                    // Try native rename as a fallback when WP_Filesystem move fails.
     760                    $move = @rename( $temp_path, $file_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.rename_rename
     761                }
    716762                if ( ! $move ) {
    717763                    // Cleanup temp file on failure.
    718                     $wp_filesystem->delete( $temp_path );
     764                    if ( isset( $wp_filesystem ) && is_object( $wp_filesystem ) ) {
     765                        @$wp_filesystem->delete( $temp_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
     766                    } else {
     767                        @unlink( $temp_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.unlink_unlink
     768                    }
    719769                }
    720770                return (bool) $move;
     
    722772
    723773            // Fallback if filesystem is unavailable.
    724             return rename( $temp_path, $file_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.rename_rename
     774            return @rename( $temp_path, $file_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.rename_rename
    725775        }
    726776    }
  • 0-day-analytics/trunk/classes/vendor/helpers/class-settings.php

    r3393178 r3398360  
    824824                                $_FILES[ self::SETTINGS_FILE_FIELD ] = array();
    825825                            } else {
    826                                 global $wp_filesystem;
    827                                 if ( null === $wp_filesystem ) {
    828                                     \WP_Filesystem(); }
    829                                 $path = \sanitize_text_field( \wp_unslash( $_FILES[ self::SETTINGS_FILE_FIELD ]['tmp_name'] ) );
    830                                 if ( $wp_filesystem->exists( $path ) ) {
    831                                     $options = json_decode( $wp_filesystem->get_contents( $path ), true );
     826                                $path    = \sanitize_text_field( \wp_unslash( $_FILES[ self::SETTINGS_FILE_FIELD ]['tmp_name'] ) );
     827                                $content = '';
     828
     829                                // Try WP_Filesystem first; fall back to native PHP on failure.
     830                                try {
     831                                    global $wp_filesystem;
     832                                    if ( ! isset( $wp_filesystem ) || ! is_object( $wp_filesystem ) ) {
     833                                        \WP_Filesystem();
     834                                    }
     835                                    if ( isset( $wp_filesystem ) && is_object( $wp_filesystem ) && $wp_filesystem->exists( $path ) ) {
     836                                        $tmp = $wp_filesystem->get_contents( $path );
     837                                        if ( false !== $tmp && '' !== $tmp ) {
     838                                            $content = (string) $tmp;
     839                                        }
     840                                    }
     841                                } catch ( \Throwable $e ) {
     842                                    // Ignore and fall back below.
     843                                }
     844
     845                                if ( '' === $content && \is_readable( $path ) ) {
     846                                    $content = @file_get_contents( $path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
     847                                }
     848
     849                                $options = array();
     850                                if ( is_string( $content ) && '' !== $content ) {
     851                                    $options = json_decode( $content, true );
    832852                                }
    833853                                if ( ! is_array( $options ) ) {
    834                                     $options = array(); }
     854                                    $options = array();
     855                                }
    835856                                if ( ! empty( $options ) ) {
    836857                                    \remove_filter( 'sanitize_option_' . ADVAN_SETTINGS_NAME, array( self::class, 'collect_and_sanitize_options' ) );
  • 0-day-analytics/trunk/classes/vendor/helpers/class-system-analytics.php

    r3391413 r3398360  
    327327
    328328        /**
     329         * Check if a path is allowed by open_basedir restrictions.
     330         *
     331         * @param string $path The path to check.
     332         *
     333         * @return bool
     334         *
     335         * @since 4.2.0
     336         */
     337        public static function is_allowed_by_open_base_dir( string $path ): bool {
     338            $ob = ini_get( 'open_basedir' );
     339            if ( ! $ob ) {
     340                return true; // no restrictions.
     341            }
     342
     343            $allowed = explode( PATH_SEPARATOR, $ob );
     344
     345            $real = @realpath( $path );
     346            if ( false === $real ) {
     347                return false;
     348            }
     349
     350            foreach ( $allowed as $dir ) {
     351                $dir = rtrim( $dir, '/' );
     352                if ( strpos( $real, $dir ) === 0 ) {
     353                    return true;
     354                }
     355            }
     356            return false;
     357        }
     358
     359        /**
    329360         * Returns the disk usage
    330361         *
     
    335366        public static function get_disk_usage() {
    336367            $total = @disk_total_space( '/' );
    337             $free  = @disk_free_space( '/' );
     368            if ( self::is_allowed_by_open_base_dir( '/' ) ) {
     369                $free = @disk_free_space( '/' );
     370            }
    338371            if ( $total && $free ) {
    339372                $used = 100 - ( ( $free / $total ) * 100 );
  • 0-day-analytics/trunk/classes/vendor/helpers/class-wp-error-handler.php

    r3393178 r3398360  
    259259         */
    260260        public static function trigger_error( $status, string $function_name, $errstr, $version, $errno = E_USER_NOTICE ) {
    261 
    262261            if ( false === $status ) {
    263262                return $status;
  • 0-day-analytics/trunk/classes/vendor/lists/class-crons-list.php

    r3393178 r3398360  
    1919use ADVAN\Helpers\WP_Helper;
    2020use ADVAN\Helpers\Crons_Helper;
     21use ADVAN\Helpers\Plugin_Theme_Helper;
    2122use ADVAN\Lists\Views\Crons_View;
    2223use ADVAN\Lists\Traits\List_Trait;
     
    5758        public const CRON_MENU_SLUG = 'advan_cron_jobs';
    5859
     60        public const PLUGIN_FILTER_ACTION = self::PAGE_SLUG . '_filter_plugin';
     61
     62        public const SITE_FILTER_ACTION = self::PAGE_SLUG . '_filter_site';
     63
    5964        /**
    6065         * Format for the file link.
     
    166171            \add_action( 'admin_post_' . self::UPDATE_ACTION, array( Crons_View::class, 'update_cron' ) );
    167172            \add_action( 'admin_post_' . self::NEW_ACTION, array( Crons_View::class, 'new_cron' ) );
     173            \add_action( 'admin_post_' . self::PLUGIN_FILTER_ACTION, array( Crons_View::class, 'plugin_filter_action' ) );
     174            \add_action( 'admin_post_' . self::SITE_FILTER_ACTION, array( Crons_View::class, 'site_filter_action' ) );
    168175        }
    169176
     
    193200
    194201            \add_action( 'load-' . $cron_hook, array( Settings::class, 'aadvana_common_help' ) );
     202            // Process actions early to avoid header warnings on redirects.
     203            \add_action( 'load-' . $cron_hook, array( self::class, 'process_actions_load' ) );
    195204
    196205            /* Crons end */
     206        }
     207
     208        /**
     209         * Handle cron table actions on the early page load hook.
     210         *
     211         * @return void
     212         *
     213         * @since 4.2.0
     214         */
     215        public static function process_actions_load() {
     216            if ( ! \current_user_can( 'manage_options' ) ) {
     217                return;
     218            }
     219            $table = new self( array() );
     220            $table->handle_table_actions();
    197221        }
    198222
     
    247271            );
    248272
     273            // Insert site column for multisite network admins for clarity.
     274            if ( function_exists( 'is_multisite' ) && is_multisite() && current_user_can( 'manage_network' ) ) {
     275                $admin_fields = array(
     276                    'cb'         => $admin_fields['cb'],
     277                    'hook'       => $admin_fields['hook'],
     278                    'site'       => __( 'Site', '0-day-analytics' ),
     279                    'schedule'   => $admin_fields['schedule'],
     280                    'recurrence' => $admin_fields['recurrence'],
     281                    'args'       => $admin_fields['args'],
     282                    'actions'    => $admin_fields['actions'],
     283                );
     284            }
     285
    249286            $screen_options = $admin_fields;
    250287
     
    273310            $sortable              = $this->get_sortable_columns();
    274311            $this->_column_headers = array( $columns, $hidden, $sortable );
    275 
    276             $this->handle_table_actions();
    277312
    278313            $this->fetch_table_data();
     
    394429            }
    395430
     431            // Plugin filtering (only when not requesting unfiltered set for counts).
     432            $site_filter = ( isset( $_REQUEST['site'] ) && '' !== trim( (string) $_REQUEST['site'] ) && -1 !== (int) $_REQUEST['site'] ) ? (int) sanitize_text_field( wp_unslash( $_REQUEST['site'] ) ) : -1;
     433            if ( -1 !== $site_filter && function_exists( 'is_multisite' ) && is_multisite() ) {
     434                self::$read_items = Crons_Helper::get_events_for_site( $site_filter );
     435            }
     436
     437            if ( isset( $_REQUEST['plugin'] ) && '' !== trim( (string) $_REQUEST['plugin'] ) && -1 !== (int) $_REQUEST['plugin'] ) {
     438                $plugin_filter    = sanitize_text_field( wp_unslash( $_REQUEST['plugin'] ) );
     439                self::$read_items = array_filter(
     440                    self::$read_items,
     441                    function ( $event ) use ( $plugin_filter ) {
     442                        if ( isset( $event['plugin_slugs'] ) && is_array( $event['plugin_slugs'] ) ) {
     443                            return in_array( $plugin_filter, $event['plugin_slugs'], true );
     444                        }
     445                        return ( isset( $event['plugin_slug'] ) && '' !== $event['plugin_slug'] && $event['plugin_slug'] === $plugin_filter );
     446                    }
     447                );
     448            }
     449
    396450            if ( ! $no_type_filtering ) {
    397451                if ( ! empty( $_REQUEST['event_type'] ) && is_string( $_REQUEST['event_type'] ) ) {
     
    442496        public static function format_column_value( $item, $column_name ) {
    443497            switch ( $column_name ) {
     498                case 'site':
     499                    if ( isset( $item['site_id'] ) ) {
     500                        $site_id   = (int) $item['site_id'];
     501                        $site_name = isset( $item['site_name'] ) ? $item['site_name'] : '';
     502                        return '<code>' . esc_html( $site_id ) . '</code> ' . ( ! empty( $site_name ) ? esc_html( $site_name ) : '' );
     503                    }
     504                    return esc_html__( 'N/A', '0-day-analytics' );
    444505                case 'hook':
    445506                    $query_args_view_data             = array();
     
    902963                ?>
    903964                <div class="alignleft actions">
     965                    <?php
     966                    // Plugin dropdown.
     967                    $selected_plugin    = ( isset( $_REQUEST['plugin'] ) && '' !== trim( (string) $_REQUEST['plugin'] ) ) ? ( ( -1 === (int) $_REQUEST['plugin'] ) ? -1 : sanitize_text_field( wp_unslash( $_REQUEST['plugin'] ) ) ) : -1;
     968                    $selected_site      = ( isset( $_REQUEST['site'] ) && '' !== trim( (string) $_REQUEST['site'] ) ) ? ( ( -1 === (int) $_REQUEST['site'] ) ? -1 : (int) sanitize_text_field( wp_unslash( $_REQUEST['site'] ) ) ) : -1;
     969                    $plugins_dropdown   = self::get_plugins_dropdown( $selected_plugin, $which );
     970                        $sites_dropdown = self::get_sites_dropdown( $selected_site, $which );
     971                    if ( ! empty( $plugins_dropdown ) ) {
     972                        echo $plugins_dropdown; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
     973                        if ( ! empty( $sites_dropdown ) ) {
     974                            echo $sites_dropdown; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
     975                            ?>
     976                            <script>
     977                            jQuery(document).ready(function(){
     978                                jQuery('form .site_filter').on('change', function(){
     979                                    jQuery('form .site_filter').val(jQuery(this).val());
     980                                    jQuery(this).closest('form')
     981                                        .attr('action','<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>')
     982                                        .append('<input type="hidden" name="action" value="<?php echo esc_attr( self::SITE_FILTER_ACTION ); ?>">')
     983                                        .append('<?php wp_nonce_field( self::SITE_FILTER_ACTION, self::SITE_FILTER_ACTION . 'nonce' ); ?>')
     984                                        .submit();
     985                                });
     986                            });
     987                            </script>
     988                            <?php
     989                        }
     990                        ?>
     991
     992                        <script>
     993                        jQuery(document).ready(function(){
     994                            jQuery('form .plugin_filter').on('change', function(){
     995                                jQuery('form .plugin_filter').val(jQuery(this).val());
     996                                jQuery(this).closest('form')
     997                                    .attr('action','<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>')
     998                                    .append('<input type="hidden" name="action" value="<?php echo esc_attr( self::PLUGIN_FILTER_ACTION ); ?>">')
     999                                    .append('<?php wp_nonce_field( self::PLUGIN_FILTER_ACTION, self::PLUGIN_FILTER_ACTION . 'nonce' ); ?>')
     1000                                    .submit();
     1001                            });
     1002                        });
     1003                        </script>
     1004                        <?php
     1005                    }
     1006                    ?>
    9041007                    <select class="schedules-filter" name="schedules_filter">
    9051008                    <?php
     
    9271030                <div id="export-form">
    9281031                    <div>
    929                         <button id="start-export" class="button" data-type-export="cron">
     1032                        <button id="start-export" class="button" data-type-export="cron" data-plugin_filter="<?php echo esc_attr( $selected_plugin ); ?>" data-site_filter="<?php echo esc_attr( $selected_site ); ?>">
    9301033                            <?php echo esc_html__( 'CSV Export', '0-day-analytics' ); ?>
    9311034                        </button>
     
    13691472            return $filtered;
    13701473        }
     1474
     1475        /**
     1476         * Builds sites dropdown (multisite only) listing all sites (optionally only those having cron events).
     1477         *
     1478         * @param int    $selected Selected site id or -1.
     1479         * @param string $which  Position top|bottom.
     1480         * @return string
     1481         */
     1482        public static function get_sites_dropdown( $selected = -1, string $which = 'top' ): string {
     1483            if ( ! function_exists( 'is_multisite' ) || ! is_multisite() ) {
     1484                return '';
     1485            }
     1486            if ( ! current_user_can( 'manage_network' ) ) {
     1487                return '';
     1488            }
     1489            $which = in_array( $which, array( 'top', 'bottom' ), true ) ? $which : 'top';
     1490            $sites = get_sites( array( 'number' => 0 ) );
     1491            if ( empty( $sites ) ) {
     1492                return '';
     1493            }
     1494            $output  = '<select class="site_filter" name="site_' . esc_attr( $which ) . '" id="site_' . esc_attr( $which ) . '">';
     1495            $output .= '<option value="-1">' . __( 'All sites', '0-day-analytics' ) . '</option>';
     1496            foreach ( $sites as $site ) {
     1497                $blog_id = (int) $site->blog_id;
     1498                $details = get_blog_details( $blog_id );
     1499                $name    = ( $details && isset( $details->blogname ) ) ? $details->blogname : 'Site ' . $blog_id;
     1500                $sel     = ( (int) $selected === $blog_id ) ? ' selected' : '';
     1501                $output .= '<option value="' . esc_attr( $blog_id ) . '"' . $sel . '>' . esc_html( $name ) . '</option>';
     1502            }
     1503            $output .= '</select>';
     1504            return $output;
     1505        }
     1506
     1507        /**
     1508         * Builds plugins dropdown based on detected plugin slugs from cron callbacks.
     1509         *
     1510         * @param string|int $selected Currently selected plugin slug or -1 for all.
     1511         * @param string     $which    Position (top|bottom) for unique element naming.
     1512         *
     1513         * @return string HTML select or empty string if no plugins detected.
     1514         */
     1515        public static function get_plugins_dropdown( $selected = -1, $which = 'top' ): string {
     1516            $which = in_array( $which, array( 'top', 'bottom' ), true ) ? $which : 'top';
     1517
     1518            $all_events = self::get_cron_items( true );
     1519            $plugins    = array();
     1520            foreach ( $all_events as $event ) {
     1521                if ( isset( $event['plugin_slugs'] ) && is_array( $event['plugin_slugs'] ) ) {
     1522                    foreach ( $event['plugin_slugs'] as $slug ) {
     1523                        if ( ! empty( $slug ) ) {
     1524                            $plugins[ $slug ] = $slug;
     1525                        }
     1526                    }
     1527                } elseif ( isset( $event['plugin_slug'] ) && '' !== $event['plugin_slug'] ) {
     1528                    $plugins[ $event['plugin_slug'] ] = $event['plugin_slug'];
     1529                }
     1530            }
     1531
     1532            if ( empty( $plugins ) ) {
     1533                return '';
     1534            }
     1535
     1536            $output  = '<select class="plugin_filter" name="plugin_' . esc_attr( $which ) . '" id="plugin_' . esc_attr( $which ) . '">';
     1537            $output .= '<option value="-1">' . __( 'All plugins', '0-day-analytics' ) . '</option>';
     1538            foreach ( $plugins as $slug ) {
     1539                $details  = Plugin_Theme_Helper::get_plugin_from_path( $slug );
     1540                $name     = ( isset( $details['Name'] ) && ! empty( $details['Name'] ) ) ? $details['Name'] : $slug;
     1541                $sel_attr = ( (string) $selected === (string) $slug ) ? ' selected' : '';
     1542                $output  .= '<option value="' . esc_attr( $slug ) . '"' . $sel_attr . '>' . esc_html( $name ) . '</option>';
     1543            }
     1544            $output .= '</select>';
     1545
     1546            return $output;
     1547        }
    13711548    }
    13721549}
  • 0-day-analytics/trunk/classes/vendor/lists/class-fatals-list.php

    r3393178 r3398360  
    1717use ADVAN\Helpers\Settings;
    1818use ADVAN\Helpers\WP_Helper;
    19 use ADVAN\Helpers\File_Helper;
    2019use ADVAN\Helpers\Miscellaneous;
    2120use ADVAN\Lists\Traits\List_Trait;
     
    184183
    185184            \add_action( 'load-' . $fatals_hook, array( Settings::class, 'aadvana_common_help' ) );
     185            // Process actions early to allow safe redirects before any output.
     186            \add_action( 'load-' . $fatals_hook, array( self::class, 'process_actions_load' ) );
     187        }
     188
     189        /**
     190         * Handle bulk and row actions during the early page load hook.
     191         *
     192         * @return void
     193         *
     194         * @since 4.2.0
     195         */
     196        public static function process_actions_load() {
     197            if ( ! \current_user_can( 'manage_options' ) ) {
     198                return;
     199            }
     200            $table = new self( '' );
     201            $table->handle_table_actions();
    186202        }
    187203
     
    205221         */
    206222        public function prepare_items() {
    207             $this->handle_table_actions();
     223            // Actions are processed during load-<hook> to avoid header warnings.
    208224
    209225            $per_page = self::get_screen_option_per_page();
     
    855871                        <?php } ?>
    856872                <div class="flex flex-row grow-0 p-2 w-full border-0 border-t border-solid justify-between">
    857                     <div class=""> <?php \esc_html_e( 'Size: ', '0-day-analytics' ); ?> <?php echo \esc_attr( File_Helper::show_size( Common_Table::get_table_size() ) ); ?>
     873                    <div class=""> <?php \esc_html_e( 'Size: ', '0-day-analytics' ); ?> <?php echo \esc_attr( \size_format( Common_Table::get_table_size() ) ); ?>
    858874
    859875                        <?php
  • 0-day-analytics/trunk/classes/vendor/lists/class-requests-list.php

    r3393178 r3398360  
    1717use ADVAN\Helpers\Settings;
    1818use ADVAN\Helpers\WP_Helper;
    19 use ADVAN\Helpers\File_Helper;
    2019use ADVAN\Helpers\Miscellaneous;
    2120use ADVAN\Lists\Traits\List_Trait;
     
    203202
    204203            \add_action( 'load-' . $requests_hook, array( Settings::class, 'aadvana_common_help' ) );
     204            // Process any actions early to allow safe redirects before output.
     205            \add_action( 'load-' . $requests_hook, array( self::class, 'process_actions_load' ) );
     206        }
     207
     208        /**
     209         * Handle actions on the early page load hook to avoid header issues.
     210         *
     211         * @return void
     212         *
     213         * @since 4.2.0
     214         */
     215        public static function process_actions_load() {
     216            if ( ! \current_user_can( 'manage_options' ) ) {
     217                return;
     218            }
     219            $table = new self( '' );
     220            $table->handle_table_actions();
    205221        }
    206222
     
    224240         */
    225241        public function prepare_items() {
    226             $this->handle_table_actions();
     242            // Actions are processed during load-<hook> to avoid late redirects.
    227243            $per_page = self::get_screen_option_per_page();
    228244
     
    957973                        <?php } ?>
    958974                <div class="flex flex-row grow-0 p-2 w-full border-0 border-t border-solid justify-between">
    959                     <div class=""> <?php \esc_html_e( 'Size: ', '0-day-analytics' ); ?> <?php echo \esc_attr( File_Helper::show_size( Common_Table::get_table_size() ) ); ?>
     975                    <div class=""> <?php \esc_html_e( 'Size: ', '0-day-analytics' ); ?> <?php echo \esc_attr( \size_format( Common_Table::get_table_size() ) ); ?>
    960976
    961977                        <?php
  • 0-day-analytics/trunk/classes/vendor/lists/class-table-list.php

    r3393178 r3398360  
    1717use ADVAN\Helpers\Settings;
    1818use ADVAN\Helpers\WP_Helper;
    19 use ADVAN\Helpers\File_Helper;
    2019use ADVAN\Helpers\Miscellaneous;
    2120use ADVAN\Lists\Views\Table_View;
     
    164163            // \add_filter( 'manage_' . $table_hook . '_columns', array( Table_List::class, 'manage_columns' ) );
    165164
     165            // Process bulk/table actions early on page load before any output.
     166            \add_action( 'load-' . $table_hook, array( __CLASS__, 'process_actions_load' ), 5 );
     167
    166168            \add_action( 'load-' . $table_hook, array( Settings::class, 'aadvana_common_help' ) );
     169        }
     170
     171        /**
     172         * Runs table actions during the load-<hook> phase to avoid premature output.
     173         *
     174         * @return void
     175         *
     176         * @since 4.2.0
     177         */
     178        public static function process_actions_load() {
     179            if ( ! \is_user_logged_in() || ! \current_user_can( 'manage_options' ) ) {
     180                return;
     181            }
     182
     183            $table_name      = Common_Table::get_default_table();
     184            $requested_table = isset( $_REQUEST['show_table'] ) ? \sanitize_key( \wp_unslash( $_REQUEST['show_table'] ) ) : '';
     185            if ( $requested_table && \in_array( $requested_table, Common_Table::get_tables(), true ) ) {
     186                $table_name = $requested_table;
     187            }
     188
     189            // Instantiate and process actions. Redirects will exit before any output.
     190            $table = new self( $table_name );
     191            $table->handle_table_actions();
    167192        }
    168193
     
    186211         */
    187212        public function prepare_items() {
    188             $this->handle_table_actions();
    189213
    190214            $per_page = self::get_screen_option_per_page();
     
    932956                        <?php } ?>
    933957                <div class="flex flex-row grow-0 p-2 w-full border-0 border-t border-solid justify-between">
    934                     <div class=""> <?php \esc_html_e( 'Size: ', '0-day-analytics' ); ?> <?php echo \esc_attr( File_Helper::show_size( Common_Table::get_table_size() ) ); ?>
     958                    <div class=""> <?php \esc_html_e( 'Size: ', '0-day-analytics' ); ?> <?php echo \esc_attr( \size_format( Common_Table::get_table_size() ) ); ?>
    935959
    936960                    <?php
     
    971995                    </div>
    972996                    <div>
    973                         <b><?php \esc_html_e( 'Schema: ', '0-day-analytics' ); ?></b> <span class="italic"><?php echo \esc_attr( $wpdb->dbname ); ?></span> | <b><?php \esc_html_e( 'Tables: ', '0-day-analytics' ); ?></b><span class="italic"><?php echo \esc_attr( count( Common_Table::get_tables() ) ); ?></span>
     997                        <b><?php \esc_html_e( 'Schema: ', '0-day-analytics' ); ?></b> <span class="italic"><?php echo \esc_attr( defined( 'DB_NAME' ) ? DB_NAME : '' ); ?></span> | <b><?php \esc_html_e( 'Tables: ', '0-day-analytics' ); ?></b><span class="italic"><?php echo \esc_attr( count( Common_Table::get_tables() ) ); ?></span>
    974998                    </div>
    975999                </div>
  • 0-day-analytics/trunk/classes/vendor/lists/class-transients-list.php

    r3392179 r3398360  
    187187            \add_filter( 'manage_' . $transients_hook . '_columns', array( self::class, 'manage_columns' ) );
    188188
    189             \add_action( 'load-' . $transients_hook, array( Settings::class, 'aadvana_common_help' ) );
     189                    \add_action( 'load-' . $transients_hook, array( Settings::class, 'aadvana_common_help' ) );
     190                    // Process bulk/table actions early before any output.
     191                    \add_action( 'load-' . $transients_hook, array( self::class, 'process_actions_load' ) );
     192        }
     193
     194        /**
     195         * Handle any table actions during the early page load hook to allow safe redirects.
     196         *
     197         * @return void
     198         *
     199         * @since 4.2.0
     200         */
     201        public static function process_actions_load() {
     202            if ( ! \current_user_can( 'manage_options' ) ) {
     203                return;
     204            }
     205            $table = new self( array() );
     206            $table->handle_table_actions();
    190207        }
    191208
     
    303320                )
    304321            );
    305 
    306322            $hidden = \get_user_option( 'manage' . WP_Helper::get_wp_screen()->id . 'columnshidden', false );
    307323            if ( ! $hidden ) {
     
    611627
    612628                    \wp_safe_redirect( $redirect );
     629                    exit;
    613630                }
    614631            }
  • 0-day-analytics/trunk/classes/vendor/lists/class-wp-mail-list.php

    r3393178 r3398360  
    1717use ADVAN\Helpers\Settings;
    1818use ADVAN\Helpers\WP_Helper;
    19 use ADVAN\Helpers\File_Helper;
    2019use ADVAN\Entities_Global\Common_Table;
    2120use ADVAN\Controllers\WP_Mail_Log;
     
    6160        public const SITE_ID_FILTER_ACTION = 'filter_site_id';
    6261
     62        public const PLUGIN_FILTER_ACTION = self::PAGE_SLUG . '_filter_plugin';
     63
    6364        /**
    6465         * The table to show
     
    160161            \add_action( 'admin_post_' . self::NEW_ACTION, array( WP_Mail_View::class, 'new_mail' ) );
    161162            \add_action( 'admin_post_' . self::SITE_ID_FILTER_ACTION, array( WP_Mail_View::class, 'site_id_filter_action' ) );
     163            \add_action( 'admin_post_' . self::PLUGIN_FILTER_ACTION, array( WP_Mail_View::class, 'plugin_filter_action' ) );
    162164            \add_filter( 'advan_cron_hooks', array( __CLASS__, 'add_cron_job' ) );
    163165        }
     
    218220
    219221            \add_action( 'load-' . $wp_mail_hook, array( Settings::class, 'aadvana_common_help' ) );
     222            // Process actions early to ensure redirects happen before output.
     223            \add_action( 'load-' . $wp_mail_hook, array( self::class, 'process_actions_load' ) );
     224        }
     225
     226        /**
     227         * Handle actions during the early page load hook to avoid header warnings.
     228         *
     229         * @return void
     230         *
     231         * @since 4.2.0
     232         */
     233        public static function process_actions_load() {
     234            if ( ! \current_user_can( 'manage_options' ) ) {
     235                return;
     236            }
     237            $table = new self( '' );
     238            $table->handle_table_actions();
    220239        }
    221240
     
    239258         */
    240259        public function prepare_items() {
    241             $this->handle_table_actions();
     260            // Actions are processed on load-<hook> to prevent late redirects.
    242261
    243262            // Vars.
     
    252271            $type = ! empty( $_GET['mail_type'] ) ? \sanitize_text_field( \wp_unslash( $_GET['mail_type'] ) ) : '';
    253272
     273            if ( isset( $_REQUEST['plugin'] ) && ! empty( $_REQUEST['plugin'] ) ) {
     274                if ( -1 === (int) $_REQUEST['plugin'] ) {
     275                    $plugin = -1;
     276                } else {
     277                    $plugin = \sanitize_text_field( \wp_unslash( $_REQUEST['plugin'] ) );
     278                }
     279            } else {
     280                $plugin = '';
     281            }
     282
    254283            if ( isset( $_REQUEST['site_id'] ) && ! empty( $_REQUEST['site_id'] ) ) {
    255284                if ( -1 === (int) $_REQUEST['site_id'] ) {
     
    275304                    'type'     => $type,
    276305                    'site_id'  => $site_id,
     306                    'plugin'   => $plugin,
    277307                )
    278308            );
     
    372402                    'count'    => false,
    373403                    'site_id'  => '',
     404                    'plugin'   => '',
    374405                )
    375406            );
     
    394425            $site_id       = \sanitize_text_field( \wp_unslash( (string) $parsed_args['site_id'] ) );
    395426            $type          = \sanitize_text_field( \wp_unslash( $parsed_args['type'] ?? '' ) );
     427            $plugin        = \sanitize_text_field( \wp_unslash( (string) ( $parsed_args['plugin'] ?? '' ) ) );
    396428
    397429            if ( '' !== $search_string ) {
     
    433465                    $where_parts[] = 'AND attachments != "[]"';
    434466                }
     467            }
     468
     469            if ( '' !== $plugin && -1 !== (int) $plugin ) {
     470                $where_parts[] = 'AND plugin_slug = %s';
     471                $where_args[]  = $plugin;
    435472            }
    436473
     
    10351072        public function extra_tablenav( $which ) {
    10361073
     1074            // Plugin filter dropdown (mirrors Requests list behavior).
     1075            if ( isset( $_REQUEST['plugin'] ) && ! empty( $_REQUEST['plugin'] ) ) {
     1076                if ( -1 === (int) $_REQUEST['plugin'] ) {
     1077                    $plugin = -1;
     1078                } else {
     1079                    $plugin = \sanitize_text_field( \wp_unslash( $_REQUEST['plugin'] ) );
     1080                }
     1081            } else {
     1082                $plugin = 0;
     1083            }
     1084            ?>
     1085                <div class="alignleft actions bulkactions">
     1086                    <?php echo WP_Mail_Entity::get_all_plugins_dropdown( $plugin, $which ); ?>
     1087                </div>
     1088                <script>
     1089                    jQuery('form .plugin_filter').on('change', function(e) {
     1090                        jQuery('form .plugin_filter').val(jQuery(this).val());
     1091                        jQuery( this ).closest( 'form' )
     1092                            .attr( 'action', '<?php echo \esc_url( \admin_url( 'admin-post.php' ) ); ?>')
     1093                            .append('<input type="hidden" name="action" value="<?php echo \esc_attr( self::PLUGIN_FILTER_ACTION ); ?>">')
     1094                            .append('<input type="hidden" name="context" value="<?php echo \esc_attr( ( \is_network_admin() ) ? 'network' : 'site' ); ?>">')
     1095                            .append('<?php \wp_nonce_field( self::PLUGIN_FILTER_ACTION, self::PLUGIN_FILTER_ACTION . 'nonce' ); ?>')
     1096                            .submit();
     1097                    });
     1098                </script>
     1099            <?php
     1100
    10371101            if ( WP_Helper::is_multisite() ) {
    10381102                if ( isset( $_REQUEST['site_id'] ) && ! empty( $_REQUEST['site_id'] ) ) {
     
    10571121                ?>
    10581122                <div class="alignleft actions bulkactions">
    1059                    
    10601123                    <?php echo WP_Mail_Entity::get_all_sites_dropdown( $site_id, $which ); ?>
    1061                    
    10621124                </div>
    10631125                <script>
     
    10731135                <div id="export-form">
    10741136                    <div>
    1075                         <button id="start-export" class="button" data-type-export="mail" data-search="<?php echo self::escaped_search_input(); ?>">
     1137                        <button id="start-export" class="button" data-type-export="mail" data-search="<?php echo self::escaped_search_input(); ?>" data-plugin="<?php echo \esc_attr( isset( $plugin ) ? (string) $plugin : '' ); ?>">
    10761138                            <?php echo \esc_html__( 'CSV Export', '0-day-analytics' ); ?>
    10771139                        </button>
     
    11121174            <?php } ?>
    11131175                <div class="flex flex-row grow-0 p-2 w-full border-0 border-t border-solid justify-between">
    1114                     <div class=""> <?php \esc_html_e( 'Size: ', '0-day-analytics' ); ?> <?php echo \esc_attr( File_Helper::show_size( Common_Table::get_table_size() ) ); ?>
     1176                    <div class=""> <?php \esc_html_e( 'Size: ', '0-day-analytics' ); ?> <?php echo \esc_attr( \size_format( Common_Table::get_table_size() ) ); ?>
    11151177
    11161178                        <?php
  • 0-day-analytics/trunk/classes/vendor/lists/views/class-crons-view.php

    r3391413 r3398360  
    128128                                '<input type="hidden" name="event_type" value="%s"/>',
    129129                                \esc_attr( isset( $_REQUEST['event_type'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['event_type'] ) ) : '' )
     130                            );
     131                            printf(
     132                                '<input type="hidden" name="plugin" value="%s"/>',
     133                                \esc_attr( isset( $_REQUEST['plugin'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['plugin'] ) ) : '' )
     134                            );
     135                            printf(
     136                                '<input type="hidden" name="site" value="%s"/>',
     137                                \esc_attr( isset( $_REQUEST['site'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['site'] ) ) : '' )
    130138                            );
    131139                            ?>
     
    200208                                \esc_attr( isset( $_REQUEST['event_type'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['event_type'] ) ) : '' )
    201209                            );
     210                            printf(
     211                                '<input type="hidden" name="plugin" value="%s"/>',
     212                                \esc_attr( isset( $_REQUEST['plugin'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['plugin'] ) ) : '' )
     213                            );
    202214                        ?>
    203215                        <?php \wp_nonce_field( Crons_List::NONCE_NAME ); ?>
     
    302314                        \esc_attr( isset( $_REQUEST['event_type'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['event_type'] ) ) : '' )
    303315                    );
     316                    printf(
     317                        '<input type="hidden" name="plugin" value="%s"/>',
     318                        \esc_attr( isset( $_REQUEST['plugin'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['plugin'] ) ) : '' )
     319                    );
     320                    printf(
     321                        '<input type="hidden" name="site" value="%s"/>',
     322                        \esc_attr( isset( $_REQUEST['site'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['site'] ) ) : '' )
     323                    );
     324                    printf(
     325                        '<input type="hidden" name="site" value="%s"/>',
     326                        \esc_attr( isset( $_REQUEST['site'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['site'] ) ) : '' )
     327                    );
    304328
    305329                ?>
     
    399423            exit;
    400424        }
     425
     426        /**
     427         * Handles plugin dropdown filter submission for cron list.
     428         *
     429         * @return void
     430         */
     431        public static function plugin_filter_action() {
     432            if ( isset( $_REQUEST['plugin_top'] ) || isset( $_REQUEST['plugin_bottom'] ) ) {
     433                if ( \check_admin_referer( Crons_List::PLUGIN_FILTER_ACTION, Crons_List::PLUGIN_FILTER_ACTION . 'nonce' ) ) {
     434                    $plugin = isset( $_REQUEST['plugin_top'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['plugin_top'] ) ) : \sanitize_text_field( \wp_unslash( $_REQUEST['plugin_bottom'] ) );
     435
     436                    \wp_safe_redirect(
     437                        \remove_query_arg(
     438                            array( 'deleted' ),
     439                            \add_query_arg(
     440                                array(
     441                                    'page'       => Crons_List::CRON_MENU_SLUG,
     442                                    Crons_List::SEARCH_INPUT => Crons_List::escaped_search_input(),
     443                                    'plugin'     => rawurlencode( $plugin ),
     444                                    'site'       => ( isset( $_REQUEST['site'] ) ? rawurlencode( sanitize_text_field( wp_unslash( $_REQUEST['site'] ) ) ) : '' ),
     445                                    'event_type' => ( isset( $_REQUEST['event_type'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['event_type'] ) ) : '' ),
     446                                    'schedules_filter' => ( isset( $_REQUEST['schedules_filter'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['schedules_filter'] ) ) : '' ),
     447                                ),
     448                                \admin_url( 'admin.php' )
     449                            )
     450                        )
     451                    );
     452                    exit;
     453                }
     454            }
     455        }
     456
     457        /**
     458         * Handles site filter submission for cron list.
     459         *
     460         * @return void
     461         */
     462        public static function site_filter_action() {
     463            // Only network admins can filter by site.
     464            if ( ! ( function_exists( 'is_multisite' ) && is_multisite() && current_user_can( 'manage_network' ) ) ) {
     465                return;
     466            }
     467            if ( isset( $_REQUEST['site_top'] ) || isset( $_REQUEST['site_bottom'] ) ) {
     468                if ( \check_admin_referer( Crons_List::SITE_FILTER_ACTION, Crons_List::SITE_FILTER_ACTION . 'nonce' ) ) {
     469                    $site = isset( $_REQUEST['site_top'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['site_top'] ) ) : \sanitize_text_field( \wp_unslash( $_REQUEST['site_bottom'] ) );
     470                    \wp_safe_redirect(
     471                        \remove_query_arg(
     472                            array( 'deleted' ),
     473                            \add_query_arg(
     474                                array(
     475                                    'page'       => Crons_List::CRON_MENU_SLUG,
     476                                    Crons_List::SEARCH_INPUT => Crons_List::escaped_search_input(),
     477                                    'site'       => rawurlencode( $site ),
     478                                    'plugin'     => ( isset( $_REQUEST['plugin'] ) ? rawurlencode( sanitize_text_field( wp_unslash( $_REQUEST['plugin'] ) ) ) : '' ),
     479                                    'event_type' => ( isset( $_REQUEST['event_type'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['event_type'] ) ) : '' ),
     480                                    'schedules_filter' => ( isset( $_REQUEST['schedules_filter'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['schedules_filter'] ) ) : '' ),
     481                                ),
     482                                \admin_url( 'admin.php' )
     483                            )
     484                        )
     485                    );
     486                    exit;
     487                }
     488            }
     489        }
    401490    }
    402491}
  • 0-day-analytics/trunk/classes/vendor/lists/views/class-fatals-view.php

    r3393178 r3398360  
    506506        public static function page_load() {
    507507            if ( ! empty( $_GET['_wp_http_referer'] ) ) {
    508                 \wp_redirect(
     508                \wp_safe_redirect(
    509509                    \remove_query_arg( array( '_wp_http_referer', 'bulk_action' ), \wp_unslash( $_SERVER['REQUEST_URI'] ) )
    510510                );
  • 0-day-analytics/trunk/classes/vendor/lists/views/class-requests-view.php

    r3393178 r3398360  
    651651        public static function page_load() {
    652652            if ( ! empty( $_GET['_wp_http_referer'] ) ) {
    653                 \wp_redirect(
     653                \wp_safe_redirect(
    654654                    \remove_query_arg( array( '_wp_http_referer', 'bulk_action' ), \wp_unslash( $_SERVER['REQUEST_URI'] ) )
    655655                );
  • 0-day-analytics/trunk/classes/vendor/lists/views/class-table-view.php

    r3393178 r3398360  
    4545                \wp_die( \esc_html__( 'You do not have permission to manage tables.', '0-day-analytics' ) );
    4646            }
     47
     48            // Determine requested table early so we can process actions before any output.
     49            $table_name      = Common_Table::get_default_table();
     50            $requested_table = isset( $_REQUEST['show_table'] ) ? \sanitize_key( \wp_unslash( $_REQUEST['show_table'] ) ) : '';
     51            if ( $requested_table && \in_array( $requested_table, Common_Table::get_tables(), true ) ) {
     52                $table_name = $requested_table;
     53            }
     54
     55            // Instantiate list table and process bulk actions BEFORE any output to avoid header issues.
     56            $table = new Table_List( $table_name );
     57
     58            // Enqueue assets and render after potential redirects are processed.
    4759            \add_thickbox();
    4860            \wp_enqueue_style( 'media-views' );
     
    6072            </script>
    6173            <?php
    62 
    63             $table_name      = Common_Table::get_default_table();
    64             $requested_table = isset( $_REQUEST['show_table'] ) ? \sanitize_key( \wp_unslash( $_REQUEST['show_table'] ) ) : '';
    65             if ( $requested_table && \in_array( $requested_table, Common_Table::get_tables(), true ) ) {
    66                 $table_name = $requested_table;
    67             }
    6874
    6975            $action = ! empty( $_REQUEST['action'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
     
    202208            } else {
    203209
    204                 $table = new Table_List( $table_name );
    205210                $table->prepare_items();
    206211                $core_table = '';
     
    725730        public static function page_load() {
    726731            if ( ! empty( $_GET['_wp_http_referer'] ) ) {
    727                 \wp_redirect(
     732                \wp_safe_redirect(
    728733                    \remove_query_arg( array( '_wp_http_referer' ), \wp_unslash( $_SERVER['REQUEST_URI'] ) )
    729734                );
  • 0-day-analytics/trunk/classes/vendor/lists/views/class-wp-mail-view.php

    r3393178 r3398360  
    795795            }
    796796        }
     797
     798        /**
     799         * Responsible for filtering table by plugin slug.
     800         *
     801         * @return void
     802         *
     803         * @since 4.1.1
     804         */
     805        public static function plugin_filter_action() {
     806
     807            if ( isset( $_REQUEST['plugin_top'] ) || isset( $_REQUEST['plugin_filter_bottom'] ) ) {
     808
     809                if ( \check_admin_referer( WP_Mail_List::PLUGIN_FILTER_ACTION, WP_Mail_List::PLUGIN_FILTER_ACTION . 'nonce' ) ) {
     810                    $id = \sanitize_text_field( \wp_unslash( $_REQUEST['plugin_top'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
     811
     812                    $context    = isset( $_REQUEST['context'] ) ? \sanitize_text_field( $_REQUEST['context'] ) : 'site';
     813                    $is_network = ( $context === 'network' && \is_multisite() );
     814
     815                    \wp_safe_redirect(
     816                        \remove_query_arg(
     817                            array( 'deleted' ),
     818                            \add_query_arg(
     819                                array(
     820                                    'page'                          => WP_Mail_List::WP_MAIL_MENU_SLUG,
     821                                    WP_Mail_List::SEARCH_INPUT       => WP_Mail_List::escaped_search_input(),
     822                                    'plugin'                        => rawurlencode( $id ),
     823                                ),
     824                                ( ( $is_network ) ? \network_admin_url( 'admin.php' ) : \admin_url( 'admin.php' ) )
     825                            )
     826                        )
     827                    );
     828                    exit;
     829                }
     830            }
     831        }
    797832    }
    798833}
  • 0-day-analytics/trunk/classes/vendor/views/class-file-editor.php

    r3393178 r3398360  
    298298                        </div>
    299299                    </div>
     300                    <div class="wfe-resizer" role="separator" aria-label="Resize sidebar" aria-orientation="vertical" tabindex="0"></div>
    300301
    301302                    <div class="wfe-editor-area">
     
    489490                }
    490491                $path    = $real . \DIRECTORY_SEPARATOR . $file;
     492                $type    = is_dir( $path ) ? 'dir' : 'file';
     493                $display = $file;
     494                if ( 'file' === $type ) {
     495                    $size     = @filesize( $path );
     496                    if ( false !== $size ) {
     497                        $display .= ' (' . \size_format( (int) $size ) . ')';
     498                    }
     499                }
    491500                $items[] = array(
    492                     'name' => $file,
     501                    'name' => $display,
    493502                    'path' => $path,
    494                     'type' => is_dir( $path ) ? 'dir' : 'file',
     503                    'type' => $type,
    495504                );
    496505            }
     
    763772            \wp_send_json_success( array( 'diff' => $diff ) );
    764773        }
     774
     775        /**
     776         * AJAX: Downloads a regular file (not backup)
     777         *
     778         * @return void
     779         *
     780         * @since 4.1.1
     781         */
     782        public static function ajax_download_file() {
     783            WP_Helper::verify_admin_nonce( 'advan_file_editor_nonce', '_ajax_nonce' );
     784
     785            $file = \sanitize_text_field( $_GET['file'] ?? '' );
     786            $real = self::safe_path( $file );
     787            if ( ! $real || ! is_file( $real ) || ! is_readable( $real ) ) {
     788                \wp_die( 'Invalid file.' );
     789            }
     790
     791            $filename = basename( $real );
     792            header( 'Content-Type: application/octet-stream' );
     793            header( 'X-Content-Type-Options: nosniff' );
     794            header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
     795            header( 'Content-Length: ' . filesize( $real ) );
     796            header( 'Cache-Control: no-store, no-cache, must-revalidate, max-age=0' );
     797            header( 'Pragma: no-cache' );
     798            readfile( $real );
     799            exit;
     800        }
     801
     802        /**
     803         * AJAX: Renames a file or directory
     804         *
     805         * @return void
     806         *
     807         * @since 4.1.1
     808         */
     809        public static function ajax_rename() {
     810            WP_Helper::verify_admin_nonce( 'advan_file_editor_nonce', '_ajax_nonce' );
     811
     812            $src      = \sanitize_text_field( $_POST['file'] ?? '' );
     813            $new_name = \sanitize_file_name( $_POST['new_name'] ?? '' );
     814
     815            if ( empty( $src ) || empty( $new_name ) ) {
     816                \wp_send_json_error( 'Missing parameters.' );
     817            }
     818
     819            $real = self::safe_path( $src );
     820            if ( ! $real || ! file_exists( $real ) ) {
     821                \wp_send_json_error( 'Source not found.' );
     822            }
     823
     824            $target = dirname( $real ) . \DIRECTORY_SEPARATOR . $new_name;
     825            if ( file_exists( $target ) ) {
     826                \wp_send_json_error( 'Target exists.' );
     827            }
     828
     829            if ( ! @rename( $real, $target ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
     830                \wp_send_json_error( 'Unable to rename.' );
     831            }
     832
     833            \wp_send_json_success( array( 'new_path' => $target, 'new_name' => basename( $target ) ) );
     834        }
     835
     836        /**
     837         * AJAX: Duplicates a file or directory
     838         *
     839         * @return void
     840         *
     841         * @since 4.1.1
     842         */
     843        public static function ajax_duplicate() {
     844            WP_Helper::verify_admin_nonce( 'advan_file_editor_nonce', '_ajax_nonce' );
     845
     846            $src = \sanitize_text_field( $_POST['file'] ?? '' );
     847            if ( empty( $src ) ) {
     848                \wp_send_json_error( 'Missing parameters.' );
     849            }
     850
     851            $real = self::safe_path( $src );
     852            if ( ! $real || ! file_exists( $real ) ) {
     853                \wp_send_json_error( 'Source not found.' );
     854            }
     855
     856            $is_dir   = is_dir( $real );
     857            $dirname  = dirname( $real );
     858            $basename = basename( $real );
     859
     860            // Determine target name.
     861            $ext      = '';
     862            $name_only = $basename;
     863            if ( ! $is_dir && false !== strpos( $basename, '.' ) ) {
     864                $parts    = explode( '.', $basename );
     865                $ext      = array_pop( $parts );
     866                $name_only = implode( '.', $parts );
     867            }
     868
     869            $base_candidate = $name_only . '-copy';
     870            $counter        = 1;
     871            $target         = '';
     872            while ( true ) {
     873                $suffix = ( $counter > 1 ) ? '-' . $counter : '';
     874                $target_name = $base_candidate . $suffix . ( $is_dir ? '' : ( ( '' !== $ext ) ? '.' . $ext : '' ) );
     875                $target      = $dirname . \DIRECTORY_SEPARATOR . $target_name;
     876                if ( ! file_exists( $target ) ) {
     877                    break;
     878                }
     879                $counter++;
     880            }
     881
     882            // Perform copy.
     883            if ( $is_dir ) {
     884                self::recursive_copy( $real, $target );
     885            } else {
     886                if ( ! @copy( $real, $target ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
     887                    \wp_send_json_error( 'Unable to duplicate file.' );
     888                }
     889            }
     890
     891            \wp_send_json_success( array( 'new_path' => $target, 'new_name' => basename( $target ) ) );
     892        }
     893
     894        /**
     895         * Recursively copies a directory.
     896         *
     897         * @param string $src Source dir.
     898         * @param string $dst Destination dir.
     899         *
     900         * @return void
     901         *
     902         * @since 4.1.1
     903         */
     904        private static function recursive_copy( $src, $dst ) {
     905            if ( ! is_dir( $src ) ) {
     906                return;
     907            }
     908            @wp_mkdir_p( $dst );
     909            $dir_handle = opendir( $src );
     910            if ( false === $dir_handle ) {
     911                return;
     912            }
     913            while ( false !== ( $item = readdir( $dir_handle ) ) ) {
     914                if ( '.' === $item || '..' === $item ) {
     915                    continue;
     916                }
     917                $from = $src . \DIRECTORY_SEPARATOR . $item;
     918                $to   = $dst . \DIRECTORY_SEPARATOR . $item;
     919                if ( is_dir( $from ) ) {
     920                    self::recursive_copy( $from, $to );
     921                } else {
     922                    @copy( $from, $to ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
     923                }
     924            }
     925            closedir( $dir_handle );
     926        }
    765927    }
    766928}
  • 0-day-analytics/trunk/css/wfe.css

    r3392179 r3398360  
    77.wfe-sidebar {
    88    width: 280px;
    9     /* border-right: 1px solid #ccc; */
    109    padding-right: 10px;
    11     max-height: 80vh;
    1210    background: transparent;
    1311    display: flex;
    1412    flex-direction: column;
    1513    min-height: 30px;
    16     resize: both;
    1714    overflow: auto;
    18     max-height: fit-content;
    19     max-width: fit-content;
     15    max-width: 100%;
     16    position: relative;
    2017}
    2118
     
    8784    height: 100%;
    8885}
     86.wfe-resizer {
     87    width: 6px;
     88    cursor: col-resize;
     89    background: linear-gradient(to right, #ddd, #bbb);
     90    border-left: 1px solid #aaa;
     91    border-right: 1px solid #aaa;
     92    margin: 0;
     93    flex: 0 0 auto;
     94}
     95.aadvana-darkskin .wfe-resizer {
     96    background: linear-gradient(to right, #233445, #1a2734);
     97    border-left: 1px solid #2f4a60;
     98    border-right: 1px solid #2f4a60;
     99}
     100.wfe-resizer:hover, .wfe-resizer:focus {
     101    background: #92b7dd;
     102    outline: none;
     103}
     104.wfe-resizing {
     105    user-select: none !important;
     106    cursor: col-resize !important;
     107}
     108.wfe-context-menu {
     109    position: absolute;
     110    z-index: 99999;
     111    background: #fff;
     112    border: 1px solid #ccc;
     113    box-shadow: 0 2px 6px rgba(0,0,0,0.15);
     114    padding: 4px 0;
     115    min-width: 160px;
     116    font-size: 13px;
     117    border-radius: 4px;
     118}
     119.wfe-context-menu button {
     120    background: none;
     121    border: none;
     122    width: 100%;
     123    text-align: left;
     124    padding: 6px 12px;
     125    cursor: pointer;
     126    font-size: 13px;
     127}
     128.wfe-context-menu button:hover, .wfe-context-menu button:focus {
     129    background: #f0f6ff;
     130    outline: none;
     131}
     132.aadvana-darkskin .wfe-context-menu {
     133    background: #13273b;
     134    border-color: #2f4a60;
     135    box-shadow: 0 2px 6px rgba(0,0,0,0.4);
     136}
     137.aadvana-darkskin .wfe-context-menu button:hover, .aadvana-darkskin .wfe-context-menu button:focus {
     138    background: #1e3e59;
     139}
    89140.wfe-tree {
    90141    border-left: 1px solid #8d7a7a73;
     
    105156.wfe-item:hover {
    106157    background: #e2f0ff26;
     158}
     159.wfe-item.active {
     160    background: #c7e3ff;
     161    font-weight: 600;
     162}
     163.aadvana-darkskin .wfe-item.active {
     164    background: #1e3e59;
    107165}
    108166.toggle {
  • 0-day-analytics/trunk/js/admin/wfe.js

    r3392179 r3398360  
    5858    $('#wfe-tree').on('click', '.wfe-item.file', function (e) {
    5959        e.stopPropagation();
    60         const path = $(this).data('path');
     60        const $li = $(this);
     61        const path = $li.data('path');
    6162        currentDir = path.substring(0, path.lastIndexOf('/'));
    6263        $.post(AFE_Ajax.ajax_url, { action: 'advan_file_editor_get_file', file: path, _ajax_nonce: AFE_Ajax.nonce }, (res) => {
     
    6667                $('#wfe-filename').text(path);
    6768                $('#wfe-diff').hide();
     69                $('#wfe-tree .wfe-item.file.active').removeClass('active');
     70                $li.addClass('active');
    6871                listBackups();
    6972            } else alert(res.data);
     
    139142            $('#wfe-filename').text(__('No file selected', '0-day-analytics'));
    140143            editor.setValue('');
     144            $('#wfe-tree .wfe-item.file.active').removeClass('active');
    141145        });
    142146    });
     
    180184        }, (res) => {
    181185            alert(res.success ? '✅ ' + res.data : '❌ ' + res.data);
     186            if (res.success && file) {
     187                // Reload the file content into the editor to reflect restored version
     188                $.post(AFE_Ajax.ajax_url, {
     189                    action: 'advan_file_editor_get_file',
     190                    file: file,
     191                    _ajax_nonce: AFE_Ajax.nonce
     192                }, (res2) => {
     193                    if (res2.success) {
     194                        editor.setValue(res2.data.content);
     195                        $('#wfe-diff').hide();
     196                    }
     197                });
     198                // Refresh backups list
     199                listBackups();
     200            }
    182201        });
    183202    });
     
    240259        });
    241260    }
     261
     262    // --- Resizable Sidebar ---
     263    const $sidebar = $('.wfe-sidebar');
     264    const $resizer = $('.wfe-resizer');
     265    const $container = $('.wfe-container');
     266    const isReverse = $container.css('flex-direction') === 'row-reverse';
     267    const savedWidth = localStorage.getItem('wfeSidebarWidth');
     268    if (savedWidth && parseInt(savedWidth, 10) > 0) {
     269        $sidebar.css('width', parseInt(savedWidth, 10) + 'px');
     270    }
     271
     272    let startX = 0, startWidth = 0, dragging = false;
     273
     274    function onMouseMove(e) {
     275        if (!dragging) return;
     276        const dx = e.pageX - startX;
     277        const adjust = isReverse ? -dx : dx; // row-reverse flips direction
     278        let newWidth = startWidth + adjust;
     279        const min = 160;
     280        const max = 720;
     281        if (newWidth < min) newWidth = min;
     282        if (newWidth > max) newWidth = max;
     283        $sidebar.css('width', newWidth + 'px');
     284    }
     285
     286    function stopDrag() {
     287        if (!dragging) return;
     288        dragging = false;
     289        $('body').removeClass('wfe-resizing');
     290        $(document).off('mousemove.wfeResize mouseup.wfeResize');
     291        const finalWidth = parseInt($sidebar.width(), 10);
     292        if (finalWidth) {
     293            localStorage.setItem('wfeSidebarWidth', finalWidth);
     294        }
     295    }
     296
     297    $resizer.on('mousedown', function (e) {
     298        e.preventDefault();
     299        startX = e.pageX;
     300        startWidth = parseInt($sidebar.width(), 10) || 280;
     301        dragging = true;
     302        $('body').addClass('wfe-resizing');
     303        $(document).on('mousemove.wfeResize', onMouseMove).on('mouseup.wfeResize', stopDrag);
     304    });
     305
     306    // Keyboard accessibility: left/right arrows
     307    $resizer.on('keydown', function(e){
     308        const key = e.key;
     309        let width = parseInt($sidebar.width(), 10) || 280;
     310        const step = (e.shiftKey ? 40 : 20);
     311        if (key === 'ArrowLeft' || key === 'ArrowRight') {
     312            const dirFactor = (key === 'ArrowRight' ? 1 : -1) * (isReverse ? -1 : 1);
     313            width += step * dirFactor;
     314            if (width < 160) width = 160;
     315            if (width > 720) width = 720;
     316            $sidebar.css('width', width + 'px');
     317            localStorage.setItem('wfeSidebarWidth', width);
     318            e.preventDefault();
     319        }
     320    });
     321
     322    // --- File Context Menu (Download) ---
     323    let $ctxMenu = null;
     324    function hideContextMenu(){
     325        if($ctxMenu){
     326            $ctxMenu.remove();
     327            $ctxMenu = null;
     328        }
     329        // Remove temporary listeners
     330        $(document).off('.wfeCtx');
     331        $(window).off('.wfeCtx');
     332    }
     333    function showContextMenu(e, filePath){
     334        hideContextMenu();
     335        $ctxMenu = $('<div class="wfe-context-menu" role="menu"></div>');
     336        // Download
     337        const $downloadBtn = $('<button type="button" role="menuitem">⬇️ '+__('Download','0-day-analytics')+'</button>');
     338        $downloadBtn.on('click', function(){
     339            const url = `${AFE_Ajax.ajax_url}?action=advan_file_editor_download_file&_ajax_nonce=${AFE_Ajax.nonce}&file=${encodeURIComponent(filePath)}`;
     340            hideContextMenu();
     341            window.location.href = url;
     342        });
     343        // Rename
     344        const $renameBtn = $('<button type="button" role="menuitem">✏️ '+__('Rename','0-day-analytics')+'</button>');
     345        $renameBtn.on('click', function(){
     346            const currentBase = filePath.substring(filePath.lastIndexOf('/'));
     347            const newName = prompt(__('Enter new name:','0-day-analytics'), currentBase.replace('/',''));
     348            if(!newName){ return; }
     349            $.post(AFE_Ajax.ajax_url, { action:'advan_file_editor_rename', file:filePath, new_name:newName, _ajax_nonce: AFE_Ajax.nonce }, function(res){
     350                alert(res.success ? '✅ '+__('Renamed','0-day-analytics') : '❌ '+res.data);
     351                if(res.success){
     352                    if(currentFile === filePath){
     353                        currentFile = res.data.new_path;
     354                        $('#wfe-filename').text(res.data.new_path);
     355                    }
     356                    $('#wfe-tree').empty();
     357                    loadDir(AFE_Ajax.base, $('#wfe-tree'));
     358                }
     359            });
     360            hideContextMenu();
     361        });
     362        // Copy Path
     363        const $copyPathBtn = $('<button type="button" role="menuitem">📋 '+__('Copy Path','0-day-analytics')+'</button>');
     364        $copyPathBtn.on('click', function(){
     365            navigator.clipboard.writeText(filePath).then(()=>{
     366                alert('📋 '+__('Path copied','0-day-analytics'));
     367            }).catch(()=>{ alert('❌ '+__('Unable to copy path','0-day-analytics')); });
     368            hideContextMenu();
     369        });
     370        // Delete
     371        const $deleteBtn = $('<button type="button" role="menuitem">🗑️ '+__('Delete','0-day-analytics')+'</button>');
     372        $deleteBtn.on('click', function(){
     373            if(!confirm(__('Delete file?','0-day-analytics')+'\n'+filePath)) { return; }
     374            $.post(AFE_Ajax.ajax_url, { action:'advan_file_editor_delete', path:filePath, _ajax_nonce:AFE_Ajax.nonce }, function(res){
     375                alert(res.success ? '🗑️ '+__('Deleted','0-day-analytics') : '❌ '+res.data);
     376                if(res.success){
     377                    if(currentFile === filePath){
     378                        currentFile = null;
     379                        editor.setValue('');
     380                        $('#wfe-filename').text(__('No file selected','0-day-analytics'));
     381                    }
     382                    $('#wfe-tree').empty();
     383                    loadDir(AFE_Ajax.base, $('#wfe-tree'));
     384                }
     385            });
     386            hideContextMenu();
     387        });
     388        // Duplicate
     389        const $duplicateBtn = $('<button type="button" role="menuitem">🧬 '+__('Duplicate','0-day-analytics')+'</button>');
     390        $duplicateBtn.on('click', function(){
     391            $.post(AFE_Ajax.ajax_url, { action:'advan_file_editor_duplicate', file:filePath, _ajax_nonce:AFE_Ajax.nonce }, function(res){
     392                alert(res.success ? '🧬 '+__('Duplicated','0-day-analytics')+': '+res.data.new_name : '❌ '+res.data);
     393                if(res.success){
     394                    $('#wfe-tree').empty();
     395                    loadDir(AFE_Ajax.base, $('#wfe-tree'));
     396                }
     397            });
     398            hideContextMenu();
     399        });
     400        $ctxMenu.append($downloadBtn, $renameBtn, $duplicateBtn, $copyPathBtn, $deleteBtn);
     401        $('body').append($ctxMenu);
     402        const x = e.pageX;
     403        const y = e.pageY;
     404        $ctxMenu.css({ top: y + 'px', left: x + 'px' });
     405        // Delay binding to avoid immediate self-close from originating event
     406        setTimeout(function(){
     407            $(document).on('mousedown.wfeCtx', function(ev){
     408                if(!$ctxMenu) return;
     409                if(!$(ev.target).closest('.wfe-context-menu').length){ hideContextMenu(); }
     410            });
     411            $(document).on('contextmenu.wfeCtx', function(ev){
     412                if(!$ctxMenu) return;
     413                if(!$(ev.target).closest('.wfe-context-menu').length){ hideContextMenu(); }
     414            });
     415            $(document).on('keydown.wfeCtx', function(ev){ if(ev.key === 'Escape'){ hideContextMenu(); } });
     416            $(window).on('scroll.wfeCtx resize.wfeCtx', hideContextMenu);
     417        },0);
     418        // Focus first item for accessibility
     419        $downloadBtn.focus();
     420    }
     421    $('#wfe-tree').on('contextmenu', '.wfe-item.file', function(e){
     422        e.preventDefault();
     423        const path = $(this).data('path');
     424        showContextMenu(e, path);
     425    });
     426    // Global listeners now added dynamically per open; fallback in case menu injected elsewhere
     427    // (No always-on handlers needed here.)
    242428});
  • 0-day-analytics/trunk/readme.txt

    r3393178 r3398360  
    44Tested up to: 6.8
    55Requires PHP: 7.4
    6 Stable tag: 4.1.1
     6Stable tag: 4.2.0
    77License: GPLv3 or later
    88License URI: http://www.gnu.org/licenses/gpl-3.0.txt
     
    114114== Changelog ==
    115115
     116= 4.2.0 =
     117New filters introduced. Code optimizations and bug fixes. File editor extending.
     118
    116119= 4.1.1 =
    117120Very small maintenance update - optimizations and bug fixes.
Note: See TracChangeset for help on using the changeset viewer.