Current File : /home/d/i/g/digitaw/www/wp-content/plugins/simple-history/loggers/class-core-updates-logger.php
<?php

namespace Simple_History\Loggers;

/**
 * Logs WordPress core updates
 */
class Core_Updates_Logger extends Logger {
	/** @var string Logger slug */
	public $slug = 'SimpleCoreUpdatesLogger';

	/**
	 * Get array with information about this logger
	 *
	 * @return array
	 */
	public function get_info() {
		return [
			'name'        => __( 'Core Updates Logger', 'simple-history' ),
			'description' => __( 'Logs the update of WordPress (manual and automatic updates)', 'simple-history' ),
			'capability'  => 'update_core',
			'messages'    => array(
				'core_updated'                             => __( 'Updated WordPress to {new_version} from {prev_version}', 'simple-history' ),
				'core_auto_updated'                        => __( 'WordPress auto-updated to {new_version} from {prev_version}', 'simple-history' ),
				'core_update_failed'                       => __( 'Failed to update WordPress', 'simple-history' ),
				'core_db_version_updated'                  => __( 'WordPress database version updated to {new_version} from {prev_version}', 'simple-history' ),
				'core_major_auto_updates_setting_enabled'  => __( 'Enabled automatic updates for all new versions of WordPress', 'simple-history' ),
				'core_major_auto_updates_setting_disabled' => __( 'Switched to automatic updates for maintenance and security releases of WordPress only', 'simple-history' ),
			),
			'labels'      => array(
				'search' => array(
					'label'   => _x( 'WordPress Core', 'User logger: search', 'simple-history' ),
					'options' => array(
						_x( 'WordPress core updates', 'User logger: search', 'simple-history' ) => array(
							'core_updated',
							'core_auto_updated',
							'core_update_failed',
						),
					),
				),
			),
		];
	}
	/**
	 * @inheritdoc
	 */
	public function loaded() {
		add_action( '_core_updated_successfully', array( $this, 'on_core_updated' ) );
		add_action( 'update_feedback', array( $this, 'on_update_feedback' ) );
		add_action( 'load-update-core.php', array( $this, 'on_load_update_core_handle_auto_update_core_major' ) );

		// Hook for detecting automatic core update failures.
		// This filter is called when WordPress is about to send an email about an automatic update.
		// We get the actual WP_Error result object with full error details.
		// NOTE: This only works for AUTOMATIC updates. Manual update failures cannot be reliably
		// detected due to WordPress Core_Upgrader architecture (result not stored in accessible location).
		add_filter( 'auto_core_update_send_email', array( $this, 'on_auto_core_update_send_email' ), 10, 4 );

		// TODO: check if this works after refactoring and autoloading and stuff
		// Can't log db updates at the moment, because loaded() is not called yet when the action fires.
		// phpcs:ignore Squiz.Commenting.InlineComment.InvalidEndChar
		// add_action( 'wp_upgrade', array( $this, "on_wp_upgrade" ), 10, 2 );
	}

	/**
	 * Fired when loading admin page /wp-admin/update-core.php.
	 *
	 * This site is automatically kept up to date with each new version of WordPress.
	 * Switch to automatic updates for maintenance and security releases only.
	 * http://wordpress-stable-docker-mariadb.test:8282/wp-admin/update-core.php?action=core-major-auto-updates-settings&value=disable&_wpnonce=ad1ff0569c
	 *
	 * This site is automatically kept up to date with maintenance and security releases of WordPress only.
	 * Enable automatic updates for all new versions of WordPress.
	 * http://wordpress-stable-docker-mariadb.test:8282/wp-admin/update-core.php?action=core-major-auto-updates-settings&value=enable&_wpnonce=ad1ff0569c
	 */
	public function on_load_update_core_handle_auto_update_core_major() {
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$value = $_GET['value'] ?? '';

		if ( ! in_array( $value, [ 'enable', 'disable' ], true ) ) {
			return;
		}

		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		if ( ! wp_verify_nonce( $_GET['_wpnonce'] ?? '', 'core-major-auto-updates-nonce' ) ) {
			return;
		}

		switch ( $value ) {
			case 'enable':
				$this->info_message( 'core_major_auto_updates_setting_enabled' );
				break;
			case 'disable':
				$this->info_message( 'core_major_auto_updates_setting_disabled' );
				break;
		}
	}

