Website : rimsha.abasa.com
backdoor
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
var
/
www
/
nowsquared.com
/
wp-content
/
plugins
/
wp-malware-removal
/
traits
/
Filename :
wpmr_api_operations.php
back
Copy
<?php /** * WPMR Pro Operations Trait * * Handles SaaS control plane communication and local execution of file operations. * * @package WPMR * @since 3.9.0 */ if ( ! defined( 'ABSPATH' ) ) { exit; } trait WPMR_API_Operations { /** * Public key for RSA signature verification */ private $saas_public_key = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4a5vWufgOxDbdeWt9fWRt0L89z4P6kjnL9arr8L/+79zRHIxX8Rfi8nQLy5z9UwsEhGNaqSArR+geApQOdbNmF2bCwOppcBeXFlzRcICadXC04yposn6nkBNUjDQO0qRqNtgF2cM9uzYeL2W7i2N0vy4nPNJo1geny5NVai7GcqAZSh5aAaJLqoQCQ74gZkDDsSVPUkqRwV4V+hj94e3YSTmXrCpmvfDmESfuNvrwFPh/y+zJOIEuM4QWyDcUUElpHrHTE3McB3Qc8gcXsy54wRMrXXhNMoQWufRacidX4fHNBliKu4+r+JB++jYJAWipiCmBoWqZJAtSjWFXOCiAQIDAQAB'; /** * Build a consistent SaaS state payload. * * @param array $extra Additional context to merge into the payload. * @return array State payload ready for encoding. */ function get_state( $extra = array() ) { $upload_dir = wp_upload_dir(); $wp_paths = array( 'ABSPATH' => ABSPATH, 'WP_CONTENT_DIR' => WP_CONTENT_DIR, 'WP_PLUGIN_DIR' => WP_PLUGIN_DIR, 'WPMU_PLUGIN_DIR' => WPMU_PLUGIN_DIR, 'WP_LANG_DIR' => defined( 'WP_LANG_DIR' ) ? WP_LANG_DIR : '', 'WP_TEMP_DIR' => defined( 'WP_TEMP_DIR' ) ? WP_TEMP_DIR : '', 'UPLOADS' => defined( 'UPLOADS' ) ? UPLOADS : '', 'get_theme_root' => get_theme_root(), 'theme_root' => $this->get_theme_root_paths(), 'wp_upload_dir' => isset( $upload_dir['basedir'] ) ? $upload_dir['basedir'] : '', 'is_subdirectory' => $this->is_subdirectory_install(), ); $compatibility = $this->plugin_data; if ( empty( $compatibility ) || ! is_array( $compatibility ) ) { $compatibility = (array) $compatibility; } $state = array( 'user' => $this->get_setting( 'user' ), 'compatibility' => $compatibility, 'lic' => $this->get_setting( 'license_key' ), 'license_status' => get_transient( 'WPMR_license_status' ), 'timestamp' => time(), 'site_url' => site_url(), 'home_url' => home_url(), 'wp_paths' => $wp_paths, ); // TODO: Consider caching the computed state per request if repeated calls become expensive. return array_merge( $state, (array) $extra ); } /** * Request action from SaaS control plane * * @param string $action_type Action type (saas_repair_file, saas_delete_file, saas_whitelist_file) * @param string $file Absolute file path * @return array|WP_Error Response from SaaS or error */ function request_saas_action( $action_type, $file ) { // Collect file context if ( ! $this->is_valid_file( $file ) ) { return new WP_Error( 'invalid_file', 'Invalid file provided.' ); } $file_sha256 = file_exists( $file ) ? hash_file( 'sha256', $file ) : ''; $file_type = $this->determine_file_type( $file ); if ( 'saas_repair_file' === $action_type && 'unknown' === $file_type ) { return new WP_Error( 'unsupported_repair_target', 'Repair is only available for WordPress core, plugin, or theme files.' ); } $file_repo = $this->build_file_repository_context( $file, $file_type ); $file_payload = array( 'path' => $file, 'sha256' => $file_sha256, 'kind' => $file_type, ); if ( ! empty( $file_repo ) ) { $file_payload['repo'] = $file_repo; } $response = $this->saas_request( $action_type, array( 'method' => 'POST', 'state_extra' => array( 'file' => $file_payload ), 'timeout' => 30, ) ); if ( is_wp_error( $response ) ) { return $response; } // Check if signature was verified during transport if ( empty( $response['signature_verified'] ) ) { return new WP_Error( 'saas_signature_failed', 'Response signature verification failed.' ); } $data = isset( $response['response'] ) ? $response['response'] : null; if ( empty( $data ) ) { return new WP_Error( 'saas_invalid_response', 'Invalid response from Malcure service.' ); } if ( ! isset( $data['success'] ) || ! $data['success'] ) { $error_message = ''; if ( isset( $data['data']['message'] ) ) { $error_message = $data['data']['message']; } elseif ( isset( $response['payload']['message'] ) ) { $error_message = $response['payload']['message']; } if ( empty( $error_message ) ) { $error_message = __( 'Malcure service could not process this request.', 'wp-malware-removal' ); } $error_data = isset( $response['payload'] ) ? $response['payload'] : array(); return new WP_Error( 'wpmr_saas_action_failed', $error_message, $error_data ); } if ( ! isset( $response['payload'] ) ) { return new WP_Error( 'wpmr_saas_action_failed', 'Invalid response payload.' ); } // Return the full envelope so downstream consumers have access to action_id and structure if ( isset( $data['data'] ) ) { return $data['data']; } return $response['payload']; } function sanitize_saas_reason_html( $message ) { $allowed = array( 'a' => array( 'href' => array(), 'target' => array(), 'rel' => array(), ), 'strong' => array(), 'em' => array(), ); return wp_kses( (string) $message, $allowed ); } /** * Validate SaaS response with RSA signature verification * * @param array $response Response from SaaS * @return array Validation result with 'valid' and 'error' keys */ function validate_saas_response( $response ) { // Check required fields if ( empty( $response['signature'] ) || empty( $response['action_id'] ) ) { return array( 'valid' => false, 'error' => 'Invalid response structure from Malcure service.', ); } // Check API version compatibility if ( empty( $response['api_version'] ) ) { return array( 'valid' => false, 'error' => 'Missing API version in response.', ); } if ( ! function_exists( 'openssl_verify' ) || ! extension_loaded( 'openssl' ) ) { return array( 'valid' => false, 'error' => 'OpenSSL extension is required to verify responses from the Malcure service. Please enable it on your server.', ); } // Extract signature $signature = $response['signature']; unset( $response['signature'] ); // Create canonical JSON string $json = json_encode( $response, JSON_UNESCAPED_SLASHES ); // Verify signature $public_key = "-----BEGIN PUBLIC KEY-----\n" . chunk_split( $this->saas_public_key, 64, "\n" ) . '-----END PUBLIC KEY-----'; $key = openssl_pkey_get_public( $public_key ); if ( ! $key ) { return array( 'valid' => false, 'error' => 'Failed to load public key for signature verification.', ); } $result = openssl_verify( $json, base64_decode( $signature ), $key, OPENSSL_ALGO_SHA256 ); if ( is_resource( $key ) ) { openssl_free_key( $key ); } if ( $result !== 1 ) { return array( 'valid' => false, 'error' => 'Signature verification failed. Response may have been tampered with.', ); } // Check TTL if ( ! empty( $response['ttl_seconds'] ) ) { // TTL validation can be added here if timestamp is included in response } return array( 'valid' => true, 'error' => '', ); } /** * Execute repair action locally * * @param array $response Response from SaaS with src URL * @param string $file File path to repair * @return bool|WP_Error True on success, WP_Error on failure */ function perform_repair_action( $response, $file ) { $src = isset( $response['payload']['src'] ) ? $response['payload']['src'] : ''; if ( empty( $src ) ) { return new WP_Error( 'no_source', 'No source URL provided for repair.' ); } $perform_action = apply_filters( 'wpmr_perform_edit_action', true, $response, $file, 'repair' ); if ( ! $perform_action ) { return new WP_Error( 'repair_aborted', 'Repair action aborted by filter.' ); } // Fetch file from WordPress.org $fetch_response = wp_safe_remote_get( $src, array( 'timeout' => 30, 'sslverify' => true, ) ); if ( is_wp_error( $fetch_response ) ) { return new WP_Error( 'fetch_failed', 'Failed to fetch file: ' . $fetch_response->get_error_message() ); } $fetch_code = wp_remote_retrieve_response_code( $fetch_response ); if ( $fetch_code !== 200 ) { return new WP_Error( 'fetch_failed', 'Failed to fetch file. HTTP code: ' . $fetch_code ); } $content = wp_remote_retrieve_body( $fetch_response ); if ( empty( $content ) ) { return new WP_Error( 'empty_content', 'Fetched file is empty.' ); } // Calculate SHA256 before $sha256_before = file_exists( $file ) ? hash_file( 'sha256', $file ) : ''; // Write file using WP_Filesystem global $wp_filesystem; if ( ! function_exists( 'WP_Filesystem' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } WP_Filesystem(); if ( ! $wp_filesystem->put_contents( $file, $content, FS_CHMOD_FILE ) ) { return new WP_Error( 'write_failed', 'Failed to write file.' ); } // Calculate SHA256 after $sha256_after = hash_file( 'sha256', $file ); // Log operation $this->log_event( 'file_repaired', array( 'file' => $file, 'action_id' => $response['action_id'], 'sha256_before' => $sha256_before, 'sha256_after' => $sha256_after, ) ); return true; } /** * Execute delete action locally * * @param array $response Response from SaaS * @param string $file File path to delete * @return bool|WP_Error True on success, WP_Error on failure */ function perform_delete_action( $response, $file ) { // Double-check file is deletable if ( ! $this->is_deletable( $file ) ) { return new WP_Error( 'not_deletable', 'File cannot be deleted (critical system file).' ); } if ( ! file_exists( $file ) ) { return new WP_Error( 'file_not_found', 'File does not exist.' ); } $perform_action = apply_filters( 'wpmr_perform_edit_action', true, $response, $file, 'delete' ); if ( ! $perform_action ) { return new WP_Error( 'delete_aborted', 'Delete action aborted by filter.' ); } // Calculate SHA256 before deletion $sha256_before = hash_file( 'sha256', $file ); // Delete file using WP_Filesystem global $wp_filesystem; if ( ! function_exists( 'WP_Filesystem' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } WP_Filesystem(); if ( ! $wp_filesystem->delete( $file, false, 'f' ) ) { return new WP_Error( 'delete_failed', 'Failed to delete file.' ); } // Log operation $this->log_event( 'file_deleted', array( 'file' => $file, 'action_id' => $response['action_id'], 'sha256' => $sha256_before, ) ); return true; } /** * Execute whitelist action locally * * @param array $response Response from SaaS * @param string $file File path to whitelist * @return bool|WP_Error True on success, WP_Error on failure */ function perform_whitelist_action( $response, $file ) { if ( ! file_exists( $file ) ) { return new WP_Error( 'file_not_found', 'File does not exist.' ); } $perform_action = apply_filters( 'wpmr_perform_edit_action', true, $response, $file, 'whitelist' ); if ( ! $perform_action ) { return new WP_Error( 'whitelist_aborted', 'Whitelist action aborted by filter.' ); } $whitelist = $this->get_setting( 'whitelist' ); if ( ! is_array( $whitelist ) ) { $whitelist = array(); } $file_sha256 = @hash_file( 'sha256', $file ); if ( ! $file_sha256 ) { return new WP_Error( 'whitelist_failed', 'Can\'t whitelist. File: ' . $file ); } $whitelist[ $file ] = $file_sha256; $this->update_setting( 'whitelist', $whitelist ); // Log operation $this->log_event( 'file_whitelisted', array( 'file' => $file, 'action_id' => $response['action_id'], 'sha256' => $file_sha256, ) ); return true; } /** * Gather theme root directories including registered directories. * * @return array */ private function get_theme_root_paths() { $roots = array(); $default_root = get_theme_root(); if ( $default_root ) { $roots[] = wp_normalize_path( $default_root ); } global $_wp_theme_directories; if ( ! empty( $_wp_theme_directories ) && is_array( $_wp_theme_directories ) ) { foreach ( $_wp_theme_directories as $dir ) { $roots[] = wp_normalize_path( $dir ); } } return array_values( array_unique( $roots ) ); } /** * Determine file type (core, plugin, theme, unknown) * * @param string $file Absolute file path * @return string File type */ function determine_file_type( $file ) { if ( ! $this->is_repairable( $file ) ) { return 'unknown'; } if ( strpos( $file, WP_PLUGIN_DIR ) !== false ) { // file is inside plugins directory if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $plugins = get_plugins(); foreach ( $plugins as $plugin_file => $plugin_data ) { $plugin_dir = dirname( $plugin_file ); if ( '' === $plugin_dir || '.' === $plugin_dir ) { continue; // skip single-file plugins without directory for now } $plugin_path = wp_normalize_path( trailingslashit( WP_PLUGIN_DIR ) . trailingslashit( $plugin_dir ) ); if ( strpos( wp_normalize_path( $file ), $plugin_path ) === 0 ) { return 'plugin'; } } } $themes = wp_get_themes(); foreach ( $themes as $tk => $tv ) { if ( strpos( wp_normalize_path( $file ), wp_normalize_path( get_theme_root( $tk ) . DIRECTORY_SEPARATOR . $tk ) ) !== false ) { return 'theme'; } } remove_filter( 'serve_checksums', array( $this, 'get_cached_checksums' ), 11 ); $checksums = $this->get_checksums(); if ( array_key_exists( $this->normalise_path( $file ), $checksums ) ) { return 'core'; } return 'unknown'; } /** * Build repository context for the requested file. * * @param string $file Absolute file path. * @param string $file_type Classified file type. * @return array Repository context metadata. */ private function build_file_repository_context( $file, $file_type ) { $file_type = strtolower( $file_type ); $file = wp_normalize_path( $file ); switch ( $file_type ) { case 'core': return $this->get_core_repository_context( $file ); case 'plugin': return $this->get_plugin_repository_context( $file ); case 'theme': return $this->get_theme_repository_context( $file ); } return array(); } /** * Repository context for WordPress core files. * * @param string $file Normalized absolute file path. * @return array */ private function get_core_repository_context( $file ) { $abspath = wp_normalize_path( trailingslashit( ABSPATH ) ); if ( strpos( $file, $abspath ) !== 0 ) { return array(); } $relative = $this->sanitize_relative_path( substr( $file, strlen( $abspath ) ) ); if ( '' === $relative ) { return array(); } $version = get_bloginfo( 'version' ); if ( empty( $version ) && isset( $GLOBALS['wp_version'] ) ) { $version = $GLOBALS['wp_version']; } return array( 'type' => 'core', 'slug' => 'wordpress', 'version' => $version, 'relative_path' => $relative, ); } /** * Repository context for plugin files. * * @param string $file Normalized absolute file path. * @return array */ private function get_plugin_repository_context( $file ) { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $plugins = get_plugins(); $plugins_base_dir = wp_normalize_path( trailingslashit( WP_PLUGIN_DIR ) ); foreach ( $plugins as $plugin_file => $plugin_data ) { $plugin_abs_path = wp_normalize_path( $plugins_base_dir . $plugin_file ); $plugin_dir_path = wp_normalize_path( trailingslashit( dirname( $plugin_abs_path ) ) ); if ( strpos( $file, $plugin_dir_path ) !== 0 ) { continue; } $relative_path = $this->sanitize_relative_path( substr( $file, strlen( $plugin_dir_path ) ) ); $parts = explode( '/', $plugin_file ); $plugin_slug = ( count( $parts ) > 1 ) ? $parts[0] : basename( $plugin_file, '.php' ); $version = isset( $plugin_data['Version'] ) ? $plugin_data['Version'] : ''; return array( 'type' => 'plugin', 'slug' => sanitize_key( $plugin_slug ), 'version' => $version, 'relative_path' => $relative_path, ); } $mu_base_dir = wp_normalize_path( trailingslashit( WPMU_PLUGIN_DIR ) ); if ( ! empty( $mu_base_dir ) && strpos( $file, $mu_base_dir ) === 0 ) { $relative_path = $this->sanitize_relative_path( substr( $file, strlen( $mu_base_dir ) ) ); $slug = $relative_path; $slash_pos = strpos( $relative_path, '/' ); if ( false !== $slash_pos ) { $slug = substr( $relative_path, 0, $slash_pos ); } return array( 'type' => 'plugin', 'slug' => sanitize_key( $slug ), 'version' => '', 'relative_path' => $relative_path, ); } return array(); } /** * Repository context for theme files. * * @param string $file Normalized absolute file path. * @return array */ private function get_theme_repository_context( $file ) { if ( ! function_exists( 'wp_get_themes' ) ) { require_once ABSPATH . 'wp-includes/theme.php'; } $themes = wp_get_themes(); foreach ( $themes as $stylesheet => $theme ) { $theme_dir = wp_normalize_path( trailingslashit( $theme->get_stylesheet_directory() ) ); if ( strpos( $file, $theme_dir ) !== 0 ) { continue; } $relative_path = $this->sanitize_relative_path( substr( $file, strlen( $theme_dir ) ) ); $version = $theme->get( 'Version' ); return array( 'type' => 'theme', 'slug' => sanitize_key( $stylesheet ), 'version' => $version, 'relative_path' => $relative_path, ); } return array(); } /** * Normalize and sanitize a relative path segment for transport. * * @param string $path Path fragment. * @return string */ private function sanitize_relative_path( $path ) { $path = wp_normalize_path( $path ); $segments = explode( '/', $path ); $clean = array(); foreach ( $segments as $segment ) { if ( '' === $segment || '.' === $segment || '..' === $segment ) { continue; } $clean[] = $segment; } return implode( '/', $clean ); } /** * Detect if WordPress is installed in a subdirectory * * @return bool True if subdirectory install */ function is_subdirectory_install() { if ( site_url() !== home_url() ) { return true; } if ( ! empty( $_SERVER['DOCUMENT_ROOT'] ) ) { $doc_root = trailingslashit( $_SERVER['DOCUMENT_ROOT'] ); $abspath = trailingslashit( ABSPATH ); if ( $abspath !== $doc_root ) { return true; } } return false; } /** * Perform a SaaS request with consistent logging/state handling. * * @param string $wpmr_action Target action handled by the control plane. * @param array $options Request overrides (method, body, headers, etc.). * @return array|WP_Error Normalized response payload or WP_Error on failure. */ function saas_request( $wpmr_action, $options = array() ) { $defaults = array( 'method' => 'GET', 'query' => array(), 'body' => array(), 'headers' => array(), 'send_state' => 'body', 'state_extra' => array(), 'timeout' => 15, 'sslverify' => true, 'blocking' => true, 'log' => true, ); $options = wp_parse_args( $options, $defaults ); // $options = apply_filters( 'wpmr_saas_request_options', $options, $wpmr_action ); if ( $options['log'] ) { $this->flog( '' ); $this->flog( '' ); $this->flog( '================================' ); $this->flog( '================================' ); $this->flog( '' ); $this->flog( '' ); } if ( empty( $wpmr_action ) ) { return new WP_Error( 'wpmr_missing_action', 'Missing SaaS action parameter.' ); } $method = strtoupper( $options['method'] ); $query = is_array( $options['query'] ) ? $options['query'] : array(); $body = is_array( $options['body'] ) ? $options['body'] : array(); $headers = is_array( $options['headers'] ) ? $options['headers'] : array(); $query = array_merge( array( 'wpmr_action' => $wpmr_action, 'cachebust' => microtime( true ), ), $query ); $state = array(); $state_location = in_array( $options['send_state'], array( 'query', 'body', 'none' ), true ) ? $options['send_state'] : 'body'; $encoded_state = ''; if ( 'none' !== $state_location ) { $state = $this->get_state( $options['state_extra'] ); if ( $options['log'] ) { $this->flog( __FUNCTION__ . ' State for ' . $wpmr_action . ': ' ); $this->flog( $state ); } $encoded_state = $this->encode( $state ); if ( 'query' === $state_location ) { $query['state'] = $encoded_state; } else { $body['state'] = $encoded_state; } } $request_id = uniqid( 'wpmr_saas_', true ); $url = add_query_arg( $query, WPMR_SERVER ); $start_time = microtime( true ); if ( 'GET' === $method && ! empty( $body ) ) { $url = add_query_arg( $body, $url ); $body = array(); } $args = array( 'headers' => $headers, 'cookies' => array(), 'compress' => false, 'sslverify' => (bool) $options['sslverify'], 'timeout' => (float) $options['timeout'], 'blocking' => (bool) $options['blocking'], ); if ( ! empty( $body ) ) { $args['body'] = $body; } $args['method'] = $method; $transport_snapshot = array( 'url' => $url, 'method' => $method, 'args' => $args, 'query' => $query, 'state' => $state, 'state_location' => $state_location, ); $transport_snapshot = apply_filters( 'saas_msg_payload', $transport_snapshot, $wpmr_action, $options ); if ( is_array( $transport_snapshot ) ) { if ( isset( $transport_snapshot['url'] ) ) { $url = $transport_snapshot['url']; } if ( isset( $transport_snapshot['method'] ) ) { $method = strtoupper( $transport_snapshot['method'] ); } if ( isset( $transport_snapshot['args'] ) && is_array( $transport_snapshot['args'] ) ) { $args = $transport_snapshot['args']; } if ( isset( $transport_snapshot['query'] ) && is_array( $transport_snapshot['query'] ) ) { $query = $transport_snapshot['query']; } } $args['method'] = $method; if ( $options['log'] ) { $this->flog( __FUNCTION__ . ' $url ' . $url . "\n" . '$args' ); $this->flog( json_encode( $args ) ); } $response = wp_remote_request( $url, $args ); $duration = round( ( microtime( true ) - $start_time ) * 1000, 2 ); if ( is_wp_error( $response ) ) { if ( $options['log'] ) { $this->flog( __FUNCTION__ . ' wp_remote_request error: ' . $response->get_error_message() ); } return new WP_Error( 'wpmr_saas_transport', 'Could not reach the Malcure service: ' . $response->get_error_message() ); } if ( false === $options['blocking'] ) { $async_result = array( 'request_id' => $request_id, 'url' => $url, 'method' => $method, 'code' => null, 'body' => null, 'headers' => array(), 'response' => null, 'duration' => $duration, ); $async_result = apply_filters( 'saas_msg_response', $async_result, $wpmr_action, $options ); return $async_result; } $code = (int) wp_remote_retrieve_response_code( $response ); $body = wp_remote_retrieve_body( $response ); if ( 200 !== $code ) { return new WP_Error( 'wpmr_saas_http', 'Malcure service returned HTTP ' . $code . '.', array( 'body' => $body ) ); } $data = null; $signature_verified = false; $signed_payload_error = null; $data = json_decode( $body, true ); if ( $options['log'] ) { $this->flog( '' ); $this->flog( '================================' ); $this->flog( '' ); $this->flog( __FUNCTION__ . ' decoded json server response to: ' ); $this->flog( $data ); } if ( null === $data && JSON_ERROR_NONE !== json_last_error() ) { return new WP_Error( 'wpmr_saas_json', 'Unable to decode service response.' ); } if ( is_array( $data ) ) { $signed_payload = null; if ( isset( $data['data'] ) && is_array( $data['data'] ) && isset( $data['data']['signature'], $data['data']['action_id'] ) ) { $signed_payload = $data['data']; } if ( $signed_payload ) { $validation = $this->validate_saas_response( $signed_payload ); if ( ! $validation['valid'] ) { $signed_payload_error = $validation['error']; } } } if ( $signed_payload_error ) { if ( $options['log'] ) { $this->flog( __FUNCTION__ . ' signature verification error: ' . $signed_payload_error ); } return new WP_Error( 'wpmr_saas_signature', $signed_payload_error ); } if ( isset( $signed_payload ) && empty( $signed_payload_error ) && ! empty( $signed_payload ) ) { $signature_verified = true; } $payload = null; if ( isset( $data['data'] ) && is_array( $data['data'] ) && array_key_exists( 'payload', $data['data'] ) ) { $payload = $data['data']['payload']; } $result = array( 'request_id' => $request_id, 'url' => $url, 'method' => $method, 'code' => $code, 'body' => $body, 'headers' => wp_remote_retrieve_headers( $response ), 'response' => $data, 'payload' => $payload, 'duration' => $duration, 'signature_verified' => $signature_verified, ); $result = apply_filters( 'saas_msg_response', $result, $wpmr_action, $options ); return $result; } /** * Retrieve SaaS compatibility metadata. * * @param bool $force_refresh Whether to bypass the cached result. * @return array Compatibility payload. */ protected function get_saas_compatibility_status( $force_refresh = false ) { $cache_key = 'WPMR_compatibility'; if ( ! $force_refresh ) { $cached = get_transient( $cache_key ); if ( false !== $cached ) { return $cached; } } $response = $this->saas_request( 'saas_test_compatibility', array( 'method' => 'POST', 'send_state' => 'body', 'timeout' => 15, 'log' => false, ) ); if ( is_wp_error( $response ) ) { $expected = array( 'supported' => false, 'error' => $response->get_error_message(), 'checked_at' => time(), 'api_version' => '', 'message' => '', ); set_transient( $cache_key, $expected, HOUR_IN_SECONDS * 3 ); return $expected; } $body = isset( $response['payload'] ) ? $response['payload'] : null; // Fallback for compatibility check which returns data directly in data object if ( empty( $body ) && isset( $response['response']['data'] ) && is_array( $response['response']['data'] ) ) { $body = $response['response']['data']; } if ( empty( $body ) || ! is_array( $body ) ) { $expected = array( 'supported' => false, 'error' => __( 'Malcure API returned an empty response.', 'wp-malware-removal' ), 'checked_at' => time(), 'api_version' => '', 'message' => '', ); set_transient( $cache_key, $expected, HOUR_IN_SECONDS * 3 ); return $expected; } $expected = array( 'supported' => isset( $body['supported'] ) ? (bool) $body['supported'] : false, 'api_version' => isset( $body['api_version'] ) ? $body['api_version'] : '', 'message' => isset( $body['message'] ) ? $body['message'] : '', 'checked_at' => time(), 'error' => '', ); if ( ! array_key_exists( 'supported', $body ) ) { $expected['supported'] = false; $expected['error'] = __( 'Malcure API returned an unexpected payload.', 'wp-malware-removal' ); } set_transient( $cache_key, $expected, HOUR_IN_SECONDS * 12 ); return $expected; } }