Changeset 3398360
- Timestamp:
- 11/18/2025 08:26:28 PM (3 months ago)
- Location:
- 0-day-analytics
- Files:
-
- 3 deleted
- 50 edited
- 1 copied
-
tags/3.9.2 (deleted)
-
tags/3.9.3 (deleted)
-
tags/3.9.4 (deleted)
-
tags/4.2.0 (copied) (copied from 0-day-analytics/trunk)
-
tags/4.2.0/advanced-analytics.php (modified) (3 diffs)
-
tags/4.2.0/classes/migration/class-migration.php (modified) (1 diff)
-
tags/4.2.0/classes/vendor/controllers/class-wp-mail-log.php (modified) (7 diffs)
-
tags/4.2.0/classes/vendor/entities/class-wp-mail-entity.php (modified) (6 diffs)
-
tags/4.2.0/classes/vendor/helpers/class-ajax-helper.php (modified) (4 diffs)
-
tags/4.2.0/classes/vendor/helpers/class-crons-helper.php (modified) (4 diffs)
-
tags/4.2.0/classes/vendor/helpers/class-file-helper.php (modified) (10 diffs)
-
tags/4.2.0/classes/vendor/helpers/class-settings.php (modified) (1 diff)
-
tags/4.2.0/classes/vendor/helpers/class-system-analytics.php (modified) (2 diffs)
-
tags/4.2.0/classes/vendor/helpers/class-wp-error-handler.php (modified) (1 diff)
-
tags/4.2.0/classes/vendor/lists/class-crons-list.php (modified) (11 diffs)
-
tags/4.2.0/classes/vendor/lists/class-fatals-list.php (modified) (4 diffs)
-
tags/4.2.0/classes/vendor/lists/class-requests-list.php (modified) (4 diffs)
-
tags/4.2.0/classes/vendor/lists/class-table-list.php (modified) (5 diffs)
-
tags/4.2.0/classes/vendor/lists/class-transients-list.php (modified) (3 diffs)
-
tags/4.2.0/classes/vendor/lists/class-wp-mail-list.php (modified) (14 diffs)
-
tags/4.2.0/classes/vendor/lists/views/class-crons-view.php (modified) (4 diffs)
-
tags/4.2.0/classes/vendor/lists/views/class-fatals-view.php (modified) (1 diff)
-
tags/4.2.0/classes/vendor/lists/views/class-requests-view.php (modified) (1 diff)
-
tags/4.2.0/classes/vendor/lists/views/class-table-view.php (modified) (4 diffs)
-
tags/4.2.0/classes/vendor/lists/views/class-wp-mail-view.php (modified) (1 diff)
-
tags/4.2.0/classes/vendor/views/class-file-editor.php (modified) (3 diffs)
-
tags/4.2.0/css/wfe.css (modified) (3 diffs)
-
tags/4.2.0/js/admin/wfe.js (modified) (5 diffs)
-
tags/4.2.0/readme.txt (modified) (2 diffs)
-
trunk/advanced-analytics.php (modified) (3 diffs)
-
trunk/classes/migration/class-migration.php (modified) (1 diff)
-
trunk/classes/vendor/controllers/class-wp-mail-log.php (modified) (7 diffs)
-
trunk/classes/vendor/entities/class-wp-mail-entity.php (modified) (6 diffs)
-
trunk/classes/vendor/helpers/class-ajax-helper.php (modified) (4 diffs)
-
trunk/classes/vendor/helpers/class-crons-helper.php (modified) (4 diffs)
-
trunk/classes/vendor/helpers/class-file-helper.php (modified) (10 diffs)
-
trunk/classes/vendor/helpers/class-settings.php (modified) (1 diff)
-
trunk/classes/vendor/helpers/class-system-analytics.php (modified) (2 diffs)
-
trunk/classes/vendor/helpers/class-wp-error-handler.php (modified) (1 diff)
-
trunk/classes/vendor/lists/class-crons-list.php (modified) (11 diffs)
-
trunk/classes/vendor/lists/class-fatals-list.php (modified) (4 diffs)
-
trunk/classes/vendor/lists/class-requests-list.php (modified) (4 diffs)
-
trunk/classes/vendor/lists/class-table-list.php (modified) (5 diffs)
-
trunk/classes/vendor/lists/class-transients-list.php (modified) (3 diffs)
-
trunk/classes/vendor/lists/class-wp-mail-list.php (modified) (14 diffs)
-
trunk/classes/vendor/lists/views/class-crons-view.php (modified) (4 diffs)
-
trunk/classes/vendor/lists/views/class-fatals-view.php (modified) (1 diff)
-
trunk/classes/vendor/lists/views/class-requests-view.php (modified) (1 diff)
-
trunk/classes/vendor/lists/views/class-table-view.php (modified) (4 diffs)
-
trunk/classes/vendor/lists/views/class-wp-mail-view.php (modified) (1 diff)
-
trunk/classes/vendor/views/class-file-editor.php (modified) (3 diffs)
-
trunk/css/wfe.css (modified) (3 diffs)
-
trunk/js/admin/wfe.js (modified) (5 diffs)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
0-day-analytics/tags/4.2.0/advanced-analytics.php
r3393178 r3398360 11 11 * Plugin Name: 0 Day Analytics 12 12 * Description: Take full control of error log, crons, transients, plugins, requests, mails and DB tables. 13 * Version: 4. 1.113 * Version: 4.2.0 14 14 * Author: Stoil Dobrev 15 15 * Author URI: https://github.com/sdobreff/ … … 37 37 // Constants. 38 38 if ( ! defined( 'ADVAN_VERSION' ) ) { 39 define( 'ADVAN_VERSION', '4. 1.1' );39 define( 'ADVAN_VERSION', '4.2.0' ); 40 40 define( 'ADVAN_TEXTDOMAIN', '0-day-analytics' ); 41 41 define( 'ADVAN_NAME', '0 Day Analytics' ); … … 67 67 sprintf( 68 68 // translators: the minimum version of the PHP required by the plugin. 69 __(69 \__( 70 70 '"%1$s" requires PHP %2$s or newer. Plugin is automatically deactivated.', 71 71 '0-day-analytics' -
0-day-analytics/tags/4.2.0/classes/migration/class-migration.php
r3384847 r3398360 216 216 } 217 217 } 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 } 218 233 } 219 234 } -
0-day-analytics/tags/4.2.0/classes/vendor/controllers/class-wp-mail-log.php
r3393178 r3398360 13 13 14 14 use ADVAN\Entities\WP_Mail_Entity; 15 use ADVAN\Helpers\Plugin_Theme_Helper; 15 16 use ADVAN\Helpers\Settings; 16 17 … … 123 124 $message = $email_class->get( 'content_plaintext', 'replace-tokens' ); 124 125 } 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 } 125 134 self::$bp_mail = array( 126 135 'time' => time(), … … 129 138 'message' => self::filter_html( $message ), 130 139 'backtrace_segment' => \wp_json_encode( self::get_backtrace() ), 140 'plugin_slug' => $plugin_slug, 131 141 'status' => 1, 132 142 'attachments' => \wp_json_encode( self::get_attachment_locations( array() ) ), … … 150 160 151 161 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 } 152 170 $log_entry = array( 153 171 'time' => time(), … … 156 174 'message' => self::filter_html( $args['message'] ), 157 175 'backtrace_segment' => \wp_json_encode( self::get_backtrace() ), 176 'plugin_slug' => $plugin_slug, 158 177 'status' => 1, 159 178 'attachments' => \wp_json_encode( self::get_attachment_locations( $args['attachments'] ) ), … … 202 221 $mail_header = $prop->getValue( $phpmailer ); 203 222 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 } 204 236 $log_entry = array( 205 237 'time' => time(), … … 209 241 'message' => self::filter_html( $phpmailer->Body ), // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase 210 242 'backtrace_segment' => \wp_json_encode( self::get_backtrace() ), 243 'plugin_slug' => $plugin_slug, 211 244 'status' => 1, 212 245 '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 13 13 14 14 use ADVAN\Helpers\WP_Helper; 15 use ADVAN\Helpers\Plugin_Theme_Helper; 15 16 use ADVAN\Entities_Global\Common_Table; 16 17 … … 54 55 'id' => 'int', 55 56 'blog_id' => 'int', 57 'plugin_slug' => 'string', 56 58 'time' => 'string', 57 59 'email_to' => 'string', … … 77 79 'id' => 0, 78 80 'blog_id' => 0, 81 'plugin_slug' => '', 79 82 'time' => '', 80 83 'email_to' => '', … … 115 118 id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, 116 119 blog_id INT NOT NULL, 120 plugin_slug VARCHAR(255) DEFAULT NULL, 117 121 time DOUBLE NOT NULL DEFAULT 0, 118 122 email_to TEXT DEFAULT NULL, … … 168 172 169 173 /** 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 /** 170 191 * Helper to check if a column exists in the given table. 171 192 * … … 260 281 return $output; 261 282 } 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 } 262 341 } 263 342 } -
0-day-analytics/tags/4.2.0/classes/vendor/helpers/class-ajax-helper.php
r3393178 r3398360 171 171 \add_action( 'wp_ajax_advan_file_editor_compare_backup', array( File_Editor::class, 'ajax_compare_backup' ) ); 172 172 \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' ) ); 173 179 } 174 180 } … … 708 714 if ( isset( $_POST['typeExport'] ) && ! empty( $_POST['typeExport'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing 709 715 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 ); 711 738 $rows = \array_slice( $rows, $offset, $batch_size, true ); 712 $total = count( $rows );713 739 } 714 740 if ( 'logs' === $_POST['typeExport'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing … … 768 794 if ( 'mail' === $_POST['typeExport'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing 769 795 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 772 800 $extra_file_name = '_' . str_replace( ' ', '_', $list_table->get_table_name() ) . '_'; 801 if ( ! empty( $plugin ) && -1 !== (int) $plugin ) { 802 $extra_file_name .= $plugin . '_'; 803 } 773 804 774 805 $rows = $list_table->fetch_table_data( … … 779 810 'site_id' => '', 780 811 'search' => $search, 812 'plugin' => $plugin, 781 813 ) 782 814 ); -
0-day-analytics/tags/4.2.0/classes/vendor/helpers/class-crons-helper.php
r3393178 r3398360 15 15 16 16 use ADVAN\Lists\Crons_List; 17 use ADVAN\Helpers\Plugin_Theme_Helper; 17 18 18 19 // Exit if accessed directly. … … 263 264 continue; 264 265 } 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; 274 304 } 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 );280 305 } 281 self::$events[ $cron_item['hash'] ] = $cron_item;282 }283 306 } 284 307 } … … 286 309 287 310 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; 288 338 } 289 339 … … 566 616 } 567 617 568 if ( defined( 'ALTERNATE_WP_CRON' ) && \ALTERNATE_WP_CRON) {618 if ( defined( 'ALTERNATE_WP_CRON' ) && constant( 'ALTERNATE_WP_CRON' ) ) { 569 619 return new \WP_Error( 570 620 'advana_cron_info', -
0-day-analytics/tags/4.2.0/classes/vendor/helpers/class-file-helper.php
r3393178 r3398360 28 28 29 29 /** 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 /** 30 78 * Normalize a filesystem path in a cross-platform way. 31 79 * … … 117 165 */ 118 166 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 124 167 $logging_dir = dirname( $filename ); 125 168 … … 132 175 } 133 176 134 $result = false; 135 177 // Ensure destination directory exists, try WP first then native. 136 178 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 ) { 138 192 self::$last_error = new \WP_Error( 139 193 'mkdir_failed', … … 144 198 ) 145 199 ); 146 147 return $result; 200 return false; 148 201 } 149 202 } … … 157 210 } 158 211 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 ); 165 234 } 166 235 … … 174 243 ) 175 244 ); 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; 185 252 } 186 253 … … 211 278 $size = filesize( $filename ); 212 279 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 ); 244 281 } 245 282 … … 287 324 288 325 // 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 ); 290 327 $real_allowed_base = realpath( $allowed_base ); 291 328 $real_requested = realpath( $file_path ); … … 713 750 \WP_Filesystem(); 714 751 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 } 716 762 if ( ! $move ) { 717 763 // 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 } 719 769 } 720 770 return (bool) $move; … … 722 772 723 773 // Fallback if filesystem is unavailable. 724 return rename( $temp_path, $file_path ); // phpcs:ignoreWordPress.WP.AlternativeFunctions.rename_rename774 return @rename( $temp_path, $file_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.rename_rename 725 775 } 726 776 } -
0-day-analytics/tags/4.2.0/classes/vendor/helpers/class-settings.php
r3393178 r3398360 824 824 $_FILES[ self::SETTINGS_FILE_FIELD ] = array(); 825 825 } 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 ); 832 852 } 833 853 if ( ! is_array( $options ) ) { 834 $options = array(); } 854 $options = array(); 855 } 835 856 if ( ! empty( $options ) ) { 836 857 \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 327 327 328 328 /** 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 /** 329 360 * Returns the disk usage 330 361 * … … 335 366 public static function get_disk_usage() { 336 367 $total = @disk_total_space( '/' ); 337 $free = @disk_free_space( '/' ); 368 if ( self::is_allowed_by_open_base_dir( '/' ) ) { 369 $free = @disk_free_space( '/' ); 370 } 338 371 if ( $total && $free ) { 339 372 $used = 100 - ( ( $free / $total ) * 100 ); -
0-day-analytics/tags/4.2.0/classes/vendor/helpers/class-wp-error-handler.php
r3393178 r3398360 259 259 */ 260 260 public static function trigger_error( $status, string $function_name, $errstr, $version, $errno = E_USER_NOTICE ) { 261 262 261 if ( false === $status ) { 263 262 return $status; -
0-day-analytics/tags/4.2.0/classes/vendor/lists/class-crons-list.php
r3393178 r3398360 19 19 use ADVAN\Helpers\WP_Helper; 20 20 use ADVAN\Helpers\Crons_Helper; 21 use ADVAN\Helpers\Plugin_Theme_Helper; 21 22 use ADVAN\Lists\Views\Crons_View; 22 23 use ADVAN\Lists\Traits\List_Trait; … … 57 58 public const CRON_MENU_SLUG = 'advan_cron_jobs'; 58 59 60 public const PLUGIN_FILTER_ACTION = self::PAGE_SLUG . '_filter_plugin'; 61 62 public const SITE_FILTER_ACTION = self::PAGE_SLUG . '_filter_site'; 63 59 64 /** 60 65 * Format for the file link. … … 166 171 \add_action( 'admin_post_' . self::UPDATE_ACTION, array( Crons_View::class, 'update_cron' ) ); 167 172 \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' ) ); 168 175 } 169 176 … … 193 200 194 201 \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' ) ); 195 204 196 205 /* 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(); 197 221 } 198 222 … … 247 271 ); 248 272 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 249 286 $screen_options = $admin_fields; 250 287 … … 273 310 $sortable = $this->get_sortable_columns(); 274 311 $this->_column_headers = array( $columns, $hidden, $sortable ); 275 276 $this->handle_table_actions();277 312 278 313 $this->fetch_table_data(); … … 394 429 } 395 430 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 396 450 if ( ! $no_type_filtering ) { 397 451 if ( ! empty( $_REQUEST['event_type'] ) && is_string( $_REQUEST['event_type'] ) ) { … … 442 496 public static function format_column_value( $item, $column_name ) { 443 497 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' ); 444 505 case 'hook': 445 506 $query_args_view_data = array(); … … 902 963 ?> 903 964 <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 ?> 904 1007 <select class="schedules-filter" name="schedules_filter"> 905 1008 <?php … … 927 1030 <div id="export-form"> 928 1031 <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 ); ?>"> 930 1033 <?php echo esc_html__( 'CSV Export', '0-day-analytics' ); ?> 931 1034 </button> … … 1369 1472 return $filtered; 1370 1473 } 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 } 1371 1548 } 1372 1549 } -
0-day-analytics/tags/4.2.0/classes/vendor/lists/class-fatals-list.php
r3393178 r3398360 17 17 use ADVAN\Helpers\Settings; 18 18 use ADVAN\Helpers\WP_Helper; 19 use ADVAN\Helpers\File_Helper;20 19 use ADVAN\Helpers\Miscellaneous; 21 20 use ADVAN\Lists\Traits\List_Trait; … … 184 183 185 184 \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(); 186 202 } 187 203 … … 205 221 */ 206 222 public function prepare_items() { 207 $this->handle_table_actions();223 // Actions are processed during load-<hook> to avoid header warnings. 208 224 209 225 $per_page = self::get_screen_option_per_page(); … … 855 871 <?php } ?> 856 872 <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() ) ); ?> 858 874 859 875 <?php -
0-day-analytics/tags/4.2.0/classes/vendor/lists/class-requests-list.php
r3393178 r3398360 17 17 use ADVAN\Helpers\Settings; 18 18 use ADVAN\Helpers\WP_Helper; 19 use ADVAN\Helpers\File_Helper;20 19 use ADVAN\Helpers\Miscellaneous; 21 20 use ADVAN\Lists\Traits\List_Trait; … … 203 202 204 203 \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(); 205 221 } 206 222 … … 224 240 */ 225 241 public function prepare_items() { 226 $this->handle_table_actions();242 // Actions are processed during load-<hook> to avoid late redirects. 227 243 $per_page = self::get_screen_option_per_page(); 228 244 … … 957 973 <?php } ?> 958 974 <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() ) ); ?> 960 976 961 977 <?php -
0-day-analytics/tags/4.2.0/classes/vendor/lists/class-table-list.php
r3393178 r3398360 17 17 use ADVAN\Helpers\Settings; 18 18 use ADVAN\Helpers\WP_Helper; 19 use ADVAN\Helpers\File_Helper;20 19 use ADVAN\Helpers\Miscellaneous; 21 20 use ADVAN\Lists\Views\Table_View; … … 164 163 // \add_filter( 'manage_' . $table_hook . '_columns', array( Table_List::class, 'manage_columns' ) ); 165 164 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 166 168 \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(); 167 192 } 168 193 … … 186 211 */ 187 212 public function prepare_items() { 188 $this->handle_table_actions();189 213 190 214 $per_page = self::get_screen_option_per_page(); … … 932 956 <?php } ?> 933 957 <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() ) ); ?> 935 959 936 960 <?php … … 971 995 </div> 972 996 <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> 974 998 </div> 975 999 </div> -
0-day-analytics/tags/4.2.0/classes/vendor/lists/class-transients-list.php
r3392179 r3398360 187 187 \add_filter( 'manage_' . $transients_hook . '_columns', array( self::class, 'manage_columns' ) ); 188 188 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(); 190 207 } 191 208 … … 303 320 ) 304 321 ); 305 306 322 $hidden = \get_user_option( 'manage' . WP_Helper::get_wp_screen()->id . 'columnshidden', false ); 307 323 if ( ! $hidden ) { … … 611 627 612 628 \wp_safe_redirect( $redirect ); 629 exit; 613 630 } 614 631 } -
0-day-analytics/tags/4.2.0/classes/vendor/lists/class-wp-mail-list.php
r3393178 r3398360 17 17 use ADVAN\Helpers\Settings; 18 18 use ADVAN\Helpers\WP_Helper; 19 use ADVAN\Helpers\File_Helper;20 19 use ADVAN\Entities_Global\Common_Table; 21 20 use ADVAN\Controllers\WP_Mail_Log; … … 61 60 public const SITE_ID_FILTER_ACTION = 'filter_site_id'; 62 61 62 public const PLUGIN_FILTER_ACTION = self::PAGE_SLUG . '_filter_plugin'; 63 63 64 /** 64 65 * The table to show … … 160 161 \add_action( 'admin_post_' . self::NEW_ACTION, array( WP_Mail_View::class, 'new_mail' ) ); 161 162 \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' ) ); 162 164 \add_filter( 'advan_cron_hooks', array( __CLASS__, 'add_cron_job' ) ); 163 165 } … … 218 220 219 221 \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(); 220 239 } 221 240 … … 239 258 */ 240 259 public function prepare_items() { 241 $this->handle_table_actions();260 // Actions are processed on load-<hook> to prevent late redirects. 242 261 243 262 // Vars. … … 252 271 $type = ! empty( $_GET['mail_type'] ) ? \sanitize_text_field( \wp_unslash( $_GET['mail_type'] ) ) : ''; 253 272 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 254 283 if ( isset( $_REQUEST['site_id'] ) && ! empty( $_REQUEST['site_id'] ) ) { 255 284 if ( -1 === (int) $_REQUEST['site_id'] ) { … … 275 304 'type' => $type, 276 305 'site_id' => $site_id, 306 'plugin' => $plugin, 277 307 ) 278 308 ); … … 372 402 'count' => false, 373 403 'site_id' => '', 404 'plugin' => '', 374 405 ) 375 406 ); … … 394 425 $site_id = \sanitize_text_field( \wp_unslash( (string) $parsed_args['site_id'] ) ); 395 426 $type = \sanitize_text_field( \wp_unslash( $parsed_args['type'] ?? '' ) ); 427 $plugin = \sanitize_text_field( \wp_unslash( (string) ( $parsed_args['plugin'] ?? '' ) ) ); 396 428 397 429 if ( '' !== $search_string ) { … … 433 465 $where_parts[] = 'AND attachments != "[]"'; 434 466 } 467 } 468 469 if ( '' !== $plugin && -1 !== (int) $plugin ) { 470 $where_parts[] = 'AND plugin_slug = %s'; 471 $where_args[] = $plugin; 435 472 } 436 473 … … 1035 1072 public function extra_tablenav( $which ) { 1036 1073 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 1037 1101 if ( WP_Helper::is_multisite() ) { 1038 1102 if ( isset( $_REQUEST['site_id'] ) && ! empty( $_REQUEST['site_id'] ) ) { … … 1057 1121 ?> 1058 1122 <div class="alignleft actions bulkactions"> 1059 1060 1123 <?php echo WP_Mail_Entity::get_all_sites_dropdown( $site_id, $which ); ?> 1061 1062 1124 </div> 1063 1125 <script> … … 1073 1135 <div id="export-form"> 1074 1136 <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 : '' ); ?>"> 1076 1138 <?php echo \esc_html__( 'CSV Export', '0-day-analytics' ); ?> 1077 1139 </button> … … 1112 1174 <?php } ?> 1113 1175 <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() ) ); ?> 1115 1177 1116 1178 <?php -
0-day-analytics/tags/4.2.0/classes/vendor/lists/views/class-crons-view.php
r3391413 r3398360 128 128 '<input type="hidden" name="event_type" value="%s"/>', 129 129 \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'] ) ) : '' ) 130 138 ); 131 139 ?> … … 200 208 \esc_attr( isset( $_REQUEST['event_type'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['event_type'] ) ) : '' ) 201 209 ); 210 printf( 211 '<input type="hidden" name="plugin" value="%s"/>', 212 \esc_attr( isset( $_REQUEST['plugin'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['plugin'] ) ) : '' ) 213 ); 202 214 ?> 203 215 <?php \wp_nonce_field( Crons_List::NONCE_NAME ); ?> … … 302 314 \esc_attr( isset( $_REQUEST['event_type'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['event_type'] ) ) : '' ) 303 315 ); 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 ); 304 328 305 329 ?> … … 399 423 exit; 400 424 } 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 } 401 490 } 402 491 } -
0-day-analytics/tags/4.2.0/classes/vendor/lists/views/class-fatals-view.php
r3393178 r3398360 506 506 public static function page_load() { 507 507 if ( ! empty( $_GET['_wp_http_referer'] ) ) { 508 \wp_ redirect(508 \wp_safe_redirect( 509 509 \remove_query_arg( array( '_wp_http_referer', 'bulk_action' ), \wp_unslash( $_SERVER['REQUEST_URI'] ) ) 510 510 ); -
0-day-analytics/tags/4.2.0/classes/vendor/lists/views/class-requests-view.php
r3393178 r3398360 651 651 public static function page_load() { 652 652 if ( ! empty( $_GET['_wp_http_referer'] ) ) { 653 \wp_ redirect(653 \wp_safe_redirect( 654 654 \remove_query_arg( array( '_wp_http_referer', 'bulk_action' ), \wp_unslash( $_SERVER['REQUEST_URI'] ) ) 655 655 ); -
0-day-analytics/tags/4.2.0/classes/vendor/lists/views/class-table-view.php
r3393178 r3398360 45 45 \wp_die( \esc_html__( 'You do not have permission to manage tables.', '0-day-analytics' ) ); 46 46 } 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. 47 59 \add_thickbox(); 48 60 \wp_enqueue_style( 'media-views' ); … … 60 72 </script> 61 73 <?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 }68 74 69 75 $action = ! empty( $_REQUEST['action'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended … … 202 208 } else { 203 209 204 $table = new Table_List( $table_name );205 210 $table->prepare_items(); 206 211 $core_table = ''; … … 725 730 public static function page_load() { 726 731 if ( ! empty( $_GET['_wp_http_referer'] ) ) { 727 \wp_ redirect(732 \wp_safe_redirect( 728 733 \remove_query_arg( array( '_wp_http_referer' ), \wp_unslash( $_SERVER['REQUEST_URI'] ) ) 729 734 ); -
0-day-analytics/tags/4.2.0/classes/vendor/lists/views/class-wp-mail-view.php
r3393178 r3398360 795 795 } 796 796 } 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 } 797 832 } 798 833 } -
0-day-analytics/tags/4.2.0/classes/vendor/views/class-file-editor.php
r3393178 r3398360 298 298 </div> 299 299 </div> 300 <div class="wfe-resizer" role="separator" aria-label="Resize sidebar" aria-orientation="vertical" tabindex="0"></div> 300 301 301 302 <div class="wfe-editor-area"> … … 489 490 } 490 491 $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 } 491 500 $items[] = array( 492 'name' => $ file,501 'name' => $display, 493 502 'path' => $path, 494 'type' => is_dir( $path ) ? 'dir' : 'file',503 'type' => $type, 495 504 ); 496 505 } … … 763 772 \wp_send_json_success( array( 'diff' => $diff ) ); 764 773 } 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 } 765 927 } 766 928 } -
0-day-analytics/tags/4.2.0/css/wfe.css
r3392179 r3398360 7 7 .wfe-sidebar { 8 8 width: 280px; 9 /* border-right: 1px solid #ccc; */10 9 padding-right: 10px; 11 max-height: 80vh;12 10 background: transparent; 13 11 display: flex; 14 12 flex-direction: column; 15 13 min-height: 30px; 16 resize: both;17 14 overflow: auto; 18 max- height: fit-content;19 max-width: fit-content;15 max-width: 100%; 16 position: relative; 20 17 } 21 18 … … 87 84 height: 100%; 88 85 } 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 } 89 140 .wfe-tree { 90 141 border-left: 1px solid #8d7a7a73; … … 105 156 .wfe-item:hover { 106 157 background: #e2f0ff26; 158 } 159 .wfe-item.active { 160 background: #c7e3ff; 161 font-weight: 600; 162 } 163 .aadvana-darkskin .wfe-item.active { 164 background: #1e3e59; 107 165 } 108 166 .toggle { -
0-day-analytics/tags/4.2.0/js/admin/wfe.js
r3392179 r3398360 58 58 $('#wfe-tree').on('click', '.wfe-item.file', function (e) { 59 59 e.stopPropagation(); 60 const path = $(this).data('path'); 60 const $li = $(this); 61 const path = $li.data('path'); 61 62 currentDir = path.substring(0, path.lastIndexOf('/')); 62 63 $.post(AFE_Ajax.ajax_url, { action: 'advan_file_editor_get_file', file: path, _ajax_nonce: AFE_Ajax.nonce }, (res) => { … … 66 67 $('#wfe-filename').text(path); 67 68 $('#wfe-diff').hide(); 69 $('#wfe-tree .wfe-item.file.active').removeClass('active'); 70 $li.addClass('active'); 68 71 listBackups(); 69 72 } else alert(res.data); … … 139 142 $('#wfe-filename').text(__('No file selected', '0-day-analytics')); 140 143 editor.setValue(''); 144 $('#wfe-tree .wfe-item.file.active').removeClass('active'); 141 145 }); 142 146 }); … … 180 184 }, (res) => { 181 185 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 } 182 201 }); 183 202 }); … … 240 259 }); 241 260 } 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.) 242 428 }); -
0-day-analytics/tags/4.2.0/readme.txt
r3393178 r3398360 4 4 Tested up to: 6.8 5 5 Requires PHP: 7.4 6 Stable tag: 4. 1.16 Stable tag: 4.2.0 7 7 License: GPLv3 or later 8 8 License URI: http://www.gnu.org/licenses/gpl-3.0.txt … … 114 114 == Changelog == 115 115 116 = 4.2.0 = 117 New filters introduced. Code optimizations and bug fixes. File editor extending. 118 116 119 = 4.1.1 = 117 120 Very small maintenance update - optimizations and bug fixes. -
0-day-analytics/trunk/advanced-analytics.php
r3393178 r3398360 11 11 * Plugin Name: 0 Day Analytics 12 12 * Description: Take full control of error log, crons, transients, plugins, requests, mails and DB tables. 13 * Version: 4. 1.113 * Version: 4.2.0 14 14 * Author: Stoil Dobrev 15 15 * Author URI: https://github.com/sdobreff/ … … 37 37 // Constants. 38 38 if ( ! defined( 'ADVAN_VERSION' ) ) { 39 define( 'ADVAN_VERSION', '4. 1.1' );39 define( 'ADVAN_VERSION', '4.2.0' ); 40 40 define( 'ADVAN_TEXTDOMAIN', '0-day-analytics' ); 41 41 define( 'ADVAN_NAME', '0 Day Analytics' ); … … 67 67 sprintf( 68 68 // translators: the minimum version of the PHP required by the plugin. 69 __(69 \__( 70 70 '"%1$s" requires PHP %2$s or newer. Plugin is automatically deactivated.', 71 71 '0-day-analytics' -
0-day-analytics/trunk/classes/migration/class-migration.php
r3384847 r3398360 216 216 } 217 217 } 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 } 218 233 } 219 234 } -
0-day-analytics/trunk/classes/vendor/controllers/class-wp-mail-log.php
r3393178 r3398360 13 13 14 14 use ADVAN\Entities\WP_Mail_Entity; 15 use ADVAN\Helpers\Plugin_Theme_Helper; 15 16 use ADVAN\Helpers\Settings; 16 17 … … 123 124 $message = $email_class->get( 'content_plaintext', 'replace-tokens' ); 124 125 } 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 } 125 134 self::$bp_mail = array( 126 135 'time' => time(), … … 129 138 'message' => self::filter_html( $message ), 130 139 'backtrace_segment' => \wp_json_encode( self::get_backtrace() ), 140 'plugin_slug' => $plugin_slug, 131 141 'status' => 1, 132 142 'attachments' => \wp_json_encode( self::get_attachment_locations( array() ) ), … … 150 160 151 161 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 } 152 170 $log_entry = array( 153 171 'time' => time(), … … 156 174 'message' => self::filter_html( $args['message'] ), 157 175 'backtrace_segment' => \wp_json_encode( self::get_backtrace() ), 176 'plugin_slug' => $plugin_slug, 158 177 'status' => 1, 159 178 'attachments' => \wp_json_encode( self::get_attachment_locations( $args['attachments'] ) ), … … 202 221 $mail_header = $prop->getValue( $phpmailer ); 203 222 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 } 204 236 $log_entry = array( 205 237 'time' => time(), … … 209 241 'message' => self::filter_html( $phpmailer->Body ), // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase 210 242 'backtrace_segment' => \wp_json_encode( self::get_backtrace() ), 243 'plugin_slug' => $plugin_slug, 211 244 'status' => 1, 212 245 'attachments' => \wp_json_encode( self::get_attachment_locations( $attachment ) ), -
0-day-analytics/trunk/classes/vendor/entities/class-wp-mail-entity.php
r3393178 r3398360 13 13 14 14 use ADVAN\Helpers\WP_Helper; 15 use ADVAN\Helpers\Plugin_Theme_Helper; 15 16 use ADVAN\Entities_Global\Common_Table; 16 17 … … 54 55 'id' => 'int', 55 56 'blog_id' => 'int', 57 'plugin_slug' => 'string', 56 58 'time' => 'string', 57 59 'email_to' => 'string', … … 77 79 'id' => 0, 78 80 'blog_id' => 0, 81 'plugin_slug' => '', 79 82 'time' => '', 80 83 'email_to' => '', … … 115 118 id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, 116 119 blog_id INT NOT NULL, 120 plugin_slug VARCHAR(255) DEFAULT NULL, 117 121 time DOUBLE NOT NULL DEFAULT 0, 118 122 email_to TEXT DEFAULT NULL, … … 168 172 169 173 /** 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 /** 170 191 * Helper to check if a column exists in the given table. 171 192 * … … 260 281 return $output; 261 282 } 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 } 262 341 } 263 342 } -
0-day-analytics/trunk/classes/vendor/helpers/class-ajax-helper.php
r3393178 r3398360 171 171 \add_action( 'wp_ajax_advan_file_editor_compare_backup', array( File_Editor::class, 'ajax_compare_backup' ) ); 172 172 \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' ) ); 173 179 } 174 180 } … … 708 714 if ( isset( $_POST['typeExport'] ) && ! empty( $_POST['typeExport'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing 709 715 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 ); 711 738 $rows = \array_slice( $rows, $offset, $batch_size, true ); 712 $total = count( $rows );713 739 } 714 740 if ( 'logs' === $_POST['typeExport'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing … … 768 794 if ( 'mail' === $_POST['typeExport'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing 769 795 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 772 800 $extra_file_name = '_' . str_replace( ' ', '_', $list_table->get_table_name() ) . '_'; 801 if ( ! empty( $plugin ) && -1 !== (int) $plugin ) { 802 $extra_file_name .= $plugin . '_'; 803 } 773 804 774 805 $rows = $list_table->fetch_table_data( … … 779 810 'site_id' => '', 780 811 'search' => $search, 812 'plugin' => $plugin, 781 813 ) 782 814 ); -
0-day-analytics/trunk/classes/vendor/helpers/class-crons-helper.php
r3393178 r3398360 15 15 16 16 use ADVAN\Lists\Crons_List; 17 use ADVAN\Helpers\Plugin_Theme_Helper; 17 18 18 19 // Exit if accessed directly. … … 263 264 continue; 264 265 } 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; 274 304 } 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 );280 305 } 281 self::$events[ $cron_item['hash'] ] = $cron_item;282 }283 306 } 284 307 } … … 286 309 287 310 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; 288 338 } 289 339 … … 566 616 } 567 617 568 if ( defined( 'ALTERNATE_WP_CRON' ) && \ALTERNATE_WP_CRON) {618 if ( defined( 'ALTERNATE_WP_CRON' ) && constant( 'ALTERNATE_WP_CRON' ) ) { 569 619 return new \WP_Error( 570 620 'advana_cron_info', -
0-day-analytics/trunk/classes/vendor/helpers/class-file-helper.php
r3393178 r3398360 28 28 29 29 /** 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 /** 30 78 * Normalize a filesystem path in a cross-platform way. 31 79 * … … 117 165 */ 118 166 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 124 167 $logging_dir = dirname( $filename ); 125 168 … … 132 175 } 133 176 134 $result = false; 135 177 // Ensure destination directory exists, try WP first then native. 136 178 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 ) { 138 192 self::$last_error = new \WP_Error( 139 193 'mkdir_failed', … … 144 198 ) 145 199 ); 146 147 return $result; 200 return false; 148 201 } 149 202 } … … 157 210 } 158 211 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 ); 165 234 } 166 235 … … 174 243 ) 175 244 ); 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; 185 252 } 186 253 … … 211 278 $size = filesize( $filename ); 212 279 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 ); 244 281 } 245 282 … … 287 324 288 325 // 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 ); 290 327 $real_allowed_base = realpath( $allowed_base ); 291 328 $real_requested = realpath( $file_path ); … … 713 750 \WP_Filesystem(); 714 751 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 } 716 762 if ( ! $move ) { 717 763 // 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 } 719 769 } 720 770 return (bool) $move; … … 722 772 723 773 // Fallback if filesystem is unavailable. 724 return rename( $temp_path, $file_path ); // phpcs:ignoreWordPress.WP.AlternativeFunctions.rename_rename774 return @rename( $temp_path, $file_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.rename_rename 725 775 } 726 776 } -
0-day-analytics/trunk/classes/vendor/helpers/class-settings.php
r3393178 r3398360 824 824 $_FILES[ self::SETTINGS_FILE_FIELD ] = array(); 825 825 } 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 ); 832 852 } 833 853 if ( ! is_array( $options ) ) { 834 $options = array(); } 854 $options = array(); 855 } 835 856 if ( ! empty( $options ) ) { 836 857 \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 327 327 328 328 /** 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 /** 329 360 * Returns the disk usage 330 361 * … … 335 366 public static function get_disk_usage() { 336 367 $total = @disk_total_space( '/' ); 337 $free = @disk_free_space( '/' ); 368 if ( self::is_allowed_by_open_base_dir( '/' ) ) { 369 $free = @disk_free_space( '/' ); 370 } 338 371 if ( $total && $free ) { 339 372 $used = 100 - ( ( $free / $total ) * 100 ); -
0-day-analytics/trunk/classes/vendor/helpers/class-wp-error-handler.php
r3393178 r3398360 259 259 */ 260 260 public static function trigger_error( $status, string $function_name, $errstr, $version, $errno = E_USER_NOTICE ) { 261 262 261 if ( false === $status ) { 263 262 return $status; -
0-day-analytics/trunk/classes/vendor/lists/class-crons-list.php
r3393178 r3398360 19 19 use ADVAN\Helpers\WP_Helper; 20 20 use ADVAN\Helpers\Crons_Helper; 21 use ADVAN\Helpers\Plugin_Theme_Helper; 21 22 use ADVAN\Lists\Views\Crons_View; 22 23 use ADVAN\Lists\Traits\List_Trait; … … 57 58 public const CRON_MENU_SLUG = 'advan_cron_jobs'; 58 59 60 public const PLUGIN_FILTER_ACTION = self::PAGE_SLUG . '_filter_plugin'; 61 62 public const SITE_FILTER_ACTION = self::PAGE_SLUG . '_filter_site'; 63 59 64 /** 60 65 * Format for the file link. … … 166 171 \add_action( 'admin_post_' . self::UPDATE_ACTION, array( Crons_View::class, 'update_cron' ) ); 167 172 \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' ) ); 168 175 } 169 176 … … 193 200 194 201 \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' ) ); 195 204 196 205 /* 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(); 197 221 } 198 222 … … 247 271 ); 248 272 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 249 286 $screen_options = $admin_fields; 250 287 … … 273 310 $sortable = $this->get_sortable_columns(); 274 311 $this->_column_headers = array( $columns, $hidden, $sortable ); 275 276 $this->handle_table_actions();277 312 278 313 $this->fetch_table_data(); … … 394 429 } 395 430 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 396 450 if ( ! $no_type_filtering ) { 397 451 if ( ! empty( $_REQUEST['event_type'] ) && is_string( $_REQUEST['event_type'] ) ) { … … 442 496 public static function format_column_value( $item, $column_name ) { 443 497 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' ); 444 505 case 'hook': 445 506 $query_args_view_data = array(); … … 902 963 ?> 903 964 <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 ?> 904 1007 <select class="schedules-filter" name="schedules_filter"> 905 1008 <?php … … 927 1030 <div id="export-form"> 928 1031 <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 ); ?>"> 930 1033 <?php echo esc_html__( 'CSV Export', '0-day-analytics' ); ?> 931 1034 </button> … … 1369 1472 return $filtered; 1370 1473 } 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 } 1371 1548 } 1372 1549 } -
0-day-analytics/trunk/classes/vendor/lists/class-fatals-list.php
r3393178 r3398360 17 17 use ADVAN\Helpers\Settings; 18 18 use ADVAN\Helpers\WP_Helper; 19 use ADVAN\Helpers\File_Helper;20 19 use ADVAN\Helpers\Miscellaneous; 21 20 use ADVAN\Lists\Traits\List_Trait; … … 184 183 185 184 \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(); 186 202 } 187 203 … … 205 221 */ 206 222 public function prepare_items() { 207 $this->handle_table_actions();223 // Actions are processed during load-<hook> to avoid header warnings. 208 224 209 225 $per_page = self::get_screen_option_per_page(); … … 855 871 <?php } ?> 856 872 <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() ) ); ?> 858 874 859 875 <?php -
0-day-analytics/trunk/classes/vendor/lists/class-requests-list.php
r3393178 r3398360 17 17 use ADVAN\Helpers\Settings; 18 18 use ADVAN\Helpers\WP_Helper; 19 use ADVAN\Helpers\File_Helper;20 19 use ADVAN\Helpers\Miscellaneous; 21 20 use ADVAN\Lists\Traits\List_Trait; … … 203 202 204 203 \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(); 205 221 } 206 222 … … 224 240 */ 225 241 public function prepare_items() { 226 $this->handle_table_actions();242 // Actions are processed during load-<hook> to avoid late redirects. 227 243 $per_page = self::get_screen_option_per_page(); 228 244 … … 957 973 <?php } ?> 958 974 <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() ) ); ?> 960 976 961 977 <?php -
0-day-analytics/trunk/classes/vendor/lists/class-table-list.php
r3393178 r3398360 17 17 use ADVAN\Helpers\Settings; 18 18 use ADVAN\Helpers\WP_Helper; 19 use ADVAN\Helpers\File_Helper;20 19 use ADVAN\Helpers\Miscellaneous; 21 20 use ADVAN\Lists\Views\Table_View; … … 164 163 // \add_filter( 'manage_' . $table_hook . '_columns', array( Table_List::class, 'manage_columns' ) ); 165 164 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 166 168 \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(); 167 192 } 168 193 … … 186 211 */ 187 212 public function prepare_items() { 188 $this->handle_table_actions();189 213 190 214 $per_page = self::get_screen_option_per_page(); … … 932 956 <?php } ?> 933 957 <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() ) ); ?> 935 959 936 960 <?php … … 971 995 </div> 972 996 <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> 974 998 </div> 975 999 </div> -
0-day-analytics/trunk/classes/vendor/lists/class-transients-list.php
r3392179 r3398360 187 187 \add_filter( 'manage_' . $transients_hook . '_columns', array( self::class, 'manage_columns' ) ); 188 188 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(); 190 207 } 191 208 … … 303 320 ) 304 321 ); 305 306 322 $hidden = \get_user_option( 'manage' . WP_Helper::get_wp_screen()->id . 'columnshidden', false ); 307 323 if ( ! $hidden ) { … … 611 627 612 628 \wp_safe_redirect( $redirect ); 629 exit; 613 630 } 614 631 } -
0-day-analytics/trunk/classes/vendor/lists/class-wp-mail-list.php
r3393178 r3398360 17 17 use ADVAN\Helpers\Settings; 18 18 use ADVAN\Helpers\WP_Helper; 19 use ADVAN\Helpers\File_Helper;20 19 use ADVAN\Entities_Global\Common_Table; 21 20 use ADVAN\Controllers\WP_Mail_Log; … … 61 60 public const SITE_ID_FILTER_ACTION = 'filter_site_id'; 62 61 62 public const PLUGIN_FILTER_ACTION = self::PAGE_SLUG . '_filter_plugin'; 63 63 64 /** 64 65 * The table to show … … 160 161 \add_action( 'admin_post_' . self::NEW_ACTION, array( WP_Mail_View::class, 'new_mail' ) ); 161 162 \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' ) ); 162 164 \add_filter( 'advan_cron_hooks', array( __CLASS__, 'add_cron_job' ) ); 163 165 } … … 218 220 219 221 \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(); 220 239 } 221 240 … … 239 258 */ 240 259 public function prepare_items() { 241 $this->handle_table_actions();260 // Actions are processed on load-<hook> to prevent late redirects. 242 261 243 262 // Vars. … … 252 271 $type = ! empty( $_GET['mail_type'] ) ? \sanitize_text_field( \wp_unslash( $_GET['mail_type'] ) ) : ''; 253 272 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 254 283 if ( isset( $_REQUEST['site_id'] ) && ! empty( $_REQUEST['site_id'] ) ) { 255 284 if ( -1 === (int) $_REQUEST['site_id'] ) { … … 275 304 'type' => $type, 276 305 'site_id' => $site_id, 306 'plugin' => $plugin, 277 307 ) 278 308 ); … … 372 402 'count' => false, 373 403 'site_id' => '', 404 'plugin' => '', 374 405 ) 375 406 ); … … 394 425 $site_id = \sanitize_text_field( \wp_unslash( (string) $parsed_args['site_id'] ) ); 395 426 $type = \sanitize_text_field( \wp_unslash( $parsed_args['type'] ?? '' ) ); 427 $plugin = \sanitize_text_field( \wp_unslash( (string) ( $parsed_args['plugin'] ?? '' ) ) ); 396 428 397 429 if ( '' !== $search_string ) { … … 433 465 $where_parts[] = 'AND attachments != "[]"'; 434 466 } 467 } 468 469 if ( '' !== $plugin && -1 !== (int) $plugin ) { 470 $where_parts[] = 'AND plugin_slug = %s'; 471 $where_args[] = $plugin; 435 472 } 436 473 … … 1035 1072 public function extra_tablenav( $which ) { 1036 1073 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 1037 1101 if ( WP_Helper::is_multisite() ) { 1038 1102 if ( isset( $_REQUEST['site_id'] ) && ! empty( $_REQUEST['site_id'] ) ) { … … 1057 1121 ?> 1058 1122 <div class="alignleft actions bulkactions"> 1059 1060 1123 <?php echo WP_Mail_Entity::get_all_sites_dropdown( $site_id, $which ); ?> 1061 1062 1124 </div> 1063 1125 <script> … … 1073 1135 <div id="export-form"> 1074 1136 <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 : '' ); ?>"> 1076 1138 <?php echo \esc_html__( 'CSV Export', '0-day-analytics' ); ?> 1077 1139 </button> … … 1112 1174 <?php } ?> 1113 1175 <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() ) ); ?> 1115 1177 1116 1178 <?php -
0-day-analytics/trunk/classes/vendor/lists/views/class-crons-view.php
r3391413 r3398360 128 128 '<input type="hidden" name="event_type" value="%s"/>', 129 129 \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'] ) ) : '' ) 130 138 ); 131 139 ?> … … 200 208 \esc_attr( isset( $_REQUEST['event_type'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['event_type'] ) ) : '' ) 201 209 ); 210 printf( 211 '<input type="hidden" name="plugin" value="%s"/>', 212 \esc_attr( isset( $_REQUEST['plugin'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['plugin'] ) ) : '' ) 213 ); 202 214 ?> 203 215 <?php \wp_nonce_field( Crons_List::NONCE_NAME ); ?> … … 302 314 \esc_attr( isset( $_REQUEST['event_type'] ) ? \sanitize_text_field( \wp_unslash( $_REQUEST['event_type'] ) ) : '' ) 303 315 ); 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 ); 304 328 305 329 ?> … … 399 423 exit; 400 424 } 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 } 401 490 } 402 491 } -
0-day-analytics/trunk/classes/vendor/lists/views/class-fatals-view.php
r3393178 r3398360 506 506 public static function page_load() { 507 507 if ( ! empty( $_GET['_wp_http_referer'] ) ) { 508 \wp_ redirect(508 \wp_safe_redirect( 509 509 \remove_query_arg( array( '_wp_http_referer', 'bulk_action' ), \wp_unslash( $_SERVER['REQUEST_URI'] ) ) 510 510 ); -
0-day-analytics/trunk/classes/vendor/lists/views/class-requests-view.php
r3393178 r3398360 651 651 public static function page_load() { 652 652 if ( ! empty( $_GET['_wp_http_referer'] ) ) { 653 \wp_ redirect(653 \wp_safe_redirect( 654 654 \remove_query_arg( array( '_wp_http_referer', 'bulk_action' ), \wp_unslash( $_SERVER['REQUEST_URI'] ) ) 655 655 ); -
0-day-analytics/trunk/classes/vendor/lists/views/class-table-view.php
r3393178 r3398360 45 45 \wp_die( \esc_html__( 'You do not have permission to manage tables.', '0-day-analytics' ) ); 46 46 } 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. 47 59 \add_thickbox(); 48 60 \wp_enqueue_style( 'media-views' ); … … 60 72 </script> 61 73 <?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 }68 74 69 75 $action = ! empty( $_REQUEST['action'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended … … 202 208 } else { 203 209 204 $table = new Table_List( $table_name );205 210 $table->prepare_items(); 206 211 $core_table = ''; … … 725 730 public static function page_load() { 726 731 if ( ! empty( $_GET['_wp_http_referer'] ) ) { 727 \wp_ redirect(732 \wp_safe_redirect( 728 733 \remove_query_arg( array( '_wp_http_referer' ), \wp_unslash( $_SERVER['REQUEST_URI'] ) ) 729 734 ); -
0-day-analytics/trunk/classes/vendor/lists/views/class-wp-mail-view.php
r3393178 r3398360 795 795 } 796 796 } 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 } 797 832 } 798 833 } -
0-day-analytics/trunk/classes/vendor/views/class-file-editor.php
r3393178 r3398360 298 298 </div> 299 299 </div> 300 <div class="wfe-resizer" role="separator" aria-label="Resize sidebar" aria-orientation="vertical" tabindex="0"></div> 300 301 301 302 <div class="wfe-editor-area"> … … 489 490 } 490 491 $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 } 491 500 $items[] = array( 492 'name' => $ file,501 'name' => $display, 493 502 'path' => $path, 494 'type' => is_dir( $path ) ? 'dir' : 'file',503 'type' => $type, 495 504 ); 496 505 } … … 763 772 \wp_send_json_success( array( 'diff' => $diff ) ); 764 773 } 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 } 765 927 } 766 928 } -
0-day-analytics/trunk/css/wfe.css
r3392179 r3398360 7 7 .wfe-sidebar { 8 8 width: 280px; 9 /* border-right: 1px solid #ccc; */10 9 padding-right: 10px; 11 max-height: 80vh;12 10 background: transparent; 13 11 display: flex; 14 12 flex-direction: column; 15 13 min-height: 30px; 16 resize: both;17 14 overflow: auto; 18 max- height: fit-content;19 max-width: fit-content;15 max-width: 100%; 16 position: relative; 20 17 } 21 18 … … 87 84 height: 100%; 88 85 } 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 } 89 140 .wfe-tree { 90 141 border-left: 1px solid #8d7a7a73; … … 105 156 .wfe-item:hover { 106 157 background: #e2f0ff26; 158 } 159 .wfe-item.active { 160 background: #c7e3ff; 161 font-weight: 600; 162 } 163 .aadvana-darkskin .wfe-item.active { 164 background: #1e3e59; 107 165 } 108 166 .toggle { -
0-day-analytics/trunk/js/admin/wfe.js
r3392179 r3398360 58 58 $('#wfe-tree').on('click', '.wfe-item.file', function (e) { 59 59 e.stopPropagation(); 60 const path = $(this).data('path'); 60 const $li = $(this); 61 const path = $li.data('path'); 61 62 currentDir = path.substring(0, path.lastIndexOf('/')); 62 63 $.post(AFE_Ajax.ajax_url, { action: 'advan_file_editor_get_file', file: path, _ajax_nonce: AFE_Ajax.nonce }, (res) => { … … 66 67 $('#wfe-filename').text(path); 67 68 $('#wfe-diff').hide(); 69 $('#wfe-tree .wfe-item.file.active').removeClass('active'); 70 $li.addClass('active'); 68 71 listBackups(); 69 72 } else alert(res.data); … … 139 142 $('#wfe-filename').text(__('No file selected', '0-day-analytics')); 140 143 editor.setValue(''); 144 $('#wfe-tree .wfe-item.file.active').removeClass('active'); 141 145 }); 142 146 }); … … 180 184 }, (res) => { 181 185 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 } 182 201 }); 183 202 }); … … 240 259 }); 241 260 } 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.) 242 428 }); -
0-day-analytics/trunk/readme.txt
r3393178 r3398360 4 4 Tested up to: 6.8 5 5 Requires PHP: 7.4 6 Stable tag: 4. 1.16 Stable tag: 4.2.0 7 7 License: GPLv3 or later 8 8 License URI: http://www.gnu.org/licenses/gpl-3.0.txt … … 114 114 == Changelog == 115 115 116 = 4.2.0 = 117 New filters introduced. Code optimizations and bug fixes. File editor extending. 118 116 119 = 4.1.1 = 117 120 Very small maintenance update - optimizations and bug fixes.
Note: See TracChangeset
for help on using the changeset viewer.