🔐 Sid Gifari File Manager Pro
v8.0.5 | 2026-08-08 08:13:15 | PHP 8.2.32
📂
/ (Root)
/
opt
/
alt
/
php-xray
/
php
/
smart-advice-plugin
/
cl-smart-advice
/
App
📍 /opt/alt/php-xray/php/smart-advice-plugin/cl-smart-advice/App
🔄 Refresh
✏️
Editing: Debug.php
Read Only
<?php /** * Copyright (с) Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2022 All Rights Reserved * * Licensed under CLOUD LINUX LICENSE AGREEMENT * https://www.cloudlinux.com/legal/ */ namespace CloudLinux\SmartAdvice\App; use CloudLinux\SmartAdvice; /** * Debug */ class Debug { /** * Website url. * * @var string */ private $website = ''; /** * Home path. * * @var string */ private $home_path = ''; /** * User. * * @var string */ private $user = ''; /** * Request uri. * * @var string */ private $request_uri = ''; /** * Http code. * * @var int */ private $http_code = 0; /** * Server ip. * * @var string */ private $server_ip = ''; /** * Error codes. * * @var array<string> * * PHP Core Exceptions */ public $codes = array( E_ERROR => 'E_ERROR', E_WARNING => 'E_WARNING', E_PARSE => 'E_PARSE', E_NOTICE => 'E_NOTICE', E_CORE_ERROR => 'E_CORE_ERROR', E_CORE_WARNING => 'E_CORE_WARNING', E_COMPILE_ERROR => 'E_COMPILE_ERROR', E_COMPILE_WARNING => 'E_COMPILE_WARNING', E_USER_ERROR => 'E_USER_ERROR', E_USER_WARNING => 'E_USER_WARNING', E_USER_NOTICE => 'E_USER_NOTICE', E_STRICT => 'E_STRICT', E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', E_DEPRECATED => 'E_DEPRECATED', E_USER_DEPRECATED => 'E_USER_DEPRECATED', E_ALL => 'E_ALL', ); /** * Environment. * * @var string */ private $environment; /** * Constructor. * * @param string $environment Environment. */ public function __construct( $environment ) { $this->environment = $environment; add_action( 'cl_smart_advice_set_error', array( $this, 'error' ), 10, 5 ); add_action( 'cl_smart_advice_set_error_handler', array( $this, 'setErrorHandler' ), 10, 0 ); add_action( 'cl_smart_advice_restore_error_handler', array( $this, 'restoreErrorHandler' ), 10, 0 ); } /** * Get constant home. * * @return string */ protected function wpHomeConstant() { if ( defined( 'WP_HOME' ) && WP_HOME ) { return (string) WP_HOME; } return ''; } /** * Get option home. * * @return string */ protected function wpHomeOption() { if ( function_exists( 'get_option' ) ) { $home = get_option( 'home' ); if ( is_string( $home ) ) { return $home; } } return ''; } /** * Get home path. * * @return string */ public function homePath() { if ( ! empty( $this->home_path ) ) { return $this->home_path; } if ( defined( 'ABSPATH' ) ) { return ABSPATH; } return $this->home_path; } /** * Get website. * * @return string */ public function website() { if ( ! empty( $this->website ) ) { return $this->website; } $wp_home_constant = $this->wpHomeConstant(); $wp_home_option = $this->wpHomeOption(); if ( ! empty( $wp_home_constant ) ) { $this->website = $wp_home_constant; } elseif ( ! empty( $wp_home_option ) ) { $this->website = $wp_home_option; } elseif ( is_array( $_SERVER ) && array_key_exists( 'SERVER_NAME', $_SERVER ) ) { $this->website = esc_url_raw( wp_unslash( $_SERVER['SERVER_NAME'] ) ); } return $this->website; } /** * Get user. * * @return string */ public function user() { if ( ! empty( $this->user ) ) { return $this->user; } $parse = wp_parse_url( $this->website() ); if ( is_array( $parse ) && array_key_exists( 'host', $parse ) ) { $this->user = $parse['host']; } return $this->user; } /** * Get request uri. * * @return string */ public function requestUri() { if ( ! empty( $this->request_uri ) ) { return $this->request_uri; } if ( is_array( $_SERVER ) && array_key_exists( 'REQUEST_URI', $_SERVER ) ) { $raw = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ); // Strip query string and userinfo: query params on public routes (e.g. // the unsubscribe token) are bearer secrets that must not leave the site. $this->request_uri = $this->strip_uri_secrets( $raw ); } return $this->request_uri; } /** * Drop the query string and any userinfo from a URI, leaving only the * (optional) scheme/host and path. Used so request URLs reported in * telemetry never carry secrets passed as query parameters. * * @param string $uri Raw request URI (absolute or path-only). * * @return string URI without query string or userinfo. */ private function strip_uri_secrets( $uri ) { $uri = (string) $uri; // Drop the query string (and any fragment) wholesale, independent of the // length of individual params, so short tokens are dropped too. $uri = preg_replace( '/[?#].*$/s', '', $uri ); $parts = wp_parse_url( $uri ); if ( ! is_array( $parts ) ) { return is_string( $uri ) ? $uri : ''; } // Reassemble scheme://host/path, deliberately omitting user:pass@ userinfo. $result = ''; if ( ! empty( $parts['scheme'] ) && ! empty( $parts['host'] ) ) { $result .= $parts['scheme'] . '://'; } if ( ! empty( $parts['host'] ) ) { $result .= $parts['host']; if ( ! empty( $parts['port'] ) ) { $result .= ':' . $parts['port']; } } if ( ! empty( $parts['path'] ) ) { $result .= $parts['path']; } return $result; } /** * Get http code. * * @return int */ public function httpCode() { if ( ! empty( $this->http_code ) ) { return $this->http_code; } if ( function_exists( 'http_response_code' ) ) { $this->http_code = (int) http_response_code(); } return $this->http_code; } /** * Get current server IP Address. * * @return string */ public function serverIp() { if ( ! empty( $this->server_ip ) ) { return $this->server_ip; } if ( isset( $_SERVER['SERVER_ADDR'] ) ) { $ip = filter_var( sanitize_text_field( wp_unslash( $_SERVER['SERVER_ADDR'] ) ), FILTER_VALIDATE_IP ); if ( is_string( $ip ) ) { $this->server_ip = $ip; return $this->server_ip; } } if ( function_exists( 'gethostbyname' ) && function_exists( 'gethostname' ) ) { $hostname = gethostname(); if ( is_string( $hostname ) ) { $ip = gethostbyname( $hostname ); if ( filter_var( $ip, FILTER_VALIDATE_IP ) ) { $this->server_ip = $ip; } } } return $this->server_ip; } /** * Set error handler. * * @return void */ public function setErrorHandler() { // @phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler set_error_handler( array( $this, 'error' ), E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED ); // @phpstan-ignore-line } /** * Restore error handler. * * @return void */ public function restoreErrorHandler() { restore_error_handler(); } /** * Send error. * * @param int $errno number. * @param string $errstr message. * @param ?string $errfile file. * @param ?int $errline line. * @param array $extra extra data. * * @return void */ public function error( $errno, $errstr, $errfile = null, $errline = null, $extra = array() ) { if ( in_array( $errno, array( E_DEPRECATED, E_USER_DEPRECATED ) ) ) { return; } $data = $this->data( $errno, $errstr, $errfile, $errline, $extra ); $this->send( $data ); } /** * Data. * * @param int $errno number. * @param string $errstr message. * @param ?string $errfile file. * @param ?int $errline line. * @param array $extra extra data. * * @return array */ public function data( $errno, $errstr, $errfile = null, $errline = null, $extra = array() ) { $errstr = $this->redact_string( (string) $errstr ); // Redact PII/secrets from the WHOLE extra channel before it leaves the // site: both the built-in telemetry ($this->extra(), which carries // request_uri) and the caller-supplied $extra (see redact()). // // $this->extra() is listed LAST so the server-derived, already-normalized // built-in telemetry WINS the merge over caller-supplied keys of the same // name. This matters because the built-in keys are safe-value keys that // bypass the token scrubber in redact(): a caller must not be able to // override e.g. request_uri (normalized by strip_uri_secrets) with a raw // value carrying a query token that would then ship unscrubbed. Caller // keys NOT produced by extra() are still merged in and redacted. $merged_extra = $this->redact( array_merge( (array) $extra, $this->extra() ) ); return array( 'environment' => $this->environment, 'release' => $this->release(), 'tags' => $this->tags(), 'extra' => $merged_extra, 'user' => $this->userdata(), 'sentry.interfaces.Exception' => array( 'exc_omitted' => null, 'values' => array( array( 'stacktrace' => array( 'frames' => $this->prepare_stack_frames( $errfile, $errno ), 'frames_omitted' => null, ), 'type' => isset( $this->codes[ $errno ] ) ? $this->codes[ $errno ] : 'Undefined: ' . $errno, 'value' => $errstr, ), ), ), ); } /** * Keys whose values are sensitive (PII or bearer capability) and must not * be sent off-site, matched case-insensitively as a substring of the key. * * @var array<string> */ private $sensitive_keys = array( 'email', 'token', 'password', 'passwd', 'secret', 'authorization', 'auth', 'api_key', 'apikey', 'access_key', 'private_key', ); /** * Keys whose values are STRUCTURED, server/enum-derived telemetry -- never * free-form text or visitor-controlled input. Their string values bypass the * redact_string() opaque-token scrubber (the 24+ char run), which would * otherwise mask an enum identifier such as advice_type into '[redacted]'. * Deliberately EXCLUDES visitor-derived keys (request_uri, website, * home_path): those can carry a path-embedded bearer-like token after query * stripping, so they must stay on the full scrub -- under-redacting a path * secret (data exposure) outweighs the rare over-redaction of a 24+ char * slug. Matched case-insensitively as an EXACT key name (unlike * $sensitive_keys, which match as a substring). An exact match here also * EXEMPTS the key from the substring-based sensitive-key removal in redact() * -- e.g. 'email_type' contains the 'email' substring but holds a category * enum ('advices'/'reminders'), not an address, so it must not be masked. * Real address keys ('email', 'user_email', ...) are NOT listed here and so * stay masked. * * @var array<string> */ private $safe_value_keys = array( 'http_code', 'server_ip', 'advice_type', 'email_type', ); /** * Redacted-value placeholder. * * @var string */ private $redaction_mask = '[redacted]'; /** * Redact sensitive entries from a caller-supplied $extra array. * * Values keyed by anything matching $sensitive_keys are replaced with the * mask; the array is walked recursively so nested payloads are covered. * Scalar string values are additionally scrubbed by redact_string(). * * @param array $extra Caller-supplied extra data. * * @return array Redacted extra data. */ private function redact( $extra ) { $result = array(); foreach ( $extra as $key => $value ) { // $safe_value_keys is an exact-match allowlist of structured enum // keys; a key on it is exempt from the broad substring sensitive-key // removal so e.g. 'email_type' (a category enum) is not masked just // because its name contains 'email'. Its value is still PII-scrubbed // via redact_pii_in_string() in the safe-value branch below. if ( is_string( $key ) && $this->is_sensitive_key( $key ) && ! $this->is_safe_value_key( $key ) ) { $result[ $key ] = $this->redaction_mask; continue; } if ( is_array( $value ) ) { $result[ $key ] = $this->redact( $value ); } elseif ( is_string( $value ) ) { // Structured, server/enum-derived telemetry (advice_type, // http_code, server_ip) skips ONLY the broad opaque-token scrub, // which would otherwise turn a long enum identifier into // '[redacted]' and ship misleading telemetry; email masking is // still applied to them as cheap defence in depth. Everything // else -- including the visitor-derived request_uri/website/ // home_path, which can carry a path-embedded token or email after // query stripping -- gets the FULL scrub. The sensitive-key // removal above applies to all. $result[ $key ] = ( is_string( $key ) && $this->is_safe_value_key( $key ) ) ? $this->redact_pii_in_string( $value ) : $this->redact_string( $value ); } else { $result[ $key ] = $value; } } return $result; } /** * Whether a key name denotes a sensitive value. * * @param string $key Array key. * * @return bool */ private function is_sensitive_key( $key ) { $key = strtolower( $key ); foreach ( $this->sensitive_keys as $needle ) { if ( false !== strpos( $key, $needle ) ) { return true; } } return false; } /** * Whether a key denotes a structured telemetry value that must bypass the * free-text opaque-token scrubber (see $safe_value_keys). * * @param string $key Array key. * * @return bool */ private function is_safe_value_key( $key ) { return in_array( strtolower( $key ), $this->safe_value_keys, true ); } /** * Redact obvious PII/secrets from a free-form string before it is shipped * off-site (e.g. embedded in $errstr or a string $extra value). * * Masks email addresses and long opaque tokens; leaves ordinary error text * intact so telemetry stays useful. * * @param string $value String to scrub. * * @return string Scrubbed string. */ private function redact_string( $value ) { // Unambiguous PII (always masked, even for safe-value keys). $value = $this->redact_pii_in_string( $value ); // Long opaque tokens (>= 24 chars of base62/url-safe) such as unsubscribe // tokens or API keys. This broad rule can also mangle legitimate long // route slugs / identifiers, so redact() relaxes ONLY this rule (not the // PII rule above) for safe-value keys (see is_safe_value_key()). $value = preg_replace( '/\b[A-Za-z0-9_-]{24,}\b/', $this->redaction_mask, $value ); return is_string( $value ) ? $value : $this->redaction_mask; } /** * Mask unambiguous PII (email addresses) in a free-form string. This subset * of redact_string() is applied to EVERY value, including safe-value keys: an * email embedded in a request_uri path, website or home_path is PII, never a * benign slug, so it must be scrubbed even when the broad opaque-token rule * is relaxed for structured telemetry. * * @param string $value String to scrub. * * @return string Scrubbed string. */ private function redact_pii_in_string( $value ) { $value = preg_replace( '/[\w.+-]+@[\w-]+(?:\.[\w-]+)+/', $this->redaction_mask, $value ); return is_string( $value ) ? $value : $this->redaction_mask; } /** * Send to sentry. * * @param array $body send. * * @return string|false */ public function send( $body ) { if ( ! function_exists( 'curl_init' ) || empty( $body ) || defined( 'IS_TESTING' ) ) { return false; } $url = 'https://cl.sentry.cloudlinux.com/api/' . $this->project_id() . '/store/'; $headers = array( 'Content-Type: application/json', 'X-Sentry-Auth: Sentry sentry_version=7,sentry_timestamp=' . time() . ',sentry_client=php-curl/1.0,sentry_key=' . $this->key(), ); $ch = curl_init(); curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers ); curl_setopt( $ch, CURLOPT_URL, $url ); curl_setopt( $ch, CURLOPT_POST, true ); curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, 'POST' ); curl_setopt( $ch, CURLOPT_POSTFIELDS, wp_json_encode( $body ) ); curl_setopt( $ch, CURLOPT_TIMEOUT, 1 ); curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 1 ); curl_setopt( $ch, CURLOPT_HEADER, 0 ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); $response = curl_exec( $ch ); curl_close( $ch ); return $response; } /** * Minimize an absolute filesystem path before it is shipped off-site. * * Mirrors the profiler sibling's minimizePath (F-17): keep only the * basename when it is plausibly a real source/asset file (so a stack-frame * filename stays useful), otherwise return the literal '<path>' token. A * bare directory/account leaf -- e.g. ABSPATH's '/home/<account>/...' or * a docroot -- is therefore reduced to '<path>', so the OS account name and * filesystem layout never reach Sentry. Telemetry goes to CloudLinux's own * first-party Sentry, so this is defence-in-depth consistency with F-17, not * a fix for a cross-tenant leak. * * @param string|null $path Absolute path. * * @return string|null Basename, '<path>', or the input unchanged when empty. */ private function minimize_path( $path ) { if ( ! is_string( $path ) || '' === $path ) { return $path; } $normalized = trim( str_replace( '\\', '/', $path ), '/' ); if ( '' === $normalized ) { return '<path>'; } $segments = explode( '/', $normalized ); $basename = end( $segments ); $source_ext_pattern = '/\.(php\d?|phtml|phps|inc|module|install' . '|engine|theme|profile|tpl|twig|html?|js|mjs|cjs|jsx|tsx?' . '|css|s[ac]ss|less|xml|ya?ml|json)$/i'; if ( '' === $basename || ! preg_match( $source_ext_pattern, $basename ) ) { return '<path>'; } return $basename; } /** * Prepares stack trace data for Sentry. * * @param string $errfile File name. * @param int $errline Line number. * * @return array * * @since 0.1-9 */ private function prepare_stack_frames( $errfile = null, $errline = null ) { // Default result will contain only the error file and line. The filename // is path-minimized so an absolute /home/<account>/... path is not // shipped to Sentry (consistency with F-17; see minimize_path()). $default = array( array( 'filename' => $this->minimize_path( $errfile ), 'lineno' => $errline, ), ); if ( ! function_exists( 'debug_backtrace' ) ) { return $default; } $backtrace = debug_backtrace(); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace // Ignore last couple of frames that are not relevant to the error. $frames_to_omit = array( 'App/Debug.php', // Call to CloudLinux\SmartAdvice\App::prepare_stack_frames(). 'App/Debug.php', // Call to CloudLinux\SmartAdvice\App::data(). 'wp-includes/class-wp-hook.php', // WordPress executing a filter. 'wp-includes/class-wp-hook.php', // WordPress executing a filter. 'wp-includes/plugin.php', // Call to apply_filters(). ); foreach ( $frames_to_omit as $frame_to_omit ) { if ( isset( $backtrace[0]['file'] ) && strpos( $backtrace[0]['file'], $frame_to_omit ) !== false ) { array_shift( $backtrace ); } } $frames = array(); while ( ! empty( $backtrace ) ) { $frame = array_pop( $backtrace ); if ( isset( $frame['file'] ) && isset( $frame['line'] ) ) { $frames[] = array( 'filename' => $this->minimize_path( $frame['file'] ), 'lineno' => $frame['line'], ); } } if ( empty( $frames ) ) { return $default; } return $frames; } /** * Determines the plugin version. * * @return string Plugin version. * * @since 0.1-11 */ public function plugin_version() { return defined( 'CL_SMART_ADVICE_VERSION' ) ? CL_SMART_ADVICE_VERSION : 'dev'; } /** * Get environment name. * * @return string Environment name. * * @since 0.1-11 */ public function environment() { return $this->environment; } /** * Get release name. * * @return string Release name. * * @since 0.1-11 */ public function release() { return 'php-smart-advice-plugin@' . $this->plugin_version(); } /** * Get Sentry key. * * @return string Sentry Key. * * @since 0.1-11 */ public function key() { return 'ef44dbb648894027a282678f691abc5a'; } /** * Get Sentry project ID. * * @return int Sentry project ID. * * @since 0.1-11 */ public function project_id() { return 24; } /** * Get tags. * * @return array<string> Tags. * * @since 0.1-11 */ public function tags() { return array( 'php_version' => phpversion(), 'plugin_version' => $this->plugin_version(), 'xray_version' => defined( 'CL_SMART_ADVICE_XRAY_RELEASE' ) ? CL_SMART_ADVICE_XRAY_RELEASE : null, ); } /** * Get user data. * * @return array<string> User data. * * @since 0.1-11 */ public function userdata() { return array( 'username' => $this->user(), ); } /** * Get extra data. * * @return array<string> Extra data. * * @since 0.1-11 */ public function extra() { return array( 'website' => $this->website(), // home_path is ABSPATH ('/home/<account>/public_html/...'); minimize // it so the OS account name / FS layout is not shipped to Sentry // (consistency with F-17; redact_string alone left the username). 'home_path' => $this->minimize_path( $this->homePath() ), 'request_uri' => $this->requestUri(), 'http_code' => $this->httpCode(), 'server_ip' => $this->serverIp(), ); } }
💾 Save Changes
❌ Cancel