AlkantarClanX12

Your IP : 216.73.217.24


Current Path : /www/capitalgmcbuickregina_830/public/wp-content/plugins/wp-cerber/includes/
Upload File :
Current File : /www/capitalgmcbuickregina_830/public/wp-content/plugins/wp-cerber/includes/cerber-bug-hunter.php

<?php
/*
	Copyright (C) 2015-26 CERBER TECH INC., https://wpcerber.com

    Licensed under the GNU GPL.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

*/

/**
 * Handles detection, storage, and reporting of PHP errors within WP Cerber code.
 *
 * This class serves as a central point for capturing exceptions, runtime errors,
 * and fatal shutdown errors.
 *
 * @version 2.0
 *
 * @since 9.6.9.1
 */
final class CRB_Bug_Hunter {

	/**
	 * Name of the log file.
	 */
	public const CRB_ERROR_LOG = 'cerber-errors.log';

	/**
	 * Remote service URL for reporting bugs.
	 */
	public const SERVICE_URL = 'https://downloads.wpcerber.com/bughunter/';

	// Limits and settings
	public const BODY_SIZE_LIMIT = 64 * 1024; // 64KB
	public const MSG_MAX_LEN = 2048;
	public const FILE_MAX_LEN = 1024;
	public const URI_MAX_LEN = 1024;
	public const REMOTE_TIMEOUT = 3;
	public const DEFAULT_LINES_TO_KEEP = 50;

	/**
	 * Buffer for collected errors before saving.
	 *
	 * Structure: [errno, message, file, line, uri]
	 *
	 * @var array<int, array{0: int, 1: string, 2: string, 3: int, 4: string}>
	 */
	private static array $collected_errors = [];

	/**
	 * Index for deduplicating errors within a single request.
	 *
	 * @var array<string, bool>
	 */
	private static array $dedup_index = [];

	/**
	 * The folder where WP Cerber is installed. No trailing slash.
	 *
	 * @var string
	 */
	private static string $plugin_dir = '';

	/**
	 * Returns the full path to the error log file.
	 *
	 * @return string Empty string if the diagnostic directory is unavailable.
	 *
	 * @since 9.6.9.1
	 */
	private static function get_log_file_path(): string {

		$dir = crb_get_diag_dir();

		if ( ! $dir ) {
			return '';
		}

		return $dir . self::CRB_ERROR_LOG;
	}

	/**
	 * Reads all lines from the error log file.
	 *
	 * @return string[] Array of log lines or an empty array if unreadable.
	 *
	 * @since 9.6.9.1
	 */
	private static function read_log_lines(): array {

		$file_path = self::get_log_file_path();

		// Robustness: explicitly check availability and readability
		if ( ! $file_path || ! file_exists( $file_path ) || ! is_readable( $file_path ) ) {
			return array();
		}

		$lines = file( $file_path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );

		if ( ! is_array( $lines ) ) {
			return array();
		}

		return $lines;
	}

