| 1 |
<?php |
| 2 |
/** |
| 3 |
* Plugin Name: Plugin Compatibility Checker (Portal-integrated) |
| 4 |
* Description: Check which WordPress and PHP versions your plugins are compatible with before updating WordPress.adds Portal integration: license settings, request scan, fetch latest result, cron poller, admin email on new results. |
| 5 |
* Version: 7.0.7 |
| 6 |
* Author: CompatShield |
| 7 |
* Author URI: https://www.compatshield.com/ |
| 8 |
*/ |
| 9 |
|
| 10 |
|
| 11 |
if ( ! defined('ABSPATH') ) exit; |
| 12 |
|
| 13 |
if ( ! class_exists('PCC') ) : |
| 14 |
|
| 15 |
class PCC { |
| 16 |
|
| 17 |
const MENU_SLUG = 'PCC_Check'; |
| 18 |
const SUBMENU_SYSINFO_SLUG = 'websiteinfo'; |
| 19 |
const SUBMENU_SETTINGS_SLUG = 'pcc_settings'; |
| 20 |
const CAP_SINGLE = 'manage_options'; |
| 21 |
const CAP_NETWORK = 'manage_network'; |
| 22 |
const TRANSIENT_KEY = 'pcc_scan_results'; |
| 23 |
const TRANSIENT_TTL_DEFAULT = 6 * HOUR_IN_SECONDS; |
| 24 |
const NONCE_ACTION = 'pcc_rescan'; |
| 25 |
const AJAX_ACTION = 'pcc_rescan'; |
| 26 |
|
| 27 |
const OPTION_LICENSE_KEY = 'pcc_license_key'; |
| 28 |
const OPTION_LICENSE_VALID = 'pcc_license_valid'; |
| 29 |
const OPTION_REMOTE_MAP = 'pcc_remote_php_compat'; |
| 30 |
const CRON_HOOK = 'pcc_cron_fetch_remote'; |
| 31 |
const PORTAL_API = 'https://www.compatshield.com/portal-api'; |
| 32 |
const SIGNUP_URL = 'https://www.compatshield.com/product'; // free plan signup |
| 33 |
const PRODUCT_URL = 'https://www.compatshield.com/product/'; // pricing / pro |
| 34 |
|
| 35 |
public function __construct() { |
| 36 |
if ( is_multisite() ) { |
| 37 |
add_action('network_admin_menu', [ $this, 'register_network_menus' ]); |
| 38 |
} else { |
| 39 |
add_action('admin_menu', [ $this, 'register_menus' ]); |
| 40 |
} |
| 41 |
|
| 42 |
add_action('admin_enqueue_scripts', [ $this, 'enqueue_assets' ]); |
| 43 |
add_action('wp_ajax_' . self::AJAX_ACTION, [ $this, 'ajax_rescan' ]); |
| 44 |
add_action('wp_ajax_pcc_request_scan', [ $this, 'ajax_request_scan' ]); |
| 45 |
add_action('wp_ajax_pcc_fetch_remote', [ $this, 'ajax_fetch_remote' ]); |
| 46 |
add_action('wp_ajax_pcc_validate_license', [ $this, 'ajax_validate_license' ]); |
| 47 |
|
| 48 |
add_action('init', [ $this, 'maybe_schedule_cron' ]); |
| 49 |
add_action(self::CRON_HOOK, [ $this, 'cron_fetch_remote' ]); |
| 50 |
|
| 51 |
register_deactivation_hook(__FILE__, [ $this, 'on_deactivate' ]); |
| 52 |
} |
| 53 |
|
| 54 |
public function on_deactivate() { |
| 55 |
$ts = wp_next_scheduled(self::CRON_HOOK); |
| 56 |
if ($ts) wp_unschedule_event($ts, self::CRON_HOOK); |
| 57 |
} |
| 58 |
|
| 59 |
/* Menus */ |
| 60 |
public function register_menus() { |
| 61 |
add_menu_page( |
| 62 |
__('Plugin Compatibility Checker', 'pcc'), |
| 63 |
__('Plugin Compatibility Checker', 'pcc'), |
| 64 |
self::CAP_SINGLE, |
| 65 |
self::MENU_SLUG, |
| 66 |
[ $this, 'render_single_site' ], |
| 67 |
'dashicons-screenoptions', |
| 68 |
90 |
| 69 |
); |
| 70 |
|
| 71 |
add_submenu_page(self::MENU_SLUG, __('System Info', 'pcc'), __('System Info', 'pcc'), self::CAP_SINGLE, self::SUBMENU_SYSINFO_SLUG, [ $this, 'render_sysinfo' ]); |
| 72 |
add_submenu_page(self::MENU_SLUG, __('PCC Settings', 'pcc'), __('Settings', 'pcc'), self::CAP_SINGLE, self::SUBMENU_SETTINGS_SLUG, [ $this, 'render_settings' ]); |
| 73 |
} |
| 74 |
|
| 75 |
public function register_network_menus() { |
| 76 |
add_menu_page(__('Plugin Compatibility Checker', 'pcc'), __('Plugin Compatibility Checker', 'pcc'), self::CAP_NETWORK, self::MENU_SLUG, [ $this, 'render_network' ], 'dashicons-screenoptions', 90); |
| 77 |
add_submenu_page(self::MENU_SLUG, __('System Info', 'pcc'), __('System Info', 'pcc'), self::CAP_NETWORK, self::SUBMENU_SYSINFO_SLUG, [ $this, 'render_sysinfo' ]); |
| 78 |
add_submenu_page(self::MENU_SLUG, __('PCC Settings', 'pcc'), __('Settings', 'pcc'), self::CAP_NETWORK, self::SUBMENU_SETTINGS_SLUG, [ $this, 'render_settings' ]); |
| 79 |
} |
| 80 |
|
| 81 |
/* Assets */ |
| 82 |
public function enqueue_assets($hook) { |
| 83 |
$screen = get_current_screen(); |
| 84 |
if ( empty($screen->id) ) return; |
| 85 |
if ( false === strpos($screen->id, self::MENU_SLUG) && false === strpos($screen->id, self::SUBMENU_SYSINFO_SLUG) && false === strpos($screen->id, self::SUBMENU_SETTINGS_SLUG) ) return; |
| 86 |
|
| 87 |
wp_enqueue_style('pcc-custom', plugin_dir_url(__FILE__) . 'customcss/pcccustom.css', [], '1.0.0'); |
| 88 |
wp_enqueue_style('pcc-bootstrap', plugin_dir_url(__FILE__) . 'customcss/bootstrap.min.css', [], '1.0.1'); |
| 89 |
|
| 90 |
wp_enqueue_script('pcc-filter', plugin_dir_url(__FILE__) . 'customjs/filtertable.js', ['jquery'], '1.0.0', true); |
| 91 |
wp_enqueue_script('pcc-export', plugin_dir_url(__FILE__) . 'customjs/export.js', ['jquery'], '1.0.0', true); |
| 92 |
|
| 93 |
wp_register_script('pcc-rescan', plugin_dir_url(__FILE__) . 'customjs/pcc-rescan.js', ['jquery'], '1.0.0', true); |
| 94 |
wp_localize_script('pcc-rescan', 'PCCVars', [ |
| 95 |
'ajaxUrl' => admin_url('admin-ajax.php'), |
| 96 |
'nonce' => wp_create_nonce(self::NONCE_ACTION), |
| 97 |
'action' => self::AJAX_ACTION, |
| 98 |
]); |
| 99 |
wp_enqueue_script('pcc-rescan'); |
| 100 |
|
| 101 |
$settings_rel = 'customjs/pcc-settings.js'; |
| 102 |
$settings_path = plugin_dir_path(__FILE__) . $settings_rel; |
| 103 |
$settings_url = plugin_dir_url(__FILE__) . $settings_rel; |
| 104 |
|
| 105 |
$license = trim(get_option(self::OPTION_LICENSE_KEY, '')); |
| 106 |
$license_valid = (bool) get_option(self::OPTION_LICENSE_VALID, false); |
| 107 |
|
| 108 |
if ( file_exists($settings_path) ) { |
| 109 |
wp_register_script('pcc-settings-js', $settings_url, ['jquery'], '1.0.2', true); |
| 110 |
wp_localize_script('pcc-settings-js', 'PCCSettings', [ |
| 111 |
'ajaxUrl' => admin_url('admin-ajax.php'), |
| 112 |
'validateAction' => 'pcc_validate_license', |
| 113 |
'requestScanAction' => 'pcc_request_scan', |
| 114 |
'fetchRemoteAction' => 'pcc_fetch_remote', |
| 115 |
'nonce' => wp_create_nonce('pcc_settings_nonce'), |
| 116 |
'siteUrl' => get_site_url(), |
| 117 |
'licenseActive' => ($license !== '' && $license_valid), |
| 118 |
]); |
| 119 |
wp_enqueue_script('pcc-settings-js'); |
| 120 |
} else { |
| 121 |
wp_register_script('pcc-settings-fallback', false); |
| 122 |
wp_enqueue_script('pcc-settings-fallback'); |
| 123 |
$localized = [ |
| 124 |
'ajaxUrl' => admin_url('admin-ajax.php'), |
| 125 |
'validateAction' => 'pcc_validate_license', |
| 126 |
'requestScanAction' => 'pcc_request_scan', |
| 127 |
'fetchRemoteAction' => 'pcc_fetch_remote', |
| 128 |
'nonce' => wp_create_nonce('pcc_settings_nonce'), |
| 129 |
'siteUrl' => get_site_url(), |
| 130 |
'licenseActive' => ($license !== '' && $license_valid), |
| 131 |
]; |
| 132 |
wp_localize_script('pcc-settings-fallback', 'PCCSettings', $localized); |
| 133 |
$inline_handlers = <<<JS |
| 134 |
(function($){ |
| 135 |
$(function(){}); |
| 136 |
})(jQuery); |
| 137 |
JS; |
| 138 |
wp_add_inline_script('pcc-settings-fallback', $inline_handlers); |
| 139 |
} |
| 140 |
} |
| 141 |
|
| 142 |
/* Server-side helper: validate license against portal validate endpoint */ |
| 143 |
private function server_validate_license($license, $site_url = '') { |
| 144 |
if ( empty($license) ) return new WP_Error('missing_license', 'Missing license'); |
| 145 |
|
| 146 |
$license = trim($license); |
| 147 |
|
| 148 |
$site_raw = trim($site_url ?: get_site_url()); |
| 149 |
$host = parse_url($site_raw, PHP_URL_HOST); |
| 150 |
if ( ! $host ) { |
| 151 |
$tmp = @parse_url('https://' . ltrim($site_raw, '/')); |
| 152 |
$host = $tmp ? ($tmp['host'] ?? '') : ''; |
| 153 |
} |
| 154 |
$site_to_send = $host ? trim(strtolower($host)) : trim($site_raw); |
| 155 |
|
| 156 |
$validate_url = rtrim(self::PORTAL_API, '/') . '/validate_license.php'; |
| 157 |
|
| 158 |
$payload = [ |
| 159 |
'site_url' => $site_to_send, |
| 160 |
'license' => $license, |
| 161 |
]; |
| 162 |
|
| 163 |
$args = [ |
| 164 |
'headers' => [ |
| 165 |
'X-Portal-License' => $license, |
| 166 |
'Content-Type' => 'application/json', |
| 167 |
], |
| 168 |
'timeout' => 10, |
| 169 |
'body' => wp_json_encode($payload), |
| 170 |
]; |
| 171 |
|
| 172 |
if ( defined('WP_DEBUG') && WP_DEBUG ) { |
| 173 |
error_log('[PCC] validate_license -> POST ' . $validate_url . ' payload=' . wp_json_encode($payload)); |
| 174 |
} |
| 175 |
|
| 176 |
$res = wp_remote_post($validate_url, $args); |
| 177 |
if ( is_wp_error($res) ) return $res; |
| 178 |
|
| 179 |
$code = wp_remote_retrieve_response_code($res); |
| 180 |
$body = wp_remote_retrieve_body($res); |
| 181 |
$json = json_decode($body, true); |
| 182 |
|
| 183 |
if ( defined('WP_DEBUG') && WP_DEBUG ) { |
| 184 |
error_log('[PCC] validate_license response_code=' . $code . ' body=' . $body); |
| 185 |
} |
| 186 |
|
| 187 |
if ( $code >= 200 && $code < 300 && is_array($json) && isset($json['valid']) && $json['valid'] === true ) { |
| 188 |
return $json; |
| 189 |
} |
| 190 |
|
| 191 |
if ( is_array($json) && isset($json['valid']) && $json['valid'] === false ) { |
| 192 |
return new WP_Error('license_invalid', 'License not valid', ['portal' => $json, 'http_code' => $code, 'body' => $body]); |
| 193 |
} |
| 194 |
|
| 195 |
return new WP_Error('invalid_response', 'License validation failed', ['http_code' => $code, 'body' => $body, 'json' => $json]); |
| 196 |
} |
| 197 |
|
| 198 |
/** |
| 199 |
* Get full DB version string using SELECT VERSION(). |
| 200 |
* Returns the raw string e.g. "8.0.32" or "10.6.14-MariaDB". |
| 201 |
* Use this instead of $wpdb->db_version() which strips the MariaDB suffix. |
| 202 |
*/ |
| 203 |
private function get_db_version() { |
| 204 |
global $wpdb; |
| 205 |
$full = $wpdb->get_var('SELECT VERSION()'); |
| 206 |
return $full ?: 'unknown'; |
| 207 |
} |
| 208 |
|
| 209 |
/** |
| 210 |
* Parse the full DB version string into type and clean version number. |
| 211 |
* Returns array: ['type' => 'MySQL'|'MariaDB', 'clean' => '8.0.32', 'full' => '8.0.32-log'] |
| 212 |
*/ |
| 213 |
private function parse_db_version($full_db = '') { |
| 214 |
if ( empty($full_db) || $full_db === 'unknown' ) { |
| 215 |
return [ 'type' => 'MySQL', 'clean' => 'unknown', 'full' => 'unknown' ]; |
| 216 |
} |
| 217 |
$is_maria = stripos($full_db, 'mariadb') !== false; |
| 218 |
$clean = preg_replace('/[^0-9.].*$/', '', $full_db); |
| 219 |
return [ |
| 220 |
'type' => $is_maria ? 'MariaDB' : 'MySQL', |
| 221 |
'clean' => $clean, |
| 222 |
'full' => $full_db, |
| 223 |
]; |
| 224 |
} |
| 225 |
|
| 226 |
/** |
| 227 |
* Normalize a site URL to host-only (example.com or sub.example.com). |
| 228 |
*/ |
| 229 |
private function normalize_site_host($site_raw = '') { |
| 230 |
$site_raw = trim($site_raw ?: get_site_url()); |
| 231 |
if ( function_exists('wp_parse_url') ) { |
| 232 |
$host = wp_parse_url($site_raw, PHP_URL_HOST); |
| 233 |
} else { |
| 234 |
$parts = @parse_url($site_raw); |
| 235 |
$host = $parts['host'] ?? null; |
| 236 |
} |
| 237 |
if ( ! $host ) { |
| 238 |
$tmp = @parse_url('https://' . ltrim($site_raw, '/')); |
| 239 |
$host = $tmp ? ($tmp['host'] ?? null) : null; |
| 240 |
} |
| 241 |
return $host ? strtolower(trim($host)) : trim($site_raw); |
| 242 |
} |
| 243 |
|
| 244 |
/* AJAX: Rescan */ |
| 245 |
public function ajax_rescan() { |
| 246 |
check_ajax_referer(self::NONCE_ACTION, 'nonce'); |
| 247 |
|
| 248 |
if ( ! current_user_can(self::CAP_SINGLE) ) { |
| 249 |
return wp_send_json_error(['message'=>'forbidden'], 403); |
| 250 |
} |
| 251 |
|
| 252 |
if ( is_multisite() ) delete_site_transient(self::TRANSIENT_KEY); |
| 253 |
else delete_transient(self::TRANSIENT_KEY); |
| 254 |
|
| 255 |
$license = trim(get_option(self::OPTION_LICENSE_KEY, '')); |
| 256 |
$license_valid = (bool) get_option(self::OPTION_LICENSE_VALID, false); |
| 257 |
|
| 258 |
if ( empty($license) ) { |
| 259 |
delete_option(self::OPTION_REMOTE_MAP); |
| 260 |
$this->get_scan_results(true); |
| 261 |
return wp_send_json_success(['from_remote'=>false,'updated'=>0,'scan_pending'=>false,'message'=>'no_license_fallback']); |
| 262 |
} |
| 263 |
|
| 264 |
if ( ! $license_valid ) { |
| 265 |
$v = $this->server_validate_license($license, get_site_url()); |
| 266 |
if ( is_wp_error($v) ) { |
| 267 |
update_option(self::OPTION_LICENSE_VALID, false); |
| 268 |
delete_option(self::OPTION_REMOTE_MAP); |
| 269 |
$this->get_scan_results(true); |
| 270 |
return wp_send_json_error(['message'=>'license_invalid_or_network','detail'=>$v->get_error_message()], 401); |
| 271 |
} else { |
| 272 |
update_option(self::OPTION_LICENSE_KEY, $license); |
| 273 |
update_option(self::OPTION_LICENSE_VALID, true); |
| 274 |
$license_valid = true; |
| 275 |
} |
| 276 |
} |
| 277 |
|
| 278 |
$endpoint = rtrim(self::PORTAL_API, '/') . '/receive_client.php'; |
| 279 |
$site_host = $this->normalize_site_host(get_site_url()); |
| 280 |
|
| 281 |
// Build DB info cleanly before constructing payload |
| 282 |
$db_full = $this->get_db_version(); |
| 283 |
$db_parsed = $this->parse_db_version($db_full); |
| 284 |
|
| 285 |
$payload = [ |
| 286 |
'site_url' => $site_host, |
| 287 |
'wp_version' => get_bloginfo('version'), |
| 288 |
'php_version'=> phpversion(), |
| 289 |
'server_info'=> [ |
| 290 |
'os' => PHP_OS, |
| 291 |
'sapi' => php_sapi_name(), |
| 292 |
'db_version' => $db_parsed['clean'], |
| 293 |
'db_type' => $db_parsed['type'], |
| 294 |
'db_version_full' => $db_parsed['full'], |
| 295 |
], |
| 296 |
'components' => $this->build_components_payload(), |
| 297 |
]; |
| 298 |
|
| 299 |
$args = [ |
| 300 |
'headers' => [ |
| 301 |
'Content-Type' => 'application/json', |
| 302 |
'X-Portal-License' => $license, |
| 303 |
], |
| 304 |
'body' => wp_json_encode($payload), |
| 305 |
'timeout' => 20, |
| 306 |
]; |
| 307 |
|
| 308 |
if ( defined('WP_DEBUG') && WP_DEBUG ) { |
| 309 |
error_log('[PCC rescan] Payload sent: ' . wp_json_encode($payload)); |
| 310 |
} |
| 311 |
|
| 312 |
$res = wp_remote_post($endpoint, $args); |
| 313 |
|
| 314 |
if ( defined('WP_DEBUG') && WP_DEBUG ) { |
| 315 |
error_log('[PCC rescan] HTTP code: ' . wp_remote_retrieve_response_code($res)); |
| 316 |
error_log('[PCC rescan] Body: ' . wp_remote_retrieve_body($res)); |
| 317 |
} |
| 318 |
|
| 319 |
if ( is_wp_error($res) ) { |
| 320 |
$this->get_scan_results(true); |
| 321 |
return wp_send_json_success(['from_remote'=>false,'updated'=>0,'scan_pending'=>false,'remote_error'=>$res->get_error_message()]); |
| 322 |
} |
| 323 |
|
| 324 |
$code = wp_remote_retrieve_response_code($res); |
| 325 |
$body = wp_remote_retrieve_body($res); |
| 326 |
$json = json_decode($body, true); |
| 327 |
|
| 328 |
if ( $code >= 200 && $code < 300 ) { |
| 329 |
if ( is_array($json) && isset($json['status']) && $json['status'] === 'ok' && ! empty($json['scan_results']) ) { |
| 330 |
$remote_map = get_option(self::OPTION_REMOTE_MAP, []); |
| 331 |
if ( ! is_array($remote_map) ) $remote_map = []; |
| 332 |
$acc = []; |
| 333 |
|
| 334 |
foreach ( $json['scan_results'] as $sr ) { |
| 335 |
$standards = $sr['standards'] ?? null; |
| 336 |
if ( empty($standards) || stripos($standards,'PHPCompatibilityWP') === false ) continue; |
| 337 |
if ( empty($sr['slug']) ) continue; |
| 338 |
|
| 339 |
$is_compat = false; |
| 340 |
if ( isset($sr['is_compatible']) ) { |
| 341 |
$val = $sr['is_compatible']; |
| 342 |
if ( is_bool($val) ) $is_compat = $val; |
| 343 |
elseif ( is_numeric($val) ) $is_compat = ((int)$val === 1); |
| 344 |
elseif ( is_string($val) ) $is_compat = in_array(strtolower($val), ['1','true','yes'], true); |
| 345 |
} |
| 346 |
if ( ! $is_compat ) continue; |
| 347 |
|
| 348 |
$pv = $sr['php_version'] ?? ''; |
| 349 |
if ( ! $pv ) continue; |
| 350 |
$parts = preg_split('/\s*,\s*/', trim($pv)); |
| 351 |
if ( ! isset($acc[$sr['slug']]) ) $acc[$sr['slug']] = []; |
| 352 |
foreach ($parts as $p) { if ($p !== '') $acc[$sr['slug']][] = $p; } |
| 353 |
} |
| 354 |
|
| 355 |
$updated = 0; |
| 356 |
foreach ($acc as $slug => $list) { |
| 357 |
$list = array_values(array_unique($list)); |
| 358 |
usort($list, 'version_compare'); |
| 359 |
$norm = implode(', ', $list); |
| 360 |
if ( ! isset($remote_map[$slug]) || $remote_map[$slug] !== $norm ) { |
| 361 |
$remote_map[$slug] = $norm; |
| 362 |
$updated++; |
| 363 |
} |
| 364 |
} |
| 365 |
|
| 366 |
if ( $updated > 0 ) { |
| 367 |
update_option(self::OPTION_REMOTE_MAP, $remote_map, false); |
| 368 |
if ( is_multisite() ) delete_site_transient(self::TRANSIENT_KEY); |
| 369 |
else delete_transient(self::TRANSIENT_KEY); |
| 370 |
$this->get_scan_results(true); |
| 371 |
} |
| 372 |
|
| 373 |
return wp_send_json_success(['from_remote'=>true,'updated'=>$updated,'scan_pending'=>false,'portal_response'=>$json]); |
| 374 |
} |
| 375 |
|
| 376 |
$this->get_scan_results(true); |
| 377 |
return wp_send_json_success(['from_remote'=>false,'updated'=>0,'scan_pending'=>true,'portal_response'=>$json]); |
| 378 |
} |
| 379 |
|
| 380 |
$this->get_scan_results(true); |
| 381 |
return wp_send_json_error(['code'=>$code,'response'=>$json,'body'=>$body], 502); |
| 382 |
} |
| 383 |
|
| 384 |
/* AJAX: Request Scan (explicit) */ |
| 385 |
public function ajax_request_scan() { |
| 386 |
if ( ! current_user_can(self::CAP_SINGLE) ) wp_send_json_error('forbidden', 403); |
| 387 |
check_ajax_referer('pcc_settings_nonce', 'nonce'); |
| 388 |
|
| 389 |
$license = trim(get_option(self::OPTION_LICENSE_KEY, '')); |
| 390 |
$license_valid = (bool) get_option(self::OPTION_LICENSE_VALID, false); |
| 391 |
if ( empty($license) ) return wp_send_json_error(['error'=>'no_license_configured','message'=>'License not configured']); |
| 392 |
|
| 393 |
if ( ! $license_valid ) { |
| 394 |
$v = $this->server_validate_license($license, get_site_url()); |
| 395 |
if ( is_wp_error($v) ) { |
| 396 |
update_option(self::OPTION_LICENSE_VALID, false); |
| 397 |
return wp_send_json_error(['message'=>'license_invalid_or_network','detail'=>$v->get_error_message()], 401); |
| 398 |
} else { |
| 399 |
update_option(self::OPTION_LICENSE_VALID, true); |
| 400 |
$license_valid = true; |
| 401 |
} |
| 402 |
} |
| 403 |
|
| 404 |
$endpoint = rtrim(self::PORTAL_API, '/') . '/receive_client.php'; |
| 405 |
$site_host = $this->normalize_site_host(get_site_url()); |
| 406 |
|
| 407 |
// Build DB info cleanly before constructing payload |
| 408 |
$db_full = $this->get_db_version(); |
| 409 |
$db_parsed = $this->parse_db_version($db_full); |
| 410 |
|
| 411 |
$payload = [ |
| 412 |
'site_url' => $site_host, |
| 413 |
'wp_version' => get_bloginfo('version'), |
| 414 |
'php_version'=> phpversion(), |
| 415 |
'server_info'=> [ |
| 416 |
'os' => PHP_OS, |
| 417 |
'sapi' => php_sapi_name(), |
| 418 |
'db_version' => $db_parsed['clean'], |
| 419 |
'db_type' => $db_parsed['type'], |
| 420 |
'db_version_full' => $db_parsed['full'], |
| 421 |
], |
| 422 |
'components' => $this->build_components_payload(), |
| 423 |
]; |
| 424 |
|
| 425 |
$args = [ |
| 426 |
'headers' => [ |
| 427 |
'Content-Type' => 'application/json', |
| 428 |
'X-Portal-License' => $license, |
| 429 |
], |
| 430 |
'body' => wp_json_encode($payload), |
| 431 |
'timeout' => 20, |
| 432 |
]; |
| 433 |
|
| 434 |
$res = wp_remote_post($endpoint, $args); |
| 435 |
if ( is_wp_error($res) ) { |
| 436 |
return wp_send_json_error(['error'=>'request_failed','message'=>$res->get_error_message()]); |
| 437 |
} |
| 438 |
|
| 439 |
$code = wp_remote_retrieve_response_code($res); |
| 440 |
$body = wp_remote_retrieve_body($res); |
| 441 |
$json = json_decode($body, true); |
| 442 |
|
| 443 |
if ( $code >= 200 && $code < 300 ) { |
| 444 |
if ( is_array($json) && isset($json['status']) && $json['status'] === 'ok' && ! empty($json['scan_results']) ) { |
| 445 |
$remote_map = get_option(self::OPTION_REMOTE_MAP, []); |
| 446 |
if ( ! is_array($remote_map) ) $remote_map = []; |
| 447 |
|
| 448 |
$acc = []; |
| 449 |
foreach ( $json['scan_results'] as $sr ) { |
| 450 |
$standards = $sr['standards'] ?? null; |
| 451 |
if ( empty($standards) || stripos($standards,'PHPCompatibilityWP') === false ) continue; |
| 452 |
if ( empty($sr['slug']) ) continue; |
| 453 |
|
| 454 |
$is_compat = false; |
| 455 |
if ( isset($sr['is_compatible']) ) { |
| 456 |
$val = $sr['is_compatible']; |
| 457 |
if ( is_bool($val) ) $is_compat = $val; |
| 458 |
elseif ( is_numeric($val) ) $is_compat = ((int)$val === 1); |
| 459 |
elseif ( is_string($val) ) $is_compat = in_array(strtolower($val), ['1','true','yes'], true); |
| 460 |
} |
| 461 |
if ( ! $is_compat ) continue; |
| 462 |
|
| 463 |
$pv = $sr['php_version'] ?? ''; |
| 464 |
if ( ! $pv ) continue; |
| 465 |
$parts = preg_split('/\s*,\s*/', trim($pv)); |
| 466 |
if ( ! isset($acc[$sr['slug']]) ) $acc[$sr['slug']] = []; |
| 467 |
foreach ($parts as $p) { if ($p !== '') $acc[$sr['slug']][] = $p; } |
| 468 |
} |
| 469 |
|
| 470 |
$updated = 0; |
| 471 |
foreach ($acc as $slug => $list) { |
| 472 |
$list = array_values(array_unique($list)); |
| 473 |
usort($list, 'version_compare'); |
| 474 |
$norm = implode(', ', $list); |
| 475 |
if ( ! isset($remote_map[$slug]) || $remote_map[$slug] !== $norm ) { |
| 476 |
$remote_map[$slug] = $norm; |
| 477 |
$updated++; |
| 478 |
} |
| 479 |
} |
| 480 |
|
| 481 |
if ( $updated > 0 ) { |
| 482 |
update_option(self::OPTION_REMOTE_MAP, $remote_map, false); |
| 483 |
if ( is_multisite() ) delete_site_transient(self::TRANSIENT_KEY); |
| 484 |
else delete_transient(self::TRANSIENT_KEY); |
| 485 |
$this->get_scan_results(true); |
| 486 |
} |
| 487 |
|
| 488 |
return wp_send_json_success(['response'=>$json,'message'=>'accepted_and_applied','applied_updated'=>$updated]); |
| 489 |
} |
| 490 |
|
| 491 |
return wp_send_json_success(['response'=>$json,'message'=>'accepted']); |
| 492 |
} else { |
| 493 |
return wp_send_json_error(['code'=>$code,'response'=>$json,'body'=>$body]); |
| 494 |
} |
| 495 |
} |
| 496 |
|
| 497 |
/* AJAX: Fetch Remote */ |
| 498 |
public function ajax_fetch_remote() { |
| 499 |
if ( ! current_user_can(self::CAP_SINGLE) ) wp_send_json_error('forbidden', 403); |
| 500 |
check_ajax_referer('pcc_settings_nonce', 'nonce'); |
| 501 |
|
| 502 |
$result = $this->fetch_and_apply_remote_results(); |
| 503 |
if ( is_wp_error($result) ) { |
| 504 |
$msg = $result->get_error_message(); |
| 505 |
return wp_send_json_error(['error'=>'fetch_failed','message'=>$msg], 502); |
| 506 |
} |
| 507 |
|
| 508 |
$updated = isset($result['updated']) ? (int)$result['updated'] : 0; |
| 509 |
$scan_pending = ! empty($result['scan_pending']); |
| 510 |
|
| 511 |
if ( $updated > 0 ) { |
| 512 |
$this->get_scan_results(true); |
| 513 |
} |
| 514 |
|
| 515 |
return wp_send_json_success(['ok'=>true, 'updated'=>$updated, 'scan_pending'=>$scan_pending]); |
| 516 |
} |
| 517 |
|
| 518 |
/* AJAX: Validate license */ |
| 519 |
public function ajax_validate_license() { |
| 520 |
if ( ! current_user_can(self::CAP_SINGLE) ) wp_send_json_error('forbidden', 403); |
| 521 |
check_ajax_referer('pcc_settings_nonce', 'nonce'); |
| 522 |
|
| 523 |
$license = isset($_POST['license']) ? trim(sanitize_text_field($_POST['license'])) : get_option(self::OPTION_LICENSE_KEY, ''); |
| 524 |
$site_url = isset($_POST['site_url']) ? trim(sanitize_text_field($_POST['site_url'])) : get_site_url(); |
| 525 |
|
| 526 |
if ( empty($license) ) { |
| 527 |
update_option(self::OPTION_LICENSE_VALID, false); |
| 528 |
return wp_send_json_error(['message'=>'missing_license'], 400); |
| 529 |
} |
| 530 |
|
| 531 |
$v = $this->server_validate_license($license, $site_url); |
| 532 |
if ( is_wp_error($v) ) { |
| 533 |
update_option(self::OPTION_LICENSE_VALID, false); |
| 534 |
$err_data = $v->get_error_data(); |
| 535 |
return wp_send_json_error(['message'=>'validate_failed','detail'=>$v->get_error_message(),'detail_data'=>$err_data], 502); |
| 536 |
} |
| 537 |
|
| 538 |
update_option(self::OPTION_LICENSE_KEY, $license); |
| 539 |
update_option(self::OPTION_LICENSE_VALID, true); |
| 540 |
return wp_send_json_success(['message'=>'license_valid','response'=>$v]); |
| 541 |
} |
| 542 |
|
| 543 |
/* Cron scheduling */ |
| 544 |
public function maybe_schedule_cron() { |
| 545 |
add_filter('cron_schedules', function($s){ |
| 546 |
if (!isset($s['ten_minutes'])) $s['ten_minutes'] = ['interval'=>600, 'display'=>'Every Ten Minutes']; |
| 547 |
return $s; |
| 548 |
}); |
| 549 |
|
| 550 |
if ( ! wp_next_scheduled(self::CRON_HOOK) ) { |
| 551 |
wp_schedule_event(time()+60, 'ten_minutes', self::CRON_HOOK); |
| 552 |
} |
| 553 |
} |
| 554 |
|
| 555 |
public function cron_fetch_remote() { |
| 556 |
$this->fetch_and_apply_remote_results(true); |
| 557 |
} |
| 558 |
|
| 559 |
/* Fetch & apply remote mapping */ |
| 560 |
protected function fetch_and_apply_remote_results($is_cron=false) { |
| 561 |
$license = trim(get_option(self::OPTION_LICENSE_KEY, '')); |
| 562 |
$license_valid = (bool) get_option(self::OPTION_LICENSE_VALID, false); |
| 563 |
|
| 564 |
if ( empty(self::PORTAL_API) || empty($license) || ! $license_valid ) { |
| 565 |
return new WP_Error('missing_config', 'Portal or license not configured or not validated'); |
| 566 |
} |
| 567 |
|
| 568 |
$fetch_url = rtrim(self::PORTAL_API, '/') . '/fetch_data.php'; |
| 569 |
$payload = [ 'site_url' => $this->normalize_site_host(get_site_url()) ]; |
| 570 |
|
| 571 |
$args = [ |
| 572 |
'headers' => [ |
| 573 |
'Content-Type' => 'application/json', |
| 574 |
'X-Portal-License' => $license, |
| 575 |
], |
| 576 |
'body' => wp_json_encode($payload), |
| 577 |
'timeout' => 20, |
| 578 |
]; |
| 579 |
|
| 580 |
$res = wp_remote_post($fetch_url, $args); |
| 581 |
if ( is_wp_error($res) ) return $res; |
| 582 |
|
| 583 |
$code = wp_remote_retrieve_response_code($res); |
| 584 |
$body = wp_remote_retrieve_body($res); |
| 585 |
$json = json_decode($body, true); |
| 586 |
|
| 587 |
if ( ! is_array($json) || ! isset($json['status']) || $json['status'] !== 'ok' ) { |
| 588 |
return [ 'updated' => 0, 'scan_pending' => true ]; |
| 589 |
} |
| 590 |
|
| 591 |
$remote_map = get_option(self::OPTION_REMOTE_MAP, []); |
| 592 |
if ( ! is_array($remote_map) ) $remote_map = []; |
| 593 |
|
| 594 |
$updated = 0; |
| 595 |
$new_map = $remote_map; |
| 596 |
|
| 597 |
if ( isset($json['scan_results']) && is_array($json['scan_results']) && count($json['scan_results']) > 0 ) { |
| 598 |
$acc = []; |
| 599 |
|
| 600 |
foreach ( $json['scan_results'] as $sr ) { |
| 601 |
$standards = $sr['standards'] ?? null; |
| 602 |
if ( empty($standards) || stripos($standards,'PHPCompatibilityWP') === false ) continue; |
| 603 |
if ( empty($sr['slug']) ) continue; |
| 604 |
$slug = $sr['slug']; |
| 605 |
|
| 606 |
$is_compat = false; |
| 607 |
if ( isset($sr['is_compatible']) ) { |
| 608 |
$val = $sr['is_compatible']; |
| 609 |
if ( is_bool($val) ) $is_compat = $val; |
| 610 |
elseif ( is_numeric($val) ) $is_compat = ((int)$val === 1); |
| 611 |
elseif ( is_string($val) ) $is_compat = in_array(strtolower($val), ['1','true','yes'], true); |
| 612 |
} |
| 613 |
if ( ! $is_compat ) continue; |
| 614 |
|
| 615 |
$pv = $sr['php_version'] ?? ''; |
| 616 |
if ( ! $pv ) continue; |
| 617 |
|
| 618 |
$parts = preg_split('/\s*,\s*/', trim($pv)); |
| 619 |
if ( ! isset($acc[$slug]) ) $acc[$slug] = []; |
| 620 |
foreach ($parts as $p) { |
| 621 |
if ($p === '') continue; |
| 622 |
$acc[$slug][] = $p; |
| 623 |
} |
| 624 |
} |
| 625 |
|
| 626 |
foreach ($acc as $slug => $ver_list) { |
| 627 |
$ver_list = array_values(array_unique($ver_list)); |
| 628 |
usort($ver_list, 'version_compare'); |
| 629 |
$norm = implode(', ', $ver_list); |
| 630 |
|
| 631 |
if ( ! isset($new_map[$slug]) || $new_map[$slug] !== $norm ) { |
| 632 |
$new_map[$slug] = $norm; |
| 633 |
$updated++; |
| 634 |
} |
| 635 |
} |
| 636 |
} else { |
| 637 |
return [ 'updated' => 0, 'scan_pending' => true ]; |
| 638 |
} |
| 639 |
|
| 640 |
if ( $updated > 0 ) { |
| 641 |
update_option(self::OPTION_REMOTE_MAP, $new_map, false); |
| 642 |
if ( is_multisite() ) delete_site_transient(self::TRANSIENT_KEY); |
| 643 |
else delete_transient(self::TRANSIENT_KEY); |
| 644 |
|
| 645 |
if ( $is_cron ) { |
| 646 |
$admin_email = get_option('admin_email'); |
| 647 |
$subject = 'CompatShield: New remote scan results fetched'; |
| 648 |
$message = sprintf("Fetched %d new PHP compatibility entries from the portal for site %s\n\nVisit %s to view results.", $updated, get_site_url(), admin_url('admin.php?page=' . self::MENU_SLUG)); |
| 649 |
@wp_mail($admin_email, $subject, $message); |
| 650 |
} |
| 651 |
} |
| 652 |
|
| 653 |
return [ 'updated' => $updated, 'scan_pending' => false ]; |
| 654 |
} |
| 655 |
|
| 656 |
/* UI renderers */ |
| 657 |
private function stat_card($icon_url, $title, $value_html) { |
| 658 |
echo '<div class="stat-card" style="display:inline-block;margin:8px;padding:18px;border-radius:8px;box-shadow:0 1px 6px rgba(0,0,0,0.06);width:180px;text-align:center;vertical-align:top;background:#fff;">'; |
| 659 |
if ($icon_url) { |
| 660 |
echo '<div class="stat-icon" style="height:64px;margin-bottom:8px;"><img src="'.esc_url($icon_url).'" alt="'.esc_attr($title).' Icon" style="max-height:64px;max-width:64px;"></div>'; |
| 661 |
} |
| 662 |
echo '<div class="stat-content">'; |
| 663 |
echo '<h3 style="margin:6px 0 4px;font-size:16px;">'.esc_html($title).'</h3>'; |
| 664 |
echo '<p style="margin:0;font-size:18px;font-weight:600;">'.esc_html($value_html).'</p>'; |
| 665 |
echo '</div>'; |
| 666 |
echo '</div>'; |
| 667 |
} |
| 668 |
|
| 669 |
/* Build payload for portal */ |
| 670 |
protected function build_components_payload() { |
| 671 |
$plugins = get_plugins(); |
| 672 |
$out = []; |
| 673 |
|
| 674 |
foreach ( $plugins as $file => $info ) { |
| 675 |
$candidate = WP_PLUGIN_DIR . '/' . $file; |
| 676 |
if ( ! file_exists($candidate) ) continue; |
| 677 |
|
| 678 |
$slug = $this->resolve_plugin_slug($file, $info['TextDomain'] ?? '', $info['PluginURI'] ?? ''); |
| 679 |
$out[] = [ |
| 680 |
'type' => 'plugin', |
| 681 |
'slug' => $slug ?: basename(dirname($file)), |
| 682 |
'name' => $info['Name'] ?? $file, |
| 683 |
'version' => $info['Version'] ?? '', |
| 684 |
'source' => 'wporg', |
| 685 |
]; |
| 686 |
} |
| 687 |
|
| 688 |
$theme = wp_get_theme(); |
| 689 |
$out[] = [ |
| 690 |
'type' => 'theme', |
| 691 |
'slug' => $theme->get_stylesheet(), |
| 692 |
'name' => $theme->get('Name'), |
| 693 |
'version' => $theme->get('Version'), |
| 694 |
'source' => 'wporg', |
| 695 |
]; |
| 696 |
|
| 697 |
return $out; |
| 698 |
} |
| 699 |
|
| 700 |
public function render_network() { |
| 701 |
if ( ! current_user_can(self::CAP_NETWORK) ) { |
| 702 |
wp_die(esc_html__('You do not have permission to access this page.', 'pcc')); |
| 703 |
} |
| 704 |
$this->render_dashboard(true); |
| 705 |
} |
| 706 |
|
| 707 |
public function render_single_site() { |
| 708 |
if ( ! current_user_can(self::CAP_SINGLE) ) { |
| 709 |
wp_die(esc_html__('You do not have permission to access this page.', 'pcc')); |
| 710 |
} |
| 711 |
$this->render_dashboard(false); |
| 712 |
} |
| 713 |
|
| 714 |
private function render_dashboard($is_network = false) { |
| 715 |
$license_valid = (bool) get_option(self::OPTION_LICENSE_VALID, false); |
| 716 |
$buy_link = esc_url(self::PRODUCT_URL); |
| 717 |
$signup_link = esc_url(self::SIGNUP_URL); |
| 718 |
$stats = $this->get_environment_stats($is_network); |
| 719 |
$scan = $this->get_scan_results(false, $is_network); |
| 720 |
$icons = [ |
| 721 |
'wp' => plugins_url('icons/wordpress.png', __FILE__), |
| 722 |
'php' => plugins_url('icons/php.png', __FILE__), |
| 723 |
'db' => plugins_url('icons/database-storage.png', __FILE__), |
| 724 |
'plug' => plugins_url('icons/plug.png', __FILE__), |
| 725 |
'active' => plugins_url('icons/check-mark.png', __FILE__), |
| 726 |
'inactive' => plugins_url('icons/multiplication.png', __FILE__), |
| 727 |
]; |
| 728 |
|
| 729 |
echo '<h1 class="pluginheading">'.esc_html__('Check Your Plugin Compatibility', 'pcc').'</h1>'; |
| 730 |
|
| 731 |
if ( ! $license_valid ) { |
| 732 |
echo '<div class="notice" style="margin:12px 0;padding:14px;border-left:4px solid #135e96;background:#f7fbff;"> |
| 733 |
<strong>Unlock PHP 8.1–8.5 checks — only $1/month (subscription).</strong> |
| 734 |
Subscribe for $1/month to get a CompatShield license key and view newer PHP compatibility in this dashboard. |
| 735 |
<a class="button button-primary" href="'.$signup_link.'" target="_blank" rel="noopener">Subscribe — $1/month </a> |
| 736 |
<a class="button" href="'.$buy_link.'" target="_blank" rel="noopener">See Pro features</a> |
| 737 |
</div>'; |
| 738 |
} |
| 739 |
|
| 740 |
// Stat cards |
| 741 |
echo '<div class="stats-grid">'; |
| 742 |
$this->stat_card($icons['wp'], 'WordPress', $stats['wp_version']); |
| 743 |
$this->stat_card($icons['php'], 'PHP', $stats['php_version']); |
| 744 |
$this->stat_card($icons['db'], $stats['db_type'], $stats['db_version']); |
| 745 |
$this->stat_card($icons['plug'], 'Plugins Installed', (string) $stats['plugins_total']); |
| 746 |
$this->stat_card($icons['active'], $is_network ? 'Plugins Active (Network)' : 'Plugins Active', (string) $stats['plugins_active']); |
| 747 |
$this->stat_card($icons['inactive'], $is_network ? 'Plugins Inactive (Network)' : 'Plugins Inactive', (string) $stats['plugins_inactive']); |
| 748 |
echo '</div>'; |
| 749 |
|
| 750 |
// WordPress version notice |
| 751 |
if ( version_compare($stats['wp_version'], $stats['wp_latest'], '>=') ) { |
| 752 |
echo '<br><b>'.esc_html__('You are already on the latest WordPress version.', 'pcc').'</b><br><br>'; |
| 753 |
} else { |
| 754 |
echo '<br><b>'.sprintf(esc_html__('The latest stable WordPress version available is: %s', 'pcc'), esc_html($stats['wp_latest'])).'</b><br><br>'; |
| 755 |
} |
| 756 |
|
| 757 |
// ── Database version warning banner ────────────────────────────────────── |
| 758 |
$clean_db = $stats['db_version']; // e.g. "8.0.32" or "10.6.14" |
| 759 |
$db_label = $stats['db_type']; // "MySQL" or "MariaDB" |
| 760 |
$min_required = ( $db_label === 'MariaDB' ) ? '10.6' : '8.0'; |
| 761 |
$db_too_old = ( $clean_db !== 'unknown' ) && version_compare($clean_db, $min_required, '<'); |
| 762 |
|
| 763 |
if ( $db_too_old ) { |
| 764 |
echo '<div style="margin:0 0 16px 0;padding:14px 18px;border-left:4px solid #b71c1c;background:#fff5f5;border-radius:4px;display:flex;align-items:flex-start;gap:12px;"> |
| 765 |
<span style="font-size:22px;line-height:1.3;">⚠️</span> |
| 766 |
<div> |
| 767 |
<strong style="color:#b71c1c;font-size:14px;">Database Compatibility Warning — WordPress 7.0</strong> |
| 768 |
<p style="margin:6px 0 0;color:#333;font-size:13px;line-height:1.6;"> |
| 769 |
Your server is running <strong>' . esc_html($db_label . ' ' . $clean_db) . '</strong>, |
| 770 |
but WordPress 7.0 requires a minimum of <strong>' . esc_html($db_label . ' ' . $min_required) . '</strong>. |
| 771 |
Sites on older database versions will <strong>not be offered the WordPress 7.0 update</strong> |
| 772 |
and will remain on 6.9 until the database is upgraded. |
| 773 |
Contact your hosting provider to upgrade your database before updating WordPress. |
| 774 |
</p> |
| 775 |
</div> |
| 776 |
</div>'; |
| 777 |
} else { |
| 778 |
echo '<div style="margin:0 0 16px 0;padding:14px 18px;border-left:4px solid #155724;background:#f0fff4;border-radius:4px;display:flex;align-items:flex-start;gap:12px;"> |
| 779 |
<span style="font-size:22px;line-height:1.3;">� |
| 780 |
</span> |
| 781 |
<div> |
| 782 |
<strong style="color:#155724;font-size:14px;">Database Compatible with WordPress 7.0</strong> |
| 783 |
<p style="margin:6px 0 0;color:#333;font-size:13px;line-height:1.6;"> |
| 784 |
Your server is running <strong>' . esc_html($db_label . ' ' . $clean_db) . '</strong>, |
| 785 |
which meets the WordPress 7.0 minimum database requirement |
| 786 |
of <strong>' . esc_html($db_label . ' ' . $min_required) . '</strong>. |
| 787 |
</p> |
| 788 |
</div> |
| 789 |
</div>'; |
| 790 |
} |
| 791 |
// ── End database banner ────────────────────────────────────────────────── |
| 792 |
|
| 793 |
$settings_url = admin_url('admin.php?page=' . self::SUBMENU_SETTINGS_SLUG); |
| 794 |
$license = trim(get_option(self::OPTION_LICENSE_KEY, '')); |
| 795 |
$license_valid = (bool) get_option(self::OPTION_LICENSE_VALID, false); |
| 796 |
$remote_map = get_option(self::OPTION_REMOTE_MAP, []); |
| 797 |
if ( ! is_array($remote_map) ) $remote_map = []; |
| 798 |
|
| 799 |
echo ' |
| 800 |
<div class="tnip" style="display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;"> |
| 801 |
<div style="flex:1;min-width:260px;"> |
| 802 |
<b class="filter">'.esc_html__('Filter By Plugin Status', 'pcc').'</b> |
| 803 |
<select class="form-control fltr" data-role="select-dropdown" id="plgstatus" style="display:inline-block;max-width:220px;margin-left:8px;"> |
| 804 |
<option value="all">'.esc_html__('Plugin Status', 'pcc').'</option> |
| 805 |
<option value="Activated">'.esc_html__('Activated', 'pcc').'</option> |
| 806 |
<option value="Deactivated">'.esc_html__('Deactivated', 'pcc').'</option> |
| 807 |
</select> |
| 808 |
</div> |
| 809 |
<div style="flex:1;text-align:right;min-width:320px;"> |
| 810 |
<a href="' . esc_url($settings_url) . '" class="button button-secondary" style="margin-right:8px;">' . esc_html__('Settings', 'pcc') . '</a> |
| 811 |
<button id="exportButton" class="button button-secondary" style="margin-right:8px;">'.esc_html__('Export to CSV', 'pcc').'</button> |
| 812 |
<button id="pcc-fetch-latest" class="button button-secondary" style="margin-right:8px;">Fetch latest result</button> |
| 813 |
<button id="pcc-rescan" class="button button-primary" style="margin-right:8px;">'.esc_html__('Rescan', 'pcc').'</button> |
| 814 |
<div id="pcc-mode-notice" style="display:inline-block;vertical-align:middle;margin-left:12px;font-weight:600;"></div> |
| 815 |
</div> |
| 816 |
</div>'; |
| 817 |
|
| 818 |
echo '<hr />'; |
| 819 |
echo '<h2>'.esc_html__('Local Plugin Compatibility Summary', 'pcc').'</h2>'; |
| 820 |
|
| 821 |
if ( $license_valid ) { |
| 822 |
echo '<p style="color:#155724;font-weight:500;"> |
| 823 |
The results you are viewing are fetched directly from the CompatShield Portal because your license is active. |
| 824 |
These include advanced PHP compatibility checks (PHP 8.1–8.5). |
| 825 |
</p>'; |
| 826 |
} else { |
| 827 |
echo '<p>'.esc_html__( |
| 828 |
'This table uses WPTide data until you validate a CompatShield license. Subscribe to the $1/month entry plan (recurring) to unlock Portal results (PHP 8.1–8.5). Use "Rescan" to request a fresh Portal scan (when licensed) or refresh WPTide (when not). Use "Fetch latest result" to pull Portal output when available.', |
| 829 |
'pcc' |
| 830 |
).'</p>'; |
| 831 |
} |
| 832 |
|
| 833 |
$this->render_dashboard_table_only(); |
| 834 |
|
| 835 |
$notice_text = $license_valid |
| 836 |
? 'Portal mode — license validated. Showing CompatShield results (includes PHP 8.1–8.5).' |
| 837 |
: 'WPTide mode — showing community results (limited to PHP ≤ 8.0). <a href="'.$signup_link.'" target="_blank" rel="noopener">Subscribe — $1/month </a> to enable CompatShield results (PHP 8.1–8.5).'; |
| 838 |
|
| 839 |
$pcc_settings_nonce = wp_create_nonce('pcc_settings_nonce'); |
| 840 |
?> |
| 841 |
<script> |
| 842 |
(function($){ |
| 843 |
$(document).ready(function(){ |
| 844 |
var noticeHtml = <?php echo json_encode($notice_text); ?>; |
| 845 |
$('#pcc-mode-notice').html(noticeHtml).css('color','<?php echo $license_valid ? '#155724' : '#b71c1c'; ?>'); |
| 846 |
|
| 847 |
$('#pcc-rescan').on('click', function(){ |
| 848 |
var btn = $(this); |
| 849 |
btn.prop('disabled',true).text('Requesting scan...'); |
| 850 |
$.post(ajaxurl, { |
| 851 |
action: '<?php echo self::AJAX_ACTION; ?>', |
| 852 |
nonce: '<?php echo wp_create_nonce(self::NONCE_ACTION); ?>' |
| 853 |
}).done(function(resp){ |
| 854 |
if (resp.success) { |
| 855 |
var data = resp.data || {}; |
| 856 |
if (data.scan_pending) { |
| 857 |
$('#pcc-mode-notice').html('Portal mode — scan in progress; results will appear when ready.').css('color','#856404'); |
| 858 |
alert('Scan requested on Portal. Results will be available when the Portal completes scanning.'); |
| 859 |
} else { |
| 860 |
alert('Local scan refreshed (fallback) or scan request failed to queue; check messages.'); |
| 861 |
} |
| 862 |
setTimeout(function(){ location.reload(); }, 700); |
| 863 |
} else { |
| 864 |
alert('Rescan failed: ' + JSON.stringify(resp.data)); |
| 865 |
} |
| 866 |
}).fail(function(){ alert('Rescan failed (network)'); }) |
| 867 |
.always(function(){ btn.prop('disabled',false).text('<?php echo esc_js(__('Rescan', 'pcc')); ?>'); }); |
| 868 |
}); |
| 869 |
|
| 870 |
<?php if ( $license_valid && empty($remote_map) ) : ?> |
| 871 |
(function(){ |
| 872 |
$.post(ajaxurl, { |
| 873 |
action: 'pcc_fetch_remote', |
| 874 |
nonce: '<?php echo esc_js($pcc_settings_nonce); ?>' |
| 875 |
}).done(function(resp){ |
| 876 |
if ( resp && resp.success && resp.data && resp.data.updated > 0 ) { |
| 877 |
setTimeout(function(){ location.reload(); }, 700); |
| 878 |
} |
| 879 |
}).fail(function(){ /* ignore silently */ }); |
| 880 |
})(); |
| 881 |
<?php endif; ?> |
| 882 |
}); |
| 883 |
})(jQuery); |
| 884 |
</script> |
| 885 |
<?php |
| 886 |
} |
| 887 |
|
| 888 |
public function render_sysinfo() { |
| 889 |
$cap = is_multisite() ? self::CAP_SINGLE : self::CAP_SINGLE; |
| 890 |
if ( ! current_user_can($cap) ) { |
| 891 |
wp_die(esc_html__('You do not have permission to access this page.', 'pcc')); |
| 892 |
} |
| 893 |
echo '<h1>'.esc_html__('System Info', 'pcc').'</h1><br>'; |
| 894 |
$php = phpversion(); |
| 895 |
echo '<b style="font-size:18px;">'.sprintf(esc_html__('Your Current PHP Version is: %s', 'pcc'), esc_html($php)).'</b><br>'; |
| 896 |
|
| 897 |
$space_total_gb = (int) ( @disk_total_space(ABSPATH) / 1024 / 1024 / 1024 ); |
| 898 |
$space_free_gb = (int) ( @disk_free_space(ABSPATH) / 1024 / 1024 / 1024 ); |
| 899 |
$space_used_gb = max(0, $space_total_gb - $space_free_gb); |
| 900 |
|
| 901 |
echo '<table class="sysinfo"><tr><td><b>'.esc_html__('Disk Total Space', 'pcc').'</b></td><td><b>'.intval($space_total_gb).' GB</b></td></tr>'; |
| 902 |
echo '<tr><td><b>'.esc_html__('Disk Space Used', 'pcc').'</b></td><td><b>'.intval($space_used_gb).' GB</b></td></tr>'; |
| 903 |
echo '<tr><td><b>'.esc_html__('Disk Space Free', 'pcc').'</b></td><td><b>'.intval($space_free_gb).' GB</b></td></tr></table>'; |
| 904 |
|
| 905 |
$ini = [ |
| 906 |
'Max Execution Time' => ini_get('max_execution_time'), |
| 907 |
'Max File Uploads' => ini_get('max_file_uploads'), |
| 908 |
'Max Input Vars' => ini_get('max_input_vars'), |
| 909 |
'Post Max Size' => ini_get('post_max_size'), |
| 910 |
'Memory Limit' => ini_get('memory_limit'), |
| 911 |
'Upload Max Filesize' => ini_get('upload_max_filesize'), |
| 912 |
]; |
| 913 |
|
| 914 |
echo '<table class="sysinfo">'; |
| 915 |
foreach ($ini as $k => $v) { |
| 916 |
echo '<tr><td><b>'.esc_html($k).'</b></td><td><b>'.esc_html($v).'</b></td></tr>'; |
| 917 |
} |
| 918 |
echo '</table>'; |
| 919 |
|
| 920 |
$exts = get_loaded_extensions(); |
| 921 |
sort($exts, SORT_NATURAL | SORT_FLAG_CASE); |
| 922 |
echo '<center><p class="pheading">'.esc_html__('List of Loaded Extensions', 'pcc').'</p><br>'; |
| 923 |
echo '<table class="extntable">'; |
| 924 |
foreach ( $exts as $ext ) { |
| 925 |
echo '<tr><td class="tdstyle">'.esc_html($ext).'</td></tr>'; |
| 926 |
} |
| 927 |
echo '</table></center>'; |
| 928 |
} |
| 929 |
|
| 930 |
public function render_settings() { |
| 931 |
if ( ! current_user_can(self::CAP_SINGLE) ) { |
| 932 |
wp_die(esc_html__('You do not have permission to access this page.', 'pcc')); |
| 933 |
} |
| 934 |
|
| 935 |
if ( isset($_POST['pcc_save_settings']) && check_admin_referer('pcc_settings_save') ) { |
| 936 |
$license = isset($_POST['pcc_license_key']) ? sanitize_text_field(trim($_POST['pcc_license_key'])) : ''; |
| 937 |
update_option(self::OPTION_LICENSE_KEY, $license); |
| 938 |
update_option(self::OPTION_LICENSE_VALID, false); |
| 939 |
echo '<div class="updated"><p>Settings saved. Please validate the license using the "Validate License" button.</p></div>'; |
| 940 |
} |
| 941 |
|
| 942 |
$license = esc_attr(get_option(self::OPTION_LICENSE_KEY, '')); |
| 943 |
$license_valid = (bool) get_option(self::OPTION_LICENSE_VALID, false); |
| 944 |
$buy_link = esc_url(self::PRODUCT_URL); |
| 945 |
$signup_link = esc_url(self::SIGNUP_URL); |
| 946 |
?> |
| 947 |
<div class="wrap"> |
| 948 |
<h1><?php esc_html_e('Plugin Compatibility Checker', 'pcc'); ?></h1> |
| 949 |
|
| 950 |
<form method="post"> |
| 951 |
<?php wp_nonce_field('pcc_settings_save'); ?> |
| 952 |
<table class="form-table"> |
| 953 |
<tr> |
| 954 |
<th scope="row"><label for="pcc_license_key">Portal License Key</label></th> |
| 955 |
<td> |
| 956 |
<input type="text" id="pcc_license_key" name="pcc_license_key" value="<?php echo $license; ?>" class="regular-text" /> |
| 957 |
<p class="description"> |
| 958 |
Enter your CompatShield license key to enable Portal mode. |
| 959 |
Without a validated license, the plugin uses WPTide data limited to PHP ≤ 8.0. |
| 960 |
With a validated license (entry plan $1/month or Pro), CompatShield shows newer PHP compatibility (8.1–8.5) right in your dashboard. |
| 961 |
Don't have a key? <a href="<?php echo $signup_link; ?>" target="_blank" rel="noopener">Subscribe for $1/month</a>. |
| 962 |
</p> |
| 963 |
</td> |
| 964 |
</tr> |
| 965 |
</table> |
| 966 |
<p class="submit"><input type="submit" name="pcc_save_settings" id="submit" class="button button-primary" value="Save Settings"></p> |
| 967 |
</form> |
| 968 |
|
| 969 |
<p> |
| 970 |
<button id="pcc-request-scan" class="button button-secondary">Request Scan on Portal</button> |
| 971 |
|
| 972 |
<button id="pcc-fetch-latest" class="button">Fetch latest result</button> |
| 973 |
|
| 974 |
<button id="pcc-validate-license" class="button">Validate License</button> |
| 975 |
<span id="pcc-settings-msg" style="margin-left:12px;"></span> |
| 976 |
<span id="pcc-license-mode" style="margin-left:12px;font-weight:600;"></span> |
| 977 |
</p> |
| 978 |
|
| 979 |
<p style="margin-top:8px;"> |
| 980 |
<?php if (! $license_valid && $license) : ?> |
| 981 |
<em style="color:#856404;">Saved license not validated yet. Click <strong>Validate License</strong> to enable Portal mode.</em> |
| 982 |
<?php elseif (! $license) : ?> |
| 983 |
<em style="color:#b71c1c;">No license configured — using WPTide results (PHP ≤ 8.0). <a href="<?php echo $signup_link; ?>" target="_blank" rel="noopener">Subscribe — $1/month</a> or <a href="<?php echo $buy_link; ?>" target="_blank" rel="noopener">upgrade to Pro</a>.</em> |
| 984 |
<?php else: ?> |
| 985 |
<em style="color:#155724;">License validated — Portal mode enabled (PHP 8.1–8.5 shown).</em> |
| 986 |
<?php endif; ?> |
| 987 |
</p> |
| 988 |
|
| 989 |
<div style="margin-top:12px;padding:14px;border:1px solid #e5e5e5;border-radius:6px;background:#fafafa;"> |
| 990 |
<strong>No license yet?</strong> |
| 991 |
<a class="button button-primary" href="<?php echo $signup_link; ?>" target="_blank" rel="noopener">Subscribe — $1/month</a> |
| 992 |
or |
| 993 |
<a class="button" href="<?php echo $buy_link; ?>" target="_blank" rel="noopener">View Pro</a> |
| 994 |
<div style="margin-top:6px;color:#555;">The $1 entry plan unlocks PHP 8.1–8.5 compatibility visibility via the Portal. Pro adds deeper summaries and recommendations.</div> |
| 995 |
</div> |
| 996 |
|
| 997 |
<hr /> |
| 998 |
|
| 999 |
<p><em><?php esc_html_e('Use the main "Plugin Compatibility Checker" menu item to view the local compatibility table and dashboard. Settings page only manages portal/license and remote actions.', 'pcc'); ?></em></p> |
| 1000 |
<p><em><?php esc_html_e('Use the User-Guide if you face any issue after license activation or you can raise a ticket or mail at support@compatshield.com.', 'pcc'); ?></em></p> |
| 1001 |
<p><a href="https://www.compatshield.com/user-guide/" target="_blank"><strong><?php esc_html_e('Open Full User Guide', 'pcc'); ?></strong></a></p> |
| 1002 |
|
| 1003 |
<div style="margin-top:15px;margin-bottom:25px;"> |
| 1004 |
<iframe width="100%" height="350" src="https://www.youtube.com/embed/PCxhJmO-Tb4" frameborder="0" allowfullscreen></iframe> |
| 1005 |
</div> |
| 1006 |
|
| 1007 |
<p><strong><?php esc_html_e('Quick Steps:', 'pcc'); ?></strong></p> |
| 1008 |
<ol style="margin-left:20px;"> |
| 1009 |
<li><?php esc_html_e('Login to Portal Dashboard → Add your domain inside License tab', 'pcc'); ?></li> |
| 1010 |
<li><?php esc_html_e('Copy your License Key from Portal', 'pcc'); ?></li> |
| 1011 |
<li><?php esc_html_e('Paste License Key inside Plugin settings here', 'pcc'); ?></li> |
| 1012 |
<li><?php esc_html_e('Click Validate License', 'pcc'); ?></li> |
| 1013 |
<li><?php esc_html_e('Click Save Settings', 'pcc'); ?></li> |
| 1014 |
<li><?php esc_html_e('Click Rescan on main scan page to fetch plugins', 'pcc'); ?></li> |
| 1015 |
</ol> |
| 1016 |
</div> |
| 1017 |
|
| 1018 |
<script> |
| 1019 |
(function($){ |
| 1020 |
$(document).ready(function(){ |
| 1021 |
if (typeof PCCSettings !== 'undefined' && typeof PCCSettings.licenseActive !== 'undefined') { |
| 1022 |
var active = PCCSettings.licenseActive; |
| 1023 |
$('#pcc-license-mode').text(active ? 'Portal mode — license validated' : 'Fallback mode — no validated license').css('color', active ? '#155724' : '#b71c1c'); |
| 1024 |
} else { |
| 1025 |
var srvActive = <?php echo json_encode(($license !== '' && $license_valid)); ?>; |
| 1026 |
$('#pcc-license-mode').text(srvActive ? 'Portal mode — license validated' : 'Fallback mode — no validated license').css('color', srvActive ? '#155724' : '#b71c1c'); |
| 1027 |
} |
| 1028 |
}); |
| 1029 |
})(jQuery); |
| 1030 |
</script> |
| 1031 |
<?php |
| 1032 |
} |
| 1033 |
|
| 1034 |
private function render_dashboard_table_only() { |
| 1035 |
$is_network = is_multisite(); |
| 1036 |
$scan = $this->get_scan_results(false, $is_network); |
| 1037 |
|
| 1038 |
echo '<div class="table-responsive table-hover"><table class="table table-bordered" id="pcctable"> |
| 1039 |
<thead class="thead-dark"> |
| 1040 |
<tr> |
| 1041 |
<th scope="col">'.esc_html__('Plugin Name', 'pcc').'</th> |
| 1042 |
<th scope="col">'.esc_html__('Current Plugin Version', 'pcc').'</th> |
| 1043 |
<th scope="col">'.esc_html__('Latest Plugin Version', 'pcc').'</th> |
| 1044 |
<th scope="col">'.esc_html__('Compatible With WordPress Version', 'pcc').'</th> |
| 1045 |
<th scope="col">'.esc_html__('PHP Supported (WPTide: ≤8.0 • Portal: 8.1–8.5)', 'pcc').'</th> |
| 1046 |
<th scope="col">'.esc_html($is_network ? 'Plugin Network Status' : 'Plugin Status').'</th> |
| 1047 |
<th scope="col">'.esc_html__('Updateable With Latest Version of WordPress', 'pcc').'</th> |
| 1048 |
<th scope="col">'.esc_html__('Issues Resolved in Last Two Months', 'pcc').'</th> |
| 1049 |
</tr> |
| 1050 |
</thead> |
| 1051 |
<tbody class="tbdy">'; |
| 1052 |
|
| 1053 |
$export_rows = []; |
| 1054 |
foreach ( $scan['rows'] as $row ) { |
| 1055 |
$bg = ($row['current_version'] === $row['latest_version'] && $row['latest_version'] !== 'No Data') ? '#135e96' : '#f64855'; |
| 1056 |
|
| 1057 |
echo '<tr style="background-color:'.esc_attr($bg).'">'; |
| 1058 |
echo '<th scope="row">'.esc_html($row['name']).'</th>'; |
| 1059 |
echo '<td>'.esc_html($row['current_version']).'</td>'; |
| 1060 |
echo '<td>'.esc_html($row['latest_version']).'</td>'; |
| 1061 |
echo '<td>'.esc_html($row['tested_wp']).'</td>'; |
| 1062 |
echo '<td>'.esc_html($row['php_supported']).'</td>'; |
| 1063 |
echo '<td>'.esc_html($row['status']).'</td>'; |
| 1064 |
echo '<td>'.esc_html($row['upgradeable']).'</td>'; |
| 1065 |
echo '<td>'.esc_html($row['issues_ratio']).'</td>'; |
| 1066 |
echo '</tr>'; |
| 1067 |
|
| 1068 |
$export_rows[] = [ |
| 1069 |
'Plugin Name' => str_replace(',', ' ', $row['name']), |
| 1070 |
'Current Plugin Version' => $row['current_version'], |
| 1071 |
'Latest Plugin Version' => $row['latest_version'], |
| 1072 |
'Compatible With WordPress Version' => $row['tested_wp'], |
| 1073 |
'Supported PHP Version' => str_replace(',', ' ', $row['php_supported']), |
| 1074 |
$is_network ? 'Plugin Network Status' : 'Plugin Status' => $row['status'], |
| 1075 |
'Updateable With Latest Version of WordPress' => $row['upgradeable'], |
| 1076 |
'Issues Resolved in Last Two Months' => "'" . str_replace(':','', $row['issues_ratio']), |
| 1077 |
]; |
| 1078 |
} |
| 1079 |
|
| 1080 |
echo '</tbody></table></div>'; |
| 1081 |
|
| 1082 |
echo '<div class="pcc-table-note" style="margin-top:12px;padding:10px;border-left:4px solid #135e96;background:#f7fbff;">'; |
| 1083 |
echo '<strong>' . esc_html__('Note:', 'pcc') . '</strong> ' . esc_html__('Plugins showing No Data are likely custom/premium — check with the plugin author or the plugin version you are using have been removed by the author from wporg.', 'pcc'); |
| 1084 |
echo '</div>'; |
| 1085 |
|
| 1086 |
printf('<script>window.PCCExportData=%s;</script>', wp_json_encode($export_rows)); |
| 1087 |
} |
| 1088 |
|
| 1089 |
/* Helpers */ |
| 1090 |
|
| 1091 |
private function resolve_plugin_slug($plugin_file, $text_domain, $plugin_uri) { |
| 1092 |
$dir = dirname($plugin_file); |
| 1093 |
if ( $dir && $dir !== '.' ) { |
| 1094 |
return $dir; |
| 1095 |
} |
| 1096 |
if ( ! empty($text_domain) ) { |
| 1097 |
return sanitize_title($text_domain); |
| 1098 |
} |
| 1099 |
if ( $plugin_uri ) { |
| 1100 |
$parts = explode('/', $plugin_uri, 5); |
| 1101 |
if ( isset($parts[4]) && ! empty($parts[4]) ) { |
| 1102 |
return rtrim($parts[4], '/'); |
| 1103 |
} |
| 1104 |
} |
| 1105 |
return ''; |
| 1106 |
} |
| 1107 |
|
| 1108 |
private function fetch_wp_latest_version() { |
| 1109 |
$url = 'https://api.wordpress.org/core/version-check/1.7/'; |
| 1110 |
$res = wp_remote_get($url, [ 'timeout' => 15 ]); |
| 1111 |
if ( is_wp_error($res) ) { |
| 1112 |
return get_bloginfo('version'); |
| 1113 |
} |
| 1114 |
$body = wp_remote_retrieve_body($res); |
| 1115 |
$obj = json_decode($body); |
| 1116 |
return isset($obj->offers[0]->version) ? $obj->offers[0]->version : get_bloginfo('version'); |
| 1117 |
} |
| 1118 |
|
| 1119 |
private function fetch_wporg_plugin_info($slug) { |
| 1120 |
if ( empty($slug) ) return []; |
| 1121 |
$url = 'https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&request[slug]=' . rawurlencode($slug); |
| 1122 |
$res = wp_remote_get($url, [ 'timeout' => 20 ]); |
| 1123 |
if ( is_wp_error($res) ) return []; |
| 1124 |
$body = wp_remote_retrieve_body($res); |
| 1125 |
$data = json_decode($body, true); |
| 1126 |
if ( ! is_array($data) || isset($data['error']) ) return []; |
| 1127 |
return [ |
| 1128 |
'tested' => $data['tested'] ?? null, |
| 1129 |
'version' => $data['version'] ?? null, |
| 1130 |
'support_threads' => $data['support_threads'] ?? null, |
| 1131 |
'support_resolved' => $data['support_threads_resolved'] ?? null, |
| 1132 |
'requires_php' => $data['requires_php'] ?? null, |
| 1133 |
]; |
| 1134 |
} |
| 1135 |
|
| 1136 |
private function fetch_wptide_php_compat_list($slug, $latest_version) { |
| 1137 |
$url = sprintf('https://wptide.org/api/v1/audit/wporg/plugin/%s/%s?reports=all', rawurlencode($slug), rawurlencode($latest_version)); |
| 1138 |
$res = wp_remote_get($url, [ 'timeout' => 20 ]); |
| 1139 |
if ( is_wp_error($res) ) return null; |
| 1140 |
$body = wp_remote_retrieve_body($res); |
| 1141 |
$data = json_decode($body, true); |
| 1142 |
if ( ! is_array($data) || isset($data['error']) ) return null; |
| 1143 |
|
| 1144 |
$compat_arr = $data['reports']['phpcs_phpcompatibilitywp']['report']['compatible'] ?? null; |
| 1145 |
if ( ! is_array($compat_arr) || empty($compat_arr) ) return null; |
| 1146 |
|
| 1147 |
return implode(', ', $compat_arr); |
| 1148 |
} |
| 1149 |
|
| 1150 |
private function get_environment_stats($is_network) { |
| 1151 |
$wp_version = get_bloginfo('version'); |
| 1152 |
$php_ver = phpversion(); |
| 1153 |
$plugins = get_plugins(); |
| 1154 |
$total = count($plugins); |
| 1155 |
|
| 1156 |
if ( $is_network ) { |
| 1157 |
$active_sitewide = (array) get_site_option('active_sitewide_plugins', []); |
| 1158 |
$active = 0; |
| 1159 |
foreach ($plugins as $file => $info) { |
| 1160 |
if ( isset($active_sitewide[$file]) ) $active++; |
| 1161 |
} |
| 1162 |
} else { |
| 1163 |
$active_plugins = (array) get_option('active_plugins', []); |
| 1164 |
$active = 0; |
| 1165 |
foreach ($plugins as $file => $info) { |
| 1166 |
if ( in_array($file, $active_plugins, true) ) $active++; |
| 1167 |
} |
| 1168 |
} |
| 1169 |
$inactive = max(0, $total - $active); |
| 1170 |
|
| 1171 |
$wp_latest = $this->fetch_wp_latest_version(); |
| 1172 |
|
| 1173 |
// Get full DB version string via SELECT VERSION() for accurate MariaDB detection |
| 1174 |
$full_db = $this->get_db_version(); |
| 1175 |
$db_parsed = $this->parse_db_version($full_db); |
| 1176 |
|
| 1177 |
return [ |
| 1178 |
'wp_version' => $wp_version, |
| 1179 |
'php_version' => $php_ver, |
| 1180 |
'db_version' => $db_parsed['clean'], // e.g. "8.0.32" or "10.6.14" |
| 1181 |
'db_type' => $db_parsed['type'], // "MySQL" or "MariaDB" |
| 1182 |
'db_version_full' => $db_parsed['full'], // raw string e.g. "10.6.14-MariaDB" |
| 1183 |
'plugins_total' => $total, |
| 1184 |
'plugins_active' => $active, |
| 1185 |
'plugins_inactive'=> $inactive, |
| 1186 |
'wp_latest' => $wp_latest, |
| 1187 |
]; |
| 1188 |
} |
| 1189 |
|
| 1190 |
private function get_scan_results($force_rebuild = false, $is_network = null) { |
| 1191 |
if ( is_null($is_network) ) $is_network = is_multisite(); |
| 1192 |
$key = self::TRANSIENT_KEY; |
| 1193 |
$ttl = apply_filters('pcc_cache_ttl', self::TRANSIENT_TTL_DEFAULT); |
| 1194 |
|
| 1195 |
$data = $is_network ? get_site_transient($key) : get_transient($key); |
| 1196 |
if ( $force_rebuild || empty($data) || ! is_array($data) ) { |
| 1197 |
$data = $this->build_scan_results($is_network); |
| 1198 |
if ( $is_network ) { |
| 1199 |
set_site_transient($key, $data, $ttl); |
| 1200 |
} else { |
| 1201 |
set_transient($key, $data, $ttl); |
| 1202 |
} |
| 1203 |
} |
| 1204 |
return $data; |
| 1205 |
} |
| 1206 |
|
| 1207 |
private function build_scan_results($is_network) { |
| 1208 |
$plugins = get_plugins(); |
| 1209 |
$wp_latest = $this->fetch_wp_latest_version(); |
| 1210 |
|
| 1211 |
if ( $is_network ) { |
| 1212 |
$active_map = (array) get_site_option('active_sitewide_plugins', []); |
| 1213 |
} else { |
| 1214 |
$active_map = array_fill_keys( (array) get_option('active_plugins', []), true ); |
| 1215 |
} |
| 1216 |
|
| 1217 |
$rows = []; |
| 1218 |
$remote_map = get_option(self::OPTION_REMOTE_MAP, []); |
| 1219 |
if ( ! is_array($remote_map) ) $remote_map = []; |
| 1220 |
|
| 1221 |
foreach ( $plugins as $file => $plug ) { |
| 1222 |
$candidate = WP_PLUGIN_DIR . '/' . $file; |
| 1223 |
if ( ! file_exists($candidate) ) continue; |
| 1224 |
|
| 1225 |
$name = isset($plug['Name']) ? $plug['Name'] : $file; |
| 1226 |
$current_ver = isset($plug['Version']) ? $plug['Version'] : ''; |
| 1227 |
$plugin_uri = isset($plug['PluginURI']) ? $plug['PluginURI'] : ''; |
| 1228 |
$text_domain = isset($plug['TextDomain']) ? $plug['TextDomain'] : ''; |
| 1229 |
$slug = $this->resolve_plugin_slug($file, $text_domain, $plugin_uri); |
| 1230 |
|
| 1231 |
$info = $slug ? $this->fetch_wporg_plugin_info($slug) : []; |
| 1232 |
|
| 1233 |
$tested_wp = $info['tested'] ?? 'No Data'; |
| 1234 |
$latest_ver = $info['version'] ?? 'No Data'; |
| 1235 |
$support_total = $info['support_threads'] ?? 'No Data'; |
| 1236 |
$support_res = $info['support_resolved'] ?? 'No Data'; |
| 1237 |
$requires_php = $info['requires_php'] ?? 'No Data'; |
| 1238 |
|
| 1239 |
$status = ( isset($active_map[$file]) ? 'Activated' : 'Deactivated' ); |
| 1240 |
|
| 1241 |
$upgradeable = 'No Data'; |
| 1242 |
if ( 'No Data' !== $tested_wp ) { |
| 1243 |
if ( version_compare($tested_wp, $wp_latest, '>=') ) $upgradeable = 'Yes'; |
| 1244 |
else $upgradeable = ( $tested_wp === '6.2.0' ) ? 'Yes' : 'No'; |
| 1245 |
} |
| 1246 |
|
| 1247 |
$issues_ratio = 'No Data'; |
| 1248 |
if ($support_total === ':0' && $support_res === ':0') { |
| 1249 |
$issues_ratio = 'There Are No Issues'; |
| 1250 |
} elseif ($support_total !== 'No Data' && $support_res !== 'No Data') { |
| 1251 |
$issues_ratio = str_replace(':','', $support_res) . '/' . str_replace(':','', $support_total); |
| 1252 |
} |
| 1253 |
|
| 1254 |
$php_supported = 'No Data'; |
| 1255 |
if ( $slug && isset($remote_map[$slug]) && ! empty($remote_map[$slug]) ) { |
| 1256 |
$php_supported = $remote_map[$slug]; |
| 1257 |
} else { |
| 1258 |
if ( $slug && 'No Data' !== $latest_ver ) { |
| 1259 |
$compat = $this->fetch_wptide_php_compat_list($slug, $latest_ver); |
| 1260 |
$php_supported = $compat ? $compat : 'No Data'; |
| 1261 |
} |
| 1262 |
} |
| 1263 |
|
| 1264 |
$rows[] = [ |
| 1265 |
'name' => $name, |
| 1266 |
'current_version' => $current_ver ?: 'No Data', |
| 1267 |
'latest_version' => $latest_ver, |
| 1268 |
'tested_wp' => $tested_wp, |
| 1269 |
'php_supported' => $php_supported, |
| 1270 |
'status' => $status, |
| 1271 |
'upgradeable' => $upgradeable, |
| 1272 |
'issues_ratio' => $issues_ratio, |
| 1273 |
]; |
| 1274 |
} |
| 1275 |
|
| 1276 |
return [ 'rows' => $rows ]; |
| 1277 |
} |
| 1278 |
} |
| 1279 |
|
| 1280 |
new PCC(); |
| 1281 |
|
| 1282 |
endif; |