Your IP : 216.73.217.81


Current Path : /home3/vocl2671/labiso/wp-content/plugins/mainwp-child/class/
Upload File :
Current File : /home3/vocl2671/labiso/wp-content/plugins/mainwp-child/class/class-mainwp-child-patchstack.php

<?php
/**
 * MainWP Patchstack
 *
 * MainWP Patchstack extension handler.
 * Extension URL: https://mainwp.com/extension/patchstack/
 *
 * @package MainWP\Child
 */

namespace MainWP\Child;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

// phpcs:disable PSR1.Classes.ClassDeclaration, WordPress.WP.AlternativeFunctions, Squiz.Commenting.FunctionComment.MissingParamTag -- required to achieve desired results, pull request solutions appreciated.

/**
 * Class MainWP_Child_Patchstack
 *
 * MainWP Patchstack extension handler
 */
class MainWP_Child_Patchstack { //phpcs:ignore -- NOSONAR - multi methods.

    /** Durable visibility receipt option. */
    const ABILITIES_V2_RECEIPTS_OPTION = 'mainwp_child_patchstack_v2_receipts';

    /** Durable protection-operation receipt option. */
    const ABILITIES_V2_PROTECTION_RECEIPTS_OPTION = 'mainwp_child_patchstack_v2_protection_receipts';

    /** Atomic visibility mutation lock. */
    const ABILITIES_V2_LOCK_OPTION = 'mainwp_child_patchstack_v2_lock';

    /**
     * Current visibility lock owner.
     *
     * @var string
     */
    private $abilities_v2_lock_owner = '';

    /**
     * Public static variable to hold the single instance of the class.
     *
     * @var mixed Default null
     */
    public static $instance = null;

    /**
     * Patchstack install status. True if the plugin is installed on the child site.
     *
     * @var bool If Patchstack plugin installed, return true, if not, return false.
     */
    protected $is_plugin_installed = false;

    /**
     * The plugin slug.
     *
     * @var string slug string.
     */
    protected $the_plugin_slug = 'patchstack/patchstack.php';

    /**
     * API URL of Patchstack to communicate with.
     *
     * @var   string API URL.
     */
    protected $api_url = 'https://api.patchstack.com/monitor';

    /**
     * Option name to hide the Patchstack Insights plugin.
     *
     * @var string Option name.
     */
    protected $option_hide_name = 'mainwp_patchstack_hide_plugin';

    /**
     * Whitelist of allowed sanitization functions.
     *
     * @var array $allowed_callbacks allowed sanitization functions.
     */
    protected static $allowed_callbacks = array(
        'sanitize_text_field',
        'sanitize_textarea_field',
        'sanitize_email',
        'sanitize_url',
        'intval',
        'absint',
        'wp_kses_post',
    );

    /**
     * Method instance()
     *
     * Create a public static instance.
     *
     * @return mixed Class instance.
     */
    public static function instance() {
        if ( null === static::$instance ) {
            static::$instance = new self();
        }

        return static::$instance;
    }

    /**
     * MainWP_Child_Patchstack constructor.
     *
     * Run any time class is called.
     */
    public function __construct() {
        $this->is_plugin_installed = is_plugin_active( $this->the_plugin_slug );
    }

    /**
     * Method init()
     *
     * Initiate action hooks.
     *
     * @return void
     */
    public function init() {
        add_filter( 'mainwp_site_sync_others_data', array( $this, 'sync_others_data' ), 10, 2 );
        if ( get_option( $this->option_hide_name ) === 'hide' ) {
            add_filter( 'all_plugins', array( $this, 'all_plugins' ) );
            add_action( 'admin_menu', array( $this, 'remove_menu' ) );
        }
    }

    /**
     * Method actions()
     *
     * Fire off certain Patchstack Insights plugin actions.
     *
     * @uses \MainWP\Child\MainWP_Child_Patchstack::save_settings() Save the plugin settings.
     * @uses \MainWP\Child\MainWP_Child_Patchstack::set_showhide() Hide or unhide the Patchstack Insights plugin.
     * @uses \MainWP\Child\MainWP_Child_Patchstack::install_plugin() Get the Patchstack Insights plugin data and store it in the sync request.
     * @uses \MainWP\Child\MainWP_Helper::write()
     */
    public function action() {
        $information = array();
        $mwp_action  = MainWP_System::instance()->validate_params( 'action' );
        if ( ! empty( $mwp_action ) ) {
            switch ( $mwp_action ) {
                case 'patchstack_capabilities_v2':
                    $information = $this->abilities_v2_action();
                    break;
                case 'install_plugin':
                    $information = $this->install_plugin();
                    break;
                case 'sync_data':
                    $information = $this->sync_data();
                    break;
                case 'show_hide':
                    $information = $this->set_showhide();
                    break;
                case 'save_settings':
                default:
                    $information = $this->save_settings();
                    break;
            }
        }

        MainWP_Helper::write( $information );
    }