	/**
	 * Saves accumulated errors to the log file.
	 *
	 * Performs filtering, formatting (JSON), and thread-safe writing (flock).
	 *
	 * @return void
	 *
	 * @since 9.6.9.1
	 */
	public static function save_errors(): void {
		// Use locally collected errors
		$errors = self::$collected_errors;

		// Clear collected errors immediately to prevent duplicate processing
		self::$collected_errors = [];

		if ( empty( $errors ) ) {
			return;
		}

		$crb_errors = array();
		$php        = phpversion();
		$wp         = cerber_get_wp_version();

		foreach ( $errors as $error_info ) {
			// Ensure the array structure is valid
			if ( ! isset( $error_info[0], $error_info[1], $error_info[2], $error_info[3] ) ) {
				continue;
			}

			$err_level = $error_info[0];
			$msg       = $error_info[1];
			$file      = $error_info[2];
			$line      = $error_info[3];
			$uri       = $error_info[4] ?? '';

			// Filter out specific warnings (e.g. Permission denied) to reduce noise
			// Logic cleanup: Use strict comparison !== false
			if ( E_WARNING === $err_level && false !== strripos( $msg, 'Permission denied' ) ) {
				continue;
			}

			// We collect WP Cerber errors only
			if ( self::is_own_file( $file ) ) {
				// Use substr to enforce byte-level limits
				$msg  = substr( $msg, 0, self::MSG_MAX_LEN );
				$file = substr( $file, 0, self::FILE_MAX_LEN );
				$uri  = substr( $uri, 0, self::URI_MAX_LEN );

				$crb_errors[] = array( $err_level, $msg, $file, $line, CERBER_VER, $php, $wp, $uri );
			}
		}

		if ( empty( $crb_errors ) ) {
			return;
		}

		// One log entry can contain multiple errors
		$log_entry = array(
			'time'   => time(),
			'errors' => $crb_errors,
		);

		$json = json_encode( $log_entry, JSON_UNESCAPED_UNICODE );
		if ( false === $json ) {
			return;
		}

		$file_path = self::get_log_file_path();
		if ( ! $file_path ) {
			return;
		}

		// Robustness: Check write permissions explicitly to avoid warnings
		if ( file_exists( $file_path ) && ! is_writable( $file_path ) ) {
			return;
		}
		// If file doesn't exist, check directory permissions
		if ( ! file_exists( $file_path ) ) {
			$dir = dirname( $file_path );
			if ( ! is_dir( $dir ) || ! is_writable( $dir ) ) {
				return;
			}
		}

		$log = fopen( $file_path, 'a' );
		if ( false === $log ) {
			return;
		}

		// Robustness: Use explicit flock check. If locked, skip writing to avoid waiting/blocking.
		if ( flock( $log, LOCK_EX | LOCK_NB ) ) {
			fwrite( $log, $json . PHP_EOL );
			flock( $log, LOCK_UN );
		}

		fclose( $log );
	}

	/**
	 * Buffers an error for later saving.
	 *
	 * @param int    $errno   Error level (e.g., E_WARNING, E_NOTICE).
	 * @param string $errstr  Error message.
	 * @param string $errfile Filename where the error occurred.
	 * @param int    $errline Line number.
	 *
	 * @return void
	 */
	public static function collect_error( int $errno, string $errstr, string $errfile, int $errline ): void {
		// URI is intentionally excluded to deduplicate the same defect across different pages.
		$dedup_key = sha1( $errno . '|' . $errfile . '|' . $errline . '|' . $errstr );

		if ( isset( self::$dedup_index[ $dedup_key ] ) ) {
			return;
		}

		self::$dedup_index[ $dedup_key ] = true;

		$uri = $_SERVER['REQUEST_URI'] ?? '';

		self::$collected_errors[] = array( $errno, $errstr, $errfile, $errline, $uri );
	}

	/**
	 * Returns the list of currently collected (buffered) errors.
	 *
	 * @return array<int, array{0: int, 1: string, 2: string, 3: int, 4: string}>
	 */
	public static function get_collected_errors(): array {
		return self::$collected_errors;
	}

	/**
	 * Checks if there are any collected errors in the buffer.
	 *
	 * Optionally filters by specific error code(s).
	 *
	 * @param int|int[]|null $code Optional error code(s) to check for.
	 * See https://www.php.net/manual/en/errorfunc.constants.php
	 *
	 * @return bool True if relevant errors exist, false otherwise.
	 */
	public static function has_errors( $code = null ): bool {
		if ( empty( self::$collected_errors ) ) {
			return false;
		}

		if ( null === $code ) {
			return true;
		}

		// Extract error codes (index 0) from the collected errors
		$collected_codes = array_column( self::$collected_errors, 0 );

		if ( is_array( $code ) ) {
			return ! empty( array_intersect( $collected_codes, $code ) );
		}

		return in_array( $code, $collected_codes, true );
	}

	/**
	 * Captures and buffers a Throwable for logging.
	 *
	 * Normalizes the exception/error into a standard array format
	 * and adds it to the internal buffer.
	 *
	 * @param Throwable $e The caught exception or error.
	 *
	 * @return void
	 */
	public static function log_throwable( Throwable $e ): void {
		list( $errno, $errstr, $errfile, $errline ) = self::normalize_throwable( $e );

		self::collect_error( $errno, $errstr, $errfile, $errline );
	}

	/**
	 * Custom exception handler compatible with set_exception_handler().
	 *
	 * Logs the exception and immediately persists the buffer to disk,
	 * as script execution halts after this handler.
	 *
	 * @param Throwable $e The uncaught exception.
	 *
	 * @return void
	 */
	public static function exception_handler( Throwable $e ): void {
		self::log_throwable( $e );
		// Since execution stops after this handler, save immediately.
		self::save_errors();
	}

