| @@ -21,8 +21,26 @@ | ||
| 21 | 21 | */ |
| 22 | 22 | class MainWP_Utility { // phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace -- NOSONAR. |
| 23 | 23 | |
| 24 | 24 | /** |
| 25 | + * Prefix for short-term notice options. | |
| 26 | + */ | |
| 27 | + const SHORT_TERM_NOTICE_OPTION_PREFIX = '_mainwp_temp_notice_'; | |
| 28 | + | |
| 29 | + | |
| 30 | + /** | |
| 31 | + * Prefix for per-user short-term notice options. | |
| 32 | + */ | |
| 33 | + const USER_SHORT_TERM_NOTICE_OPTION_PREFIX = '_mainwp_user_temp_notice_'; | |
| 34 | + | |
| 35 | + | |
| 36 | + /** | |
| 37 | + * New short term notice TTL. | |
| 38 | + */ | |
| 39 | + const SHORT_TERM_NOTICE_TTL = MONTH_IN_SECONDS; | |
| 40 | + | |
| 41 | + | |
| 42 | + /** | |
| 25 | 43 | * Yoast SEO is enabled return true else return null. |
| 26 | 44 | * |
| 27 | 45 | * @static |
| 28 | 46 | * @var boolean $enabled_wp_seo If Yoast SEO is enabled return true else return null. |
| @@ -115,18 +133,17 @@ | ||
| 115 | 133 | return substr( $haystack, - $length ) === $needle; |
| 116 | 134 | } |
| 117 | 135 | |
| 118 | 136 | /** |
| 119 | - * Method get_nice_url() | |
| 137 | + * Get a nice URL. | |
| 120 | 138 | * |
| 121 | - * Grab url. | |
| 139 | + * @param string $pUrl Website URL. | |
| 140 | + * @param bool $showHttp Whether to show HTTP. | |
| 141 | + * @param bool $removeTrailingSlash Whether to removev the trailing slash. | |
| 122 | 142 | * |
| 123 | - * @param string $pUrl Website URL. | |
| 124 | - * @param bool $showHttp Show HTTP. | |
| 125 | - * | |
| 126 | - * @return string $url. | |
| 143 | + * @return string The formatted URL. | |
| 127 | 144 | */ |
| 128 | - public static function get_nice_url( $pUrl, $showHttp = false ) { | |
| 145 | + public static function get_nice_url( $pUrl, $showHttp = false, $removeTrailingSlash = false ) { | |
| 129 | 146 | $url = $pUrl; |
| 130 | 147 | |
| 131 | 148 | if ( static::starts_with( $url, 'http://' ) ) { |
| 132 | 149 | if ( ! $showHttp ) { |
| @@ -131,14 +148,14 @@ | ||
| 131 | 148 | if ( static::starts_with( $url, 'http://' ) ) { |
| 132 | 149 | if ( ! $showHttp ) { |
| 133 | 150 | $url = substr( $url, 7 ); |
| 134 | 151 | } |
| 135 | - } elseif ( static::starts_with( $pUrl, 'https://' ) ) { | |
| 152 | + } elseif ( static::starts_with( $url, 'https://' ) ) { | |
| 136 | 153 | if ( ! $showHttp ) { |
| 137 | 154 | $url = substr( $url, 8 ); |
| 138 | 155 | } |
| 139 | 156 | } elseif ( $showHttp ) { |
| 140 | - $url = 'http://' . $url; | |
| 157 | + $url = 'http://' . $url; // phpcs:ignore -- NOSONAR -compatible. | |
| 141 | 158 | } |
| 142 | 159 | |
| 143 | 160 | if ( static::ends_with( $url, '/' ) ) { |
| 144 | 161 | if ( ! $showHttp ) { |
| @@ -144,11 +161,15 @@ | ||
| 144 | 161 | if ( ! $showHttp ) { |
| 145 | 162 | $url = substr( $url, 0, strlen( $url ) - 1 ); |
| 146 | 163 | } |
| 147 | 164 | } else { |
| 148 | - $url = $url . '/'; | |
| 165 | + $url .= '/'; | |
| 149 | 166 | } |
| 150 | 167 | |
| 168 | + if ( $removeTrailingSlash ) { | |
| 169 | + $url = rtrim( $url, '/' ); | |
| 170 | + } | |
| 171 | + | |
| 151 | 172 | return $url; |
| 152 | 173 | } |
| 153 | 174 | |
| 154 | 175 | /** |
| @@ -862,8 +883,22 @@ | ||
| 862 | 883 | return false; |
| 863 | 884 | } |
| 864 | 885 | |
| 865 | 886 | /** |
| 887 | + * Method delete_user_option() | |
| 888 | + * | |
| 889 | + * @param string $option_name Option name. | |
| 890 | + * | |
| 891 | + * @return void. | |
| 892 | + */ | |
| 893 | + public static function delete_user_option( $option_name ) { | |
| 894 | + $user = wp_get_current_user(); | |
| 895 | + if ( $user ) { | |
| 896 | + delete_user_option( $user->ID, $option_name ); | |
| 897 | + } | |
| 898 | + } | |
| 899 | + | |
| 900 | + /** | |
| 866 | 901 | * Method remove_preslash_spaces() |
| 867 | 902 | * |
| 868 | 903 | * Remove spaces before slashes. |
| 869 | 904 | * |
| @@ -1189,8 +1224,38 @@ | ||
| 1189 | 1224 | return true; |
| 1190 | 1225 | } |
| 1191 | 1226 | |
| 1192 | 1227 | /** |
| 1228 | + * Un-hide a MainWP message for the current user. | |
| 1229 | + * | |
| 1230 | + * Removes the notice ID from the saved notice status so the message | |
| 1231 | + * can be displayed again. | |
| 1232 | + * | |
| 1233 | + * @param mixed $notice_id Notice ID. | |
| 1234 | + * | |
| 1235 | + * @return bool True if the notice was un-hidden, otherwise false. | |
| 1236 | + */ | |
| 1237 | + public static function unhide_mainwp_message( $notice_id ) { | |
| 1238 | + $status = get_user_option( 'mainwp_notice_saved_status' ); | |
| 1239 | + | |
| 1240 | + if ( ! is_array( $status ) || ! isset( $status[ $notice_id ] ) ) { | |
| 1241 | + return false; | |
| 1242 | + } | |
| 1243 | + | |
| 1244 | + unset( $status[ $notice_id ] ); | |
| 1245 | + | |
| 1246 | + if ( empty( $status ) ) { | |
| 1247 | + return delete_user_option( get_current_user_id(), 'mainwp_notice_saved_status' ); | |
| 1248 | + } | |
| 1249 | + | |
| 1250 | + return update_user_option( | |
| 1251 | + get_current_user_id(), | |
| 1252 | + 'mainwp_notice_saved_status', | |
| 1253 | + $status | |
| 1254 | + ); | |
| 1255 | + } | |
| 1256 | + | |
| 1257 | + /** | |
| 1193 | 1258 | * Method get_hide_notice_status() |
| 1194 | 1259 | * |
| 1195 | 1260 | * Check whenther or not to show the MainWP Message. |
| 1196 | 1261 | * |
| @@ -1235,8 +1300,241 @@ | ||
| 1235 | 1300 | return $content; |
| 1236 | 1301 | } |
| 1237 | 1302 | |
| 1238 | 1303 | /** |
| 1304 | + * Method set_short_term_notice() | |
| 1305 | + * | |
| 1306 | + * Set new temporary notice. | |
| 1307 | + * | |
| 1308 | + * @param string $monitor Monitor. | |
| 1309 | + * @param string $noti_key Notice key. | |
| 1310 | + * @param int $checked_at Notice checked at. | |
| 1311 | + * | |
| 1312 | + * @return void. | |
| 1313 | + */ | |
| 1314 | + public static function set_short_term_notice( $monitor, $noti_key, $checked_at ) { | |
| 1315 | + if ( empty( $monitor ) || empty( $noti_key ) ) { | |
| 1316 | + return; | |
| 1317 | + } | |
| 1318 | + $noti_handle_name = static::SHORT_TERM_NOTICE_OPTION_PREFIX . $monitor . '_' . $noti_key; | |
| 1319 | + static::update_option( $noti_handle_name, $checked_at ); | |
| 1320 | + } | |
| 1321 | + | |
| 1322 | + | |
| 1323 | + /** | |
| 1324 | + * Method is_short_term_notice() | |
| 1325 | + * | |
| 1326 | + * Check whether the short-term notice is still active. | |
| 1327 | + * | |
| 1328 | + * @param string $monitor Monitor name. | |
| 1329 | + * @param string $noti_key Notice key. | |
| 1330 | + * | |
| 1331 | + * @return boolean true|false. | |
| 1332 | + */ | |
| 1333 | + public static function is_short_term_notice( $monitor, $noti_key ) { | |
| 1334 | + | |
| 1335 | + if ( empty( $monitor ) || empty( $noti_key ) ) { | |
| 1336 | + return false; | |
| 1337 | + } | |
| 1338 | + | |
| 1339 | + $noti_handle_name = static::SHORT_TERM_NOTICE_OPTION_PREFIX . $monitor . '_' . $noti_key; | |
| 1340 | + | |
| 1341 | + $notice_time = get_option( $noti_handle_name ); | |
| 1342 | + | |
| 1343 | + if ( false === $notice_time ) { | |
| 1344 | + return false; | |
| 1345 | + } | |
| 1346 | + | |
| 1347 | + if ( ! is_numeric( $notice_time ) ) { | |
| 1348 | + delete_option( $noti_handle_name ); | |
| 1349 | + } | |
| 1350 | + | |
| 1351 | + if ( time() - (int) $notice_time < self::SHORT_TERM_NOTICE_TTL ) { | |
| 1352 | + if ( static::is_short_term_dismissed_notice( $monitor, $noti_key ) ) { | |
| 1353 | + return false; | |
| 1354 | + } | |
| 1355 | + return true; | |
| 1356 | + } else { | |
| 1357 | + delete_option( $noti_handle_name ); | |
| 1358 | + } | |
| 1359 | + return false; | |
| 1360 | + } | |
| 1361 | + | |
| 1362 | + | |
| 1363 | + /** | |
| 1364 | + * Method is_short_term_dismissed_notice() | |
| 1365 | + * | |
| 1366 | + * Check whether the short-term notice is user dismissed. | |
| 1367 | + * | |
| 1368 | + * @param string $monitor Monitor. | |
| 1369 | + * @param string $noti_key Notice key. | |
| 1370 | + * | |
| 1371 | + * @return boolean true|false. | |
| 1372 | + */ | |
| 1373 | + public static function is_short_term_dismissed_notice( $monitor, $noti_key ) { | |
| 1374 | + if ( empty( $monitor ) || empty( $noti_key ) ) { | |
| 1375 | + return false; | |
| 1376 | + } | |
| 1377 | + $user_noti_handle = static::USER_SHORT_TERM_NOTICE_OPTION_PREFIX . $monitor . '_' . $noti_key; | |
| 1378 | + $user = wp_get_current_user(); | |
| 1379 | + if ( $user && ! empty( $user->ID ) ) { | |
| 1380 | + return 'dismissed' === get_option( $user_noti_handle . '_user_' . $user->ID ); | |
| 1381 | + } | |
| 1382 | + return false; | |
| 1383 | + } | |
| 1384 | + | |
| 1385 | + | |
| 1386 | + /** | |
| 1387 | + * Purge short-term notices. | |
| 1388 | + * | |
| 1389 | + * @param string $monitor Optional monitor name. When provided, only | |
| 1390 | + * notices for the specified monitor are purged. | |
| 1391 | + * @param bool $expired_only Whether to purge only expired notices. | |
| 1392 | + * | |
| 1393 | + * @return void | |
| 1394 | + */ | |
| 1395 | + public static function purge_short_term_notices( $monitor = '', $expired_only = true ) { // phpcs:ignore -- NOSONAR - complex. | |
| 1396 | + | |
| 1397 | + $opt_prefix = self::SHORT_TERM_NOTICE_OPTION_PREFIX; | |
| 1398 | + | |
| 1399 | + $prefix = '' !== $monitor ? $monitor . '_' : ''; | |
| 1400 | + | |
| 1401 | + $timeout = time() - self::SHORT_TERM_NOTICE_TTL; | |
| 1402 | + | |
| 1403 | + $db = MainWP_DB::instance()->get_wpdb_instance(); | |
| 1404 | + | |
| 1405 | + $sql = $db->prepare( | |
| 1406 | + " | |
| 1407 | + SELECT option_name | |
| 1408 | + FROM {$db->options} | |
| 1409 | + WHERE option_name LIKE %s | |
| 1410 | + ", | |
| 1411 | + $db->esc_like( $opt_prefix . (string) $prefix ) . '%', | |
| 1412 | + ); | |
| 1413 | + | |
| 1414 | + if ( $expired_only ) { | |
| 1415 | + $sql .= $db->prepare( | |
| 1416 | + ' AND CAST(option_value AS UNSIGNED) <= %d ', | |
| 1417 | + $timeout | |
| 1418 | + ); | |
| 1419 | + } | |
| 1420 | + | |
| 1421 | + $options = $db->get_col( $sql ); | |
| 1422 | + | |
| 1423 | + foreach ( $options as $option_name ) { | |
| 1424 | + delete_option( $option_name ); | |
| 1425 | + } | |
| 1426 | + } | |
| 1427 | + | |
| 1428 | + | |
| 1429 | + /** | |
| 1430 | + * Purge short-term notices for a specific issue. | |
| 1431 | + * | |
| 1432 | + * @param string $monitor Monitor name. | |
| 1433 | + * @param string $issue_code Issue code. | |
| 1434 | + * | |
| 1435 | + * @return bool True if the operation completed, false if the input is invalid. | |
| 1436 | + */ | |
| 1437 | + public static function delete_short_term_notices_for_issue( $monitor, $issue_code ) { | |
| 1438 | + | |
| 1439 | + if ( empty( $monitor ) || empty( $issue_code ) ) { | |
| 1440 | + return false; | |
| 1441 | + } | |
| 1442 | + | |
| 1443 | + $opt_prefix = self::SHORT_TERM_NOTICE_OPTION_PREFIX; | |
| 1444 | + $prefix = $monitor . '_' . $issue_code; | |
| 1445 | + | |
| 1446 | + $db = MainWP_DB::instance()->get_wpdb_instance(); | |
| 1447 | + | |
| 1448 | + $sql = $db->prepare( | |
| 1449 | + " | |
| 1450 | + SELECT option_name | |
| 1451 | + FROM {$db->options} | |
| 1452 | + WHERE option_name LIKE %s | |
| 1453 | + ", | |
| 1454 | + $db->esc_like( $opt_prefix . $prefix ) . '%' | |
| 1455 | + ); | |
| 1456 | + | |
| 1457 | + $options = $db->get_col( $sql ); | |
| 1458 | + | |
| 1459 | + foreach ( $options as $option_name ) { | |
| 1460 | + delete_option( $option_name ); // Delete options and cache. | |
| 1461 | + } | |
| 1462 | + | |
| 1463 | + return true; | |
| 1464 | + } | |
| 1465 | + | |
| 1466 | + /** | |
| 1467 | + * Method dismiss_short_term_notice() | |
| 1468 | + * | |
| 1469 | + * Hide short term notice. | |
| 1470 | + * | |
| 1471 | + * @param string $monitor_noti_key Notice key. | |
| 1472 | + * | |
| 1473 | + * @return void. | |
| 1474 | + */ | |
| 1475 | + public static function dismiss_short_term_notice( $monitor_noti_key ) { | |
| 1476 | + $noti_handle_name = static::USER_SHORT_TERM_NOTICE_OPTION_PREFIX . $monitor_noti_key; | |
| 1477 | + $user = wp_get_current_user(); | |
| 1478 | + if ( $user && ! empty( $user->ID ) ) { | |
| 1479 | + static::update_option( $noti_handle_name . '_user_' . $user->ID, 'dismissed' ); | |
| 1480 | + } | |
| 1481 | + } | |
| 1482 | + | |
| 1483 | + /** | |
| 1484 | + * Delete user short-term notice options for the specified monitor. | |
| 1485 | + * | |
| 1486 | + * Optionally preserves the dismissed state for the specified issue codes. | |
| 1487 | + * | |
| 1488 | + * @param string $monitor Monitor name. | |
| 1489 | + * @param array $preserved_issues Issue codes whose dismissed state should be preserved. | |
| 1490 | + * | |
| 1491 | + * @return void | |
| 1492 | + */ | |
| 1493 | + public static function delete_short_term_notice_options( $monitor, $preserved_issues = array() ) { | |
| 1494 | + | |
| 1495 | + if ( empty( $monitor ) ) { | |
| 1496 | + return; | |
| 1497 | + } | |
| 1498 | + | |
| 1499 | + $db = MainWP_DB::instance()->get_wpdb_instance(); | |
| 1500 | + | |
| 1501 | + $sql = " | |
| 1502 | + SELECT option_name | |
| 1503 | + FROM {$db->options} | |
| 1504 | + WHERE option_name LIKE %s | |
| 1505 | + "; | |
| 1506 | + | |
| 1507 | + $args = array( | |
| 1508 | + $db->esc_like( | |
| 1509 | + static::USER_SHORT_TERM_NOTICE_OPTION_PREFIX . $monitor . '_' | |
| 1510 | + ) . '%', | |
| 1511 | + ); | |
| 1512 | + | |
| 1513 | + // Preserve the user's dismissed state for the specified issues. | |
| 1514 | + if ( ! empty( $preserved_issues ) && is_array( $preserved_issues ) ) { | |
| 1515 | + foreach ( $preserved_issues as $issue_code ) { | |
| 1516 | + $sql .= ' AND option_name NOT LIKE %s'; | |
| 1517 | + $args[] = $db->esc_like( | |
| 1518 | + static::USER_SHORT_TERM_NOTICE_OPTION_PREFIX . $monitor . '_' . $issue_code . '_user_' | |
| 1519 | + ) . '%'; | |
| 1520 | + } | |
| 1521 | + } | |
| 1522 | + | |
| 1523 | + $option_names = $db->get_col( | |
| 1524 | + $db->prepare( $sql, ...$args ) | |
| 1525 | + ); | |
| 1526 | + | |
| 1527 | + if ( empty( $option_names ) ) { | |
| 1528 | + return; | |
| 1529 | + } | |
| 1530 | + | |
| 1531 | + foreach ( $option_names as $option_name ) { | |
| 1532 | + delete_option( $option_name ); | |
| 1533 | + } | |
| 1534 | + } | |
| 1535 | + | |
| 1536 | + /** | |
| 1239 | 1537 | * Method update_flash_message() |
| 1240 | 1538 | * |
| 1241 | 1539 | * Check whenther or not to show the MainWP Message. |
| 1242 | 1540 | * |
| @@ -1394,19 +1692,30 @@ | ||
| 1394 | 1692 | * @return array Health status value. |
| 1395 | 1693 | */ |
| 1396 | 1694 | public static function get_site_health( $issue_counts ) { |
| 1397 | 1695 | |
| 1398 | - if ( empty( $issue_counts ) ) { | |
| 1399 | - $issue_counts = array( | |
| 1696 | + // Coerce non-array/empty/partial input to a full set of counts so a scalar | |
| 1697 | + // (e.g. a JSON-decoded string) or a missing key never fatals or warns. | |
| 1698 | + $issue_counts = array_merge( | |
| 1699 | + array( | |
| 1400 | 1700 | 'good' => 0, |
| 1401 | 1701 | 'recommended' => 0, |
| 1402 | 1702 | 'critical' => 0, |
| 1403 | - ); | |
| 1404 | - } | |
| 1703 | + ), | |
| 1704 | + is_array( $issue_counts ) ? $issue_counts : array() | |
| 1705 | + ); | |
| 1405 | 1706 | |
| 1406 | - $totalTests = intval( $issue_counts['good'] ) + intval( $issue_counts['recommended'] ) + intval( $issue_counts['critical'] ) * 1.5; | |
| 1407 | - $failedTests = intval( $issue_counts['recommended'] ) * 0.5 + $issue_counts['critical'] * 1.5; | |
| 1707 | + // Normalize each counter to a non-negative integer before arithmetic so a | |
| 1708 | + // non-numeric or negative stored value cannot fatal or skew the score. | |
| 1709 | + // is_numeric() first: intval() maps a non-empty array to 1, which would | |
| 1710 | + // count a malformed nested value as a real issue. | |
| 1711 | + $good = is_numeric( $issue_counts['good'] ) ? max( 0, intval( $issue_counts['good'] ) ) : 0; | |
| 1712 | + $recommended = is_numeric( $issue_counts['recommended'] ) ? max( 0, intval( $issue_counts['recommended'] ) ) : 0; | |
| 1713 | + $critical = is_numeric( $issue_counts['critical'] ) ? max( 0, intval( $issue_counts['critical'] ) ) : 0; | |
| 1408 | 1714 | |
| 1715 | + $totalTests = $good + $recommended + $critical * 1.5; | |
| 1716 | + $failedTests = $recommended * 0.5 + $critical * 1.5; | |
| 1717 | + | |
| 1409 | 1718 | if ( empty( $totalTests ) ) { |
| 1410 | 1719 | $val = 100; |
| 1411 | 1720 | } else { |
| 1412 | 1721 | $val = 100 - ceil( ( $failedTests / $totalTests ) * 100 ); |
| @@ -1421,9 +1730,9 @@ | ||
| 1421 | 1730 | } |
| 1422 | 1731 | |
| 1423 | 1732 | return array( |
| 1424 | 1733 | 'val' => $val, |
| 1425 | - 'critical' => $issue_counts['critical'], | |
| 1734 | + 'critical' => $critical, | |
| 1426 | 1735 | ); |
| 1427 | 1736 | } |
| 1428 | 1737 | |
| 1429 | 1738 | |
| @@ -2321,6 +2630,15 @@ | ||
| 2321 | 2630 | */ |
| 2322 | 2631 | public static function get_decoded_array( $data ) { |
| 2323 | 2632 | $decoded = ! empty( $data ) ? json_decode( $data, true ) : array(); |
| 2324 | 2633 | return is_array( $decoded ) ? $decoded : array(); |
| 2634 | + } | |
| 2635 | + | |
| 2636 | + /** | |
| 2637 | + * Method get_use_cron() | |
| 2638 | + * | |
| 2639 | + * @return bool Whether use cron. | |
| 2640 | + */ | |
| 2641 | + public static function get_use_cron() { | |
| 2642 | + return ( get_option( 'mainwp_wp_cron' ) === false ) || ( (int) get_option( 'mainwp_wp_cron' ) === 1 ) ? 1 : 0; | |
| 2325 | 2643 | } |
| 2326 | 2644 | } |