Plugin Directory

Changeset 3448917


Ignore:
Timestamp:
01/28/2026 05:49:05 PM (3 weeks ago)
Author:
awesomefootnotes
Message:

Adding the first version of my plugin

Location:
0-day-analytics
Files:
18 edited
1 copied

Legend:

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

    r3442473 r3448917  
    1111 * Plugin Name:     0 Day Analytics
    1212 * Description:     Take full control of error log, crons, transients, plugins, requests, mails and DB tables.
    13  * Version:         4.5.2
     13 * Version:         4.6.0
    1414 * Author:          Stoil Dobrev
    1515 * Author URI:      https://github.com/sdobreff/
     
    3939// Constants.
    4040if ( ! defined( 'ADVAN_VERSION' ) ) {
    41     define( 'ADVAN_VERSION', '4.5.2' );
     41    define( 'ADVAN_VERSION', '4.6.0' );
    4242    define( 'ADVAN_TEXTDOMAIN', '0-day-analytics' );
    4343    define( 'ADVAN_NAME', '0 Day Analytics' );
  • 0-day-analytics/tags/4.6.0/classes/class-advanced-analytics.php

    r3442115 r3448917  
    3131use ADVAN\Lists\Hooks_Capture_List;
    3232use ADVAN\Lists\Hooks_Management_List;
    33 use ADVAN\Controllers\Hooks_Capture;
    3433use ADVAN\Migration\Migration;
    3534use ADVAN\Controllers\Pointers;
    36 use ADVAN\Controllers\Cron_Jobs;
    3735use ADVAN\Helpers\Miscellaneous;
    3836use ADVAN\Lists\Transients_List;
     
    7169
    7270                \add_action( 'admin_init', array( __CLASS__, 'plugin_redirect' ) );
     71
     72                // Initialize hooks capture list admin features.
     73                Hooks_Capture_List::init_admin_hooks();
    7374
    7475                // Setup screen options. Needs to be here as admin_init hook is too late. Per page set is below.
  • 0-day-analytics/tags/4.6.0/classes/migration/class-migration.php

    r3413453 r3448917  
    252252            }
    253253        }
     254
     255        /**
     256         * Migrates the plugin up-to version 4.6.0 (adds request_id column to hooks capture).
     257         *
     258         * @return void
     259         *
     260         * @since 4.6.0
     261         */
     262        public static function migrate_up_to_460() {
     263            if ( \class_exists( '\\ADVAN\\Entities\\Hooks_Capture_Entity' ) ) {
     264                if ( Common_Table::check_table_exists( \ADVAN\Entities\Hooks_Capture_Entity::get_table_name() ) && ! Common_Table::check_column( 'request_id', 'varchar(50)', \ADVAN\Entities\Hooks_Capture_Entity::get_table_name() ) ) {
     265                    \ADVAN\Entities\Hooks_Capture_Entity::alter_table_460();
     266                }
     267            }
     268        }
    254269    }
    255270}
  • 0-day-analytics/tags/4.6.0/classes/vendor/controllers/class-hooks-capture.php

    r3442473 r3448917  
    3131
    3232        /**
     33         * Maximum depth for capturing nested hooks.
     34         *
     35         * @var int
     36         *
     37         * @since 4.5.0
     38         */
     39        private const MAX_CAPTURE_DEPTH = 3;
     40
     41        /**
     42         * Maximum number of hook logs to store in memory before forcing a commit.
     43         *
     44         * @var int
     45         *
     46         * @since 4.6.1
     47         */
     48        private const MAX_MEMORY_LOGS = 1000;
     49
     50        /**
     51         * Maximum size for parameter/output JSON strings (64KB).
     52         *
     53         * @var int
     54         *
     55         * @since 4.5.0
     56         */
     57        private const MAX_JSON_SIZE = 65536;
     58
     59        /**
     60         * Maximum length for string parameters before truncation.
     61         *
     62         * @var int
     63         *
     64         * @since 4.5.0
     65         */
     66        private const MAX_STRING_LENGTH = 255;
     67
     68        /**
     69         * Maximum depth for recursive array/object sanitization.
     70         *
     71         * @var int
     72         *
     73         * @since 4.5.0
     74         */
     75        private const MAX_SANITIZE_DEPTH = 2;
     76
     77        /**
     78         * Maximum number of backtrace frames to capture.
     79         *
     80         * @var int
     81         *
     82         * @since 4.5.0
     83         */
     84        private const MAX_BACKTRACE_FRAMES = 3;
     85
     86        /**
     87         * Maximum number of properties to capture from objects.
     88         *
     89         * @var int
     90         *
     91         * @since 4.5.0
     92         */
     93        private const MAX_OBJECT_PROPERTIES = 50;
     94
     95        /**
     96         * Maximum length for hook names.
     97         *
     98         * @var int
     99         *
     100         * @since 4.6.1
     101         */
     102        private const MAX_HOOK_NAME_LENGTH = 255;
     103
     104        /**
    33105         * Array of hooks currently being captured to prevent infinite loops.
    34106         *
     
    83155         */
    84156        private static $cache_dir_path = null;
     157
     158        /**
     159         * Unique request ID for grouping hook calls per request.
     160         *
     161         * @var string|null
     162         *
     163         * @since 4.5.0
     164         */
     165        private static $request_id = null;
     166
     167        /**
     168         * In-memory storage for hook logs to deduplicate per request.
     169         *
     170         * @var array
     171         *
     172         * @since 4.6.0
     173         */
     174        private static $hook_logs = array();
    85175
    86176        /**
     
    96186            }
    97187
    98             // In WP-CLI context, ensure hooks are attached properly
     188            // Generate unique request ID for this execution.
     189            self::$request_id = uniqid( 'req_', true );
     190
     191            // self::debug_log( 'Initializing hooks capture', array( 'request_id' => self::$request_id ) );
     192
     193            // In WP-CLI context, ensure hooks are attached properly.
    99194            if ( defined( 'WP_CLI' ) && WP_CLI ) {
    100195                self::attach_hooks_cli();
     
    109204            // Re-attach hooks after cache clear to pick up changes.
    110205            \add_action( 'advan_hooks_management_updated', array( __CLASS__, 'detach_and_reattach_hooks' ) );
     206
     207            // Commit hook logs at the end of the request.
     208            \add_action( 'shutdown', array( __CLASS__, 'commit_hook_logs' ) );
     209
     210            // =======================================================================
     211            // MEMORY POOL: Initialize memory pool for reusing structures
     212            // =======================================================================
     213            self::init_memory_pool();
     214
     215            // =======================================================================
     216            // ERROR RECOVERY: Setup error recovery for serialization failures
     217            // =======================================================================
     218            \add_filter( 'advan_serialize_hook_data', array( __CLASS__, 'safe_json_encode' ), 10, 2 );
     219            \add_filter( 'advan_unserialize_hook_data', array( __CLASS__, 'safe_json_decode' ), 10, 2 );
     220
     221            // Cleanup memory pool on shutdown.
     222            \add_action( 'shutdown', array( __CLASS__, 'cleanup_memory_pool' ), 999 );
    111223        }
    112224
     
    123235
    124236            // Regenerate cache file with latest hooks configuration.
    125             self::regenerate_cache_file();
     237            // Defer regeneration if WordPress isn't fully loaded yet.
     238            if ( ! \did_action( 'init' ) ) {
     239                \add_action( 'init', array( __CLASS__, 'regenerate_cache_file' ), 1 );
     240            } else {
     241                self::regenerate_cache_file();
     242            }
    126243        }
    127244
     
    167284         */
    168285        public static function attach_hooks_cli() {
    169             // In CLI context, always load from DB to ensure hooks are attached
     286            // In CLI context, always load from DB to ensure hooks are attached.
    170287            self::$enabled_hooks = Hooks_Management_Entity::get_enabled_hooks();
    171288
     
    176293            // Attach monitoring to each enabled hook.
    177294            foreach ( self::$enabled_hooks as $hook_config ) {
    178                 if ( empty( $hook_config['hook_name'] ) ) {
     295                if ( empty( $hook_config['hook_name'] ) || ! self::is_valid_hook_name( $hook_config['hook_name'] ) ) {
    179296                    continue;
    180297                }
     
    186303                // Use a high number of accepted args to capture all parameters.
    187304                if ( 'action' === $hook_type ) {
    188                     \add_action( $hook_name, array( __CLASS__, 'capture_action' ), $priority, 10 );
     305                    \add_action( $hook_name, array( __CLASS__, 'capture_action' ), $priority, self::get_accepted_args() );
    189306                } else {
    190                     \add_filter( $hook_name, array( __CLASS__, 'capture_filter' ), $priority, 10 );
     307                    \add_filter( $hook_name, array( __CLASS__, 'capture_filter' ), $priority, self::get_accepted_args() );
    191308                }
    192309            }
     
    224341            // Attach monitoring to each enabled hook.
    225342            foreach ( self::$enabled_hooks as $hook_config ) {
    226                 if ( empty( $hook_config['hook_name'] ) ) {
     343                if ( empty( $hook_config['hook_name'] ) || ! self::is_valid_hook_name( $hook_config['hook_name'] ) ) {
    227344                    continue;
    228345                }
     
    234351                // Use a high number of accepted args to capture all parameters.
    235352                if ( 'action' === $hook_type ) {
    236                     \add_action( $hook_name, array( __CLASS__, 'capture_action' ), $priority, 10 );
     353                    \add_action( $hook_name, array( __CLASS__, 'capture_action' ), $priority, self::get_accepted_args() );
    237354                } else {
    238                     \add_filter( $hook_name, array( __CLASS__, 'capture_filter' ), $priority, 10 );
     355                    \add_filter( $hook_name, array( __CLASS__, 'capture_filter' ), $priority, self::get_accepted_args() );
    239356                }
    240357            }
     
    286403         */
    287404        private static function log_hook( string $hook_name, string $hook_type, array $args, $output ) {
     405            // Validate hook name for security.
     406            if ( ! self::is_valid_hook_name( $hook_name ) ) {
     407                return;
     408            }
     409
     410            // =======================================================================
     411            // EARLY FILTERING: Filter out unwanted hooks before processing
     412            // =======================================================================
     413            if ( ! self::should_capture_hook_early( $hook_name ) ) {
     414                return;
     415            }
     416
     417            // =======================================================================
     418            // SAMPLING: Apply sampling for high-frequency hooks to reduce storage
     419            // =======================================================================
     420            if ( ! self::should_sample_hook( $hook_name ) ) {
     421                return;
     422            }
     423
    288424            // Prevent infinite loops.
    289425            if ( isset( self::$capturing_hooks[ $hook_name ] ) ) {
     
    292428
    293429            // Prevent excessive nesting.
    294             if ( self::$current_depth >= self::$max_depth ) {
     430            if ( self::$current_depth >= self::MAX_CAPTURE_DEPTH ) {
    295431                return;
    296432            }
     
    335471
    336472                    // Limit parameter size (max 64KB).
    337                     if ( strlen( $parameters_json ) > 65536 ) {
    338                         $parameters_json = substr( $parameters_json, 0, 65536 ) . '... [truncated]';
     473                    if ( strlen( $parameters_json ) > self::MAX_JSON_SIZE ) {
     474                        $parameters_json = substr( $parameters_json, 0, self::MAX_JSON_SIZE ) . '... [truncated]';
    339475                    }
    340476                }
     
    346482
    347483                    // Limit output size (max 64KB).
    348                     if ( strlen( $output_json ) > 65536 ) {
    349                         $output_json = substr( $output_json, 0, 65536 ) . '... [truncated]';
     484                    if ( strlen( $output_json ) > self::MAX_JSON_SIZE ) {
     485                        $output_json = substr( $output_json, 0, self::MAX_JSON_SIZE ) . '... [truncated]';
    350486                    }
    351487                }
     
    357493                $memory_usage   = memory_get_usage() - $start_memory;
    358494
    359                 // Prepare log entry.
    360                 $log_entry = array(
    361                     'blog_id'             => \is_multisite() ? \get_current_blog_id() : 0,
    362                     'user_id'             => $user_id,
    363                     'user_login'          => $user_login,
    364                     'trigger_source'      => $trigger_source,
    365                     'hook_name'           => $hook_name,
    366                     'hook_type'           => $hook_type,
    367                     'parameters'          => $parameters_json,
    368                     'output'              => $output_json,
    369                     'backtrace'           => \wp_json_encode( $backtrace ),
    370                     'execution_time'      => $execution_time,
    371                     'memory_usage'        => $memory_usage,
    372                     'is_cli'              => (int) self::is_cli(),
    373                     'hooks_management_id' => self::get_hook_management_id( $hook_name ),
    374                     'date_added'          => microtime( true ),
    375                 );
    376 
    377                 // Insert asynchronously if possible, synchronously as fallback.
    378                 if ( function_exists( 'wp_schedule_single_event' ) ) {
    379                     // For very high-traffic hooks, consider batching.
    380                     Hooks_Capture_Entity::insert( $log_entry );
     495                // Collect performance metrics for monitoring.
     496                self::collect_performance_metrics( $execution_time, $memory_usage, count( self::$hook_logs ) );
     497
     498                // Create unique key for deduplication based on hook name and args.
     499                // Use optimized key generation for better performance.
     500                $key = self::generate_deduplication_key( $hook_name, $args );
     501
     502                if ( ! isset( self::$hook_logs[ $key ] ) ) {
     503                    // Check if we've exceeded memory limits and force a commit if needed.
     504                    if ( count( self::$hook_logs ) >= self::MAX_MEMORY_LOGS ) {
     505                        self::commit_hook_logs();
     506                    }
     507
     508                    // Prepare log data.
     509                    $log_data = self::prepare_hook_log_data( $hook_name, $hook_type, $args, $output, $capture_args, $capture_output, $trigger_source, $user_id, $user_login, $backtrace, $execution_time, $memory_usage );
     510
     511                    // Store the log data in memory.
     512                    self::$hook_logs[ $key ] = $log_data;
    381513                } else {
    382                     Hooks_Capture_Entity::insert( $log_entry );
     514                    // Increment count for duplicate hook calls.
     515                    ++self::$hook_logs[ $key ]['count'];
    383516                }
    384517            } finally {
     
    465598         */
    466599        private static function get_backtrace(): array {
    467             // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
    468             $trace = \debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 5 );
     600            // Use Exception backtrace for better performance (faster than debug_backtrace).
     601            $trace = ( new \Exception( '' ) )->getTrace();
    469602
    470603            $simplified = array();
     
    484617                );
    485618
    486                 // Limit to 3 frames for performance.
    487                 if ( count( $simplified ) >= 3 ) {
     619                // Limit to configured number of frames for performance.
     620                if ( count( $simplified ) >= self::MAX_BACKTRACE_FRAMES ) {
    488621                    break;
    489622                }
     
    491624
    492625            return $simplified;
     626        }
     627
     628        /**
     629         * Generate optimized deduplication key for hook calls.
     630         *
     631         * @param string $hook_name Hook name.
     632         * @param array  $args Hook arguments.
     633         *
     634         * @return string Deduplication key.
     635         *
     636         * @since 4.6.1
     637         */
     638        private static function generate_deduplication_key( string $hook_name, array $args ): string {
     639            // For performance, use a simplified approach for common cases.
     640            $arg_signature = '';
     641
     642            // Limit to first few arguments to avoid expensive serialization.
     643            $max_args  = 3;
     644            $arg_count = 0;
     645
     646            foreach ( $args as $arg ) {
     647                if ( $arg_count >= $max_args ) {
     648                    break;
     649                }
     650
     651                if ( is_scalar( $arg ) ) {
     652                    $arg_signature .= (string) $arg . '|';
     653                } elseif ( is_array( $arg ) ) {
     654                    $arg_signature .= 'array(' . count( $arg ) . ')|';
     655                } elseif ( is_object( $arg ) ) {
     656                    $arg_signature .= 'object(' . get_class( $arg ) . ')|';
     657                } else {
     658                    $arg_signature .= gettype( $arg ) . '|';
     659                }
     660
     661                ++$arg_count;
     662            }
     663
     664            // Add count of remaining args if any.
     665            if ( count( $args ) > $max_args ) {
     666                $arg_signature .= '+' . ( count( $args ) - $max_args ) . 'more';
     667            }
     668
     669            return $hook_name . '_' . md5( $arg_signature );
     670        }
     671
     672        /**
     673         * Get module health status.
     674         *
     675         * @return array Health status information.
     676         *
     677         * @since 4.6.1
     678         */
     679        public static function get_health_status(): array {
     680            return self::health_check();
     681        }
     682
     683        /**
     684         * Force commit of pending hook logs.
     685         *
     686         * @return bool True on success.
     687         *
     688         * @since 4.6.1
     689         */
     690        public static function force_commit(): bool {
     691            if ( empty( self::$hook_logs ) ) {
     692                return true;
     693            }
     694
     695            self::commit_hook_logs();
     696            return empty( self::$hook_logs );
     697        }
     698
     699        /**
     700         * Get current performance metrics.
     701         *
     702         * @return array Performance metrics.
     703         *
     704         * @since 4.6.1
     705         */
     706        public static function get_performance_metrics(): array {
     707            return array(
     708                'memory_usage'  => memory_get_usage( true ),
     709                'peak_memory'   => memory_get_peak_usage( true ),
     710                'queued_logs'   => count( self::$hook_logs ),
     711                'max_logs'      => self::MAX_MEMORY_LOGS,
     712                'cache_enabled' => ! empty( self::get_cache_file_path() ),
     713                'request_id'    => self::$request_id,
     714            );
     715        }
     716
     717        /**
     718         * Perform health check for the hooks capture module.
     719         *
     720         * @return array Health check results.
     721         *
     722         * @since 4.6.1
     723         */
     724        public static function health_check(): array {
     725            $health = array(
     726                'status'    => 'healthy',
     727                'issues'    => array(),
     728                'metrics'   => array(),
     729                'timestamp' => time(),
     730            );
     731
     732            // Check memory usage.
     733            $memory_usage = memory_get_usage( true );
     734            $memory_limit = self::get_memory_limit_bytes();
     735
     736            if ( $memory_limit > 0 && $memory_usage > $memory_limit * 0.8 ) {
     737                $health['issues'][] = 'High memory usage detected';
     738                $health['status']   = 'warning';
     739            }
     740
     741            $health['metrics']['memory_usage'] = $memory_usage;
     742            $health['metrics']['memory_limit'] = $memory_limit;
     743
     744            // Check hook logs count.
     745            $log_count                        = count( self::$hook_logs );
     746            $health['metrics']['queued_logs'] = $log_count;
     747
     748            if ( $log_count > self::MAX_MEMORY_LOGS * 0.9 ) {
     749                $health['issues'][] = 'Approaching memory log limit';
     750                $health['status']   = 'warning';
     751            }
     752
     753            // Check cache file status.
     754            $cache_file = self::get_cache_file_path();
     755            if ( $cache_file ) {
     756                $health['metrics']['cache_file_exists']   = file_exists( $cache_file );
     757                $health['metrics']['cache_file_readable'] = is_readable( $cache_file );
     758
     759                if ( file_exists( $cache_file ) && is_readable( $cache_file ) ) {
     760                    $cache_content                         = file_get_contents( $cache_file );
     761                    $health['metrics']['cache_file_valid'] = self::is_valid_cache_content( $cache_content );
     762                }
     763            }
     764
     765            // Check database connectivity.
     766            try {
     767                $test_query                              = Hooks_Capture_Entity::load( '1=0' ); // Should return empty array.
     768                $health['metrics']['database_connected'] = true;
     769            } catch ( \Exception $e ) {
     770                $health['issues'][]                      = 'Database connectivity issue: ' . $e->getMessage();
     771                $health['status']                        = 'error';
     772                $health['metrics']['database_connected'] = false;
     773            }
     774
     775            return $health;
     776        }
     777
     778        /**
     779         * Get memory limit in bytes.
     780         *
     781         * @return int Memory limit in bytes, or 0 if unlimited.
     782         *
     783         * @since 4.6.1
     784         */
     785        private static function get_memory_limit_bytes(): int {
     786            $memory_limit = ini_get( 'memory_limit' );
     787
     788            if ( empty( $memory_limit ) || $memory_limit === '-1' ) {
     789                return 0; // Unlimited.
     790            }
     791
     792            $unit  = strtolower( substr( $memory_limit, -1 ) );
     793            $value = (int) substr( $memory_limit, 0, -1 );
     794
     795            switch ( $unit ) {
     796                case 'g':
     797                    return $value * 1024 * 1024 * 1024;
     798                case 'm':
     799                    return $value * 1024 * 1024;
     800                case 'k':
     801                    return $value * 1024;
     802                default:
     803                    return (int) $memory_limit;
     804            }
     805        }
     806
     807        /**
     808         * Prepare hook log data for storage.
     809         *
     810         * @param string $hook_name Hook name.
     811         * @param string $hook_type Hook type.
     812         * @param array  $args Hook arguments.
     813         * @param mixed  $output Hook output.
     814         * @param bool   $capture_args Whether to capture arguments.
     815         * @param bool   $capture_output Whether to capture output.
     816         * @param string $trigger_source Trigger source.
     817         * @param int    $user_id User ID.
     818         * @param string $user_login User login.
     819         * @param array  $backtrace Backtrace data.
     820         * @param float  $execution_time Execution time.
     821         * @param int    $memory_usage Memory usage.
     822         *
     823         * @return array Prepared log data.
     824         *
     825         * @since 4.6.1
     826         */
     827        private static function prepare_hook_log_data( string $hook_name, string $hook_type, array $args, $output, bool $capture_args, bool $capture_output, string $trigger_source, int $user_id, string $user_login, array $backtrace, float $execution_time, int $memory_usage ): array {
     828            // Capture parameters (with size limit for performance).
     829            $parameters_json = '';
     830            if ( $capture_args && ! empty( $args ) ) {
     831                $sanitized_args  = self::sanitize_args( $args, $hook_name );
     832                $parameters_json = \wp_json_encode( $sanitized_args );
     833
     834                // Limit parameter size (max 64KB).
     835                if ( strlen( $parameters_json ) > self::MAX_JSON_SIZE ) {
     836                    $parameters_json = substr( $parameters_json, 0, self::MAX_JSON_SIZE ) . '... [truncated]';
     837                }
     838            }
     839
     840            // Capture output (with size limit).
     841            $output_json = '';
     842            if ( $capture_output && null !== $output ) {
     843                $output_json = \wp_json_encode( self::sanitize_args( array( $output ) ) );
     844
     845                // Limit output size (max 64KB).
     846                if ( strlen( $output_json ) > self::MAX_JSON_SIZE ) {
     847                    $output_json = substr( $output_json, 0, self::MAX_JSON_SIZE ) . '... [truncated]';
     848                }
     849            }
     850
     851            return array(
     852                'blog_id'             => \is_multisite() ? \get_current_blog_id() : 0,
     853                'user_id'             => $user_id,
     854                'user_login'          => $user_login,
     855                'trigger_source'      => $trigger_source,
     856                'hook_name'           => $hook_name,
     857                'hook_type'           => $hook_type,
     858                'parameters'          => $parameters_json,
     859                'output'              => $output_json,
     860                'backtrace'           => \wp_json_encode( $backtrace ),
     861                'execution_time'      => $execution_time,
     862                'memory_usage'        => $memory_usage,
     863                'is_cli'              => (int) self::is_cli(),
     864                'hooks_management_id' => self::get_hook_management_id( $hook_name ),
     865                'count'               => 1,
     866                'date_added'          => microtime( true ),
     867            );
    493868        }
    494869
     
    612987                    if ( self::is_sensitive_key( (string) $key ) ) {
    613988                        $sanitized[ $key ] = '[REDACTED - Sensitive Data]';
    614                     } elseif ( is_string( $value ) && mb_strlen( $value ) > 255 ) {
    615                         $sanitized[ $key ] = mb_substr( $value, 0, 255 ) . '... (truncated)';
     989                    } elseif ( is_string( $value ) && mb_strlen( $value ) > self::MAX_STRING_LENGTH ) {
     990                        $sanitized[ $key ] = mb_substr( $value, 0, self::MAX_STRING_LENGTH ) . '... (truncated)';
    616991                    } else {
    617992                        $sanitized[ $key ] = $value;
     
    6431018         */
    6441019        private static function sanitize_args_recursive( array $args, int $depth ) {
    645             if ( $depth > 2 ) {
     1020            if ( $depth > self::MAX_SANITIZE_DEPTH ) {
    6461021                return '[nested array]';
    6471022            }
     
    6541029                    if ( self::is_sensitive_key( (string) $key ) ) {
    6551030                        $sanitized[ $key ] = '[REDACTED - Sensitive Data]';
    656                     } elseif ( is_string( $value ) && mb_strlen( $value ) > 255 ) {
    657                         $sanitized[ $key ] = mb_substr( $value, 0, 255 ) . '... (truncated)';
     1031                    } elseif ( is_string( $value ) && mb_strlen( $value ) > self::MAX_STRING_LENGTH ) {
     1032                        $sanitized[ $key ] = mb_substr( $value, 0, self::MAX_STRING_LENGTH ) . '... (truncated)';
    6581033                    } else {
    6591034                        $sanitized[ $key ] = $value;
     
    6821057         */
    6831058        private static function normalize_object( $object, int $depth ) {
    684             if ( $depth > 2 ) {
     1059            if ( $depth > self::MAX_SANITIZE_DEPTH ) {
    6851060                return '[nested object]';
    6861061            }
     
    7081083
    7091084            // Limit to reasonable number of properties.
    710             if ( \count( $properties ) > 50 ) {
    711                 $properties                  = \array_slice( $properties, 0, 50, true );
     1085            if ( \count( $properties ) > self::MAX_OBJECT_PROPERTIES ) {
     1086                $properties                  = \array_slice( $properties, 0, self::MAX_OBJECT_PROPERTIES, true );
    7121087                $normalized['__truncated__'] = true;
    7131088            }
     
    7191094                    if ( self::is_sensitive_key( (string) $key ) ) {
    7201095                        $normalized[ $key ] = '[REDACTED - Sensitive Data]';
    721                     } elseif ( is_string( $value ) && mb_strlen( $value ) > 255 ) {
    722                         $normalized[ $key ] = mb_substr( $value, 0, 255 ) . '... (truncated)';
     1096                    } elseif ( is_string( $value ) && mb_strlen( $value ) > self::MAX_STRING_LENGTH ) {
     1097                        $normalized[ $key ] = mb_substr( $value, 0, self::MAX_STRING_LENGTH ) . '... (truncated)';
    7231098                    } else {
    7241099                        $normalized[ $key ] = $value;
     
    8691244
    8701245            foreach ( $enabled_hooks as $hook_config ) {
    871                 if ( empty( $hook_config['hook_name'] ) ) {
     1246                if ( empty( $hook_config['hook_name'] ) || ! self::is_valid_hook_name( $hook_config['hook_name'] ) ) {
    8721247                    continue;
    8731248                }
     
    8841259
    8851260                if ( 'action' === $hook_type ) {
    886                     $content .= "add_action( '{$escaped_hook_name}', array( '\\ADVAN\\Controllers\\Hooks_Capture', 'capture_action' ), {$escaped_priority}, 10 );\n";
     1261                    $content .= "add_action( '{$escaped_hook_name}', array( '\\ADVAN\\Controllers\\Hooks_Capture', 'capture_action' ), {$escaped_priority}, " . self::get_accepted_args() . " );\n";
    8871262                } else {
    888                     $content .= "add_filter( '{$escaped_hook_name}', array( '\\ADVAN\\Controllers\\Hooks_Capture', 'capture_filter' ), {$escaped_priority}, 10 );\n";
     1263                    $content .= "add_filter( '{$escaped_hook_name}', array( '\\ADVAN\\Controllers\\Hooks_Capture', 'capture_filter' ), {$escaped_priority}, " . self::get_accepted_args() . " );\n";
    8891264                }
    8901265
     
    9541329            }
    9551330
    956             // Check user capabilities (only if function is available).
     1331            // Check user capabilities (only if WordPress is fully loaded and function is available).
    9571332            if ( ! self::is_cli() ) {
    958                 if ( ! \function_exists( 'current_user_can' ) || ! \current_user_can( 'manage_options' ) ) {
     1333                // During early WordPress loading, skip capability checks to avoid fatal errors.
     1334                if ( ! \did_action( 'init' ) || ! \function_exists( 'current_user_can' ) || ! \function_exists( 'wp_get_current_user' ) ) {
    9591335                    return false;
    9601336                }
     1337
     1338                if ( ! \current_user_can( 'manage_options' ) ) {
     1339                    return false;
     1340                }
    9611341            }
    9621342
    9631343            return self::generate_cache_file();
     1344        }
     1345
     1346        /**
     1347         * Get the number of arguments accepted by hook capture callbacks.
     1348         *
     1349         * @return int Number of accepted arguments.
     1350         *
     1351         * @since 4.5.0
     1352         */
     1353        private static function get_accepted_args(): int {
     1354            // Use a high number to capture all possible hook arguments.
     1355            return 99;
    9641356        }
    9651357
     
    9831375
    9841376        /**
    985          * Delete cache file.
     1377         * Maximum number of hook logs to store in memory before forcing a commit.
     1378         * This prevents excessive memory usage on long-running requests.
     1379         *
     1380         * @var int
     1381         *
     1382         * @since 4.6.1
     1383         */
     1384        private static $max_memory_logs = 1000;
     1385
     1386        /**
     1387         * Commit accumulated hook logs to the database at the end of the request.
     1388         *
     1389         * @return void
     1390         *
     1391         * @since 4.6.0
     1392         */
     1393        public static function commit_hook_logs() {
     1394            if ( empty( self::$hook_logs ) ) {
     1395                // self::debug_log( 'No hook logs to commit' );
     1396                return;
     1397            }
     1398
     1399            $log_count = count( self::$hook_logs );
     1400            // self::debug_log( 'Committing hook logs', array( 'count' => $log_count, 'request_id' => self::$request_id ) );
     1401
     1402            try {
     1403                foreach ( self::$hook_logs as $log ) {
     1404                    $log_entry = array(
     1405                        'blog_id'             => $log['blog_id'],
     1406                        'user_id'             => $log['user_id'],
     1407                        'user_login'          => $log['user_login'],
     1408                        'trigger_source'      => $log['trigger_source'],
     1409                        'request_id'          => self::$request_id,
     1410                        'hook_name'           => $log['hook_name'],
     1411                        'hook_type'           => $log['hook_type'],
     1412                        'parameters'          => $log['parameters'],
     1413                        'output'              => $log['output'],
     1414                        'backtrace'           => $log['backtrace'],
     1415                        'execution_time'      => $log['execution_time'],
     1416                        'memory_usage'        => $log['memory_usage'],
     1417                        'is_cli'              => $log['is_cli'],
     1418                        'hooks_management_id' => $log['hooks_management_id'],
     1419                        'count'               => $log['count'],
     1420                        'date_added'          => $log['date_added'],
     1421                    );
     1422
     1423                    Hooks_Capture_Entity::insert( $log_entry );
     1424                }
     1425            } catch ( \Exception $e ) {
     1426                // Log the error but don't let it break the request.
     1427                self::debug_log( 'Failed to commit hook logs', array( 'error' => $e->getMessage() ) );
     1428                if ( function_exists( 'error_log' ) ) {
     1429                    \error_log( 'Hooks Capture: Failed to commit logs - ' . $e->getMessage() );
     1430                }
     1431            } finally {
     1432                // Always clear the logs after attempting to commit.
     1433                self::$hook_logs = array();
     1434            }
     1435        }
     1436
     1437        /**
     1438         * Validate hook name for security.
     1439         *
     1440         * @param string $hook_name The hook name to validate.
     1441         *
     1442         * @return bool True if valid, false otherwise.
     1443         *
     1444         * @since 4.6.1
     1445         */
     1446        private static function is_valid_hook_name( string $hook_name ): bool {
     1447            // Basic validation: not empty, reasonable length, no dangerous characters.
     1448            if ( empty( $hook_name ) || strlen( $hook_name ) > self::MAX_HOOK_NAME_LENGTH ) {
     1449                return false;
     1450            }
     1451
     1452            // Allow alphanumeric, underscores, hyphens, slashes, and dots.
     1453            if ( ! preg_match( '/^[a-zA-Z0-9_\/\-\.]+$/', $hook_name ) ) {
     1454                return false;
     1455            }
     1456
     1457            return true;
     1458        }
     1459
     1460        /**
     1461         * Validate cache file content for security before evaluation.
     1462         *
     1463         * @param string $content Cache file content.
     1464         *
     1465         * @return bool True if content is safe to evaluate.
     1466         *
     1467         * @since 4.6.1
     1468         */
     1469        private static function is_valid_cache_content( string $content ): bool {
     1470            // Basic validation: check for expected PHP structure.
     1471            if ( empty( $content ) ) {
     1472                return false;
     1473            }
     1474
     1475            // Check for HTML content (indicates corrupted cache file).
     1476            if ( preg_match( '/<html|<head|<body|<div|<p|<span/i', $content ) ) {
     1477                self::debug_log( 'Cache content contains HTML - file is corrupted' );
     1478                return false;
     1479            }
     1480
     1481            // Check for dangerous PHP functions that shouldn't be in cache.
     1482            $dangerous_patterns = array(
     1483                '/exec\(/i',
     1484                '/system\(/i',
     1485                '/shell_exec\(/i',
     1486                '/passthru\(/i',
     1487                '/eval\(/i',
     1488                '/include\(/i',
     1489                '/require\(/i',
     1490                '/file_get_contents\(/i',
     1491                '/fopen\(/i',
     1492                '/\$\w+\s*\(/', // Variable function calls.
     1493            );
     1494
     1495            foreach ( $dangerous_patterns as $pattern ) {
     1496                if ( preg_match( $pattern, $content ) ) {
     1497                    self::debug_log( 'Cache content contains dangerous pattern', array( 'pattern' => $pattern ) );
     1498                    return false;
     1499                }
     1500            }
     1501
     1502            // Check that content contains expected PHP structure.
     1503            if ( ! preg_match( '/^<\?php/', $content ) ) {
     1504                self::debug_log( 'Cache content does not start with PHP opening tag' );
     1505                return false;
     1506            }
     1507
     1508            // Check for ABSPATH check (security measure).
     1509            if ( ! preg_match( '/defined\s*\(\s*[\'"]ABSPATH[\'"]\s*\)/', $content ) ) {
     1510                self::debug_log( 'Cache content missing ABSPATH security check' );
     1511                return false;
     1512            }
     1513
     1514            // Check that it contains our class reference (ensures it's our cache file).
     1515            if ( ! preg_match( '/ADVAN\\\\Controllers\\\\Hooks_Capture/', $content ) ) {
     1516                self::debug_log( 'Cache content does not reference our class' );
     1517                return false;
     1518            }
     1519
     1520            // Check that it contains hook registration calls.
     1521            if ( ! preg_match( '/add_(?:action|filter)\s*\(/', $content ) ) {
     1522                self::debug_log( 'Cache content does not contain hook registrations' );
     1523                return false;
     1524            }
     1525
     1526            // Basic PHP syntax check - try to parse the content.
     1527            try {
     1528                // Remove PHP opening tag for tokenization.
     1529                $code = preg_replace( '/^<\?php\s*/', '', $content );
     1530                if ( false === $code ) {
     1531                    self::debug_log( 'Failed to prepare code for syntax check' );
     1532                    return false;
     1533                }
     1534
     1535                // Use token_get_all to check for basic syntax validity.
     1536                $tokens = token_get_all( '<?php ' . $code );
     1537                if ( empty( $tokens ) ) {
     1538                    self::debug_log( 'Cache content tokenization failed' );
     1539                    return false;
     1540                }
     1541            } catch ( \Throwable $e ) {
     1542                self::debug_log( 'Cache content syntax check failed', array( 'error' => $e->getMessage() ) );
     1543                return false;
     1544            }
     1545
     1546            return true;
     1547        }
     1548
     1549        /**
     1550         * Collect performance metrics for monitoring.
     1551         *
     1552         * @param float $execution_time Hook execution time in seconds.
     1553         * @param int   $memory_usage Memory usage in bytes.
     1554         * @param int   $log_count Current number of logs in memory.
     1555         *
     1556         * @return void
     1557         *
     1558         * @since 4.6.1
     1559         */
     1560        private static function collect_performance_metrics( float $execution_time, int $memory_usage, int $log_count ) {
     1561            static $metrics = array(
     1562                'total_execution_time' => 0.0,
     1563                'total_memory_usage'   => 0,
     1564                'hook_count'           => 0,
     1565                'max_execution_time'   => 0.0,
     1566                'max_memory_usage'     => 0,
     1567                'avg_execution_time'   => 0.0,
     1568                'avg_memory_usage'     => 0,
     1569            );
     1570
     1571            $metrics['total_execution_time'] += $execution_time;
     1572            $metrics['total_memory_usage']   += $memory_usage;
     1573            ++$metrics['hook_count'];
     1574
     1575            if ( $execution_time > $metrics['max_execution_time'] ) {
     1576                $metrics['max_execution_time'] = $execution_time;
     1577            }
     1578
     1579            if ( $memory_usage > $metrics['max_memory_usage'] ) {
     1580                $metrics['max_memory_usage'] = $memory_usage;
     1581            }
     1582
     1583            $metrics['avg_execution_time'] = $metrics['total_execution_time'] / $metrics['hook_count'];
     1584            $metrics['avg_memory_usage']   = $metrics['total_memory_usage'] / $metrics['hook_count'];
     1585
     1586            // Log performance warnings if thresholds are exceeded.
     1587            if ( $execution_time > 0.1 ) { // More than 100ms.
     1588                self::debug_log(
     1589                    'Slow hook execution detected',
     1590                    array(
     1591                        'execution_time' => $execution_time,
     1592                        'memory_usage'   => $memory_usage,
     1593                        'log_count'      => $log_count,
     1594                    )
     1595                );
     1596            }
     1597
     1598            if ( $memory_usage > 1048576 ) { // More than 1MB.
     1599                self::debug_log(
     1600                    'High memory usage detected',
     1601                    array(
     1602                        'execution_time' => $execution_time,
     1603                        'memory_usage'   => $memory_usage,
     1604                        'log_count'      => $log_count,
     1605                    )
     1606                );
     1607            }
     1608
     1609            if ( $log_count > self::MAX_MEMORY_LOGS * 0.8 ) { // 80% of max capacity.
     1610                self::debug_log(
     1611                    'Approaching memory log limit',
     1612                    array(
     1613                        'log_count' => $log_count,
     1614                        'max_logs'  => self::MAX_MEMORY_LOGS,
     1615                    )
     1616                );
     1617            }
     1618        }
     1619
     1620        /**
     1621         * Log debug information for troubleshooting.
     1622         *
     1623         * @param string $message Debug message.
     1624         * @param array  $context Additional context data.
     1625         *
     1626         * @return void
     1627         *
     1628         * @since 4.6.1
     1629         */
     1630        private static function debug_log( string $message, array $context = array() ) {
     1631            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
     1632                $log_message = '[Hooks Capture] ' . $message;
     1633                if ( ! empty( $context ) ) {
     1634                    $log_message .= ' ' . wp_json_encode( $context );
     1635                }
     1636                error_log( $log_message );
     1637            }
     1638        }
     1639
     1640        /**
     1641         * Delete the existing cache file.
    9861642         *
    9871643         * @return bool True on success, false on failure.
     
    9981654            return @unlink( $cache_file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.unlink_unlink
    9991655        }
     1656
     1657        /**
     1658         * =======================================================================
     1659         * NEW FEATURES: Early Filtering, Memory Pool, Error Recovery, Sampling
     1660         * =======================================================================
     1661         */
     1662
     1663        /**
     1664         * Initialize memory pool for reusing structures.
     1665         *
     1666         * @return void
     1667         *
     1668         * @since latest
     1669         */
     1670        private static function init_memory_pool() {
     1671            if ( ! isset( $GLOBALS['advan_memory_pool'] ) ) {
     1672                $GLOBALS['advan_memory_pool'] = array(
     1673                    'backtrace_cache' => array(),
     1674                    'sanitized_data'  => array(),
     1675                    'json_cache'      => array(),
     1676                    'pool_size'       => 0,
     1677                    'max_pool_size'   => 100 * 1024 * 1024, // 100MB limit.
     1678                );
     1679            }
     1680        }
     1681
     1682        /**
     1683         * Get cached backtrace.
     1684         *
     1685         * @param string $key Cache key.
     1686         *
     1687         * @return array|null Cached backtrace or null if not found.
     1688         *
     1689         * @since latest
     1690         */
     1691        private static function get_cached_backtrace( string $key ) {
     1692            if ( isset( $GLOBALS['advan_memory_pool']['backtrace_cache'][ $key ] ) ) {
     1693                return $GLOBALS['advan_memory_pool']['backtrace_cache'][ $key ];
     1694            }
     1695            return null;
     1696        }
     1697
     1698        /**
     1699         * Set cached backtrace.
     1700         *
     1701         * @param string $key Cache key.
     1702         * @param array  $backtrace Backtrace data.
     1703         *
     1704         * @return void
     1705         *
     1706         * @since latest
     1707         */
     1708        private static function set_cached_backtrace( string $key, array $backtrace ) {
     1709            if ( ! isset( $GLOBALS['advan_memory_pool'] ) ) {
     1710                return;
     1711            }
     1712
     1713            // Check pool size limit.
     1714            $backtrace_size = strlen( \wp_json_encode( $backtrace ) );
     1715            if ( $GLOBALS['advan_memory_pool']['pool_size'] + $backtrace_size > $GLOBALS['advan_memory_pool']['max_pool_size'] ) {
     1716                // Clear oldest entries if pool is full.
     1717                array_shift( $GLOBALS['advan_memory_pool']['backtrace_cache'] );
     1718            }
     1719
     1720            $GLOBALS['advan_memory_pool']['backtrace_cache'][ $key ] = $backtrace;
     1721            $GLOBALS['advan_memory_pool']['pool_size']              += $backtrace_size;
     1722        }
     1723
     1724        /**
     1725         * Get cached JSON.
     1726         *
     1727         * @param string $key Cache key.
     1728         *
     1729         * @return string|null Cached JSON or null if not found.
     1730         *
     1731         * @since latest
     1732         */
     1733        private static function get_cached_json( string $key ) {
     1734            if ( isset( $GLOBALS['advan_memory_pool']['json_cache'][ $key ] ) ) {
     1735                return $GLOBALS['advan_memory_pool']['json_cache'][ $key ];
     1736            }
     1737            return null;
     1738        }
     1739
     1740        /**
     1741         * Set cached JSON.
     1742         *
     1743         * @param string $key  Cache key.
     1744         * @param string $json JSON data.
     1745         *
     1746         * @return void
     1747         *
     1748         * @since latest
     1749         */
     1750        private static function set_cached_json( string $key, string $json ) {
     1751            if ( ! isset( $GLOBALS['advan_memory_pool'] ) ) {
     1752                return;
     1753            }
     1754
     1755            $json_size = strlen( $json );
     1756            if ( $GLOBALS['advan_memory_pool']['pool_size'] + $json_size > $GLOBALS['advan_memory_pool']['max_pool_size'] ) {
     1757                array_shift( $GLOBALS['advan_memory_pool']['json_cache'] );
     1758            }
     1759
     1760            $GLOBALS['advan_memory_pool']['json_cache'][ $key ] = $json;
     1761            $GLOBALS['advan_memory_pool']['pool_size']         += $json_size;
     1762        }
     1763
     1764        /**
     1765         * Cleanup memory pool on shutdown.
     1766         *
     1767         * @return void
     1768         *
     1769         * @since latest
     1770         */
     1771        public static function cleanup_memory_pool() {
     1772            unset( $GLOBALS['advan_memory_pool'] );
     1773        }
     1774
     1775        /**
     1776         * Early filtering: Check if hook should be captured before processing.
     1777         *
     1778         * @param string $hook_name The hook name to check.
     1779         * @return bool True if hook should be captured, false otherwise.
     1780         */
     1781        private static function should_capture_hook_early( string $hook_name ): bool {
     1782            static $filtered_hooks = null;
     1783
     1784            // Initialize filtered hooks list on first call
     1785            if ( null === $filtered_hooks ) {
     1786                $filtered_hooks = apply_filters(
     1787                    'advan_excluded_hooks',
     1788                    array(
     1789                        // WordPress core hooks that are too frequent/noisy.
     1790                        'gettext',
     1791                        'gettext_with_context',
     1792                        'ngettext',
     1793                        'ngettext_with_context',
     1794                        'locale',
     1795                        'override_load_textdomain',
     1796                        'load_textdomain',
     1797                        'unload_textdomain',
     1798
     1799                        // Option hooks (very frequent).
     1800                        'pre_option_*',
     1801                        'default_option_*',
     1802                        'option_*',
     1803
     1804                        // Transient hooks (very frequent).
     1805                        'pre_transient_*',
     1806                        'transient_*',
     1807                        'set_transient_*',
     1808                        'delete_transient_*',
     1809
     1810                        // Cache hooks (very frequent).
     1811                        'pre_cache_*',
     1812                        'cache_*',
     1813
     1814                        // WP_Query hooks (can be very frequent).
     1815                        'pre_get_posts',
     1816                        'posts_where',
     1817                        'posts_join',
     1818                        'posts_orderby',
     1819                        'posts_fields',
     1820                        'posts_clauses',
     1821                        'posts_request',
     1822                        'posts_results',
     1823                        'posts_pre_query',
     1824
     1825                        // Meta hooks (very frequent).
     1826                        'get_*_metadata',
     1827                        'update_*_metadata',
     1828                        'add_*_metadata',
     1829                        'delete_*_metadata',
     1830
     1831                        // User hooks (frequent in some contexts).
     1832                        'get_user_metadata',
     1833                        'update_user_metadata',
     1834
     1835                        // Comment hooks (can be frequent).
     1836                        'wp_update_comment_count',
     1837                        'pre_get_comments',
     1838                        'comments_clauses',
     1839
     1840                        // Taxonomy hooks.
     1841                        'get_terms',
     1842                        'get_term',
     1843                        'get_*_terms',
     1844                    )
     1845                );
     1846            }
     1847
     1848            // Check if hook matches any filtered pattern.
     1849            foreach ( $filtered_hooks as $pattern ) {
     1850                if ( fnmatch( $pattern, $hook_name ) ) {
     1851                    return false; // Don't capture this hook.
     1852                }
     1853            }
     1854
     1855            return true;
     1856        }
     1857
     1858        /**
     1859         * Sampling: Check if hook should be sampled (for high-frequency hooks).
     1860         *
     1861         * @param string $hook_name The hook name to check.
     1862         *
     1863         * @return bool True if hook should be captured, false if skipped for sampling.
     1864         *
     1865         * @since latest
     1866         */
     1867        private static function should_sample_hook( string $hook_name ): bool {
     1868            static $hook_counters  = array();
     1869            static $sampling_rates = null;
     1870
     1871            // Initialize sampling rates on first call.
     1872            if ( null === $sampling_rates ) {
     1873                $sampling_rates = apply_filters(
     1874                    'advan_hook_sampling_rates',
     1875                    array(
     1876                        // Sample every Nth call for these hooks.
     1877                        'option_*'       => 100,  // Sample 1% of option hooks.
     1878                        'transient_*'    => 50,   // Sample 2% of transient hooks.
     1879                        'gettext*'       => 20,   // Sample 5% of gettext hooks.
     1880                        'get_*_metadata' => 10,   // Sample 10% of metadata hooks.
     1881                        'pre_get_posts'  => 5,    // Sample 20% of query hooks.
     1882                    )
     1883                );
     1884            }
     1885
     1886            // Check if this hook should be sampled.
     1887            foreach ( $sampling_rates as $pattern => $rate ) {
     1888                if ( fnmatch( $pattern, $hook_name ) ) {
     1889                    // Initialize counter for this hook.
     1890                    if ( ! isset( $hook_counters[ $hook_name ] ) ) {
     1891                        $hook_counters[ $hook_name ] = 0;
     1892                    }
     1893
     1894                    ++$hook_counters[ $hook_name ];
     1895
     1896                    // Only capture if counter is multiple of rate.
     1897                    if ( $hook_counters[ $hook_name ] % 0 !== $rate ) {
     1898                        return false; // Skip this call.
     1899                    }
     1900
     1901                    break; // Found matching pattern, no need to check others.
     1902                }
     1903            }
     1904
     1905            return true;
     1906        }
     1907
     1908        /**
     1909         * Safe JSON encoding with fallback for error recovery.
     1910         *
     1911         * @param mixed $data    Data to encode.
     1912         * @param mixed $fallback Fallback data if encoding fails.
     1913         *
     1914         * @return string JSON encoded data or fallback.
     1915         *
     1916         * @since latest
     1917         */
     1918        public static function safe_json_encode( $data, $fallback = null ) {
     1919            try {
     1920                $encoded = \wp_json_encode( $data );
     1921                if ( false === $encoded ) {
     1922                    throw new \Exception( 'JSON encoding failed' );
     1923                }
     1924                return $encoded;
     1925            } catch ( \Exception $e ) {
     1926                // Log the error.
     1927                if ( function_exists( 'error_log' ) ) {
     1928                    error_log( 'Hooks Capture: JSON encoding failed - ' . $e->getMessage() );
     1929                }
     1930
     1931                // Return fallback or simplified data.
     1932                if ( null !== $fallback ) {
     1933                    return \wp_json_encode( $fallback );
     1934                }
     1935
     1936                // Create a simplified version.
     1937                if ( is_array( $data ) ) {
     1938                    return \wp_json_encode(
     1939                        array(
     1940                            'error' => 'JSON encoding failed',
     1941                            'type'  => 'array',
     1942                            'count' => count( $data ),
     1943                        )
     1944                    );
     1945                } elseif ( is_object( $data ) ) {
     1946                    return \wp_json_encode(
     1947                        array(
     1948                            'error' => 'JSON encoding failed',
     1949                            'type'  => 'object',
     1950                            'class' => get_class( $data ),
     1951                        )
     1952                    );
     1953                } else {
     1954                    return \wp_json_encode(
     1955                        array(
     1956                            'error'   => 'JSON encoding failed',
     1957                            'type'    => gettype( $data ),
     1958                            'content' => substr( (string) $data, 0, 100 ),
     1959                        )
     1960                    );
     1961                }
     1962            }
     1963        }
     1964
     1965        /**
     1966         * Safe JSON decoding with error handling for error recovery.
     1967         *
     1968         * @param string $data     Data to decode.
     1969         * @param mixed  $fallback Fallback data if decoding fails.
     1970         *
     1971         * @return mixed Decoded data or fallback.
     1972         *
     1973         * @since latest
     1974         */
     1975        public static function safe_json_decode( string $data, $fallback = null ) {
     1976            if ( empty( $data ) ) {
     1977                return $fallback ?: array();
     1978            }
     1979
     1980            try {
     1981                $decoded = \wp_json_decode( $data, true );
     1982                if ( null === $decoded && 'null' !== $data ) {
     1983                    throw new \Exception( 'JSON decoding failed' );
     1984                }
     1985                return $decoded;
     1986            } catch ( \Exception $e ) {
     1987                // Log the error.
     1988                if ( function_exists( 'error_log' ) ) {
     1989                    error_log( 'Hooks Capture: JSON decoding failed - ' . $e->getMessage() );
     1990                }
     1991
     1992                // Return fallback.
     1993                return $fallback ?: array( 'error' => 'JSON decoding failed' );
     1994            }
     1995        }
     1996
     1997        /**
     1998         * Safe serialization with fallback for error recovery.
     1999         * DEPRECATED: Use safe_json_encode() instead for security.
     2000         *
     2001         * @param mixed $data    Data to serialize.
     2002         * @param mixed $fallback Fallback data if serialization fails.
     2003         *
     2004         * @return string Serialized data or fallback.
     2005         *
     2006         * @throws \Exception
     2007         *
     2008         * @deprecated Use safe_json_encode() instead
     2009         * @since latest
     2010         */
     2011        public static function safe_serialize( $data, $fallback = null ) {
     2012            // Log deprecation warning.
     2013            if ( function_exists( 'error_log' ) ) {
     2014                error_log( 'Hooks Capture: safe_serialize() is deprecated. Use safe_json_encode() instead.' );
     2015            }
     2016
     2017            // Fall back to JSON encoding for security.
     2018            return self::safe_json_encode( $data, $fallback );
     2019        }
     2020
     2021        /**
     2022         * Safe unserialization with error handling for error recovery.
     2023         * DEPRECATED: Use safe_json_decode() instead for security.
     2024         *
     2025         * @param string $data     Data to unserialize.
     2026         * @param mixed  $fallback Fallback data if unserialization fails.
     2027         *
     2028         * @return mixed Unserialized data or fallback.
     2029         *
     2030         * @throws \Exception
     2031         *
     2032         * @deprecated Use safe_json_decode() instead
     2033         * @since latest
     2034         */
     2035        public static function safe_unserialize( string $data, $fallback = null ) {
     2036            // Log deprecation warning.
     2037            if ( function_exists( 'error_log' ) ) {
     2038                error_log( 'Hooks Capture: safe_unserialize() is deprecated. Use safe_json_decode() instead.' );
     2039            }
     2040
     2041            // Fall back to JSON decoding for security.
     2042            return self::safe_json_decode( $data, $fallback );
     2043        }
    10002044    }
    10012045}
  • 0-day-analytics/tags/4.6.0/classes/vendor/controllers/class-wp-mail-log.php

    r3413453 r3448917  
    417417         * @since 3.0.0
    418418         */
    419         private static function get_backtrace( $function_name = 'wp_mail' ): ?array {
     419        private static function get_backtrace( $function_name = 'wp_mail' ) {
    420420            $backtrace_segment = null;
    421421
  • 0-day-analytics/tags/4.6.0/classes/vendor/entities/class-hooks-capture-entity.php

    r3442115 r3448917  
    1212namespace ADVAN\Entities;
    1313
     14use ADVAN\Entities_Global\Common_Table;
     15
    1416// Exit if accessed directly.
    1517if ( ! defined( 'ABSPATH' ) ) {
     
    4446            'hook_type'           => 'string',
    4547            'trigger_source'      => 'string',
     48            'request_id'          => 'string',
    4649            'user_id'             => 'int',
    4750            'user_login'          => 'string',
     
    5356            'is_cli'              => 'int',
    5457            'hooks_management_id' => 'int',
     58            'count'               => 'int',
    5559            'date_added'          => 'float',
    5660        );
     
    6973            'hook_type'           => 'action',
    7074            'trigger_source'      => '',
     75            'request_id'          => '',
    7176            'user_id'             => 0,
    7277            'user_login'          => '',
     
    7883            'is_cli'              => 0,
    7984            'hooks_management_id' => 0,
     85            'count'               => 1,
    8086            'date_added'          => 0.0,
    8187        );
     
    110116                    hook_type VARCHAR(10) NOT NULL DEFAULT "action",
    111117                    trigger_source VARCHAR(50) NOT NULL DEFAULT "",
     118                    request_id VARCHAR(50) NOT NULL DEFAULT "",
    112119                    user_id BIGINT unsigned NOT NULL DEFAULT 0,
    113120                    user_login VARCHAR(60) NOT NULL DEFAULT "",
     
    119126                    is_cli TINYINT(1) NOT NULL DEFAULT 0,
    120127                    hooks_management_id BIGINT unsigned NOT NULL DEFAULT 0,
     128                    count INT NOT NULL DEFAULT 1,
    121129                    date_added DOUBLE NOT NULL DEFAULT 0,
    122130                PRIMARY KEY (id),
     
    125133                KEY `hook_type` (`hook_type`),
    126134                KEY `trigger_source` (`trigger_source`),
     135                KEY `request_id` (`request_id`),
    127136                KEY `user_id` (`user_id`),
    128137                KEY `date_added` (`date_added`),
     
    152161                'memory_usage'   => __( 'Memory', '0-day-analytics' ),
    153162                'is_cli'         => __( 'CLI', '0-day-analytics' ),
     163                'count'          => __( 'Count', '0-day-analytics' ),
    154164                'parameters'     => __( 'Parameters', '0-day-analytics' ),
    155165            );
     
    165175            return $columns;
    166176        }
     177
     178        /**
     179         * Alters the table to add request_id column for version 4.6.0.
     180         *
     181         * @return void
     182         *
     183         * @since 4.6.0
     184         */
     185        public static function alter_table_460() {
     186            $table_name = self::get_table_name();
     187
     188            if ( ! Common_Table::check_table_exists( $table_name ) ) {
     189                return;
     190            }
     191
     192            $connection = self::get_connection();
     193
     194            // Check if request_id column already exists.
     195            $columns = $connection->get_results( "SHOW COLUMNS FROM `{$table_name}` LIKE 'request_id'" );
     196
     197            if ( empty( $columns ) ) {
     198                // Add the request_id column.
     199                $alter_sql = "ALTER TABLE `{$table_name}` ADD COLUMN `request_id` VARCHAR(50) NOT NULL DEFAULT '' AFTER `trigger_source`";
     200                $connection->query( $alter_sql );
     201
     202                // Add index for the new column.
     203                $index_sql = "ALTER TABLE `{$table_name}` ADD KEY `request_id` (`request_id`)";
     204                $connection->query( $index_sql );
     205            }
     206
     207            // Check if count column already exists.
     208            $columns_count = $connection->get_results( "SHOW COLUMNS FROM `{$table_name}` LIKE 'count'" );
     209
     210            if ( empty( $columns_count ) ) {
     211                // Add the count column.
     212                $alter_sql_count = "ALTER TABLE `{$table_name}` ADD COLUMN `count` INT NOT NULL DEFAULT 1 AFTER `hooks_management_id`";
     213                $connection->query( $alter_sql_count );
     214            }
     215        }
    167216    }
    168217}
  • 0-day-analytics/tags/4.6.0/classes/vendor/helpers/class-system-analytics.php

    r3442115 r3448917  
    462462                    .advan-stat-label {font-weight:600;}
    463463                    .advan-stat-value {font-size:16px;margin-top:3px;}
    464                     .advan-info-section {margin-top:20px; padding:10px; background:#f9f9f9; border-radius:5px;}
     464                    .advan-info-section {margin-top:20px; padding:10px; border-radius:5px;}
    465465                    .advan-info-item {margin-bottom:5px;}
    466466                </style>
  • 0-day-analytics/tags/4.6.0/classes/vendor/lists/class-hooks-capture-list.php

    r3442473 r3448917  
    411411                if ( ! empty( $hooks_management_results ) ) {
    412412                    $hooks_management_ids = array_column( $hooks_management_results, 'id' );
    413                     $placeholders = implode( ',', array_fill( 0, count( $hooks_management_ids ), '%d' ) );
    414                     $where_sql_parts[] = 'hooks_management_id IN (' . $placeholders . ')';
    415                     $where_args = array_merge( $where_args, $hooks_management_ids );
     413                    $placeholders         = implode( ',', array_fill( 0, count( $hooks_management_ids ), '%d' ) );
     414                    $where_sql_parts[]    = 'hooks_management_id IN (' . $placeholders . ')';
     415                    $where_args           = array_merge( $where_args, $hooks_management_ids );
    416416                }
    417417            }
     
    745745                    $hook_label = Hooks_Management_Entity::get_hook_label( $item[ $column_name ] );
    746746                    $hook_name  = '<code>' . \esc_html( $item[ $column_name ] ) . '</code>';
    747                     $display    = $hook_label ? '<strong>' . \esc_html( $hook_label ) . '</strong> ' . $hook_name : $hook_name;
     747
     748                    // Make hook name a link to hooks management if hooks_management_id is available
     749                    if ( ! empty( $item['hooks_management_id'] ) ) {
     750                        $edit_url  = \network_admin_url( 'admin.php?page=advan_hooks_management&action=edit&id=' . absint( $item['hooks_management_id'] ) );
     751                        $hook_name = '<code><a href="' . \esc_url( $edit_url ) . '" title="' . \esc_attr__( 'Edit hook in Hooks Management', '0-day-analytics' ) . '">' . \esc_html( $item[ $column_name ] ) . '</a></code>';
     752                    }
     753
     754                    // Check for post-related hooks and add post type information.
     755                    $post_type_info = '';
     756                    if ( self::is_post_related_hook( $item[ $column_name ] ) && ! empty( $item['parameters'] ) ) {
     757                        $post_type = self::extract_post_type_from_parameters( $item['parameters'] );
     758                        if ( $post_type ) {
     759                            $post_type_info = ' <code style="font-weight: normal;">(<b>' . \esc_html( $post_type ) . '</b>)</code>';
     760                        }
     761                    }
     762
     763                    $display = $hook_label ? '<strong>' . \esc_html( $hook_label ) . $post_type_info . '</strong> ' . $hook_name : $hook_name;
    748764
    749765                    // Add row actions.
     
    764780                    );
    765781
     782                    // Add disable hook action if applicable.
     783                    $actions = self::add_disable_hook_action( $actions, $item );
     784
    766785                    return sprintf(
    767786                        '%s %s',
     
    781800                case 'is_cli':
    782801                    return ! empty( $item[ $column_name ] ) ? '<span class="dashicons dashicons-yes"></span>' : '<span class="dashicons dashicons-no"></span>';
     802
     803                case 'count':
     804                    $count = isset( $item[ $column_name ] ) ? (int) $item[ $column_name ] : 1;
     805                    if ( $count > 1 ) {
     806                        return '<span class="badge badge-warning">' . \esc_html( $count ) . '</span>';
     807                    }
     808                    return \esc_html( $count );
    783809
    784810                case 'parameters':
     
    864890            echo '</tr>';
    865891        }
     892
     893        /**
     894         * Check if a hook is post-related.
     895         *
     896         * @param string $hook_name The hook name to check.
     897         *
     898         * @return bool True if post-related, false otherwise.
     899         *
     900         * @since 4.6.1
     901         */
     902        private static function is_post_related_hook( string $hook_name ): bool {
     903            $post_related_hooks = array(
     904                'wp_insert_post',
     905                'wp_update_post',
     906                'wp_delete_post',
     907                'save_post',
     908                'publish_post',
     909                'transition_post_status',
     910                'before_delete_post',
     911                'after_delete_post',
     912                'post_updated',
     913                'edit_post',
     914                'delete_post',
     915            );
     916
     917            return in_array( $hook_name, $post_related_hooks, true );
     918        }
     919
     920        /**
     921         * Extract post type from hook parameters.
     922         *
     923         * @param string $parameters_json JSON-encoded parameters.
     924         *
     925         * @return string|null Post type if found, null otherwise.
     926         *
     927         * @since 4.6.1
     928         */
     929        private static function extract_post_type_from_parameters( string $parameters_json ): ?string {
     930            if ( empty( $parameters_json ) ) {
     931                return null;
     932            }
     933
     934            $parameters = json_decode( $parameters_json, true );
     935            if ( ! is_array( $parameters ) || empty( $parameters ) ) {
     936                return null;
     937            }
     938
     939            // Try different parameter positions and structures.
     940            foreach ( $parameters as $param ) {
     941                // Check if parameter is an array/object with post_type.
     942                if ( is_array( $param ) && isset( $param['post_type'] ) ) {
     943                    return $param['post_type'];
     944                }
     945
     946                // Check if parameter is an object with post_type property.
     947                if ( is_array( $param ) && isset( $param['__class__'] ) && isset( $param['post_type'] ) ) {
     948                    return $param['post_type'];
     949                }
     950
     951                // Check if parameter is a post ID and try to get post type from database.
     952                if ( is_numeric( $param ) && $param > 0 ) {
     953                    $post = \get_post( (int) $param );
     954                    if ( $post && isset( $post->post_type ) ) {
     955                        return $post->post_type;
     956                    }
     957                }
     958            }
     959
     960            return null;
     961        }
     962
     963        /**
     964         * =======================================================================
     965         * NEW FEATURES: Clear All Logs Button & Disable Hook Actions
     966         * =======================================================================
     967         */
     968
     969        /**
     970         * Initialize admin hooks for new features.
     971         *
     972         * @return void
     973         */
     974        public static function init_admin_hooks() {
     975            // Clear logs functionality.
     976            \add_action( 'admin_notices', array( __CLASS__, 'render_clear_logs_button' ) );
     977            \add_action( 'admin_post_clear_hooks_logs', array( __CLASS__, 'handle_clear_logs' ) );
     978
     979            // Disable/enable hook functionality.
     980            \add_action( 'admin_post_disable_hook_capture', array( __CLASS__, 'handle_disable_hook' ) );
     981            \add_action( 'admin_post_enable_hook_capture', array( __CLASS__, 'handle_enable_hook' ) );
     982        }
     983
     984        /**
     985         * Render the clear logs button in admin notices.
     986         *
     987         * @return void
     988         */
     989        public static function render_clear_logs_button() {
     990            $screen = \get_current_screen();
     991
     992            if ( ! $screen || ! \in_array( $screen->id, array( '0-day_page_advan_hooks_capture' ), true ) ) {
     993                return;
     994            }
     995
     996            if ( ! \current_user_can( 'manage_options' ) ) {
     997                return;
     998            }
     999
     1000            $logs_count = self::get_logs_count();
     1001            if ( 0 === $logs_count ) {
     1002                return;
     1003            }
     1004
     1005            ?>
     1006            <div class="notice">
     1007                <p>
     1008                    <strong><?php \esc_html_e( 'Hooks Capture', '0-day-analytics' ); ?></strong>
     1009                    <?php
     1010                    printf(
     1011                        /* translators: %d: number of logs */
     1012                        \esc_html__( 'Currently tracking %d hook executions.', '0-day-analytics' ),
     1013                        \number_format_i18n( $logs_count )
     1014                    );
     1015                    ?>
     1016                    <a href="<?php echo \esc_url( \wp_nonce_url( \network_admin_url( 'admin-post.php?action=clear_hooks_logs' ), 'clear_hooks_logs' ) ); ?>"
     1017                        class="button button-secondary"
     1018                        onclick="return confirm('<?php \esc_attr_e( 'Are you sure you want to clear all hook logs? This action cannot be undone.', '0-day-analytics' ); ?>')">
     1019                        <?php \esc_html_e( 'Clear All Logs', '0-day-analytics' ); ?>
     1020                    </a>
     1021                </p>
     1022            </div>
     1023            <?php
     1024        }
     1025
     1026        /**
     1027         * Handle the clear logs action.
     1028         *
     1029         * @return void
     1030         */
     1031        public static function handle_clear_logs() {
     1032            if ( ! \current_user_can( 'manage_options' ) ) {
     1033                \wp_die( \esc_html__( 'Insufficient permissions.', '0-day-analytics' ) );
     1034            }
     1035
     1036            \check_admin_referer( 'clear_hooks_logs' );
     1037
     1038            try {
     1039                // Use the proper architectural method to truncate the table.
     1040                Common_Table::truncate_table( null, Hooks_Capture_Entity::get_table_name() );
     1041
     1042                // Clear any cached data.
     1043                \wp_cache_flush();
     1044
     1045                // Add success message.
     1046                \add_action(
     1047                    'admin_notices',
     1048                    function() {
     1049                        ?>
     1050                    <div class="notice notice-success is-dismissible">
     1051                        <p><?php \esc_html_e( 'All hook logs have been cleared successfully.', '0-day-analytics' ); ?></p>
     1052                    </div>
     1053                        <?php
     1054                    }
     1055                );
     1056            } catch ( \Exception $e ) {
     1057                // Add error message.
     1058                \add_action(
     1059                    'admin_notices',
     1060                    function() {
     1061                        ?>
     1062                    <div class="notice notice-error is-dismissible">
     1063                        <p><?php \esc_html_e( 'Failed to clear hook logs. Please try again.', '0-day-analytics' ); ?></p>
     1064                    </div>
     1065                        <?php
     1066                    }
     1067                );
     1068            }
     1069
     1070            // Redirect back to the hooks capture page.
     1071            \wp_redirect( \network_admin_url( 'admin.php?page=advan_hooks_capture' ) );
     1072            exit;
     1073        }
     1074
     1075        /**
     1076         * Get the total count of logs.
     1077         *
     1078         * @return int
     1079         */
     1080        private static function get_logs_count() {
     1081            // Use the proper architectural method through the entity class.
     1082            return Hooks_Capture_Entity::count( '1=%d', array( 1 ) );
     1083        }
     1084
     1085        /**
     1086         * Add disable/enable hook action to row actions.
     1087         *
     1088         * @param array $actions Existing actions.
     1089         * @param array $item    The current item.
     1090         * @return array Modified actions.
     1091         */
     1092        public static function add_disable_hook_action( $actions, $item ) {
     1093            if ( ! empty( $item['hooks_management_id'] ) && \current_user_can( 'manage_options' ) ) {
     1094                // Load the hook configuration to check if it's enabled or disabled.
     1095                $hook_config = Hooks_Management_Entity::load( 'id=%d', array( $item['hooks_management_id'] ) );
     1096                if ( ! $hook_config ) {
     1097                    return $actions;
     1098                }
     1099
     1100                $is_enabled = isset( $hook_config['enabled'] ) ? (bool) $hook_config['enabled'] : true;
     1101
     1102                if ( $is_enabled ) {
     1103                    // Hook is enabled, show disable action.
     1104                    $action_url = \wp_nonce_url(
     1105                        \network_admin_url( 'admin-post.php?action=disable_hook_capture&id=' . \absint( $item['hooks_management_id'] ) ),
     1106                        'disable_hook_capture_' . $item['hooks_management_id']
     1107                    );
     1108
     1109                    $actions['disable_hook'] = \sprintf(
     1110                        '<a href="%s" onclick="return confirm(\'%s\')" style="color: #dc3232;">%s</a>',
     1111                        \esc_url( $action_url ),
     1112                        \esc_js( __( 'Are you sure you want to disable this hook? It will stop being captured.', '0-day-analytics' ) ),
     1113                        __( 'Disable Hook', '0-day-analytics' )
     1114                    );
     1115                } else {
     1116                    // Hook is disabled, show enable action.
     1117                    $action_url = \wp_nonce_url(
     1118                        \network_admin_url( 'admin-post.php?action=enable_hook_capture&id=' . \absint( $item['hooks_management_id'] ) ),
     1119                        'enable_hook_capture_' . $item['hooks_management_id']
     1120                    );
     1121
     1122                    $actions['enable_hook'] = \sprintf(
     1123                        '<a href="%s" onclick="return confirm(\'%s\')" style="color: #007cba;">%s</a>',
     1124                        \esc_url( $action_url ),
     1125                        \esc_js( __( 'Are you sure you want to enable this hook? It will start being captured again.', '0-day-analytics' ) ),
     1126                        __( 'Enable Hook', '0-day-analytics' )
     1127                    );
     1128                }
     1129            }
     1130
     1131            return $actions;
     1132        }
     1133
     1134        /**
     1135         * Handle disable hook action.
     1136         *
     1137         * @return void
     1138         */
     1139        public static function handle_disable_hook() {
     1140            if ( ! \current_user_can( 'manage_options' ) ) {
     1141                \wp_die( \esc_html__( 'Insufficient permissions.', '0-day-analytics' ) );
     1142            }
     1143
     1144            $hook_id = isset( $_GET['id'] ) ? \absint( $_GET['id'] ) : 0;
     1145            if ( ! $hook_id ) {
     1146                \wp_die( \esc_html__( 'Invalid hook ID.', '0-day-analytics' ) );
     1147            }
     1148
     1149            \check_admin_referer( 'disable_hook_capture_' . $hook_id );
     1150
     1151            // Load the hook configuration.
     1152            $hook_config = Hooks_Management_Entity::load( 'id=%d', array( $hook_id ) );
     1153            if ( ! $hook_config ) {
     1154                \wp_die( \esc_html__( 'Hook not found.', '0-day-analytics' ) );
     1155            }
     1156
     1157            // Disable the hook by setting enabled to 0.
     1158            $result = Hooks_Management_Entity::insert( \array_merge( $hook_config, array( 'enabled' => 0 ) ) );
     1159
     1160            if ( $result ) {
     1161                // Clear cache to reflect changes.
     1162                \do_action( 'advan_hooks_management_updated' );
     1163
     1164                \add_action(
     1165                    'admin_notices',
     1166                    function() use ( $hook_config ) {
     1167                        ?>
     1168                    <div class="notice notice-success is-dismissible">
     1169                        <p>
     1170                            <?php
     1171                            printf(
     1172                                /* translators: %s: hook name */
     1173                                \esc_html__( 'Hook "%s" has been disabled successfully.', '0-day-analytics' ),
     1174                                \esc_html( $hook_config['hook_name'] )
     1175                            );
     1176                            ?>
     1177                        </p>
     1178                    </div>
     1179                        <?php
     1180                    }
     1181                );
     1182            } else {
     1183                \add_action(
     1184                    'admin_notices',
     1185                    function() {
     1186                        ?>
     1187                    <div class="notice notice-error is-dismissible">
     1188                        <p><?php \esc_html_e( 'Failed to disable hook. Please try again.', '0-day-analytics' ); ?></p>
     1189                    </div>
     1190                        <?php
     1191                    }
     1192                );
     1193            }
     1194
     1195            \wp_redirect( \network_admin_url( 'admin.php?page=advan_hooks_capture' ) );
     1196            exit;
     1197        }
     1198
     1199        /**
     1200         * Handle enable hook action.
     1201         *
     1202         * @return void
     1203         */
     1204        public static function handle_enable_hook() {
     1205            if ( ! \current_user_can( 'manage_options' ) ) {
     1206                \wp_die( \esc_html__( 'Insufficient permissions.', '0-day-analytics' ) );
     1207            }
     1208
     1209            $hook_id = isset( $_GET['id'] ) ? \absint( $_GET['id'] ) : 0;
     1210            if ( ! $hook_id ) {
     1211                \wp_die( \esc_html__( 'Invalid hook ID.', '0-day-analytics' ) );
     1212            }
     1213
     1214            \check_admin_referer( 'enable_hook_capture_' . $hook_id );
     1215
     1216            // Load the hook configuration.
     1217            $hook_config = Hooks_Management_Entity::load( 'id=%d', array( $hook_id ) );
     1218            if ( ! $hook_config ) {
     1219                \wp_die( \esc_html__( 'Hook not found.', '0-day-analytics' ) );
     1220            }
     1221
     1222            // Enable the hook by setting enabled to 1.
     1223            $result = Hooks_Management_Entity::insert( \array_merge( $hook_config, array( 'enabled' => 1 ) ) );
     1224
     1225            if ( $result ) {
     1226                // Clear cache to reflect changes.
     1227                \do_action( 'advan_hooks_management_updated' );
     1228
     1229                \add_action(
     1230                    'admin_notices',
     1231                    function() use ( $hook_config ) {
     1232                        ?>
     1233                    <div class="notice notice-success is-dismissible">
     1234                        <p>
     1235                            <?php
     1236                            printf(
     1237                                /* translators: %s: hook name */
     1238                                \esc_html__( 'Hook "%s" has been enabled successfully.', '0-day-analytics' ),
     1239                                \esc_html( $hook_config['hook_name'] )
     1240                            );
     1241                            ?>
     1242                        </p>
     1243                    </div>
     1244                        <?php
     1245                    }
     1246                );
     1247            } else {
     1248                \add_action(
     1249                    'admin_notices',
     1250                    function() {
     1251                        ?>
     1252                    <div class="notice notice-error is-dismissible">
     1253                        <p><?php \esc_html_e( 'Failed to enable hook. Please try again.', '0-day-analytics' ); ?></p>
     1254                    </div>
     1255                        <?php
     1256                    }
     1257                );
     1258            }
     1259
     1260            \wp_redirect( \network_admin_url( 'admin.php?page=advan_hooks_capture' ) );
     1261            exit;
     1262        }
    8661263    }
    8671264}
  • 0-day-analytics/tags/4.6.0/readme.txt

    r3442473 r3448917  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 4.5.2
     7Stable tag: 4.6.0
    88License: GPLv3 or later
    99License URI: https://www.gnu.org/licenses/gpl-3.0.txt
     
    9393== Changelog ==
    9494
     95= 4.6.0 =
     96* Hooks module improvements and small styling issues fixed.
     97
    9598= 4.5.2 =
    9699* Fixes problems with hooks quick actions - enable/disable. Fixed problem with showing human-readable data, when core object is captured, but only its ID is present.
  • 0-day-analytics/trunk/advanced-analytics.php

    r3442473 r3448917  
    1111 * Plugin Name:     0 Day Analytics
    1212 * Description:     Take full control of error log, crons, transients, plugins, requests, mails and DB tables.
    13  * Version:         4.5.2
     13 * Version:         4.6.0
    1414 * Author:          Stoil Dobrev
    1515 * Author URI:      https://github.com/sdobreff/
     
    3939// Constants.
    4040if ( ! defined( 'ADVAN_VERSION' ) ) {
    41     define( 'ADVAN_VERSION', '4.5.2' );
     41    define( 'ADVAN_VERSION', '4.6.0' );
    4242    define( 'ADVAN_TEXTDOMAIN', '0-day-analytics' );
    4343    define( 'ADVAN_NAME', '0 Day Analytics' );
  • 0-day-analytics/trunk/classes/class-advanced-analytics.php

    r3442115 r3448917  
    3131use ADVAN\Lists\Hooks_Capture_List;
    3232use ADVAN\Lists\Hooks_Management_List;
    33 use ADVAN\Controllers\Hooks_Capture;
    3433use ADVAN\Migration\Migration;
    3534use ADVAN\Controllers\Pointers;
    36 use ADVAN\Controllers\Cron_Jobs;
    3735use ADVAN\Helpers\Miscellaneous;
    3836use ADVAN\Lists\Transients_List;
     
    7169
    7270                \add_action( 'admin_init', array( __CLASS__, 'plugin_redirect' ) );
     71
     72                // Initialize hooks capture list admin features.
     73                Hooks_Capture_List::init_admin_hooks();
    7374
    7475                // Setup screen options. Needs to be here as admin_init hook is too late. Per page set is below.
  • 0-day-analytics/trunk/classes/migration/class-migration.php

    r3413453 r3448917  
    252252            }
    253253        }
     254
     255        /**
     256         * Migrates the plugin up-to version 4.6.0 (adds request_id column to hooks capture).
     257         *
     258         * @return void
     259         *
     260         * @since 4.6.0
     261         */
     262        public static function migrate_up_to_460() {
     263            if ( \class_exists( '\\ADVAN\\Entities\\Hooks_Capture_Entity' ) ) {
     264                if ( Common_Table::check_table_exists( \ADVAN\Entities\Hooks_Capture_Entity::get_table_name() ) && ! Common_Table::check_column( 'request_id', 'varchar(50)', \ADVAN\Entities\Hooks_Capture_Entity::get_table_name() ) ) {
     265                    \ADVAN\Entities\Hooks_Capture_Entity::alter_table_460();
     266                }
     267            }
     268        }
    254269    }
    255270}
  • 0-day-analytics/trunk/classes/vendor/controllers/class-hooks-capture.php

    r3442473 r3448917  
    3131
    3232        /**
     33         * Maximum depth for capturing nested hooks.
     34         *
     35         * @var int
     36         *
     37         * @since 4.5.0
     38         */
     39        private const MAX_CAPTURE_DEPTH = 3;
     40
     41        /**
     42         * Maximum number of hook logs to store in memory before forcing a commit.
     43         *
     44         * @var int
     45         *
     46         * @since 4.6.1
     47         */
     48        private const MAX_MEMORY_LOGS = 1000;
     49
     50        /**
     51         * Maximum size for parameter/output JSON strings (64KB).
     52         *
     53         * @var int
     54         *
     55         * @since 4.5.0
     56         */
     57        private const MAX_JSON_SIZE = 65536;
     58
     59        /**
     60         * Maximum length for string parameters before truncation.
     61         *
     62         * @var int
     63         *
     64         * @since 4.5.0
     65         */
     66        private const MAX_STRING_LENGTH = 255;
     67
     68        /**
     69         * Maximum depth for recursive array/object sanitization.
     70         *
     71         * @var int
     72         *
     73         * @since 4.5.0
     74         */
     75        private const MAX_SANITIZE_DEPTH = 2;
     76
     77        /**
     78         * Maximum number of backtrace frames to capture.
     79         *
     80         * @var int
     81         *
     82         * @since 4.5.0
     83         */
     84        private const MAX_BACKTRACE_FRAMES = 3;
     85
     86        /**
     87         * Maximum number of properties to capture from objects.
     88         *
     89         * @var int
     90         *
     91         * @since 4.5.0
     92         */
     93        private const MAX_OBJECT_PROPERTIES = 50;
     94
     95        /**
     96         * Maximum length for hook names.
     97         *
     98         * @var int
     99         *
     100         * @since 4.6.1
     101         */
     102        private const MAX_HOOK_NAME_LENGTH = 255;
     103
     104        /**
    33105         * Array of hooks currently being captured to prevent infinite loops.
    34106         *
     
    83155         */
    84156        private static $cache_dir_path = null;
     157
     158        /**
     159         * Unique request ID for grouping hook calls per request.
     160         *
     161         * @var string|null
     162         *
     163         * @since 4.5.0
     164         */
     165        private static $request_id = null;
     166
     167        /**
     168         * In-memory storage for hook logs to deduplicate per request.
     169         *
     170         * @var array
     171         *
     172         * @since 4.6.0
     173         */
     174        private static $hook_logs = array();
    85175
    86176        /**
     
    96186            }
    97187
    98             // In WP-CLI context, ensure hooks are attached properly
     188            // Generate unique request ID for this execution.
     189            self::$request_id = uniqid( 'req_', true );
     190
     191            // self::debug_log( 'Initializing hooks capture', array( 'request_id' => self::$request_id ) );
     192
     193            // In WP-CLI context, ensure hooks are attached properly.
    99194            if ( defined( 'WP_CLI' ) && WP_CLI ) {
    100195                self::attach_hooks_cli();
     
    109204            // Re-attach hooks after cache clear to pick up changes.
    110205            \add_action( 'advan_hooks_management_updated', array( __CLASS__, 'detach_and_reattach_hooks' ) );
     206
     207            // Commit hook logs at the end of the request.
     208            \add_action( 'shutdown', array( __CLASS__, 'commit_hook_logs' ) );
     209
     210            // =======================================================================
     211            // MEMORY POOL: Initialize memory pool for reusing structures
     212            // =======================================================================
     213            self::init_memory_pool();
     214
     215            // =======================================================================
     216            // ERROR RECOVERY: Setup error recovery for serialization failures
     217            // =======================================================================
     218            \add_filter( 'advan_serialize_hook_data', array( __CLASS__, 'safe_json_encode' ), 10, 2 );
     219            \add_filter( 'advan_unserialize_hook_data', array( __CLASS__, 'safe_json_decode' ), 10, 2 );
     220
     221            // Cleanup memory pool on shutdown.
     222            \add_action( 'shutdown', array( __CLASS__, 'cleanup_memory_pool' ), 999 );
    111223        }
    112224
     
    123235
    124236            // Regenerate cache file with latest hooks configuration.
    125             self::regenerate_cache_file();
     237            // Defer regeneration if WordPress isn't fully loaded yet.
     238            if ( ! \did_action( 'init' ) ) {
     239                \add_action( 'init', array( __CLASS__, 'regenerate_cache_file' ), 1 );
     240            } else {
     241                self::regenerate_cache_file();
     242            }
    126243        }
    127244
     
    167284         */
    168285        public static function attach_hooks_cli() {
    169             // In CLI context, always load from DB to ensure hooks are attached
     286            // In CLI context, always load from DB to ensure hooks are attached.
    170287            self::$enabled_hooks = Hooks_Management_Entity::get_enabled_hooks();
    171288
     
    176293            // Attach monitoring to each enabled hook.
    177294            foreach ( self::$enabled_hooks as $hook_config ) {
    178                 if ( empty( $hook_config['hook_name'] ) ) {
     295                if ( empty( $hook_config['hook_name'] ) || ! self::is_valid_hook_name( $hook_config['hook_name'] ) ) {
    179296                    continue;
    180297                }
     
    186303                // Use a high number of accepted args to capture all parameters.
    187304                if ( 'action' === $hook_type ) {
    188                     \add_action( $hook_name, array( __CLASS__, 'capture_action' ), $priority, 10 );
     305                    \add_action( $hook_name, array( __CLASS__, 'capture_action' ), $priority, self::get_accepted_args() );
    189306                } else {
    190                     \add_filter( $hook_name, array( __CLASS__, 'capture_filter' ), $priority, 10 );
     307                    \add_filter( $hook_name, array( __CLASS__, 'capture_filter' ), $priority, self::get_accepted_args() );
    191308                }
    192309            }
     
    224341            // Attach monitoring to each enabled hook.
    225342            foreach ( self::$enabled_hooks as $hook_config ) {
    226                 if ( empty( $hook_config['hook_name'] ) ) {
     343                if ( empty( $hook_config['hook_name'] ) || ! self::is_valid_hook_name( $hook_config['hook_name'] ) ) {
    227344                    continue;
    228345                }
     
    234351                // Use a high number of accepted args to capture all parameters.
    235352                if ( 'action' === $hook_type ) {
    236                     \add_action( $hook_name, array( __CLASS__, 'capture_action' ), $priority, 10 );
     353                    \add_action( $hook_name, array( __CLASS__, 'capture_action' ), $priority, self::get_accepted_args() );
    237354                } else {
    238                     \add_filter( $hook_name, array( __CLASS__, 'capture_filter' ), $priority, 10 );
     355                    \add_filter( $hook_name, array( __CLASS__, 'capture_filter' ), $priority, self::get_accepted_args() );
    239356                }
    240357            }
     
    286403         */
    287404        private static function log_hook( string $hook_name, string $hook_type, array $args, $output ) {
     405            // Validate hook name for security.
     406            if ( ! self::is_valid_hook_name( $hook_name ) ) {
     407                return;
     408            }
     409
     410            // =======================================================================
     411            // EARLY FILTERING: Filter out unwanted hooks before processing
     412            // =======================================================================
     413            if ( ! self::should_capture_hook_early( $hook_name ) ) {
     414                return;
     415            }
     416
     417            // =======================================================================
     418            // SAMPLING: Apply sampling for high-frequency hooks to reduce storage
     419            // =======================================================================
     420            if ( ! self::should_sample_hook( $hook_name ) ) {
     421                return;
     422            }
     423
    288424            // Prevent infinite loops.
    289425            if ( isset( self::$capturing_hooks[ $hook_name ] ) ) {
     
    292428
    293429            // Prevent excessive nesting.
    294             if ( self::$current_depth >= self::$max_depth ) {
     430            if ( self::$current_depth >= self::MAX_CAPTURE_DEPTH ) {
    295431                return;
    296432            }
     
    335471
    336472                    // Limit parameter size (max 64KB).
    337                     if ( strlen( $parameters_json ) > 65536 ) {
    338                         $parameters_json = substr( $parameters_json, 0, 65536 ) . '... [truncated]';
     473                    if ( strlen( $parameters_json ) > self::MAX_JSON_SIZE ) {
     474                        $parameters_json = substr( $parameters_json, 0, self::MAX_JSON_SIZE ) . '... [truncated]';
    339475                    }
    340476                }
     
    346482
    347483                    // Limit output size (max 64KB).
    348                     if ( strlen( $output_json ) > 65536 ) {
    349                         $output_json = substr( $output_json, 0, 65536 ) . '... [truncated]';
     484                    if ( strlen( $output_json ) > self::MAX_JSON_SIZE ) {
     485                        $output_json = substr( $output_json, 0, self::MAX_JSON_SIZE ) . '... [truncated]';
    350486                    }
    351487                }
     
    357493                $memory_usage   = memory_get_usage() - $start_memory;
    358494
    359                 // Prepare log entry.
    360                 $log_entry = array(
    361                     'blog_id'             => \is_multisite() ? \get_current_blog_id() : 0,
    362                     'user_id'             => $user_id,
    363                     'user_login'          => $user_login,
    364                     'trigger_source'      => $trigger_source,
    365                     'hook_name'           => $hook_name,
    366                     'hook_type'           => $hook_type,
    367                     'parameters'          => $parameters_json,
    368                     'output'              => $output_json,
    369                     'backtrace'           => \wp_json_encode( $backtrace ),
    370                     'execution_time'      => $execution_time,
    371                     'memory_usage'        => $memory_usage,
    372                     'is_cli'              => (int) self::is_cli(),
    373                     'hooks_management_id' => self::get_hook_management_id( $hook_name ),
    374                     'date_added'          => microtime( true ),
    375                 );
    376 
    377                 // Insert asynchronously if possible, synchronously as fallback.
    378                 if ( function_exists( 'wp_schedule_single_event' ) ) {
    379                     // For very high-traffic hooks, consider batching.
    380                     Hooks_Capture_Entity::insert( $log_entry );
     495                // Collect performance metrics for monitoring.
     496                self::collect_performance_metrics( $execution_time, $memory_usage, count( self::$hook_logs ) );
     497
     498                // Create unique key for deduplication based on hook name and args.
     499                // Use optimized key generation for better performance.
     500                $key = self::generate_deduplication_key( $hook_name, $args );
     501
     502                if ( ! isset( self::$hook_logs[ $key ] ) ) {
     503                    // Check if we've exceeded memory limits and force a commit if needed.
     504                    if ( count( self::$hook_logs ) >= self::MAX_MEMORY_LOGS ) {
     505                        self::commit_hook_logs();
     506                    }
     507
     508                    // Prepare log data.
     509                    $log_data = self::prepare_hook_log_data( $hook_name, $hook_type, $args, $output, $capture_args, $capture_output, $trigger_source, $user_id, $user_login, $backtrace, $execution_time, $memory_usage );
     510
     511                    // Store the log data in memory.
     512                    self::$hook_logs[ $key ] = $log_data;
    381513                } else {
    382                     Hooks_Capture_Entity::insert( $log_entry );
     514                    // Increment count for duplicate hook calls.
     515                    ++self::$hook_logs[ $key ]['count'];
    383516                }
    384517            } finally {
     
    465598         */
    466599        private static function get_backtrace(): array {
    467             // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
    468             $trace = \debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 5 );
     600            // Use Exception backtrace for better performance (faster than debug_backtrace).
     601            $trace = ( new \Exception( '' ) )->getTrace();
    469602
    470603            $simplified = array();
     
    484617                );
    485618
    486                 // Limit to 3 frames for performance.
    487                 if ( count( $simplified ) >= 3 ) {
     619                // Limit to configured number of frames for performance.
     620                if ( count( $simplified ) >= self::MAX_BACKTRACE_FRAMES ) {
    488621                    break;
    489622                }
     
    491624
    492625            return $simplified;
     626        }
     627
     628        /**
     629         * Generate optimized deduplication key for hook calls.
     630         *
     631         * @param string $hook_name Hook name.
     632         * @param array  $args Hook arguments.
     633         *
     634         * @return string Deduplication key.
     635         *
     636         * @since 4.6.1
     637         */
     638        private static function generate_deduplication_key( string $hook_name, array $args ): string {
     639            // For performance, use a simplified approach for common cases.
     640            $arg_signature = '';
     641
     642            // Limit to first few arguments to avoid expensive serialization.
     643            $max_args  = 3;
     644            $arg_count = 0;
     645
     646            foreach ( $args as $arg ) {
     647                if ( $arg_count >= $max_args ) {
     648                    break;
     649                }
     650
     651                if ( is_scalar( $arg ) ) {
     652                    $arg_signature .= (string) $arg . '|';
     653                } elseif ( is_array( $arg ) ) {
     654                    $arg_signature .= 'array(' . count( $arg ) . ')|';
     655                } elseif ( is_object( $arg ) ) {
     656                    $arg_signature .= 'object(' . get_class( $arg ) . ')|';
     657                } else {
     658                    $arg_signature .= gettype( $arg ) . '|';
     659                }
     660
     661                ++$arg_count;
     662            }
     663
     664            // Add count of remaining args if any.
     665            if ( count( $args ) > $max_args ) {
     666                $arg_signature .= '+' . ( count( $args ) - $max_args ) . 'more';
     667            }
     668
     669            return $hook_name . '_' . md5( $arg_signature );
     670        }
     671
     672        /**
     673         * Get module health status.
     674         *
     675         * @return array Health status information.
     676         *
     677         * @since 4.6.1
     678         */
     679        public static function get_health_status(): array {
     680            return self::health_check();
     681        }
     682
     683        /**
     684         * Force commit of pending hook logs.
     685         *
     686         * @return bool True on success.
     687         *
     688         * @since 4.6.1
     689         */
     690        public static function force_commit(): bool {
     691            if ( empty( self::$hook_logs ) ) {
     692                return true;
     693            }
     694
     695            self::commit_hook_logs();
     696            return empty( self::$hook_logs );
     697        }
     698
     699        /**
     700         * Get current performance metrics.
     701         *
     702         * @return array Performance metrics.
     703         *
     704         * @since 4.6.1
     705         */
     706        public static function get_performance_metrics(): array {
     707            return array(
     708                'memory_usage'  => memory_get_usage( true ),
     709                'peak_memory'   => memory_get_peak_usage( true ),
     710                'queued_logs'   => count( self::$hook_logs ),
     711                'max_logs'      => self::MAX_MEMORY_LOGS,
     712                'cache_enabled' => ! empty( self::get_cache_file_path() ),
     713                'request_id'    => self::$request_id,
     714            );
     715        }
     716
     717        /**
     718         * Perform health check for the hooks capture module.
     719         *
     720         * @return array Health check results.
     721         *
     722         * @since 4.6.1
     723         */
     724        public static function health_check(): array {
     725            $health = array(
     726                'status'    => 'healthy',
     727                'issues'    => array(),
     728                'metrics'   => array(),
     729                'timestamp' => time(),
     730            );
     731
     732            // Check memory usage.
     733            $memory_usage = memory_get_usage( true );
     734            $memory_limit = self::get_memory_limit_bytes();
     735
     736            if ( $memory_limit > 0 && $memory_usage > $memory_limit * 0.8 ) {
     737                $health['issues'][] = 'High memory usage detected';
     738                $health['status']   = 'warning';
     739            }
     740
     741            $health['metrics']['memory_usage'] = $memory_usage;
     742            $health['metrics']['memory_limit'] = $memory_limit;
     743
     744            // Check hook logs count.
     745            $log_count                        = count( self::$hook_logs );
     746            $health['metrics']['queued_logs'] = $log_count;
     747
     748            if ( $log_count > self::MAX_MEMORY_LOGS * 0.9 ) {
     749                $health['issues'][] = 'Approaching memory log limit';
     750                $health['status']   = 'warning';
     751            }
     752
     753            // Check cache file status.
     754            $cache_file = self::get_cache_file_path();
     755            if ( $cache_file ) {
     756                $health['metrics']['cache_file_exists']   = file_exists( $cache_file );
     757                $health['metrics']['cache_file_readable'] = is_readable( $cache_file );
     758
     759                if ( file_exists( $cache_file ) && is_readable( $cache_file ) ) {
     760                    $cache_content                         = file_get_contents( $cache_file );
     761                    $health['metrics']['cache_file_valid'] = self::is_valid_cache_content( $cache_content );
     762                }
     763            }
     764
     765            // Check database connectivity.
     766            try {
     767                $test_query                              = Hooks_Capture_Entity::load( '1=0' ); // Should return empty array.
     768                $health['metrics']['database_connected'] = true;
     769            } catch ( \Exception $e ) {
     770                $health['issues'][]                      = 'Database connectivity issue: ' . $e->getMessage();
     771                $health['status']                        = 'error';
     772                $health['metrics']['database_connected'] = false;
     773            }
     774
     775            return $health;
     776        }
     777
     778        /**
     779         * Get memory limit in bytes.
     780         *
     781         * @return int Memory limit in bytes, or 0 if unlimited.
     782         *
     783         * @since 4.6.1
     784         */
     785        private static function get_memory_limit_bytes(): int {
     786            $memory_limit = ini_get( 'memory_limit' );
     787
     788            if ( empty( $memory_limit ) || $memory_limit === '-1' ) {
     789                return 0; // Unlimited.
     790            }
     791
     792            $unit  = strtolower( substr( $memory_limit, -1 ) );
     793            $value = (int) substr( $memory_limit, 0, -1 );
     794
     795            switch ( $unit ) {
     796                case 'g':
     797                    return $value * 1024 * 1024 * 1024;
     798                case 'm':
     799                    return $value * 1024 * 1024;
     800                case 'k':
     801                    return $value * 1024;
     802                default:
     803                    return (int) $memory_limit;
     804            }
     805        }
     806
     807        /**
     808         * Prepare hook log data for storage.
     809         *
     810         * @param string $hook_name Hook name.
     811         * @param string $hook_type Hook type.
     812         * @param array  $args Hook arguments.
     813         * @param mixed  $output Hook output.
     814         * @param bool   $capture_args Whether to capture arguments.
     815         * @param bool   $capture_output Whether to capture output.
     816         * @param string $trigger_source Trigger source.
     817         * @param int    $user_id User ID.
     818         * @param string $user_login User login.
     819         * @param array  $backtrace Backtrace data.
     820         * @param float  $execution_time Execution time.
     821         * @param int    $memory_usage Memory usage.
     822         *
     823         * @return array Prepared log data.
     824         *
     825         * @since 4.6.1
     826         */
     827        private static function prepare_hook_log_data( string $hook_name, string $hook_type, array $args, $output, bool $capture_args, bool $capture_output, string $trigger_source, int $user_id, string $user_login, array $backtrace, float $execution_time, int $memory_usage ): array {
     828            // Capture parameters (with size limit for performance).
     829            $parameters_json = '';
     830            if ( $capture_args && ! empty( $args ) ) {
     831                $sanitized_args  = self::sanitize_args( $args, $hook_name );
     832                $parameters_json = \wp_json_encode( $sanitized_args );
     833
     834                // Limit parameter size (max 64KB).
     835                if ( strlen( $parameters_json ) > self::MAX_JSON_SIZE ) {
     836                    $parameters_json = substr( $parameters_json, 0, self::MAX_JSON_SIZE ) . '... [truncated]';
     837                }
     838            }
     839
     840            // Capture output (with size limit).
     841            $output_json = '';
     842            if ( $capture_output && null !== $output ) {
     843                $output_json = \wp_json_encode( self::sanitize_args( array( $output ) ) );
     844
     845                // Limit output size (max 64KB).
     846                if ( strlen( $output_json ) > self::MAX_JSON_SIZE ) {
     847                    $output_json = substr( $output_json, 0, self::MAX_JSON_SIZE ) . '... [truncated]';
     848                }
     849            }
     850
     851            return array(
     852                'blog_id'             => \is_multisite() ? \get_current_blog_id() : 0,
     853                'user_id'             => $user_id,
     854                'user_login'          => $user_login,
     855                'trigger_source'      => $trigger_source,
     856                'hook_name'           => $hook_name,
     857                'hook_type'           => $hook_type,
     858                'parameters'          => $parameters_json,
     859                'output'              => $output_json,
     860                'backtrace'           => \wp_json_encode( $backtrace ),
     861                'execution_time'      => $execution_time,
     862                'memory_usage'        => $memory_usage,
     863                'is_cli'              => (int) self::is_cli(),
     864                'hooks_management_id' => self::get_hook_management_id( $hook_name ),
     865                'count'               => 1,
     866                'date_added'          => microtime( true ),
     867            );
    493868        }
    494869
     
    612987                    if ( self::is_sensitive_key( (string) $key ) ) {
    613988                        $sanitized[ $key ] = '[REDACTED - Sensitive Data]';
    614                     } elseif ( is_string( $value ) && mb_strlen( $value ) > 255 ) {
    615                         $sanitized[ $key ] = mb_substr( $value, 0, 255 ) . '... (truncated)';
     989                    } elseif ( is_string( $value ) && mb_strlen( $value ) > self::MAX_STRING_LENGTH ) {
     990                        $sanitized[ $key ] = mb_substr( $value, 0, self::MAX_STRING_LENGTH ) . '... (truncated)';
    616991                    } else {
    617992                        $sanitized[ $key ] = $value;
     
    6431018         */
    6441019        private static function sanitize_args_recursive( array $args, int $depth ) {
    645             if ( $depth > 2 ) {
     1020            if ( $depth > self::MAX_SANITIZE_DEPTH ) {
    6461021                return '[nested array]';
    6471022            }
     
    6541029                    if ( self::is_sensitive_key( (string) $key ) ) {
    6551030                        $sanitized[ $key ] = '[REDACTED - Sensitive Data]';
    656                     } elseif ( is_string( $value ) && mb_strlen( $value ) > 255 ) {
    657                         $sanitized[ $key ] = mb_substr( $value, 0, 255 ) . '... (truncated)';
     1031                    } elseif ( is_string( $value ) && mb_strlen( $value ) > self::MAX_STRING_LENGTH ) {
     1032                        $sanitized[ $key ] = mb_substr( $value, 0, self::MAX_STRING_LENGTH ) . '... (truncated)';
    6581033                    } else {
    6591034                        $sanitized[ $key ] = $value;
     
    6821057         */
    6831058        private static function normalize_object( $object, int $depth ) {
    684             if ( $depth > 2 ) {
     1059            if ( $depth > self::MAX_SANITIZE_DEPTH ) {
    6851060                return '[nested object]';
    6861061            }
     
    7081083
    7091084            // Limit to reasonable number of properties.
    710             if ( \count( $properties ) > 50 ) {
    711                 $properties                  = \array_slice( $properties, 0, 50, true );
     1085            if ( \count( $properties ) > self::MAX_OBJECT_PROPERTIES ) {
     1086                $properties                  = \array_slice( $properties, 0, self::MAX_OBJECT_PROPERTIES, true );
    7121087                $normalized['__truncated__'] = true;
    7131088            }
     
    7191094                    if ( self::is_sensitive_key( (string) $key ) ) {
    7201095                        $normalized[ $key ] = '[REDACTED - Sensitive Data]';
    721                     } elseif ( is_string( $value ) && mb_strlen( $value ) > 255 ) {
    722                         $normalized[ $key ] = mb_substr( $value, 0, 255 ) . '... (truncated)';
     1096                    } elseif ( is_string( $value ) && mb_strlen( $value ) > self::MAX_STRING_LENGTH ) {
     1097                        $normalized[ $key ] = mb_substr( $value, 0, self::MAX_STRING_LENGTH ) . '... (truncated)';
    7231098                    } else {
    7241099                        $normalized[ $key ] = $value;
     
    8691244
    8701245            foreach ( $enabled_hooks as $hook_config ) {
    871                 if ( empty( $hook_config['hook_name'] ) ) {
     1246                if ( empty( $hook_config['hook_name'] ) || ! self::is_valid_hook_name( $hook_config['hook_name'] ) ) {
    8721247                    continue;
    8731248                }
     
    8841259
    8851260                if ( 'action' === $hook_type ) {
    886                     $content .= "add_action( '{$escaped_hook_name}', array( '\\ADVAN\\Controllers\\Hooks_Capture', 'capture_action' ), {$escaped_priority}, 10 );\n";
     1261                    $content .= "add_action( '{$escaped_hook_name}', array( '\\ADVAN\\Controllers\\Hooks_Capture', 'capture_action' ), {$escaped_priority}, " . self::get_accepted_args() . " );\n";
    8871262                } else {
    888                     $content .= "add_filter( '{$escaped_hook_name}', array( '\\ADVAN\\Controllers\\Hooks_Capture', 'capture_filter' ), {$escaped_priority}, 10 );\n";
     1263                    $content .= "add_filter( '{$escaped_hook_name}', array( '\\ADVAN\\Controllers\\Hooks_Capture', 'capture_filter' ), {$escaped_priority}, " . self::get_accepted_args() . " );\n";
    8891264                }
    8901265
     
    9541329            }
    9551330
    956             // Check user capabilities (only if function is available).
     1331            // Check user capabilities (only if WordPress is fully loaded and function is available).
    9571332            if ( ! self::is_cli() ) {
    958                 if ( ! \function_exists( 'current_user_can' ) || ! \current_user_can( 'manage_options' ) ) {
     1333                // During early WordPress loading, skip capability checks to avoid fatal errors.
     1334                if ( ! \did_action( 'init' ) || ! \function_exists( 'current_user_can' ) || ! \function_exists( 'wp_get_current_user' ) ) {
    9591335                    return false;
    9601336                }
     1337
     1338                if ( ! \current_user_can( 'manage_options' ) ) {
     1339                    return false;
     1340                }
    9611341            }
    9621342
    9631343            return self::generate_cache_file();
     1344        }
     1345
     1346        /**
     1347         * Get the number of arguments accepted by hook capture callbacks.
     1348         *
     1349         * @return int Number of accepted arguments.
     1350         *
     1351         * @since 4.5.0
     1352         */
     1353        private static function get_accepted_args(): int {
     1354            // Use a high number to capture all possible hook arguments.
     1355            return 99;
    9641356        }
    9651357
     
    9831375
    9841376        /**
    985          * Delete cache file.
     1377         * Maximum number of hook logs to store in memory before forcing a commit.
     1378         * This prevents excessive memory usage on long-running requests.
     1379         *
     1380         * @var int
     1381         *
     1382         * @since 4.6.1
     1383         */
     1384        private static $max_memory_logs = 1000;
     1385
     1386        /**
     1387         * Commit accumulated hook logs to the database at the end of the request.
     1388         *
     1389         * @return void
     1390         *
     1391         * @since 4.6.0
     1392         */
     1393        public static function commit_hook_logs() {
     1394            if ( empty( self::$hook_logs ) ) {
     1395                // self::debug_log( 'No hook logs to commit' );
     1396                return;
     1397            }
     1398
     1399            $log_count = count( self::$hook_logs );
     1400            // self::debug_log( 'Committing hook logs', array( 'count' => $log_count, 'request_id' => self::$request_id ) );
     1401
     1402            try {
     1403                foreach ( self::$hook_logs as $log ) {
     1404                    $log_entry = array(
     1405                        'blog_id'             => $log['blog_id'],
     1406                        'user_id'             => $log['user_id'],
     1407                        'user_login'          => $log['user_login'],
     1408                        'trigger_source'      => $log['trigger_source'],
     1409                        'request_id'          => self::$request_id,
     1410                        'hook_name'           => $log['hook_name'],
     1411                        'hook_type'           => $log['hook_type'],
     1412                        'parameters'          => $log['parameters'],
     1413                        'output'              => $log['output'],
     1414                        'backtrace'           => $log['backtrace'],
     1415                        'execution_time'      => $log['execution_time'],
     1416                        'memory_usage'        => $log['memory_usage'],
     1417                        'is_cli'              => $log['is_cli'],
     1418                        'hooks_management_id' => $log['hooks_management_id'],
     1419                        'count'               => $log['count'],
     1420                        'date_added'          => $log['date_added'],
     1421                    );
     1422
     1423                    Hooks_Capture_Entity::insert( $log_entry );
     1424                }
     1425            } catch ( \Exception $e ) {
     1426                // Log the error but don't let it break the request.
     1427                self::debug_log( 'Failed to commit hook logs', array( 'error' => $e->getMessage() ) );
     1428                if ( function_exists( 'error_log' ) ) {
     1429                    \error_log( 'Hooks Capture: Failed to commit logs - ' . $e->getMessage() );
     1430                }
     1431            } finally {
     1432                // Always clear the logs after attempting to commit.
     1433                self::$hook_logs = array();
     1434            }
     1435        }
     1436
     1437        /**
     1438         * Validate hook name for security.
     1439         *
     1440         * @param string $hook_name The hook name to validate.
     1441         *
     1442         * @return bool True if valid, false otherwise.
     1443         *
     1444         * @since 4.6.1
     1445         */
     1446        private static function is_valid_hook_name( string $hook_name ): bool {
     1447            // Basic validation: not empty, reasonable length, no dangerous characters.
     1448            if ( empty( $hook_name ) || strlen( $hook_name ) > self::MAX_HOOK_NAME_LENGTH ) {
     1449                return false;
     1450            }
     1451
     1452            // Allow alphanumeric, underscores, hyphens, slashes, and dots.
     1453            if ( ! preg_match( '/^[a-zA-Z0-9_\/\-\.]+$/', $hook_name ) ) {
     1454                return false;
     1455            }
     1456
     1457            return true;
     1458        }
     1459
     1460        /**
     1461         * Validate cache file content for security before evaluation.
     1462         *
     1463         * @param string $content Cache file content.
     1464         *
     1465         * @return bool True if content is safe to evaluate.
     1466         *
     1467         * @since 4.6.1
     1468         */
     1469        private static function is_valid_cache_content( string $content ): bool {
     1470            // Basic validation: check for expected PHP structure.
     1471            if ( empty( $content ) ) {
     1472                return false;
     1473            }
     1474
     1475            // Check for HTML content (indicates corrupted cache file).
     1476            if ( preg_match( '/<html|<head|<body|<div|<p|<span/i', $content ) ) {
     1477                self::debug_log( 'Cache content contains HTML - file is corrupted' );
     1478                return false;
     1479            }
     1480
     1481            // Check for dangerous PHP functions that shouldn't be in cache.
     1482            $dangerous_patterns = array(
     1483                '/exec\(/i',
     1484                '/system\(/i',
     1485                '/shell_exec\(/i',
     1486                '/passthru\(/i',
     1487                '/eval\(/i',
     1488                '/include\(/i',
     1489                '/require\(/i',
     1490                '/file_get_contents\(/i',
     1491                '/fopen\(/i',
     1492                '/\$\w+\s*\(/', // Variable function calls.
     1493            );
     1494
     1495            foreach ( $dangerous_patterns as $pattern ) {
     1496                if ( preg_match( $pattern, $content ) ) {
     1497                    self::debug_log( 'Cache content contains dangerous pattern', array( 'pattern' => $pattern ) );
     1498                    return false;
     1499                }
     1500            }
     1501
     1502            // Check that content contains expected PHP structure.
     1503            if ( ! preg_match( '/^<\?php/', $content ) ) {
     1504                self::debug_log( 'Cache content does not start with PHP opening tag' );
     1505                return false;
     1506            }
     1507
     1508            // Check for ABSPATH check (security measure).
     1509            if ( ! preg_match( '/defined\s*\(\s*[\'"]ABSPATH[\'"]\s*\)/', $content ) ) {
     1510                self::debug_log( 'Cache content missing ABSPATH security check' );
     1511                return false;
     1512            }
     1513
     1514            // Check that it contains our class reference (ensures it's our cache file).
     1515            if ( ! preg_match( '/ADVAN\\\\Controllers\\\\Hooks_Capture/', $content ) ) {
     1516                self::debug_log( 'Cache content does not reference our class' );
     1517                return false;
     1518            }
     1519
     1520            // Check that it contains hook registration calls.
     1521            if ( ! preg_match( '/add_(?:action|filter)\s*\(/', $content ) ) {
     1522                self::debug_log( 'Cache content does not contain hook registrations' );
     1523                return false;
     1524            }
     1525
     1526            // Basic PHP syntax check - try to parse the content.
     1527            try {
     1528                // Remove PHP opening tag for tokenization.
     1529                $code = preg_replace( '/^<\?php\s*/', '', $content );
     1530                if ( false === $code ) {
     1531                    self::debug_log( 'Failed to prepare code for syntax check' );
     1532                    return false;
     1533                }
     1534
     1535                // Use token_get_all to check for basic syntax validity.
     1536                $tokens = token_get_all( '<?php ' . $code );
     1537                if ( empty( $tokens ) ) {
     1538                    self::debug_log( 'Cache content tokenization failed' );
     1539                    return false;
     1540                }
     1541            } catch ( \Throwable $e ) {
     1542                self::debug_log( 'Cache content syntax check failed', array( 'error' => $e->getMessage() ) );
     1543                return false;
     1544            }
     1545
     1546            return true;
     1547        }
     1548
     1549        /**
     1550         * Collect performance metrics for monitoring.
     1551         *
     1552         * @param float $execution_time Hook execution time in seconds.
     1553         * @param int   $memory_usage Memory usage in bytes.
     1554         * @param int   $log_count Current number of logs in memory.
     1555         *
     1556         * @return void
     1557         *
     1558         * @since 4.6.1
     1559         */
     1560        private static function collect_performance_metrics( float $execution_time, int $memory_usage, int $log_count ) {
     1561            static $metrics = array(
     1562                'total_execution_time' => 0.0,
     1563                'total_memory_usage'   => 0,
     1564                'hook_count'           => 0,
     1565                'max_execution_time'   => 0.0,
     1566                'max_memory_usage'     => 0,
     1567                'avg_execution_time'   => 0.0,
     1568                'avg_memory_usage'     => 0,
     1569            );
     1570
     1571            $metrics['total_execution_time'] += $execution_time;
     1572            $metrics['total_memory_usage']   += $memory_usage;
     1573            ++$metrics['hook_count'];
     1574
     1575            if ( $execution_time > $metrics['max_execution_time'] ) {
     1576                $metrics['max_execution_time'] = $execution_time;
     1577            }
     1578
     1579            if ( $memory_usage > $metrics['max_memory_usage'] ) {
     1580                $metrics['max_memory_usage'] = $memory_usage;
     1581            }
     1582
     1583            $metrics['avg_execution_time'] = $metrics['total_execution_time'] / $metrics['hook_count'];
     1584            $metrics['avg_memory_usage']   = $metrics['total_memory_usage'] / $metrics['hook_count'];
     1585
     1586            // Log performance warnings if thresholds are exceeded.
     1587            if ( $execution_time > 0.1 ) { // More than 100ms.
     1588                self::debug_log(
     1589                    'Slow hook execution detected',
     1590                    array(
     1591                        'execution_time' => $execution_time,
     1592                        'memory_usage'   => $memory_usage,
     1593                        'log_count'      => $log_count,
     1594                    )
     1595                );
     1596            }
     1597
     1598            if ( $memory_usage > 1048576 ) { // More than 1MB.
     1599                self::debug_log(
     1600                    'High memory usage detected',
     1601                    array(
     1602                        'execution_time' => $execution_time,
     1603                        'memory_usage'   => $memory_usage,
     1604                        'log_count'      => $log_count,
     1605                    )
     1606                );
     1607            }
     1608
     1609            if ( $log_count > self::MAX_MEMORY_LOGS * 0.8 ) { // 80% of max capacity.
     1610                self::debug_log(
     1611                    'Approaching memory log limit',
     1612                    array(
     1613                        'log_count' => $log_count,
     1614                        'max_logs'  => self::MAX_MEMORY_LOGS,
     1615                    )
     1616                );
     1617            }
     1618        }
     1619
     1620        /**
     1621         * Log debug information for troubleshooting.
     1622         *
     1623         * @param string $message Debug message.
     1624         * @param array  $context Additional context data.
     1625         *
     1626         * @return void
     1627         *
     1628         * @since 4.6.1
     1629         */
     1630        private static function debug_log( string $message, array $context = array() ) {
     1631            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
     1632                $log_message = '[Hooks Capture] ' . $message;
     1633                if ( ! empty( $context ) ) {
     1634                    $log_message .= ' ' . wp_json_encode( $context );
     1635                }
     1636                error_log( $log_message );
     1637            }
     1638        }
     1639
     1640        /**
     1641         * Delete the existing cache file.
    9861642         *
    9871643         * @return bool True on success, false on failure.
     
    9981654            return @unlink( $cache_file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.unlink_unlink
    9991655        }
     1656
     1657        /**
     1658         * =======================================================================
     1659         * NEW FEATURES: Early Filtering, Memory Pool, Error Recovery, Sampling
     1660         * =======================================================================
     1661         */
     1662
     1663        /**
     1664         * Initialize memory pool for reusing structures.
     1665         *
     1666         * @return void
     1667         *
     1668         * @since latest
     1669         */
     1670        private static function init_memory_pool() {
     1671            if ( ! isset( $GLOBALS['advan_memory_pool'] ) ) {
     1672                $GLOBALS['advan_memory_pool'] = array(
     1673                    'backtrace_cache' => array(),
     1674                    'sanitized_data'  => array(),
     1675                    'json_cache'      => array(),
     1676                    'pool_size'       => 0,
     1677                    'max_pool_size'   => 100 * 1024 * 1024, // 100MB limit.
     1678                );
     1679            }
     1680        }
     1681
     1682        /**
     1683         * Get cached backtrace.
     1684         *
     1685         * @param string $key Cache key.
     1686         *
     1687         * @return array|null Cached backtrace or null if not found.
     1688         *
     1689         * @since latest
     1690         */
     1691        private static function get_cached_backtrace( string $key ) {
     1692            if ( isset( $GLOBALS['advan_memory_pool']['backtrace_cache'][ $key ] ) ) {
     1693                return $GLOBALS['advan_memory_pool']['backtrace_cache'][ $key ];
     1694            }
     1695            return null;
     1696        }
     1697
     1698        /**
     1699         * Set cached backtrace.
     1700         *
     1701         * @param string $key Cache key.
     1702         * @param array  $backtrace Backtrace data.
     1703         *
     1704         * @return void
     1705         *
     1706         * @since latest
     1707         */
     1708        private static function set_cached_backtrace( string $key, array $backtrace ) {
     1709            if ( ! isset( $GLOBALS['advan_memory_pool'] ) ) {
     1710                return;
     1711            }
     1712
     1713            // Check pool size limit.
     1714            $backtrace_size = strlen( \wp_json_encode( $backtrace ) );
     1715            if ( $GLOBALS['advan_memory_pool']['pool_size'] + $backtrace_size > $GLOBALS['advan_memory_pool']['max_pool_size'] ) {
     1716                // Clear oldest entries if pool is full.
     1717                array_shift( $GLOBALS['advan_memory_pool']['backtrace_cache'] );
     1718            }
     1719
     1720            $GLOBALS['advan_memory_pool']['backtrace_cache'][ $key ] = $backtrace;
     1721            $GLOBALS['advan_memory_pool']['pool_size']              += $backtrace_size;
     1722        }
     1723
     1724        /**
     1725         * Get cached JSON.
     1726         *
     1727         * @param string $key Cache key.
     1728         *
     1729         * @return string|null Cached JSON or null if not found.
     1730         *
     1731         * @since latest
     1732         */
     1733        private static function get_cached_json( string $key ) {
     1734            if ( isset( $GLOBALS['advan_memory_pool']['json_cache'][ $key ] ) ) {
     1735                return $GLOBALS['advan_memory_pool']['json_cache'][ $key ];
     1736            }
     1737            return null;
     1738        }
     1739
     1740        /**
     1741         * Set cached JSON.
     1742         *
     1743         * @param string $key  Cache key.
     1744         * @param string $json JSON data.
     1745         *
     1746         * @return void
     1747         *
     1748         * @since latest
     1749         */
     1750        private static function set_cached_json( string $key, string $json ) {
     1751            if ( ! isset( $GLOBALS['advan_memory_pool'] ) ) {
     1752                return;
     1753            }
     1754
     1755            $json_size = strlen( $json );
     1756            if ( $GLOBALS['advan_memory_pool']['pool_size'] + $json_size > $GLOBALS['advan_memory_pool']['max_pool_size'] ) {
     1757                array_shift( $GLOBALS['advan_memory_pool']['json_cache'] );
     1758            }
     1759
     1760            $GLOBALS['advan_memory_pool']['json_cache'][ $key ] = $json;
     1761            $GLOBALS['advan_memory_pool']['pool_size']         += $json_size;
     1762        }
     1763
     1764        /**
     1765         * Cleanup memory pool on shutdown.
     1766         *
     1767         * @return void
     1768         *
     1769         * @since latest
     1770         */
     1771        public static function cleanup_memory_pool() {
     1772            unset( $GLOBALS['advan_memory_pool'] );
     1773        }
     1774
     1775        /**
     1776         * Early filtering: Check if hook should be captured before processing.
     1777         *
     1778         * @param string $hook_name The hook name to check.
     1779         * @return bool True if hook should be captured, false otherwise.
     1780         */
     1781        private static function should_capture_hook_early( string $hook_name ): bool {
     1782            static $filtered_hooks = null;
     1783
     1784            // Initialize filtered hooks list on first call
     1785            if ( null === $filtered_hooks ) {
     1786                $filtered_hooks = apply_filters(
     1787                    'advan_excluded_hooks',
     1788                    array(
     1789                        // WordPress core hooks that are too frequent/noisy.
     1790                        'gettext',
     1791                        'gettext_with_context',
     1792                        'ngettext',
     1793                        'ngettext_with_context',
     1794                        'locale',
     1795                        'override_load_textdomain',
     1796                        'load_textdomain',
     1797                        'unload_textdomain',
     1798
     1799                        // Option hooks (very frequent).
     1800                        'pre_option_*',
     1801                        'default_option_*',
     1802                        'option_*',
     1803
     1804                        // Transient hooks (very frequent).
     1805                        'pre_transient_*',
     1806                        'transient_*',
     1807                        'set_transient_*',
     1808                        'delete_transient_*',
     1809
     1810                        // Cache hooks (very frequent).
     1811                        'pre_cache_*',
     1812                        'cache_*',
     1813
     1814                        // WP_Query hooks (can be very frequent).
     1815                        'pre_get_posts',
     1816                        'posts_where',
     1817                        'posts_join',
     1818                        'posts_orderby',
     1819                        'posts_fields',
     1820                        'posts_clauses',
     1821                        'posts_request',
     1822                        'posts_results',
     1823                        'posts_pre_query',
     1824
     1825                        // Meta hooks (very frequent).
     1826                        'get_*_metadata',
     1827                        'update_*_metadata',
     1828                        'add_*_metadata',
     1829                        'delete_*_metadata',
     1830
     1831                        // User hooks (frequent in some contexts).
     1832                        'get_user_metadata',
     1833                        'update_user_metadata',
     1834
     1835                        // Comment hooks (can be frequent).
     1836                        'wp_update_comment_count',
     1837                        'pre_get_comments',
     1838                        'comments_clauses',
     1839
     1840                        // Taxonomy hooks.
     1841                        'get_terms',
     1842                        'get_term',
     1843                        'get_*_terms',
     1844                    )
     1845                );
     1846            }
     1847
     1848            // Check if hook matches any filtered pattern.
     1849            foreach ( $filtered_hooks as $pattern ) {
     1850                if ( fnmatch( $pattern, $hook_name ) ) {
     1851                    return false; // Don't capture this hook.
     1852                }
     1853            }
     1854
     1855            return true;
     1856        }
     1857
     1858        /**
     1859         * Sampling: Check if hook should be sampled (for high-frequency hooks).
     1860         *
     1861         * @param string $hook_name The hook name to check.
     1862         *
     1863         * @return bool True if hook should be captured, false if skipped for sampling.
     1864         *
     1865         * @since latest
     1866         */
     1867        private static function should_sample_hook( string $hook_name ): bool {
     1868            static $hook_counters  = array();
     1869            static $sampling_rates = null;
     1870
     1871            // Initialize sampling rates on first call.
     1872            if ( null === $sampling_rates ) {
     1873                $sampling_rates = apply_filters(
     1874                    'advan_hook_sampling_rates',
     1875                    array(
     1876                        // Sample every Nth call for these hooks.
     1877                        'option_*'       => 100,  // Sample 1% of option hooks.
     1878                        'transient_*'    => 50,   // Sample 2% of transient hooks.
     1879                        'gettext*'       => 20,   // Sample 5% of gettext hooks.
     1880                        'get_*_metadata' => 10,   // Sample 10% of metadata hooks.
     1881                        'pre_get_posts'  => 5,    // Sample 20% of query hooks.
     1882                    )
     1883                );
     1884            }
     1885
     1886            // Check if this hook should be sampled.
     1887            foreach ( $sampling_rates as $pattern => $rate ) {
     1888                if ( fnmatch( $pattern, $hook_name ) ) {
     1889                    // Initialize counter for this hook.
     1890                    if ( ! isset( $hook_counters[ $hook_name ] ) ) {
     1891                        $hook_counters[ $hook_name ] = 0;
     1892                    }
     1893
     1894                    ++$hook_counters[ $hook_name ];
     1895
     1896                    // Only capture if counter is multiple of rate.
     1897                    if ( $hook_counters[ $hook_name ] % 0 !== $rate ) {
     1898                        return false; // Skip this call.
     1899                    }
     1900
     1901                    break; // Found matching pattern, no need to check others.
     1902                }
     1903            }
     1904
     1905            return true;
     1906        }
     1907
     1908        /**
     1909         * Safe JSON encoding with fallback for error recovery.
     1910         *
     1911         * @param mixed $data    Data to encode.
     1912         * @param mixed $fallback Fallback data if encoding fails.
     1913         *
     1914         * @return string JSON encoded data or fallback.
     1915         *
     1916         * @since latest
     1917         */
     1918        public static function safe_json_encode( $data, $fallback = null ) {
     1919            try {
     1920                $encoded = \wp_json_encode( $data );
     1921                if ( false === $encoded ) {
     1922                    throw new \Exception( 'JSON encoding failed' );
     1923                }
     1924                return $encoded;
     1925            } catch ( \Exception $e ) {
     1926                // Log the error.
     1927                if ( function_exists( 'error_log' ) ) {
     1928                    error_log( 'Hooks Capture: JSON encoding failed - ' . $e->getMessage() );
     1929                }
     1930
     1931                // Return fallback or simplified data.
     1932                if ( null !== $fallback ) {
     1933                    return \wp_json_encode( $fallback );
     1934                }
     1935
     1936                // Create a simplified version.
     1937                if ( is_array( $data ) ) {
     1938                    return \wp_json_encode(
     1939                        array(
     1940                            'error' => 'JSON encoding failed',
     1941                            'type'  => 'array',
     1942                            'count' => count( $data ),
     1943                        )
     1944                    );
     1945                } elseif ( is_object( $data ) ) {
     1946                    return \wp_json_encode(
     1947                        array(
     1948                            'error' => 'JSON encoding failed',
     1949                            'type'  => 'object',
     1950                            'class' => get_class( $data ),
     1951                        )
     1952                    );
     1953                } else {
     1954                    return \wp_json_encode(
     1955                        array(
     1956                            'error'   => 'JSON encoding failed',
     1957                            'type'    => gettype( $data ),
     1958                            'content' => substr( (string) $data, 0, 100 ),
     1959                        )
     1960                    );
     1961                }
     1962            }
     1963        }
     1964
     1965        /**
     1966         * Safe JSON decoding with error handling for error recovery.
     1967         *
     1968         * @param string $data     Data to decode.
     1969         * @param mixed  $fallback Fallback data if decoding fails.
     1970         *
     1971         * @return mixed Decoded data or fallback.
     1972         *
     1973         * @since latest
     1974         */
     1975        public static function safe_json_decode( string $data, $fallback = null ) {
     1976            if ( empty( $data ) ) {
     1977                return $fallback ?: array();
     1978            }
     1979
     1980            try {
     1981                $decoded = \wp_json_decode( $data, true );
     1982                if ( null === $decoded && 'null' !== $data ) {
     1983                    throw new \Exception( 'JSON decoding failed' );
     1984                }
     1985                return $decoded;
     1986            } catch ( \Exception $e ) {
     1987                // Log the error.
     1988                if ( function_exists( 'error_log' ) ) {
     1989                    error_log( 'Hooks Capture: JSON decoding failed - ' . $e->getMessage() );
     1990                }
     1991
     1992                // Return fallback.
     1993                return $fallback ?: array( 'error' => 'JSON decoding failed' );
     1994            }
     1995        }
     1996
     1997        /**
     1998         * Safe serialization with fallback for error recovery.
     1999         * DEPRECATED: Use safe_json_encode() instead for security.
     2000         *
     2001         * @param mixed $data    Data to serialize.
     2002         * @param mixed $fallback Fallback data if serialization fails.
     2003         *
     2004         * @return string Serialized data or fallback.
     2005         *
     2006         * @throws \Exception
     2007         *
     2008         * @deprecated Use safe_json_encode() instead
     2009         * @since latest
     2010         */
     2011        public static function safe_serialize( $data, $fallback = null ) {
     2012            // Log deprecation warning.
     2013            if ( function_exists( 'error_log' ) ) {
     2014                error_log( 'Hooks Capture: safe_serialize() is deprecated. Use safe_json_encode() instead.' );
     2015            }
     2016
     2017            // Fall back to JSON encoding for security.
     2018            return self::safe_json_encode( $data, $fallback );
     2019        }
     2020
     2021        /**
     2022         * Safe unserialization with error handling for error recovery.
     2023         * DEPRECATED: Use safe_json_decode() instead for security.
     2024         *
     2025         * @param string $data     Data to unserialize.
     2026         * @param mixed  $fallback Fallback data if unserialization fails.
     2027         *
     2028         * @return mixed Unserialized data or fallback.
     2029         *
     2030         * @throws \Exception
     2031         *
     2032         * @deprecated Use safe_json_decode() instead
     2033         * @since latest
     2034         */
     2035        public static function safe_unserialize( string $data, $fallback = null ) {
     2036            // Log deprecation warning.
     2037            if ( function_exists( 'error_log' ) ) {
     2038                error_log( 'Hooks Capture: safe_unserialize() is deprecated. Use safe_json_decode() instead.' );
     2039            }
     2040
     2041            // Fall back to JSON decoding for security.
     2042            return self::safe_json_decode( $data, $fallback );
     2043        }
    10002044    }
    10012045}
  • 0-day-analytics/trunk/classes/vendor/controllers/class-wp-mail-log.php

    r3413453 r3448917  
    417417         * @since 3.0.0
    418418         */
    419         private static function get_backtrace( $function_name = 'wp_mail' ): ?array {
     419        private static function get_backtrace( $function_name = 'wp_mail' ) {
    420420            $backtrace_segment = null;
    421421
  • 0-day-analytics/trunk/classes/vendor/entities/class-hooks-capture-entity.php

    r3442115 r3448917  
    1212namespace ADVAN\Entities;
    1313
     14use ADVAN\Entities_Global\Common_Table;
     15
    1416// Exit if accessed directly.
    1517if ( ! defined( 'ABSPATH' ) ) {
     
    4446            'hook_type'           => 'string',
    4547            'trigger_source'      => 'string',
     48            'request_id'          => 'string',
    4649            'user_id'             => 'int',
    4750            'user_login'          => 'string',
     
    5356            'is_cli'              => 'int',
    5457            'hooks_management_id' => 'int',
     58            'count'               => 'int',
    5559            'date_added'          => 'float',
    5660        );
     
    6973            'hook_type'           => 'action',
    7074            'trigger_source'      => '',
     75            'request_id'          => '',
    7176            'user_id'             => 0,
    7277            'user_login'          => '',
     
    7883            'is_cli'              => 0,
    7984            'hooks_management_id' => 0,
     85            'count'               => 1,
    8086            'date_added'          => 0.0,
    8187        );
     
    110116                    hook_type VARCHAR(10) NOT NULL DEFAULT "action",
    111117                    trigger_source VARCHAR(50) NOT NULL DEFAULT "",
     118                    request_id VARCHAR(50) NOT NULL DEFAULT "",
    112119                    user_id BIGINT unsigned NOT NULL DEFAULT 0,
    113120                    user_login VARCHAR(60) NOT NULL DEFAULT "",
     
    119126                    is_cli TINYINT(1) NOT NULL DEFAULT 0,
    120127                    hooks_management_id BIGINT unsigned NOT NULL DEFAULT 0,
     128                    count INT NOT NULL DEFAULT 1,
    121129                    date_added DOUBLE NOT NULL DEFAULT 0,
    122130                PRIMARY KEY (id),
     
    125133                KEY `hook_type` (`hook_type`),
    126134                KEY `trigger_source` (`trigger_source`),
     135                KEY `request_id` (`request_id`),
    127136                KEY `user_id` (`user_id`),
    128137                KEY `date_added` (`date_added`),
     
    152161                'memory_usage'   => __( 'Memory', '0-day-analytics' ),
    153162                'is_cli'         => __( 'CLI', '0-day-analytics' ),
     163                'count'          => __( 'Count', '0-day-analytics' ),
    154164                'parameters'     => __( 'Parameters', '0-day-analytics' ),
    155165            );
     
    165175            return $columns;
    166176        }
     177
     178        /**
     179         * Alters the table to add request_id column for version 4.6.0.
     180         *
     181         * @return void
     182         *
     183         * @since 4.6.0
     184         */
     185        public static function alter_table_460() {
     186            $table_name = self::get_table_name();
     187
     188            if ( ! Common_Table::check_table_exists( $table_name ) ) {
     189                return;
     190            }
     191
     192            $connection = self::get_connection();
     193
     194            // Check if request_id column already exists.
     195            $columns = $connection->get_results( "SHOW COLUMNS FROM `{$table_name}` LIKE 'request_id'" );
     196
     197            if ( empty( $columns ) ) {
     198                // Add the request_id column.
     199                $alter_sql = "ALTER TABLE `{$table_name}` ADD COLUMN `request_id` VARCHAR(50) NOT NULL DEFAULT '' AFTER `trigger_source`";
     200                $connection->query( $alter_sql );
     201
     202                // Add index for the new column.
     203                $index_sql = "ALTER TABLE `{$table_name}` ADD KEY `request_id` (`request_id`)";
     204                $connection->query( $index_sql );
     205            }
     206
     207            // Check if count column already exists.
     208            $columns_count = $connection->get_results( "SHOW COLUMNS FROM `{$table_name}` LIKE 'count'" );
     209
     210            if ( empty( $columns_count ) ) {
     211                // Add the count column.
     212                $alter_sql_count = "ALTER TABLE `{$table_name}` ADD COLUMN `count` INT NOT NULL DEFAULT 1 AFTER `hooks_management_id`";
     213                $connection->query( $alter_sql_count );
     214            }
     215        }
    167216    }
    168217}
  • 0-day-analytics/trunk/classes/vendor/helpers/class-system-analytics.php

    r3442115 r3448917  
    462462                    .advan-stat-label {font-weight:600;}
    463463                    .advan-stat-value {font-size:16px;margin-top:3px;}
    464                     .advan-info-section {margin-top:20px; padding:10px; background:#f9f9f9; border-radius:5px;}
     464                    .advan-info-section {margin-top:20px; padding:10px; border-radius:5px;}
    465465                    .advan-info-item {margin-bottom:5px;}
    466466                </style>
  • 0-day-analytics/trunk/classes/vendor/lists/class-hooks-capture-list.php

    r3442473 r3448917  
    411411                if ( ! empty( $hooks_management_results ) ) {
    412412                    $hooks_management_ids = array_column( $hooks_management_results, 'id' );
    413                     $placeholders = implode( ',', array_fill( 0, count( $hooks_management_ids ), '%d' ) );
    414                     $where_sql_parts[] = 'hooks_management_id IN (' . $placeholders . ')';
    415                     $where_args = array_merge( $where_args, $hooks_management_ids );
     413                    $placeholders         = implode( ',', array_fill( 0, count( $hooks_management_ids ), '%d' ) );
     414                    $where_sql_parts[]    = 'hooks_management_id IN (' . $placeholders . ')';
     415                    $where_args           = array_merge( $where_args, $hooks_management_ids );
    416416                }
    417417            }
     
    745745                    $hook_label = Hooks_Management_Entity::get_hook_label( $item[ $column_name ] );
    746746                    $hook_name  = '<code>' . \esc_html( $item[ $column_name ] ) . '</code>';
    747                     $display    = $hook_label ? '<strong>' . \esc_html( $hook_label ) . '</strong> ' . $hook_name : $hook_name;
     747
     748                    // Make hook name a link to hooks management if hooks_management_id is available
     749                    if ( ! empty( $item['hooks_management_id'] ) ) {
     750                        $edit_url  = \network_admin_url( 'admin.php?page=advan_hooks_management&action=edit&id=' . absint( $item['hooks_management_id'] ) );
     751                        $hook_name = '<code><a href="' . \esc_url( $edit_url ) . '" title="' . \esc_attr__( 'Edit hook in Hooks Management', '0-day-analytics' ) . '">' . \esc_html( $item[ $column_name ] ) . '</a></code>';
     752                    }
     753
     754                    // Check for post-related hooks and add post type information.
     755                    $post_type_info = '';
     756                    if ( self::is_post_related_hook( $item[ $column_name ] ) && ! empty( $item['parameters'] ) ) {
     757                        $post_type = self::extract_post_type_from_parameters( $item['parameters'] );
     758                        if ( $post_type ) {
     759                            $post_type_info = ' <code style="font-weight: normal;">(<b>' . \esc_html( $post_type ) . '</b>)</code>';
     760                        }
     761                    }
     762
     763                    $display = $hook_label ? '<strong>' . \esc_html( $hook_label ) . $post_type_info . '</strong> ' . $hook_name : $hook_name;
    748764
    749765                    // Add row actions.
     
    764780                    );
    765781
     782                    // Add disable hook action if applicable.
     783                    $actions = self::add_disable_hook_action( $actions, $item );
     784
    766785                    return sprintf(
    767786                        '%s %s',
     
    781800                case 'is_cli':
    782801                    return ! empty( $item[ $column_name ] ) ? '<span class="dashicons dashicons-yes"></span>' : '<span class="dashicons dashicons-no"></span>';
     802
     803                case 'count':
     804                    $count = isset( $item[ $column_name ] ) ? (int) $item[ $column_name ] : 1;
     805                    if ( $count > 1 ) {
     806                        return '<span class="badge badge-warning">' . \esc_html( $count ) . '</span>';
     807                    }
     808                    return \esc_html( $count );
    783809
    784810                case 'parameters':
     
    864890            echo '</tr>';
    865891        }
     892
     893        /**
     894         * Check if a hook is post-related.
     895         *
     896         * @param string $hook_name The hook name to check.
     897         *
     898         * @return bool True if post-related, false otherwise.
     899         *
     900         * @since 4.6.1
     901         */
     902        private static function is_post_related_hook( string $hook_name ): bool {
     903            $post_related_hooks = array(
     904                'wp_insert_post',
     905                'wp_update_post',
     906                'wp_delete_post',
     907                'save_post',
     908                'publish_post',
     909                'transition_post_status',
     910                'before_delete_post',
     911                'after_delete_post',
     912                'post_updated',
     913                'edit_post',
     914                'delete_post',
     915            );
     916
     917            return in_array( $hook_name, $post_related_hooks, true );
     918        }
     919
     920        /**
     921         * Extract post type from hook parameters.
     922         *
     923         * @param string $parameters_json JSON-encoded parameters.
     924         *
     925         * @return string|null Post type if found, null otherwise.
     926         *
     927         * @since 4.6.1
     928         */
     929        private static function extract_post_type_from_parameters( string $parameters_json ): ?string {
     930            if ( empty( $parameters_json ) ) {
     931                return null;
     932            }
     933
     934            $parameters = json_decode( $parameters_json, true );
     935            if ( ! is_array( $parameters ) || empty( $parameters ) ) {
     936                return null;
     937            }
     938
     939            // Try different parameter positions and structures.
     940            foreach ( $parameters as $param ) {
     941                // Check if parameter is an array/object with post_type.
     942                if ( is_array( $param ) && isset( $param['post_type'] ) ) {
     943                    return $param['post_type'];
     944                }
     945
     946                // Check if parameter is an object with post_type property.
     947                if ( is_array( $param ) && isset( $param['__class__'] ) && isset( $param['post_type'] ) ) {
     948                    return $param['post_type'];
     949                }
     950
     951                // Check if parameter is a post ID and try to get post type from database.
     952                if ( is_numeric( $param ) && $param > 0 ) {
     953                    $post = \get_post( (int) $param );
     954                    if ( $post && isset( $post->post_type ) ) {
     955                        return $post->post_type;
     956                    }
     957                }
     958            }
     959
     960            return null;
     961        }
     962
     963        /**
     964         * =======================================================================
     965         * NEW FEATURES: Clear All Logs Button & Disable Hook Actions
     966         * =======================================================================
     967         */
     968
     969        /**
     970         * Initialize admin hooks for new features.
     971         *
     972         * @return void
     973         */
     974        public static function init_admin_hooks() {
     975            // Clear logs functionality.
     976            \add_action( 'admin_notices', array( __CLASS__, 'render_clear_logs_button' ) );
     977            \add_action( 'admin_post_clear_hooks_logs', array( __CLASS__, 'handle_clear_logs' ) );
     978
     979            // Disable/enable hook functionality.
     980            \add_action( 'admin_post_disable_hook_capture', array( __CLASS__, 'handle_disable_hook' ) );
     981            \add_action( 'admin_post_enable_hook_capture', array( __CLASS__, 'handle_enable_hook' ) );
     982        }
     983
     984        /**
     985         * Render the clear logs button in admin notices.
     986         *
     987         * @return void
     988         */
     989        public static function render_clear_logs_button() {
     990            $screen = \get_current_screen();
     991
     992            if ( ! $screen || ! \in_array( $screen->id, array( '0-day_page_advan_hooks_capture' ), true ) ) {
     993                return;
     994            }
     995
     996            if ( ! \current_user_can( 'manage_options' ) ) {
     997                return;
     998            }
     999
     1000            $logs_count = self::get_logs_count();
     1001            if ( 0 === $logs_count ) {
     1002                return;
     1003            }
     1004
     1005            ?>
     1006            <div class="notice">
     1007                <p>
     1008                    <strong><?php \esc_html_e( 'Hooks Capture', '0-day-analytics' ); ?></strong>
     1009                    <?php
     1010                    printf(
     1011                        /* translators: %d: number of logs */
     1012                        \esc_html__( 'Currently tracking %d hook executions.', '0-day-analytics' ),
     1013                        \number_format_i18n( $logs_count )
     1014                    );
     1015                    ?>
     1016                    <a href="<?php echo \esc_url( \wp_nonce_url( \network_admin_url( 'admin-post.php?action=clear_hooks_logs' ), 'clear_hooks_logs' ) ); ?>"
     1017                        class="button button-secondary"
     1018                        onclick="return confirm('<?php \esc_attr_e( 'Are you sure you want to clear all hook logs? This action cannot be undone.', '0-day-analytics' ); ?>')">
     1019                        <?php \esc_html_e( 'Clear All Logs', '0-day-analytics' ); ?>
     1020                    </a>
     1021                </p>
     1022            </div>
     1023            <?php
     1024        }
     1025
     1026        /**
     1027         * Handle the clear logs action.
     1028         *
     1029         * @return void
     1030         */
     1031        public static function handle_clear_logs() {
     1032            if ( ! \current_user_can( 'manage_options' ) ) {
     1033                \wp_die( \esc_html__( 'Insufficient permissions.', '0-day-analytics' ) );
     1034            }
     1035
     1036            \check_admin_referer( 'clear_hooks_logs' );
     1037
     1038            try {
     1039                // Use the proper architectural method to truncate the table.
     1040                Common_Table::truncate_table( null, Hooks_Capture_Entity::get_table_name() );
     1041
     1042                // Clear any cached data.
     1043                \wp_cache_flush();
     1044
     1045                // Add success message.
     1046                \add_action(
     1047                    'admin_notices',
     1048                    function() {
     1049                        ?>
     1050                    <div class="notice notice-success is-dismissible">
     1051                        <p><?php \esc_html_e( 'All hook logs have been cleared successfully.', '0-day-analytics' ); ?></p>
     1052                    </div>
     1053                        <?php
     1054                    }
     1055                );
     1056            } catch ( \Exception $e ) {
     1057                // Add error message.
     1058                \add_action(
     1059                    'admin_notices',
     1060                    function() {
     1061                        ?>
     1062                    <div class="notice notice-error is-dismissible">
     1063                        <p><?php \esc_html_e( 'Failed to clear hook logs. Please try again.', '0-day-analytics' ); ?></p>
     1064                    </div>
     1065                        <?php
     1066                    }
     1067                );
     1068            }
     1069
     1070            // Redirect back to the hooks capture page.
     1071            \wp_redirect( \network_admin_url( 'admin.php?page=advan_hooks_capture' ) );
     1072            exit;
     1073        }
     1074
     1075        /**
     1076         * Get the total count of logs.
     1077         *
     1078         * @return int
     1079         */
     1080        private static function get_logs_count() {
     1081            // Use the proper architectural method through the entity class.
     1082            return Hooks_Capture_Entity::count( '1=%d', array( 1 ) );
     1083        }
     1084
     1085        /**
     1086         * Add disable/enable hook action to row actions.
     1087         *
     1088         * @param array $actions Existing actions.
     1089         * @param array $item    The current item.
     1090         * @return array Modified actions.
     1091         */
     1092        public static function add_disable_hook_action( $actions, $item ) {
     1093            if ( ! empty( $item['hooks_management_id'] ) && \current_user_can( 'manage_options' ) ) {
     1094                // Load the hook configuration to check if it's enabled or disabled.
     1095                $hook_config = Hooks_Management_Entity::load( 'id=%d', array( $item['hooks_management_id'] ) );
     1096                if ( ! $hook_config ) {
     1097                    return $actions;
     1098                }
     1099
     1100                $is_enabled = isset( $hook_config['enabled'] ) ? (bool) $hook_config['enabled'] : true;
     1101
     1102                if ( $is_enabled ) {
     1103                    // Hook is enabled, show disable action.
     1104                    $action_url = \wp_nonce_url(
     1105                        \network_admin_url( 'admin-post.php?action=disable_hook_capture&id=' . \absint( $item['hooks_management_id'] ) ),
     1106                        'disable_hook_capture_' . $item['hooks_management_id']
     1107                    );
     1108
     1109                    $actions['disable_hook'] = \sprintf(
     1110                        '<a href="%s" onclick="return confirm(\'%s\')" style="color: #dc3232;">%s</a>',
     1111                        \esc_url( $action_url ),
     1112                        \esc_js( __( 'Are you sure you want to disable this hook? It will stop being captured.', '0-day-analytics' ) ),
     1113                        __( 'Disable Hook', '0-day-analytics' )
     1114                    );
     1115                } else {
     1116                    // Hook is disabled, show enable action.
     1117                    $action_url = \wp_nonce_url(
     1118                        \network_admin_url( 'admin-post.php?action=enable_hook_capture&id=' . \absint( $item['hooks_management_id'] ) ),
     1119                        'enable_hook_capture_' . $item['hooks_management_id']
     1120                    );
     1121
     1122                    $actions['enable_hook'] = \sprintf(
     1123                        '<a href="%s" onclick="return confirm(\'%s\')" style="color: #007cba;">%s</a>',
     1124                        \esc_url( $action_url ),
     1125                        \esc_js( __( 'Are you sure you want to enable this hook? It will start being captured again.', '0-day-analytics' ) ),
     1126                        __( 'Enable Hook', '0-day-analytics' )
     1127                    );
     1128                }
     1129            }
     1130
     1131            return $actions;
     1132        }
     1133
     1134        /**
     1135         * Handle disable hook action.
     1136         *
     1137         * @return void
     1138         */
     1139        public static function handle_disable_hook() {
     1140            if ( ! \current_user_can( 'manage_options' ) ) {
     1141                \wp_die( \esc_html__( 'Insufficient permissions.', '0-day-analytics' ) );
     1142            }
     1143
     1144            $hook_id = isset( $_GET['id'] ) ? \absint( $_GET['id'] ) : 0;
     1145            if ( ! $hook_id ) {
     1146                \wp_die( \esc_html__( 'Invalid hook ID.', '0-day-analytics' ) );
     1147            }
     1148
     1149            \check_admin_referer( 'disable_hook_capture_' . $hook_id );
     1150
     1151            // Load the hook configuration.
     1152            $hook_config = Hooks_Management_Entity::load( 'id=%d', array( $hook_id ) );
     1153            if ( ! $hook_config ) {
     1154                \wp_die( \esc_html__( 'Hook not found.', '0-day-analytics' ) );
     1155            }
     1156
     1157            // Disable the hook by setting enabled to 0.
     1158            $result = Hooks_Management_Entity::insert( \array_merge( $hook_config, array( 'enabled' => 0 ) ) );
     1159
     1160            if ( $result ) {
     1161                // Clear cache to reflect changes.
     1162                \do_action( 'advan_hooks_management_updated' );
     1163
     1164                \add_action(
     1165                    'admin_notices',
     1166                    function() use ( $hook_config ) {
     1167                        ?>
     1168                    <div class="notice notice-success is-dismissible">
     1169                        <p>
     1170                            <?php
     1171                            printf(
     1172                                /* translators: %s: hook name */
     1173                                \esc_html__( 'Hook "%s" has been disabled successfully.', '0-day-analytics' ),
     1174                                \esc_html( $hook_config['hook_name'] )
     1175                            );
     1176                            ?>
     1177                        </p>
     1178                    </div>
     1179                        <?php
     1180                    }
     1181                );
     1182            } else {
     1183                \add_action(
     1184                    'admin_notices',
     1185                    function() {
     1186                        ?>
     1187                    <div class="notice notice-error is-dismissible">
     1188                        <p><?php \esc_html_e( 'Failed to disable hook. Please try again.', '0-day-analytics' ); ?></p>
     1189                    </div>
     1190                        <?php
     1191                    }
     1192                );
     1193            }
     1194
     1195            \wp_redirect( \network_admin_url( 'admin.php?page=advan_hooks_capture' ) );
     1196            exit;
     1197        }
     1198
     1199        /**
     1200         * Handle enable hook action.
     1201         *
     1202         * @return void
     1203         */
     1204        public static function handle_enable_hook() {
     1205            if ( ! \current_user_can( 'manage_options' ) ) {
     1206                \wp_die( \esc_html__( 'Insufficient permissions.', '0-day-analytics' ) );
     1207            }
     1208
     1209            $hook_id = isset( $_GET['id'] ) ? \absint( $_GET['id'] ) : 0;
     1210            if ( ! $hook_id ) {
     1211                \wp_die( \esc_html__( 'Invalid hook ID.', '0-day-analytics' ) );
     1212            }
     1213
     1214            \check_admin_referer( 'enable_hook_capture_' . $hook_id );
     1215
     1216            // Load the hook configuration.
     1217            $hook_config = Hooks_Management_Entity::load( 'id=%d', array( $hook_id ) );
     1218            if ( ! $hook_config ) {
     1219                \wp_die( \esc_html__( 'Hook not found.', '0-day-analytics' ) );
     1220            }
     1221
     1222            // Enable the hook by setting enabled to 1.
     1223            $result = Hooks_Management_Entity::insert( \array_merge( $hook_config, array( 'enabled' => 1 ) ) );
     1224
     1225            if ( $result ) {
     1226                // Clear cache to reflect changes.
     1227                \do_action( 'advan_hooks_management_updated' );
     1228
     1229                \add_action(
     1230                    'admin_notices',
     1231                    function() use ( $hook_config ) {
     1232                        ?>
     1233                    <div class="notice notice-success is-dismissible">
     1234                        <p>
     1235                            <?php
     1236                            printf(
     1237                                /* translators: %s: hook name */
     1238                                \esc_html__( 'Hook "%s" has been enabled successfully.', '0-day-analytics' ),
     1239                                \esc_html( $hook_config['hook_name'] )
     1240                            );
     1241                            ?>
     1242                        </p>
     1243                    </div>
     1244                        <?php
     1245                    }
     1246                );
     1247            } else {
     1248                \add_action(
     1249                    'admin_notices',
     1250                    function() {
     1251                        ?>
     1252                    <div class="notice notice-error is-dismissible">
     1253                        <p><?php \esc_html_e( 'Failed to enable hook. Please try again.', '0-day-analytics' ); ?></p>
     1254                    </div>
     1255                        <?php
     1256                    }
     1257                );
     1258            }
     1259
     1260            \wp_redirect( \network_admin_url( 'admin.php?page=advan_hooks_capture' ) );
     1261            exit;
     1262        }
    8661263    }
    8671264}
  • 0-day-analytics/trunk/readme.txt

    r3442473 r3448917  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 4.5.2
     7Stable tag: 4.6.0
    88License: GPLv3 or later
    99License URI: https://www.gnu.org/licenses/gpl-3.0.txt
     
    9393== Changelog ==
    9494
     95= 4.6.0 =
     96* Hooks module improvements and small styling issues fixed.
     97
    9598= 4.5.2 =
    9699* Fixes problems with hooks quick actions - enable/disable. Fixed problem with showing human-readable data, when core object is captured, but only its ID is present.
Note: See TracChangeset for help on using the changeset viewer.