	/**
	 * Custom error handler compatible with set_error_handler().
	 *
	 * Collects all errors regardless of error_reporting level or the silence operator (@).
	 *
	 * @param int    $errno   The level of the error raised.
	 * @param string $errstr  The error message.
	 * @param string $errfile The filename that the error was raised in.
	 * @param int    $errline The line number the error was raised at.
	 *
	 * @return bool Always returns false to allow standard PHP error handling to continue.
	 */
	public static function error_handler( int $errno, string $errstr, string $errfile, int $errline ): bool {

		// Suppress known warnings that are not actionable and would only add noise to the logs when it originates from WP Cerber code.
		if ( self::is_own_file( $errfile )
		     && self::is_suppressed( $errno, $errstr ) ) {
			return true;
		}

		// Collect all errors regardless of error_reporting level or the silence operator (@)
		self::collect_error( $errno, $errstr, $errfile, $errline );

		// Allow standard PHP error handling to proceed
		return false;
	}

	/**
	 * Handles the final error from error_get_last() during script shutdown.
	 *
	 * This method should be called inside a register_shutdown_function() callback.
	 * It ensures that fatal errors (which are not caught by set_error_handler) are logged.
	 *
	 * @param array{type: int, message: string, file: string, line: int}|null $error The error array provided by error_get_last().
	 *
	 * @return void
	 */
	public static function handle_shutdown_error( ?array $error ): void {
		// The error handler catches regular runtime errors.
		// Here we only need to capture errors that set_error_handler cannot handle (e.g. E_ERROR, E_PARSE).
		$uncatchable_errors = array(
			E_ERROR,
			E_PARSE,
			E_CORE_ERROR,
			E_CORE_WARNING,
			E_COMPILE_ERROR,
			E_COMPILE_WARNING,
		);

		if ( $error
		     && isset( $error['type'], $error['message'], $error['file'], $error['line'] )
		     && in_array( $error['type'], $uncatchable_errors, true )
		) {
			self::collect_error(
				(int) $error['type'],
				(string) $error['message'],
				(string) $error['file'],
				(int) $error['line']
			);
		}
	}

	/**
	 * Converts a Throwable into a standard error array.
	 *
	 * Unwraps nested exceptions to find the root cause.
	 *
	 * @param Throwable $e
	 *
	 * @return array{0: int, 1: string, 2: string, 3: int}
	 */
	private static function normalize_throwable( Throwable $e ): array {
		while ( $e->getPrevious() instanceof Throwable ) {
			$e = $e->getPrevious();
		}

		$errstr  = $e->getMessage();
		$errfile = $e->getFile();
		$errline = (int) $e->getLine();

		if ( $e instanceof ErrorException ) {
			$errno = (int) $e->getSeverity();
			return array( $errno, $errstr, $errfile, $errline );
		}

		if ( $e instanceof Error ) {
			return array( E_ERROR, $errstr, $errfile, $errline );
		}

		return array( E_USER_ERROR, $errstr, $errfile, $errline );
	}

	/**
	 * Determines whether the given file is located in the WP Cerber plugin directory
	 *
	 * @param string $file Absolute file path reported by PHP.
	 *
	 * @return bool True if the file resides within the WP Cerber directory tree. False otherwise.
	 *
	 * @since 9.6.16
	 */
	private static function is_own_file( string $file ): bool {
		if ( ! self::$plugin_dir ) {
			self::$plugin_dir = cerber_plugin_dir();
		}

		return strpos( $file, self::$plugin_dir ) === 0;
	}

	/**
	 * Checks whether a warning message can be safely suppressed to avoid log noise.
	 *
	 * Suppressed warnings are manually reviewed and must meet both criteria:
	 * not actionable and known to produce noisy log entries.
	 *
	 * @param int    $errno  Error level reported by PHP.
	 * @param string $errstr Error message text.
	 *
	 * @return bool True if the error matches a known suppressed warning. False otherwise.
	 *
	 * @since 9.6.16
	 */
	private static function is_suppressed( int $errno, string $errstr ): bool {

		// Case 1.

		if ( $errno === E_COMPILE_WARNING ) {
			if ( strpos( $errstr, 'ASCII=127' ) !== false
			     && strpos( $errstr, 'Unexpected character in input' ) !== false ) {
				return true;
			}
		}

		// Case 2.

		return false;
	}