	/**
	 * Fires after a site is fully upgraded.
	 * The database, that is.
	 *
	 * @param int $wp_db_version         The new $wp_db_version.
	 * @param int $wp_current_db_version The old (current) $wp_db_version.
	 */
	public function on_wp_upgrade( $wp_db_version, $wp_current_db_version ) {
		$this->debug_message(
			'core_db_version_updated',
			[
				'new_version'  => $wp_db_version,
				'prev_version' => $wp_current_db_version,
			]
		);
	}

	/**
	 * We need to store the WordPress version we are updating from.
	 * 'update_feedback' is a suitable filter.
	 */
	public function on_update_feedback() {

		if ( ! empty( $GLOBALS['wp_version'] ) && ! isset( $GLOBALS[ 'simple_history_' . $this->get_slug() . '_wp_version' ] ) ) {
			$GLOBALS[ 'simple_history_' . $this->get_slug() . '_wp_version' ] = $GLOBALS['wp_version'];
		}
	}

	/**
	 * Called when WordPress is updated
	 *
	 * @param string $new_wp_version The new WordPress version.
	 */
	public function on_core_updated( $new_wp_version ) {
		$old_wp_version = empty( $GLOBALS[ 'simple_history_' . $this->get_slug() . '_wp_version' ] ) ? $GLOBALS['wp_version'] : $GLOBALS[ 'simple_history_' . $this->get_slug() . '_wp_version' ];

		$auto_update = true;
		if ( $GLOBALS['pagenow'] === 'update-core.php' ) {
			$auto_update = false;
		}

		$message = $auto_update ? 'core_auto_updated' : 'core_updated';

		$this->notice_message(
			$message,
			[
				'prev_version' => $old_wp_version,
				'new_version'  => $new_wp_version,
			]
		);
	}

	/**
	 * Log automatic core update failures.
	 *
	 * This filter is called when WordPress is about to send an email notification
	 * about an automatic core update. We use it to log update failures because
	 * it provides reliable access to the WP_Error result object.
	 *
	 * This is the RELIABLE method for logging automatic core update failures.
	 * Unlike upgrader_process_complete, this filter receives the actual error result
	 * as a parameter, so we can access all error details.
	 *
	 * @since WordPress 3.7.0
	 *
	 * @param bool   $send        Whether to send the email. We don't modify this.
	 * @param string $type        The type of email: 'success', 'fail', 'manual', or 'critical'.
	 * @param object $core_update The update offer that was attempted.
	 * @param mixed  $result      The result for the core update. Can be WP_Error.
	 * @return bool Whether to send the email (unchanged).
	 */
	public function on_auto_core_update_send_email( $send, $type, $core_update, $result ) {
		// Only log failures (not success).
		if ( ! in_array( $type, [ 'fail', 'critical' ], true ) ) {
			return $send;
		}

		// Ensure we have a WP_Error result.
		if ( ! is_wp_error( $result ) ) {
			return $send;
		}

		// Build context for logging.
		$context = [
			'error_code'    => $result->get_error_code(),
			'error_message' => $result->get_error_message(),
			'auto_update'   => true, // This filter only fires for automatic updates.
			// phpcs:ignore Squiz.PHP.CommentedOutCode.Found -- This explains possible values.
			'failure_type'  => $type, // Is 'fail' or 'critical'.
		];

		// Add error data if available.
		$error_data = $result->get_error_data();
		if ( ! empty( $error_data ) ) {
			$context['error_data'] = $error_data;
		}

		// Add attempted version.
		if ( ! empty( $core_update->current ) ) {
			$context['new_version'] = $core_update->current;
		}

		// Get the old WordPress version if available.
		if ( ! empty( $GLOBALS[ 'simple_history_' . $this->get_slug() . '_wp_version' ] ) ) {
			$context['prev_version'] = $GLOBALS[ 'simple_history_' . $this->get_slug() . '_wp_version' ];
		}

		// Detect if this is a rollback scenario (WordPress core has its own rollback mechanism).
		// Core rollback is triggered by specific error codes.
		$error_codes          = $result->get_error_codes();
		$rollback_error_codes = [
			'rollback_was_required',
			'__copy_dir',
			'disk_full',
			'do_rollback',
		];

		// Check if any error code indicates a rollback.
		if ( ! empty( array_intersect( $error_codes, $rollback_error_codes ) ) ) {
			$context['rollback_occurred'] = true;
		}

		// For 'critical' failures, WordPress has already determined this was a critical issue
		// (occurred after we started copying core files).
		if ( $type === 'critical' ) {
			$context['critical_failure'] = true;
		}

		// Log the failure.
		$this->warning_message(
			'core_update_failed',
			$context
		);

		// Don't modify the email sending behavior.
		return $send;
	}
}