    /**
     * Handle the closed Patchstack abilities protocol envelope.
     *
     * @return array Protocol response.
     */
    private function abilities_v2_action() {
        if ( ! isset( $_POST['request'] ) || ! is_string( $_POST['request'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- authenticated MainWP request.
            return $this->abilities_v2_error( 'unknown', 'invalid_request' );
        }

        $raw = wp_unslash( $_POST['request'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Closed JSON is validated below.
        if ( '' === $raw || 16384 < strlen( $raw ) ) {
            return $this->abilities_v2_error( 'unknown', 'invalid_request' );
        }

        return $this->abilities_v2( json_decode( $raw, true ) );
    }

    /**
     * Negotiate the Patchstack abilities protocol.
     *
     * Capabilities are derived from what this Child can actually carry out, so an
     * operation this build cannot run is refused by name instead of advertised.
     *
     * @param mixed $request Decoded request envelope.
     * @return array Protocol response.
     */
    public function abilities_v2( $request ) {
        if ( ! is_array( $request ) || ! $this->abilities_v2_exact_keys( $request, array( 'protocol', 'operation', 'payload' ) ) || '2' !== $request['protocol'] || ! is_string( $request['operation'] ) || ! is_array( $request['payload'] ) ) {
            return $this->abilities_v2_error( 'unknown', 'invalid_request' );
        }

        $operation = $request['operation'];
        $mutations = array( 'patchstack_protection_execute_v2', 'patchstack_visibility_replace_v2' );
        $supported = $this->abilities_v2_supported_operations();
        if ( 'capabilities' === $operation ) {
            // Negotiation is supported; a payload on it is a malformed request, not an unknown operation.
            if ( array() !== $request['payload'] ) {
                return $this->abilities_v2_error( $operation, 'invalid_request' );
            }

            return array(
                'protocol'           => '2',
                'operation'          => 'capabilities',
                'ok'                 => true,
                'operations'         => $supported,
                'mutation_supported' => array() !== array_intersect( $mutations, $supported ),
            );
        }

        // Anything this Child cannot carry out is refused by name, before any payload is judged.
        if ( ! in_array( $operation, $supported, true ) ) {
            return $this->abilities_v2_error( $operation, 'unsupported_operation' );
        }

        if ( 'patchstack_protection_preview_v2' === $operation ) {
            return $this->abilities_v2_protection_preview( $request['payload'] );
        }
        if ( 'patchstack_protection_status_v2' === $operation ) {
            return $this->abilities_v2_protection_status( $request['payload'] );
        }
        if ( 'patchstack_protection_execute_v2' === $operation ) {
            if ( ! $this->abilities_v2_begin_mutation() ) {
                return $this->abilities_v2_error( $operation, 'lock_busy' );
            }
            $result   = $this->abilities_v2_protection_execute( $request['payload'] );
            $released = $this->abilities_v2_end_mutation();
            return $released ? $result : $this->abilities_v2_error( $operation, 'outcome_unknown' );
        }
        if ( 'patchstack_visibility_replace_v2' === $operation ) {
            if ( ! $this->abilities_v2_begin_mutation() ) {
                return $this->abilities_v2_error( $operation, 'lock_busy' );
            }
            $result   = $this->abilities_v2_visibility_replace( $request['payload'] );
            $released = $this->abilities_v2_end_mutation();
            return $released ? $result : $this->abilities_v2_error( $operation, 'outcome_unknown' );
        }

        return $this->abilities_v2_error( $operation, 'unsupported_operation' );
    }

    /**
     * List the operations this Child can actually carry out.
     *
     * Protection execution needs a publisher-signed package plus a licensing
     * adapter, and Patchstack exposes neither to the Child, so it is neither
     * advertised nor dispatched. A build that wires
     * abilities_v2_verified_package_manifest() and abilities_v2_apply_protection()
     * extends this list.
     *
     * @return array Executable operation names.
     */
    protected function abilities_v2_supported_operations() {
        return array( 'patchstack_protection_preview_v2', 'patchstack_protection_status_v2', 'patchstack_visibility_replace_v2' );
    }

    /**
     * Return local protection and package-verification readiness.
     *
     * @param array $payload Closed preview payload.
     * @return array Protocol response.
     */
    private function abilities_v2_protection_preview( $payload ) {
        $operation = 'patchstack_protection_preview_v2';
        if ( ! $this->abilities_v2_valid_common_payload( $payload ) ) {
            return $this->abilities_v2_error( $operation, 'invalid_request' );
        }
        $state      = $this->abilities_v2_plugin_state();
        $visibility = $this->abilities_v2_visibility();
        if ( ! in_array( $state, array( 'absent', 'installed', 'active', 'protected', 'unknown' ), true ) || ! in_array( $visibility, array( 'shown', 'hidden' ), true ) ) {
            return $this->abilities_v2_error( $operation, 'invalid_snapshot' );
        }
        $planned_action = array(
            'absent'    => 'install',
            'installed' => 'license',
            'active'    => 'license',
            'protected' => 'none',
            'unknown'   => 'repair',
        );
        $package_state  = $this->abilities_v2_package_state( $state );
        return array(
            'protocol'       => '2',
            'operation'      => $operation,
            'ok'             => true,
            'operation_ref'  => $payload['operation_ref'],
            'current_state'  => $state,
            'planned_action' => $planned_action[ $state ],
            'package_state'  => $package_state,
            'visibility'     => $visibility,
            'state_revision' => $this->abilities_v2_state_revision( $payload, $state, $visibility, $package_state ),
            'observed_at'    => gmdate( 'Y-m-d\TH:i:s\Z' ),
        );
    }

    /** Execute one preview-bound protection convergence attempt. */
    private function abilities_v2_protection_execute( $payload ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity -- Explicit durable reserve/effect/readback/rollback flow.
        $operation = 'patchstack_protection_execute_v2';
        if ( ! $this->abilities_v2_valid_protection_payload( $payload ) ) {
            return $this->abilities_v2_error( $operation, 'invalid_request' );
        }

        $effect_hash = $this->abilities_v2_protection_effect_hash( $payload );
        $receipts    = $this->abilities_v2_read_protection_receipts();
        if ( false === $receipts ) {
            return $this->abilities_v2_error( $operation, 'storage_unavailable' );
        }
        if ( isset( $receipts[ $payload['operation_ref'] ] ) ) {
            return $this->abilities_v2_replay_protection_receipt( $receipts[ $payload['operation_ref'] ], $effect_hash, $operation );
        }

        $state         = $this->abilities_v2_plugin_state();
        $visibility    = $this->abilities_v2_visibility();
        $package_state = $this->abilities_v2_package_state( $state );
        if ( ! in_array( $state, array( 'absent', 'installed', 'active', 'protected', 'unknown' ), true ) || ! in_array( $visibility, array( 'shown', 'hidden' ), true ) ) {
            return $this->abilities_v2_error( $operation, 'invalid_snapshot' );
        }
        $common   = array_intersect_key( $payload, array_flip( array( 'operation_ref', 'expected_plugin_slug', 'provider_binding_digest', 'expires_at' ) ) );
        $revision = $this->abilities_v2_state_revision( $common, $state, $visibility, $package_state );
        if ( ! hash_equals( $revision, $payload['if_match'] ) ) {
            return $this->abilities_v2_error( $operation, 'stale_revision' );
        }
        // 'not_needed' means the plugin is already on disk, so there is no package to verify before licensing it.
        if ( 'protected' !== $state && 'verification_unavailable' === $package_state ) {
            return $this->abilities_v2_error( $operation, 'package_verification_unavailable' );
        }

        $receipts = array_filter(
            $receipts,
            static function ( $receipt ) {
                return 'dispatching' === $receipt['state'] || $receipt['updated_at'] >= time() - 90 * DAY_IN_SECONDS;
            }
        );
        if ( 100 <= count( $receipts ) ) {
            return $this->abilities_v2_error( $operation, 'storage_unavailable' );
        }

        $dispatching                           = array(
            'effect_hash'    => $effect_hash,
            'binding_digest' => $payload['provider_binding_digest'],
            'operation_ref'  => $payload['operation_ref'],
            'prior_state'    => $state,
            'state'          => 'dispatching',
            'result'         => null,
            'updated_at'     => time(),
        );
        $receipts[ $payload['operation_ref'] ] = $dispatching;
        if ( ! $this->abilities_v2_write_protection_receipts( $receipts ) ) {
            return $this->abilities_v2_error( $operation, 'storage_unavailable' );
        }

        if ( 'protected' === $state ) {
            $result = $this->abilities_v2_protection_result( $payload['operation_ref'], 'completed', 'protected', false, null );
            return $this->abilities_v2_settle_protection_receipt( $receipts, $payload['operation_ref'], $dispatching, $result );
        }

        $manifest = null;
        if ( 'not_needed' !== $package_state ) {
            $manifest = $this->abilities_v2_verified_package_manifest();
            if ( ! $this->abilities_v2_valid_manifest( $manifest ) ) {
                $result = $this->abilities_v2_protection_result( $payload['operation_ref'], 'failed', $state, false, 'package_verification_unavailable' );
                return $this->abilities_v2_settle_protection_receipt( $receipts, $payload['operation_ref'], $dispatching, $result );
            }
        }

        $applied = $this->abilities_v2_apply_protection( $payload, $state, $manifest );
        $current = $this->abilities_v2_plugin_state();
        if ( is_array( $applied ) && $this->abilities_v2_exact_keys( $applied, array( 'changed' ) ) && is_bool( $applied['changed'] ) && 'protected' === $current ) {
            $result = $this->abilities_v2_protection_result( $payload['operation_ref'], 'completed', $current, $applied['changed'], null );
            return $this->abilities_v2_settle_protection_receipt( $receipts, $payload['operation_ref'], $dispatching, $result );
        }

        $rolled_back = $this->abilities_v2_rollback_protection( $state );
        $restored    = $this->abilities_v2_plugin_state();
        $code        = true === $rolled_back && $state === $restored ? 'write_failed' : 'outcome_unknown';
        $result      = $this->abilities_v2_protection_result( $payload['operation_ref'], 'write_failed' === $code ? 'failed' : 'unknown', $restored, false, $code );
        return $this->abilities_v2_settle_protection_receipt( $receipts, $payload['operation_ref'], $dispatching, $result );
    }

    /** Return one durable protection result without dispatching. */
    private function abilities_v2_protection_status( $payload ) {
        $operation = 'patchstack_protection_status_v2';
        if ( ! $this->abilities_v2_valid_common_payload( $payload ) ) {
            return $this->abilities_v2_error( $operation, 'invalid_request' );
        }
        $receipts = $this->abilities_v2_read_protection_receipts();
        if ( false === $receipts ) {
            return $this->abilities_v2_error( $operation, 'storage_unavailable' );
        }
        if ( ! isset( $receipts[ $payload['operation_ref'] ] ) ) {
            return $this->abilities_v2_error( $operation, 'operation_not_found' );
        }
        $receipt = $receipts[ $payload['operation_ref'] ];
        if ( ! hash_equals( $receipt['binding_digest'], $payload['provider_binding_digest'] ) ) {
            return $this->abilities_v2_error( $operation, 'operation_not_found' );
        }
        $result              = 'dispatching' === $receipt['state'] ? $this->abilities_v2_protection_result( $payload['operation_ref'], 'unknown', $receipt['prior_state'], false, 'outcome_unknown' ) : $receipt['result'];
        $result['operation'] = $operation;
        return $result;
    }

    /**
     * Replace and verify the local Patchstack visibility preference.
     *
     * @param array $payload Closed visibility payload.
     * @return array Protocol response.
     */
    private function abilities_v2_visibility_replace( $payload ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity -- Explicit receipt/write/readback flow.
        $operation = 'patchstack_visibility_replace_v2';
        if ( ! is_array( $payload ) || ! $this->abilities_v2_exact_keys( $payload, array( 'operation_ref', 'expected_plugin_slug', 'provider_binding_digest', 'expires_at', 'desired_state', 'if_match' ) ) || ! in_array( isset( $payload['desired_state'] ) ? $payload['desired_state'] : null, array( 'shown', 'hidden' ), true ) || ! isset( $payload['if_match'] ) || ! $this->abilities_v2_hash( $payload['if_match'] ) ) {
            return $this->abilities_v2_error( $operation, 'invalid_request' );
        }
        $common = array_intersect_key( $payload, array_flip( array( 'operation_ref', 'expected_plugin_slug', 'provider_binding_digest', 'expires_at' ) ) );
        if ( ! $this->abilities_v2_valid_common_payload( $common ) ) {
            return $this->abilities_v2_error( $operation, 'invalid_request' );
        }
        $effect_hash = hash( 'sha256', wp_json_encode( $payload ) );
        $receipts    = $this->abilities_v2_read_receipts();
        if ( false === $receipts ) {
            return $this->abilities_v2_error( $operation, 'storage_unavailable' );
        }
        if ( isset( $receipts[ $payload['operation_ref'] ] ) ) {
            $receipt = $receipts[ $payload['operation_ref'] ];
            if ( ! hash_equals( $receipt['effect_hash'], $effect_hash ) ) {
                return $this->abilities_v2_error( $operation, 'request_conflict' );
            }
            return 'completed' === $receipt['state'] ? $receipt['response'] : $this->abilities_v2_error( $operation, 'outcome_unknown' );
        }

        $receipts = array_filter(
            $receipts,
            static function ( $receipt ) {
                return 'pending' === $receipt['state'] || $receipt['updated_at'] >= time() - 90 * DAY_IN_SECONDS;
            }
        );
        if ( 100 <= count( $receipts ) ) {
            return $this->abilities_v2_error( $operation, 'storage_unavailable' );
        }

        $state      = $this->abilities_v2_plugin_state();
        $visibility = $this->abilities_v2_visibility();
        if ( ! in_array( $state, array( 'absent', 'installed', 'active', 'protected', 'unknown' ), true ) || ! in_array( $visibility, array( 'shown', 'hidden' ), true ) ) {
            return $this->abilities_v2_error( $operation, 'invalid_snapshot' );
        }
        // The preview that issued if_match binds the real package state, so the default would never match on a site without Patchstack.
        $package_state = $this->abilities_v2_package_state( $state );
        $revision      = $this->abilities_v2_state_revision( $common, $state, $visibility, $package_state );
        if ( ! hash_equals( $revision, $payload['if_match'] ) ) {
            return $this->abilities_v2_error( $operation, 'stale_revision' );
        }

        $receipts[ $payload['operation_ref'] ] = array(
            'effect_hash' => $effect_hash,
            'state'       => 'pending',
            'response'    => null,
            'updated_at'  => time(),
        );
        if ( ! $this->abilities_v2_write_receipts( $receipts ) ) {
            return $this->abilities_v2_error( $operation, 'storage_unavailable' );
        }

        $changed = $visibility !== $payload['desired_state'];
        if ( $changed ) {
            $write = $this->abilities_v2_write_visibility( $payload['desired_state'] );
            if ( ! is_array( $write ) || ! $this->abilities_v2_exact_keys( $write, array( 'prior', 'current' ) ) || $visibility !== $write['prior'] || $payload['desired_state'] !== $write['current'] ) {
                $this->abilities_v2_restore_visibility( $visibility );
                $response = $this->abilities_v2_error( $operation, 'write_failed' );
                return $this->abilities_v2_complete_receipt( $receipts, $payload['operation_ref'], $effect_hash, $response );
            }
        }

        $current = $this->abilities_v2_visibility();
        if ( $payload['desired_state'] !== $current ) {
            $this->abilities_v2_restore_visibility( $visibility );
            $response = $this->abilities_v2_error( $operation, 'write_failed' );
            return $this->abilities_v2_complete_receipt( $receipts, $payload['operation_ref'], $effect_hash, $response );
        }
        $response = array(
            'protocol'       => '2',
            'operation'      => $operation,
            'ok'             => true,
            'operation_ref'  => $payload['operation_ref'],
            'visibility'     => $current,
            'changed'        => $changed,
            'state_revision' => $this->abilities_v2_state_revision( $common, $state, $current, $package_state ),
        );
        return $this->abilities_v2_complete_receipt( $receipts, $payload['operation_ref'], $effect_hash, $response );
    }

    /** Return the local installed/active protection state. */
    protected function abilities_v2_plugin_state() {
        $installed = defined( 'WP_PLUGIN_DIR' ) && file_exists( WP_PLUGIN_DIR . '/' . $this->the_plugin_slug );
        if ( ! $installed ) {
            return 'absent';
        }
        return function_exists( 'is_plugin_active' ) && is_plugin_active( $this->the_plugin_slug ) ? 'active' : 'installed';
    }

    /** Return the exact local visibility preference. */
    protected function abilities_v2_visibility() {
        $value = get_site_option( $this->option_hide_name, false );
        if ( 'hide' === $value ) {
            return 'hidden';
        }
        return false === $value || '' === $value ? 'shown' : false;
    }

    /**
     * Write and read back one visibility preference.
     *
     * @param string $visibility Desired visibility.
     * @return array Readback snapshot.
     */
    protected function abilities_v2_write_visibility( $visibility ) {
        $prior = $this->abilities_v2_visibility();
        update_site_option( $this->option_hide_name, 'hidden' === $visibility ? 'hide' : '' );
        return array(
            'prior'   => $prior,
            'current' => $this->abilities_v2_visibility(),
        );
    }

    /**
     * Restore and verify one visibility preference.
     *
     * @param string $visibility Prior visibility.
     * @return bool Whether restoration was verified.
     */
    protected function abilities_v2_restore_visibility( $visibility ) {
        update_site_option( $this->option_hide_name, 'hidden' === $visibility ? 'hide' : '' );
        return $visibility === $this->abilities_v2_visibility();
    }

    /**
     * Return a publisher-verified package manifest.
     *
     * Patchstack currently exposes no committed signing trust root to the Child.
     * Keep installation unavailable until that publisher artifact is supplied.
     *
     * @return array|null Verified manifest, or null when unavailable.
     */
    protected function abilities_v2_verified_package_manifest() {
        return null;
    }

    /**
     * Apply one already verified package/license effect.
     *
     * The production implementation remains unavailable until the publisher
     * supplies the signed artifact and a direct supported licensing contract.
     *
     * @param array      $payload  Closed execution payload.
     * @param string     $before   Prior plugin state.
     * @param array|null $manifest Verified package manifest, or null when the plugin is already installed.
     * @return array|false Apply result.
     */
    protected function abilities_v2_apply_protection( $payload, $before, $manifest ) {
        unset( $payload, $before, $manifest );
        return false;
    }

    /**
     * Restore the exact prior plugin state after a failed convergence attempt.
     *
     * @param string $before Prior plugin state.
     * @return bool Whether rollback was exactly verified.
     */
    protected function abilities_v2_rollback_protection( $before ) {
        unset( $before );
        return false;
    }

    /** Read and validate bounded protection receipts. */
    protected function abilities_v2_read_protection_receipts() {
        $receipts = get_site_option( self::ABILITIES_V2_PROTECTION_RECEIPTS_OPTION, array() );
        if ( ! is_array( $receipts ) || 100 < count( $receipts ) ) {
            return false;
        }
        foreach ( $receipts as $operation_ref => $receipt ) {
            if ( ! $this->abilities_v2_valid_uuid( $operation_ref ) || ! $this->abilities_v2_valid_protection_receipt( $receipt, $operation_ref ) ) {
                return false;
            }
        }
        return $receipts;
    }

    /** Write and exactly read back bounded protection receipts. */
    protected function abilities_v2_write_protection_receipts( $receipts ) {
        if ( ! is_array( $receipts ) || 100 < count( $receipts ) ) {
            return false;
        }
        foreach ( $receipts as $operation_ref => $receipt ) {
            if ( ! $this->abilities_v2_valid_uuid( $operation_ref ) || ! $this->abilities_v2_valid_protection_receipt( $receipt, $operation_ref ) ) {
                return false;
            }
        }
        update_site_option( self::ABILITIES_V2_PROTECTION_RECEIPTS_OPTION, $receipts );
        return get_site_option( self::ABILITIES_V2_PROTECTION_RECEIPTS_OPTION, null ) === $receipts;
    }

    /** Read and validate bounded visibility receipts. */
    protected function abilities_v2_read_receipts() {
        $receipts = get_site_option( self::ABILITIES_V2_RECEIPTS_OPTION, array() );
        if ( ! is_array( $receipts ) || 100 < count( $receipts ) ) {
            return false;
        }
        foreach ( $receipts as $operation_ref => $receipt ) {
            if ( ! $this->abilities_v2_valid_uuid( $operation_ref ) || ! $this->abilities_v2_valid_receipt( $receipt ) ) {
                return false;
            }
        }
        return $receipts;
    }

    /**
     * Write and exactly read back bounded visibility receipts.
     *
     * @param array $receipts Receipt map.
     * @return bool Whether persistence was verified.
     */
    protected function abilities_v2_write_receipts( $receipts ) {
        if ( ! is_array( $receipts ) || 100 < count( $receipts ) ) {
            return false;
        }
        update_site_option( self::ABILITIES_V2_RECEIPTS_OPTION, $receipts );
        return get_site_option( self::ABILITIES_V2_RECEIPTS_OPTION, null ) === $receipts;
    }

    /** Acquire the local visibility mutation lock. */
    protected function abilities_v2_begin_mutation() {
        $now      = time();
        $existing = get_site_option( self::ABILITIES_V2_LOCK_OPTION, null );
        if ( is_array( $existing ) && isset( $existing['expires_at'] ) && is_int( $existing['expires_at'] ) && $existing['expires_at'] >= $now ) {
            return false;
        }
        if ( null !== $existing && ( ! delete_site_option( self::ABILITIES_V2_LOCK_OPTION ) || null !== get_site_option( self::ABILITIES_V2_LOCK_OPTION, null ) ) ) {
            return false;
        }
        $owner = wp_generate_uuid4();
        $lock  = array(
            'owner'      => $owner,
            'expires_at' => $now + 60,
        );
        if ( ! add_site_option( self::ABILITIES_V2_LOCK_OPTION, $lock ) || get_site_option( self::ABILITIES_V2_LOCK_OPTION, null ) !== $lock ) {
            return false;
        }
        $this->abilities_v2_lock_owner = $owner;
        return true;
    }

    /** Release and verify the local visibility mutation lock. */
    protected function abilities_v2_end_mutation() {
        $lock = get_site_option( self::ABILITIES_V2_LOCK_OPTION, null );
        if ( '' === $this->abilities_v2_lock_owner || ! is_array( $lock ) || ! isset( $lock['owner'] ) || ! is_string( $lock['owner'] ) || ! hash_equals( $this->abilities_v2_lock_owner, $lock['owner'] ) ) {
            $this->abilities_v2_lock_owner = '';
            return false;
        }
        delete_site_option( self::ABILITIES_V2_LOCK_OPTION );
        $this->abilities_v2_lock_owner = '';
        return null === get_site_option( self::ABILITIES_V2_LOCK_OPTION, null );
    }

    /**
     * Complete and persist one visibility receipt.
     *
     * @param array  $receipts     Receipt map.
     * @param string $operation_ref Operation reference.
     * @param string $effect_hash   Canonical effect digest.
     * @param array  $response      Closed response.
     * @return array Persisted response or stable error.
     */
    private function abilities_v2_complete_receipt( $receipts, $operation_ref, $effect_hash, $response ) {
        $receipts[ $operation_ref ] = array(
            'effect_hash' => $effect_hash,
            'state'       => 'completed',
            'response'    => $response,
            'updated_at'  => time(),
        );
        return $this->abilities_v2_write_receipts( $receipts ) ? $response : $this->abilities_v2_error( 'patchstack_visibility_replace_v2', 'outcome_unknown' );
    }

    /**
     * Validate the shared preview/mutation binding.
     *
     * @param array $payload Closed payload.
     * @return bool Whether the payload is valid.
     */
    private function abilities_v2_valid_common_payload( $payload ) {
        return is_array( $payload )
            && $this->abilities_v2_exact_keys( $payload, array( 'operation_ref', 'expected_plugin_slug', 'provider_binding_digest', 'expires_at' ) )
            && $this->abilities_v2_valid_uuid( $payload['operation_ref'] )
            && 'patchstack/patchstack.php' === $payload['expected_plugin_slug']
            && $this->abilities_v2_hash( $payload['provider_binding_digest'] )
            && is_int( $payload['expires_at'] )
            && time() <= $payload['expires_at']
            && time() + 600 >= $payload['expires_at'];
    }

    /**
     * Return the bound local state revision.
     *
     * @param array  $payload       Bound payload.
     * @param string $state         Plugin state.
     * @param string $visibility    Visibility state.
     * @param string $package_state Signed-package readiness.
     * @return string State digest.
     */
    private function abilities_v2_state_revision( $payload, $state, $visibility, $package_state = 'not_needed' ) {
        return hash( 'sha256', wp_json_encode( array( $payload['expected_plugin_slug'], $payload['provider_binding_digest'], $state, $visibility, $package_state ) ) );
    }

    /** Return signed-package readiness without exposing manifest material. */
    private function abilities_v2_package_state( $state ) {
        if ( in_array( $state, array( 'installed', 'active', 'protected' ), true ) ) {
            return 'not_needed';
        }
        return $this->abilities_v2_valid_manifest( $this->abilities_v2_verified_package_manifest() ) ? 'verified_available' : 'verification_unavailable';
    }

    /** Validate one redacted publisher-verified package manifest. */
    private function abilities_v2_valid_manifest( $manifest ) {
        return is_array( $manifest )
            && $this->abilities_v2_exact_keys( $manifest, array( 'version', 'package_sha256', 'manifest_digest' ) )
            && is_string( $manifest['version'] )
            && 1 === preg_match( '/^[0-9A-Za-z][0-9A-Za-z.+_-]{0,63}$/D', $manifest['version'] )
            && $this->abilities_v2_hash( $manifest['package_sha256'] )
            && $this->abilities_v2_hash( $manifest['manifest_digest'] );
    }

    /** Validate one exact protection execute payload. */
    private function abilities_v2_valid_protection_payload( $payload ) {
        if ( ! is_array( $payload ) || ! $this->abilities_v2_exact_keys( $payload, array( 'operation_ref', 'expected_plugin_slug', 'provider_binding_digest', 'expires_at', 'if_match', 'license_token' ) ) || ! $this->abilities_v2_hash( $payload['if_match'] ) || ! is_string( $payload['license_token'] ) || '' === $payload['license_token'] || 4096 < strlen( $payload['license_token'] ) || preg_match( '/[\x00-\x1F\x7F]/', $payload['license_token'] ) ) {
            return false;
        }
        $common = array_intersect_key( $payload, array_flip( array( 'operation_ref', 'expected_plugin_slug', 'provider_binding_digest', 'expires_at' ) ) );
        return $this->abilities_v2_valid_common_payload( $common );
    }

    /** Derive the protection effect digest without retaining the license token. */
    protected function abilities_v2_protection_effect_hash( $payload ) {
        return hash(
            'sha256',
            wp_json_encode(
                array(
                    'operation_ref'           => $payload['operation_ref'],
                    'expected_plugin_slug'    => $payload['expected_plugin_slug'],
                    'provider_binding_digest' => $payload['provider_binding_digest'],
                    'expires_at'              => $payload['expires_at'],
                    'if_match'                => $payload['if_match'],
                    'license_token_digest'    => hash( 'sha256', $payload['license_token'] ),
                )
            )
        );
    }

    /** Replay one exact protection receipt. */
    private function abilities_v2_replay_protection_receipt( $receipt, $effect_hash, $operation ) {
        if ( ! $this->abilities_v2_valid_protection_receipt( $receipt ) ) {
            return $this->abilities_v2_error( $operation, 'storage_unavailable' );
        }
        if ( ! hash_equals( $receipt['effect_hash'], $effect_hash ) ) {
            return $this->abilities_v2_error( $operation, 'request_conflict' );
        }
        return 'dispatching' === $receipt['state'] ? $this->abilities_v2_protection_result_from_receipt( $receipt, $operation ) : $receipt['result'];
    }

    /** Replace one dispatch marker with a closed terminal result. */
    private function abilities_v2_settle_protection_receipt( $receipts, $operation_ref, $dispatching, $result ) {
        $settled                    = $dispatching;
        $settled['state']           = 'settled';
        $settled['result']          = $result;
        $settled['updated_at']      = time();
        $receipts[ $operation_ref ] = $settled;
        if ( $this->abilities_v2_write_protection_receipts( $receipts ) ) {
            return $result;
        }
        $stored = $this->abilities_v2_read_protection_receipts();
        return is_array( $stored ) && isset( $stored[ $operation_ref ] ) && $settled === $stored[ $operation_ref ] ? $result : $this->abilities_v2_protection_result_from_receipt( $dispatching, 'patchstack_protection_execute_v2' );
    }

    /** Build one closed public protection result. */
    private function abilities_v2_protection_result( $operation_ref, $status, $current_state, $changed, $code ) {
        return array(
            'protocol'      => '2',
            'operation'     => 'patchstack_protection_execute_v2',
            'ok'            => 'completed' === $status,
            'operation_ref' => $operation_ref,
            'status'        => $status,
            'current_state' => $current_state,
            'changed'       => $changed,
            'code'          => $code,
        );
    }

    /** Return the only truthful result for an interrupted reserved effect. */
    private function abilities_v2_protection_result_from_receipt( $receipt, $operation ) {
        $result              = $this->abilities_v2_protection_result( $receipt['operation_ref'], 'unknown', $receipt['prior_state'], false, 'outcome_unknown' );
        $result['operation'] = $operation;
        return $result;
    }

    /** Validate one closed durable protection receipt. */
    private function abilities_v2_valid_protection_receipt( $receipt, $operation_ref = null ) {
        if ( ! is_array( $receipt ) || ! $this->abilities_v2_exact_keys( $receipt, array( 'effect_hash', 'binding_digest', 'operation_ref', 'prior_state', 'state', 'result', 'updated_at' ) ) || ! $this->abilities_v2_hash( $receipt['effect_hash'] ) || ! $this->abilities_v2_hash( $receipt['binding_digest'] ) || ! $this->abilities_v2_valid_uuid( $receipt['operation_ref'] ) || ! in_array( $receipt['prior_state'], array( 'absent', 'installed', 'active', 'protected', 'unknown' ), true ) || ! in_array( $receipt['state'], array( 'dispatching', 'settled' ), true ) || ! is_int( $receipt['updated_at'] ) || 0 >= $receipt['updated_at'] ) {
            return false;
        }
        if ( 'dispatching' === $receipt['state'] ) {
            return null === $receipt['result'];
        }
        if ( ! $this->abilities_v2_valid_protection_result( $receipt['result'] ) ) {
            return false;
        }
        return ( null === $operation_ref || hash_equals( $operation_ref, $receipt['operation_ref'] ) ) && hash_equals( $receipt['operation_ref'], $receipt['result']['operation_ref'] );
    }

    /** Validate one closed public protection result. */
    private function abilities_v2_valid_protection_result( $result ) {
        if ( ! is_array( $result ) || ! $this->abilities_v2_exact_keys( $result, array( 'protocol', 'operation', 'ok', 'operation_ref', 'status', 'current_state', 'changed', 'code' ) ) || '2' !== $result['protocol'] || 'patchstack_protection_execute_v2' !== $result['operation'] || ! is_bool( $result['ok'] ) || ! $this->abilities_v2_valid_uuid( $result['operation_ref'] ) || ! in_array( $result['status'], array( 'completed', 'failed', 'unknown' ), true ) || ! in_array( $result['current_state'], array( 'absent', 'installed', 'active', 'protected', 'unknown' ), true ) || ! is_bool( $result['changed'] ) || ( null !== $result['code'] && ! in_array( $result['code'], array( 'package_verification_unavailable', 'write_failed', 'outcome_unknown' ), true ) ) ) {
            return false;
        }
        return ( 'completed' === $result['status'] ) === $result['ok'] && ( $result['ok'] ? null === $result['code'] : null !== $result['code'] );
    }

    /**
     * Validate one durable receipt.
     *
     * @param mixed $receipt Receipt value.
     * @return bool Whether the receipt is valid.
     */
    private function abilities_v2_valid_receipt( $receipt ) {
        return is_array( $receipt )
            && $this->abilities_v2_exact_keys( $receipt, array( 'effect_hash', 'state', 'response', 'updated_at' ) )
            && $this->abilities_v2_hash( $receipt['effect_hash'] )
            && in_array( $receipt['state'], array( 'pending', 'completed' ), true )
            && ( ( 'pending' === $receipt['state'] && null === $receipt['response'] ) || ( 'completed' === $receipt['state'] && $this->abilities_v2_valid_visibility_response( $receipt['response'] ) ) )
            && is_int( $receipt['updated_at'] )
            && 0 < $receipt['updated_at'];
    }

    /**
     * Validate one closed visibility receipt response.
     *
     * @param mixed $response Response value.
     * @return bool Whether the response is valid.
     */
    private function abilities_v2_valid_visibility_response( $response ) {
        if ( ! is_array( $response ) || ! isset( $response['ok'] ) || ! is_bool( $response['ok'] ) || '2' !== ( isset( $response['protocol'] ) ? $response['protocol'] : null ) || 'patchstack_visibility_replace_v2' !== ( isset( $response['operation'] ) ? $response['operation'] : null ) ) {
            return false;
        }
        if ( false === $response['ok'] ) {
            return $this->abilities_v2_exact_keys( $response, array( 'protocol', 'operation', 'ok', 'code' ) )
                && is_string( $response['code'] )
                && in_array( $response['code'], array( 'write_failed', 'outcome_unknown' ), true );
        }
        return $this->abilities_v2_exact_keys( $response, array( 'protocol', 'operation', 'ok', 'operation_ref', 'visibility', 'changed', 'state_revision' ) )
            && $this->abilities_v2_valid_uuid( $response['operation_ref'] )
            && in_array( $response['visibility'], array( 'shown', 'hidden' ), true )
            && is_bool( $response['changed'] )
            && $this->abilities_v2_hash( $response['state_revision'] );
    }

    /**
     * Validate a UUID operation reference.
     *
     * @param mixed $value Candidate value.
     * @return bool Whether the value is valid.
     */
    private function abilities_v2_valid_uuid( $value ) {
        return is_string( $value ) && 1 === preg_match( '/^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/D', $value );
    }

    /**
     * Validate a SHA-256 digest.
     *
     * @param mixed $value Candidate value.
     * @return bool Whether the value is valid.
     */
    private function abilities_v2_hash( $value ) {
        return is_string( $value ) && 1 === preg_match( '/^[a-f0-9]{64}$/D', $value );
    }

    /**
     * Check that an array contains exactly the expected keys.
     *
     * @param array $value Value to inspect.
     * @param array $keys  Expected keys.
     * @return bool
     */
    private function abilities_v2_exact_keys( $value, $keys ) {
        if ( ! is_array( $value ) ) {
            return false;
        }

        $actual = array_keys( $value );
        sort( $actual );
        sort( $keys );
        return $actual === $keys;
    }

    /**
     * Build a closed Patchstack abilities error response.
     *
     * @param string $operation Operation name.
     * @param string $code      Stable error code.
     * @return array
     */
    private function abilities_v2_error( $operation, $code ) {
        return array(
            'protocol'  => '2',
            'operation' => is_string( $operation ) && '' !== $operation ? $operation : 'unknown',
            'ok'        => false,
            'code'      => $code,
        );
    }

    /**
     * Hide BackWPup Plugin from the WordPress Installed plugin list.
     *
     * @param array $plugins Installed plugins.
     * @return array $plugins Installed plugins without BackWPup Plugin on the list.
     */
    public function all_plugins( $plugins ) {
        foreach ( $plugins as $key => $value ) {
            $plugin_slug = basename( $key, '.php' );
            if ( 'patchstack' === $plugin_slug ) {
                unset( $plugins[ $key ] );
            }
        }

        return $plugins;
    }
    /**
     * Remove Patchstack Plugin from the WordPress Admin.
     */
    public function remove_menu() {
        // Remove patchstack from the admin menu.
        remove_menu_page( 'options-general.php?page=patchstack' );
        global $submenu;

        // Remove the WordPress Admin SubMenu.
        if ( isset( $submenu['patchstack'] ) ) {
            unset( $submenu['patchstack'] );
        }

        if ( isset( $submenu['options-general.php'] ) ) {
            foreach ( $submenu['options-general.php'] as $index => $item ) {
                if ( 'patchstack' === $item[2] ) {
                    unset( $submenu['options-general.php'][ $index ] );
                    break;
                }
            }
        }

        $pos = isset( $_SERVER['REQUEST_URI'] ) ? stripos( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ), 'options-general.php?page=patchstack' ) : false;
        if ( false !== $pos ) {
            wp_safe_redirect( get_option( 'siteurl' ) . '/wp-admin/index.php' );
            exit();
        }
    }

    /**
     * Method sync_others_data()
     *
     * Sync the Patchstack plugin settings.
     *
     * @param  array $information Array containing the sync information.
     * @param  array $data        Array containing the Patchstack plugin data to be synced.
     *
     * @return array $information Array containing the sync information.
     */
    public function sync_others_data( $information, $data = array() ) {
        if ( isset( $data['sync_patchstack_data'] ) && ( 'yes' === $data['sync_patchstack_data'] ) ) {
            try {
                $data['plugin_hide_status']          = get_site_option( $this->option_hide_name, false );
                $information['sync_patchstack_data'] = $data;
            } catch ( MainWP_Exception $e ) {
                // ok!
            }
        }
        return $information;
    }

    /**
     * Method install_plugin()
     *
     * Get the Patchstack Insights plugin data and store it in the sync request.
     *
     * @return array $information Array containing the sync information.
     */
    /**
     * Install (overwrite if exists) and activate the Patchstack plugin.
     *
     * @return array|\WP_Error
     */
    private function install_plugin() {  // phpcs:ignore -- NOSONAR -- complexity
        // Read settings.
        $raw_settings = $this->sanitized_post( 'settings' );
        $settings     = json_decode( $raw_settings, true );

        if ( json_last_error() !== JSON_ERROR_NONE ) {
            return new \WP_Error( 'json_decode_error', 'Invalid JSON in settings: ' . json_last_error_msg() );
        }

        if ( empty( $settings['ps_id'] ) || empty( $settings['token'] ) ) {
            return new \WP_Error( 'bad_params', 'Missing ps_id or token.' );
        }

        // Call endpoint returns FILE (ZIP), not JSON.
        $url    = '/download/wordpress/' . intval( $settings['ps_id'] );
        $binary = $this->send_request( $url, $settings['token'], 'GET', array(), false );

        if ( is_wp_error( $binary ) ) {
            return $binary;
        }
        if ( is_array( $binary ) ) {
            // API should return file; if it returns JSON then it is a business error.
            return new \WP_Error( 'api_error', 'Unexpected JSON for download endpoint.', $binary );
        }
        if ( ! is_string( $binary ) || '' === $binary ) {
            return new \WP_Error( 'empty_file', 'Empty plugin file.' );
        }

        require_once ABSPATH . 'wp-admin/includes/plugin.php';  // phpcs:ignore -- NOSONAR
        require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';  // phpcs:ignore -- NOSONAR
        require_once ABSPATH . 'wp-admin/includes/file.php';  // phpcs:ignore -- NOSONAR
        require_once ABSPATH . 'wp-admin/includes/misc.php';  // phpcs:ignore -- NOSONAR

        // Write ZIP to temporary file.
        $tmp = wp_tempnam( 'patchstack.zip' );
        if ( ! $tmp ) {
            return new \WP_Error( 'tmp_fail', 'Failed to create temp file.' );
        }

        $bytes = @file_put_contents( $tmp, $binary, LOCK_EX ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
        if ( false === $bytes || 0 === $bytes ) {
            @unlink( $tmp ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
            return new \WP_Error( 'write_fail', 'Failed to write plugin ZIP to temp file.' );
        }

        // Check slug.
        if ( empty( $this->the_plugin_slug ) || strpos( $this->the_plugin_slug, '/' ) === false ) {
            return new \WP_Error( 'bad_slug', 'Invalid plugin slug (expected "patchstack/patchstack.php").' );
        }

        // Deactivate if active.
        $was_active = function_exists( 'is_plugin_active' ) ? is_plugin_active( $this->the_plugin_slug ) : false;
        if ( $was_active ) {
            deactivate_plugins( $this->the_plugin_slug, true );
            if ( function_exists( 'is_plugin_active' ) && is_plugin_active( $this->the_plugin_slug ) ) {
                return new \WP_Error( 'deactivate_failed', 'Failed to deactivate existing plugin.' );
            }
        }

        // Delete plugin if exists (file or folder).
        $plugin_dir = WP_PLUGIN_DIR . '/' . dirname( $this->the_plugin_slug );
        if ( file_exists( WP_PLUGIN_DIR . '/' . $this->the_plugin_slug ) || is_dir( $plugin_dir ) ) {
            // delete_plugins() will try to deactivate if needed and remove files.
            $deleted = delete_plugins( array( $this->the_plugin_slug ) );
            if ( is_wp_error( $deleted ) ) {
                return new \WP_Error(
                    'delete_failed',
                    'Failed to delete existing plugin folder.',
                    array( 'error' => $deleted->get_error_message() )
                );
            }
            // In some envs a residual folder may remain; hard-delete with WP_Filesystem.
            if ( is_dir( $plugin_dir ) ) {
                global $wp_filesystem;
                if ( ! $wp_filesystem ) {
                    WP_Filesystem();
                }
                if ( $wp_filesystem && $wp_filesystem->is_dir( $plugin_dir ) ) {
                    $wp_filesystem->delete( $plugin_dir, true );
                }
                if ( is_dir( $plugin_dir ) ) {
                    return new \WP_Error( 'delete_residual_failed', 'Plugin directory still exists after deletion.' );
                }
            }
        }

        // Install/overwrite with Plugin_Upgrader (WordPress core standard).
        $skin     = new \Automatic_Upgrader_Skin();
        $upgrader = new \Plugin_Upgrader( $skin );

        $options_filter = static function ( $options ) {
            $options['clear_destination']           = true;
            $options['abort_if_destination_exists'] = false;
            return $options;
        };
        add_filter( 'upgrader_package_options', $options_filter );

        try {
            $installed = $upgrader->install( $tmp );
        } finally {
            remove_filter( 'upgrader_package_options', $options_filter );
            @unlink( $tmp ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
        }

        if ( is_wp_error( $installed ) ) {
            return $installed;
        }
        if ( ! $installed ) {
            return new \WP_Error( 'install_failed', 'Plugin installation failed.' );
        }

        // Check plugin file.
        $plugin_file = '';
        if ( is_array( $upgrader->result ?? null ) ) {
            if ( ! empty( $upgrader->result['plugin'] ) ) {
                $plugin_file = $upgrader->result['plugin'];
            } elseif ( ! empty( $upgrader->result['destination'] ) ) {
                $dest_dir = trailingslashit( $upgrader->result['destination'] );
                $all      = get_plugins();
                foreach ( $all as $rel => $headers ) {
                    if ( strpos( WP_PLUGIN_DIR . '/' . $rel, $dest_dir ) === 0 ) {
                        $plugin_file = $rel;
                        break;
                    }
                }
            }
        }

        if ( ! $plugin_file && ! empty( $this->the_plugin_slug ) && file_exists( WP_PLUGIN_DIR . '/' . $this->the_plugin_slug ) ) {
            $plugin_file = $this->the_plugin_slug;
        }

        if ( ! $plugin_file ) {
            return new \WP_Error( 'main_file_missing', 'Installed but plugin main file not found.' );
        }

        return array(
            'success'     => 1,
            'is_active'   => is_plugin_active( $this->the_plugin_slug ),
            'plugin_file' => $this->the_plugin_slug,
            'message'     => $message,
        );
    }

    /**
     * Method set_showhide()
     *
     * Hide or unhide the Patchstack Insights plugin.
     *
     * @return array $information Array containing the sync information.
     */
    private function set_showhide() {
        $raw  = $this->sanitized_post( 'show_hide' );
        $hide = ( 'hide' === sanitize_text_field( $raw ) ) ? 'hide' : '';
        update_site_option( $this->option_hide_name, $hide );

        return array( 'success' => 1 );
    }

    /**
     * Summary of sync_data
     *
     * @return array{data: mixed, success: int|array{error: string}}`
     */
    private function sync_data() {
        $oauth = $this->sanitized_post( 'oauth' );
        if ( empty( $oauth ) ) {
            return array( 'error' => 'Missing oauth data.' );
        }
        $response = wp_remote_post(
            admin_url( 'admin-ajax.php' ),
            array(
                'timeout'   => 30,
                'sslverify' => false,
                'body'      => array(
                    'action' => 'patchstack_activate_license',
                    'key'    => $oauth,
                ),
            )
        );

        $body = wp_remote_retrieve_body( $response );
        $data = json_decode( $body, true );
        return array(
            'success' => 1,
            'data'    => $data,
        );
    }

    /**
     * Method save_settings()
     *
     * Save the Patchstack Insights plugin settings.
     *
     * @return array $information Array containing the sync information.
     */
    private function save_settings() {
        return array( 'success' => 1 );
    }

    /**
     * Send an HTTP request to the API and return JSON (array) or raw string (binary).
     *
     * @param string            $url         API endpoint (e.g. '/site/plugin/resync/123').
     * @param string            $token       API token.
     * @param string            $method      HTTP method. Default 'GET'.
     * @param array|string|null $data        Data for non-GET methods (auto JSON-encoded if array).
     * @param bool              $expect_json Expect JSON response (true) or raw/binary (false).
     *
     * @return array|string|\WP_Error JSON array, raw string, or WP_Error on failure.
     */
    private function send_request( $url, $token, $method = 'GET', $data = array(), $expect_json = true ) {  // phpcs:ignore -- NOSONAR
        if ( empty( $token ) ) {
            return new \WP_Error( 'no_token', 'Missing API token.' );
        }

        $method = strtoupper( $method );
        $args   = array(
            'method'      => $method,
            'timeout'     => 90,
            'redirection' => 5,
            'blocking'    => true,
            'headers'     => array(
                'UserToken' => $token,
                'Accept'    => $expect_json ? 'application/json' : '*/*', // NOSONAR.
            ),
        );

        // Only send body when not GET/HEAD.
        if ( ! empty( $data ) && ! in_array( $method, array( 'GET', 'HEAD' ), true ) ) {
            if ( is_array( $data ) ) {
                $args['headers']['Content-Type'] = 'application/json';
                $args['body']                    = wp_json_encode( $data );
            } else {
                $args['body'] = (string) $data;
            }
        }

        $response = wp_remote_request( trailingslashit( $this->api_url ) . ltrim( $url, '/' ), $args );
        if ( is_wp_error( $response ) ) {
            return $response;
        }

        $code  = (int) wp_remote_retrieve_response_code( $response );
        $body  = wp_remote_retrieve_body( $response );
        $ctype = (string) wp_remote_retrieve_header( $response, 'content-type' );

        if ( 200 !== $code ) {
            // If the server returns an error JSON → try to parse it to get specific information.
            $message = "HTTP $code";
            if ( stripos( $ctype, 'application/json' ) !== false ) {
                $json = json_decode( $body, true );
                if ( is_array( $json ) ) {
                    $message = $json['error'] ?? $json['message'] ?? $message;
                }
            }
            return new \WP_Error(
                'http_error',
                $message,
                array(
                    'status' => $code,
                    'body'   => $body,
                    'ctype'  => $ctype,
                )
            );
        }

        if ( $expect_json || stripos( $ctype, 'application/json' ) !== false ) {
            if ( '' === $body ) {
                return array();
            }
            $decoded = json_decode( $body, true );
            if ( json_last_error() !== JSON_ERROR_NONE ) {
                return new \WP_Error(
                    'json_decode',
                    'Invalid JSON: ' . json_last_error_msg(),
                    array(
                        'body' => $body,
                    )
                );
            }
            return $decoded;
        }

        return $body;
    }

    /**
     * Method sanitized_post()
     *
     * Sanitized post field.
     *
     * @param string $key key to get from POST.
     * @param string $callback cleaning method.
     * @param mixed  $default_value Default return value.
     *
     * @return mixed data value.
     */
    private static function sanitized_post( $key, $callback = 'sanitize_text_field', $default_value = '' ) {
        if ( ! in_array( $callback, self::$allowed_callbacks, true ) ) {
            $callback = 'sanitize_text_field';
        }

        return isset( $_POST[ $key ] ) ? $callback( wp_unslash( $_POST[ $key ] ) ) : $default_value; // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
    }
}