	/**
	 * Truncates the log file, retaining the last N lines.
	 *
	 * @param int $lines_to_keep Number of lines to preserve (default: 50).
	 *
	 * @return void
	 *
	 * @since 9.6.9.1
	 */
	public static function truncate_log_file( int $lines_to_keep = self::DEFAULT_LINES_TO_KEEP ): void {

		$file_path = self::get_log_file_path();

		if ( ! $file_path || ! file_exists( $file_path ) ) {
			return;
		}

		// Robustness: Check write permissions before attempting to modify
		if ( ! is_writable( $file_path ) ) {
			return;
		}

		if ( ! $lines_to_keep ) {
			// Explicit empty string write, check result if needed, but file_put_contents usually sufficient here
			file_put_contents( $file_path, '' );
			return;
		}

		$lines = self::read_log_lines();

		if ( empty( $lines ) || count( $lines ) <= $lines_to_keep ) {
			return;
		}

		$lines_to_keep = max( 1, $lines_to_keep );
		$lines         = array_slice( $lines, - $lines_to_keep );

		$log = fopen( $file_path, 'w' );

		if ( false === $log ) {
			return;
		}

		// Robustness: Exclusive blocking lock for truncation (blocking is acceptable here for maintenance task)
		if ( flock( $log, LOCK_EX ) ) {
			foreach ( $lines as $line ) {
				fwrite( $log, $line . PHP_EOL );
			}
			flock( $log, LOCK_UN );
		}

		fclose( $log );
	}

	/**
	 * Executes maintenance tasks: flush errors and truncate log.
	 *
	 * @return void
	 *
	 * @since 9.6.9.1
	 */
	public static function run_maintenance_tasks(): void {
		self::flush_errors();
		self::truncate_log_file();
	}

	/**
	 * Sends accumulated errors to the remote collector.
	 *
	 * Performs data anonymization before sending.
	 *
	 * @return void
	 *
	 * @since 9.6.9.1
	 */
	public static function flush_errors(): void {
		$lines = self::read_log_lines();

		if ( empty( $lines ) ) {
			return;
		}

		$payload = array(
			'crb_version' => CERBER_VER,
			'wp_version'  => cerber_get_wp_version(),
			'revision'    => 1,
			'bug_pile'    => array(),
		);

		$root_dir = rtrim( ABSPATH, '/' );

		// Which entry we processed last time, if any
		$last_entry = (int) cerber_get_set( 'bug_hunter_last_processed', 0, false );
		$save_last  = 0;

		foreach ( $lines as $line ) {
			$entry = json_decode( $line, true );

			if ( json_last_error() !== JSON_ERROR_NONE
			     || empty( $entry['time'] )
			     || $entry['time'] <= $last_entry ) {
				continue;
			}

			// Remove sensitive data (anonymisation)
			if ( isset( $entry['errors'] ) && is_array( $entry['errors'] ) ) {
				foreach ( $entry['errors'] as &$error ) {
					// Ensure the error structure is valid before access
					if ( isset( $error[2] ) ) {
						$error[2] = str_replace( $root_dir, '', $error[2] );
					}
					if ( isset( $error[7] ) ) {
						$error[7] = crb_strip_query_params( $error[7], array(
							'page',
							'tab',
							'cerber_admin_do',
							'filter_activity',
							'filter_set',
						) );
					}
				}
				unset( $error ); // Break reference
			}

			// Build payload
			$payload['bug_pile'][] = $entry;
			$test_body             = json_encode( $payload, JSON_UNESCAPED_UNICODE );

			if ( false === $test_body || strlen( $test_body ) > self::BODY_SIZE_LIMIT ) {
				array_pop( $payload['bug_pile'] );
				break;
			}

			$save_last = $entry['time'];
		}

		if ( empty( $payload['bug_pile'] ) ) {
			return;
		}

		$body = json_encode( $payload, JSON_UNESCAPED_UNICODE );

		if ( false === $body ) {
			return;
		}

		$args = array(
			'body'    => $body,
			'headers' => array(
				'Content-Type'    => 'application/json',
				'X-Body-Checksum' => md5( $body ),
			),
			'timeout' => self::REMOTE_TIMEOUT,
		);

		$diag_enabled = defined( 'CERBER_NETWORK_DEBUG' ) && CERBER_NETWORK_DEBUG;

		if ( $diag_enabled ) {
			cerber_diag_log( 'Sending BugHunter payload (' . strlen( $body ) . ' bytes)', 'BugHunter' );
		}

		$response = wp_remote_post( self::SERVICE_URL, $args );

		if ( crb_is_wp_error( $response ) ) {
			if ( $diag_enabled ) {
				cerber_error_log( 'BugHunter request failed: ' . $response->get_error_message(), 'BugHunter' );
			}

			return;
		}

		$code = wp_remote_retrieve_response_code( $response );

		if ( $code !== 200 ) {
			if ( $diag_enabled ) {
				cerber_error_log( 'BugHunter bad response code: ' . $code, 'BugHunter' );
			}
		} else {
			// HTTP 200 OK = SUCCESS
			// Set marker for the next submission
			cerber_update_set( 'bug_hunter_last_processed', $save_last, 0, false );
		}
	}

	/**
	 * Generates an HTML table view of the error log.
	 *
	 * @param string $item_class CSS class for log entry wrappers.
	 *
	 * @return string Formatted HTML content or empty string if no logs found.
	 *
	 * @since 9.6.9.1
	 */
	public static function create_log_view( string $item_class = '' ): string {

		$lines = self::read_log_lines();

		if ( empty( $lines ) ) {
			return '';
		}

		$entries  = array();
		$root_dir = rtrim( ABSPATH, '/' );

		foreach ( array_reverse( $lines ) as $line ) {
			$entry = json_decode( $line, true );

			if ( json_last_error() !== JSON_ERROR_NONE || ! isset( $entry['time'], $entry['errors'] ) ) {
				continue;
			}

			$errors = '';

			foreach ( $entry['errors'] as $error ) {
				// Robustness: ensure array keys exist before access
				$level = isset( $error[0] ) ? cerber_get_err_level( $error[0] ) : 'Unknown';
				$file  = isset( $error[2] ) ? str_replace( $root_dir, '', $error[2] ) : '';
				$line  = isset( $error[3] ) ? crb_absint( $error[3] ) : 0;
				$msg   = isset( $error[1] ) ? str_replace( $root_dir, '', $error[1] ) : '';
				$ver   = $error[4] ?? '';
				$php   = $error[5] ?? '';
				$wp    = $error[6] ?? '';
				$uri   = $error[7] ?? '';

				$errors .= "Level:\t" . $level . '<br/>';
				$errors .= "File:\t" . crb_escape_html( $file ) . '<br/>';
				$errors .= "Line:\t" . $line . '<br/>';
				$errors .= "Error:\t" . crb_escape_html( $msg ) . '<br/>';
				$errors .= "Version:\t" . crb_escape_html( $ver ) . '<br/>';
				$errors .= "PHP:\t" . crb_escape_html( $php ) . '<br/>';
				$errors .= "WP:\t" . crb_escape_html( $wp ) . '<br/>';
				$errors .= "URI:\t" . crb_escape_html( $uri ) . '<br/>';
			}

			$entries[] = array(
				cerber_date( $entry['time'] ),
				'<div class="' . crb_boring_escape( $item_class ) . '" style="white-space: pre-wrap; tab-size: 10;">' . $errors . '</div>' . "\n",
			);
		}

		$ret = cerber_make_plain_table( $entries );

		return $ret;
	}
}

Home - Capital GMC Buick Regina

No data

Welcome to Capital GMC BUICK – REGINA

Thank you for choosing Capital GMC Buick | Regina, your premier certified Buick and GMC dealership proudly serving drivers in Regina and the surrounding communities. Whether you’re searching for a brand-new Buick or GMC vehicle or a meticulously inspected pre-owned model, we have a diverse selection to match your needs and lifestyle.

Beyond our impressive inventory, we offer a seamless and stress-free financing experience through our well-connected finance centre, where our team of experts is dedicated to securing the best loan or lease options for you, quickly, transparently, and hassle-free.

But our commitment to you doesn’t stop at the sale. Our state-of-the-art service centre is staffed with skilled Buick and GMC technicians who use the latest equipment and genuine OEM parts to keep your vehicle running at its best. From routine maintenance to complex repairs, we’ve got you covered.

Experience top-tier customer service, quality vehicles, and expert care, all in one place. Visit Capital GMC Buick | Regina today or call us at 306-205-8072 with any questions. We’re here to help!

Ask a Question