| @@ -57,9 +57,9 @@ | ||
| 57 | 57 | $replacement = array('owner' => $owner, 'time' => time()); |
| 58 | 58 | global $wpdb; |
| 59 | 59 | if (isset($wpdb) && is_object($wpdb) && method_exists($wpdb, 'query') && method_exists($wpdb, 'prepare')) { |
| 60 | 60 | $serialize = function ($value) { return function_exists('maybe_serialize') ? maybe_serialize($value) : serialize($value); }; |
| 61 | - $wpdb->query($wpdb->prepare( | |
| 61 | + $wpdb->query($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- atomic compare-and-swap on option rows — the WHERE-guarded UPDATE/DELETE is the lock primitive | |
| 62 | 62 | "UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = %s AND option_value = %s", |
| 63 | 63 | $serialize($replacement), $key, $serialize($held) |
| 64 | 64 | )); |
| 65 | 65 | if (function_exists('wp_cache_delete')) wp_cache_delete($key, 'options'); |
| @@ -79,9 +79,9 @@ | ||
| 79 | 79 | if (is_array($held) && isset($held['owner']) && hash_equals((string) $held['owner'], (string) $owner)) { |
| 80 | 80 | global $wpdb; |
| 81 | 81 | if (isset($wpdb) && is_object($wpdb) && method_exists($wpdb, 'query') && method_exists($wpdb, 'prepare')) { |
| 82 | 82 | $serialized = function_exists('maybe_serialize') ? maybe_serialize($held) : serialize($held); |
| 83 | - $wpdb->query($wpdb->prepare( | |
| 83 | + $wpdb->query($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- atomic compare-and-swap on option rows — the WHERE-guarded UPDATE/DELETE is the lock primitive | |
| 84 | 84 | "DELETE FROM {$wpdb->options} WHERE option_name = %s AND option_value = %s", |
| 85 | 85 | $key, |
| 86 | 86 | $serialized |
| 87 | 87 | )); |
| @@ -127,9 +127,9 @@ | ||
| 127 | 127 | public function ajax_import_external_data() |
| 128 | 128 | { |
| 129 | 129 | $execution_time = Metasync_Settings_Fields::instance()->get_execution_setting('max_execution_time'); |
| 130 | 130 | if (function_exists('set_time_limit')) { |
| 131 | - @set_time_limit($execution_time); | |
| 131 | + @set_time_limit($execution_time); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- runtime PHP setting with no WordPress API | |
| 132 | 132 | } |
| 133 | 133 | |
| 134 | 134 | Metasync_Settings_Fields::instance()->apply_memory_limit(); |
| 135 | 135 | |
| @@ -138,10 +138,10 @@ | ||
| 138 | 138 | if (!Metasync::current_user_has_plugin_access()) { |
| 139 | 139 | wp_send_json_error(['message' => 'Insufficient permissions.']); |
| 140 | 140 | } |
| 141 | 141 | |
| 142 | - $type = isset($_POST['type']) ? sanitize_text_field($_POST['type']) : ''; | |
| 143 | - $plugin = isset($_POST['plugin']) ? sanitize_text_field($_POST['plugin']) : ''; | |
| 142 | + $type = isset($_POST['type']) ? sanitize_text_field(wp_unslash($_POST['type'])) : ''; | |
| 143 | + $plugin = isset($_POST['plugin']) ? sanitize_text_field(wp_unslash($_POST['plugin'])) : ''; | |
| 144 | 144 | $offset = isset($_POST['offset']) ? intval($_POST['offset']) : 0; |
| 145 | 145 | |
| 146 | 146 | if (empty($type) || empty($plugin)) { |
| 147 | 147 | wp_send_json_error(['message' => 'Missing required parameters.']); |
| @@ -179,9 +179,9 @@ | ||
| 179 | 179 | public function ajax_import_seo_metadata() |
| 180 | 180 | { |
| 181 | 181 | $execution_time = Metasync_Settings_Fields::instance()->get_execution_setting('max_execution_time'); |
| 182 | 182 | if (function_exists('set_time_limit')) { |
| 183 | - @set_time_limit($execution_time); | |
| 183 | + @set_time_limit($execution_time); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- runtime PHP setting with no WordPress API | |
| 184 | 184 | } |
| 185 | 185 | |
| 186 | 186 | Metasync_Settings_Fields::instance()->apply_memory_limit(); |
| 187 | 187 | |
| @@ -190,9 +190,9 @@ | ||
| 190 | 190 | if (!Metasync::current_user_has_plugin_access()) { |
| 191 | 191 | wp_send_json_error(['message' => 'Insufficient permissions.']); |
| 192 | 192 | } |
| 193 | 193 | |
| 194 | - $plugin = isset($_POST['plugin']) ? sanitize_text_field($_POST['plugin']) : ''; | |
| 194 | + $plugin = isset($_POST['plugin']) ? sanitize_text_field(wp_unslash($_POST['plugin'])) : ''; | |
| 195 | 195 | $import_titles = isset($_POST['import_titles']) ? (bool) intval($_POST['import_titles']) : true; |
| 196 | 196 | $import_descriptions = isset($_POST['import_descriptions']) ? (bool) intval($_POST['import_descriptions']) : true; |
| 197 | 197 | $import_social_text = isset($_POST['import_social_text']) ? (bool) intval($_POST['import_social_text']) : true; |
| 198 | 198 | $import_social_images = isset($_POST['import_social_images']) ? (bool) intval($_POST['import_social_images']) : true; |
| @@ -563,10 +563,10 @@ | ||
| 563 | 563 | if (!Metasync::current_user_has_plugin_access()) { |
| 564 | 564 | wp_die('Insufficient permissions'); |
| 565 | 565 | } |
| 566 | 566 | |
| 567 | - $uri = sanitize_text_field($_POST['uri']); | |
| 568 | - $redirect_url = sanitize_url($_POST['redirect_url']); | |
| 567 | + $uri = sanitize_text_field(wp_unslash($_POST['uri'])); | |
| 568 | + $redirect_url = sanitize_url(wp_unslash($_POST['redirect_url'])); | |
| 569 | 569 | |
| 570 | 570 | if (empty($uri) || empty($redirect_url)) { |
| 571 | 571 | wp_send_json_error('Missing required parameters'); |
| 572 | 572 | } |
| @@ -635,9 +635,9 @@ | ||
| 635 | 635 | try { |
| 636 | 636 | delete_expired_transients(true); |
| 637 | 637 | $cleanup_stats['expired_transients'] = 'cleaned_by_wordpress'; |
| 638 | 638 | |
| 639 | - $plugin_transients = $wpdb->get_results( | |
| 639 | + $plugin_transients = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- transient enumeration/cleanup on the options table — no API to scan or bulk-delete transient rows | |
| 640 | 640 | "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_metasync_%'", |
| 641 | 641 | ARRAY_A |
| 642 | 642 | ); |
| 643 | 643 | |
| @@ -646,9 +646,9 @@ | ||
| 646 | 646 | delete_transient($transient_name); |
| 647 | 647 | $cleanup_stats['plugin_transients']++; |
| 648 | 648 | } |
| 649 | 649 | |
| 650 | - $rate_limit_transients = $wpdb->get_results( | |
| 650 | + $rate_limit_transients = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- transient enumeration/cleanup on the options table — no API to scan or bulk-delete transient rows | |
| 651 | 651 | "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_sa_connect_rate_limit_%'", |
| 652 | 652 | ARRAY_A |
| 653 | 653 | ); |
| 654 | 654 | |
| @@ -657,9 +657,9 @@ | ||
| 657 | 657 | delete_transient($transient_name); |
| 658 | 658 | $cleanup_stats['rate_limit_transients']++; |
| 659 | 659 | } |
| 660 | 660 | |
| 661 | - $telemetry_transients = $wpdb->get_results( | |
| 661 | + $telemetry_transients = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- transient enumeration/cleanup on the options table — no API to scan or bulk-delete transient rows | |
| 662 | 662 | "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_metasync_telemetry_%'", |
| 663 | 663 | ARRAY_A |
| 664 | 664 | ); |
| 665 | 665 | |
| @@ -668,9 +668,9 @@ | ||
| 668 | 668 | delete_transient($transient_name); |
| 669 | 669 | $cleanup_stats['telemetry_transients']++; |
| 670 | 670 | } |
| 671 | 671 | |
| 672 | - $sa_connect_success_transients = $wpdb->get_results( | |
| 672 | + $sa_connect_success_transients = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- transient enumeration/cleanup on the options table — no API to scan or bulk-delete transient rows | |
| 673 | 673 | "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_metasync_sa_connect_success_%'", |
| 674 | 674 | ARRAY_A |
| 675 | 675 | ); |
| 676 | 676 | |
| @@ -681,14 +681,12 @@ | ||
| 681 | 681 | } |
| 682 | 682 | |
| 683 | 683 | $cleanup_stats['execution_time'] = round((microtime(true) - $cleanup_stats['start_time']) * 1000, 2); |
| 684 | 684 | $cleanup_stats['next_run'] = wp_next_scheduled('metasync_cleanup_transients') ? |
| 685 | - date('Y-m-d H:i:s T', wp_next_scheduled('metasync_cleanup_transients')) : 'N/A'; | |
| 685 | + gmdate('Y-m-d H:i:s T', wp_next_scheduled('metasync_cleanup_transients')) : 'N/A'; | |
| 686 | 686 | |
| 687 | - error_log('MetaSync: Transient cleanup completed - ' . json_encode($cleanup_stats)); | |
| 688 | - | |
| 689 | 687 | } catch (Exception $e) { |
| 690 | - error_log('MetaSync: Transient cleanup failed - ' . $e->getMessage()); | |
| 688 | + error_log('MetaSync: Transient cleanup failed - ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- genuine failure path, bounded, no secrets | |
| 691 | 689 | } |
| 692 | 690 | } |
| 693 | 691 | |
| 694 | 692 | public function ajax_submit_issue_report() |
| @@ -818,9 +816,9 @@ | ||
| 818 | 816 | # Check if Sentry feedback function exists |
| 819 | 817 | if (!function_exists('metasync_sentry_capture_feedback')) { |
| 820 | 818 | # Log warning if function doesn't exist |
| 821 | 819 | if (defined('WP_DEBUG') && WP_DEBUG) { |
| 822 | - error_log('MetaSync: Sentry feedback function not available for report submission.'); | |
| 820 | + error_log('MetaSync: Sentry feedback function not available for report submission.'); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- debug-gated, no secrets | |
| 823 | 821 | } |
| 824 | 822 | } else { |
| 825 | 823 | $feedback_data = array( |
| 826 | 824 | 'message' => $issue_message, |
| @@ -847,13 +845,11 @@ | ||
| 847 | 845 | )); |
| 848 | 846 | } else { |
| 849 | 847 | # Fallback: Log locally if Sentry fails or is unavailable |
| 850 | 848 | if (defined('WP_DEBUG') && WP_DEBUG) { |
| 851 | - error_log(sprintf( | |
| 852 | - 'MetaSync Client Report (Fallback): UUID: %s | Title: %s | Message: %s | Severity: %s', | |
| 849 | + error_log(sprintf( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- debug-gated, no secrets | |
| 850 | + 'MetaSync Client Report (Fallback): UUID: %s | Severity: %s', | |
| 853 | 851 | $project_uuid, |
| 854 | - $issue_title, | |
| 855 | - $issue_message, | |
| 856 | 852 | $issue_severity |
| 857 | 853 | )); |
| 858 | 854 | } |
| 859 | 855 | |
| @@ -867,9 +863,9 @@ | ||
| 867 | 863 | |
| 868 | 864 | } catch (Exception $e) { |
| 869 | 865 | # Log the error securely (only in debug mode) |
| 870 | 866 | if (defined('WP_DEBUG') && WP_DEBUG) { |
| 871 | - error_log(sprintf( | |
| 867 | + error_log(sprintf( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- debug-gated, no secrets | |
| 872 | 868 | 'MetaSync Report Submission Error: %s in %s on line %d', |
| 873 | 869 | $e->getMessage(), |
| 874 | 870 | $e->getFile(), |
| 875 | 871 | $e->getLine() |
| @@ -1012,9 +1008,9 @@ | ||
| 1012 | 1008 | if ($rate_owner !== '') { |
| 1013 | 1009 | $this->release_recovery_lock($rate_owner); |
| 1014 | 1010 | } |
| 1015 | 1011 | if (defined('WP_DEBUG') && WP_DEBUG) { |
| 1016 | - error_log(sprintf( | |
| 1012 | + error_log(sprintf( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- debug-gated, no secrets | |
| 1017 | 1013 | 'MetaSync Password Recovery Error: %s in %s on line %d', |
| 1018 | 1014 | $e->getMessage(), |
| 1019 | 1015 | $e->getFile(), |
| 1020 | 1016 | $e->getLine() |
| @@ -1162,9 +1158,9 @@ | ||
| 1162 | 1158 | |
| 1163 | 1159 | } catch (Exception $e) { |
| 1164 | 1160 | # Log error if debug is enabled |
| 1165 | 1161 | if (defined('WP_DEBUG') && WP_DEBUG) { |
| 1166 | - error_log('MetaSync Theme Save Error: ' . $e->getMessage()); | |
| 1162 | + error_log('MetaSync Theme Save Error: ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- debug-gated, no secrets | |
| 1167 | 1163 | } |
| 1168 | 1164 | |
| 1169 | 1165 | wp_send_json_error(array('message' => 'Failed to save theme preference.')); |
| 1170 | 1166 | } |
| @@ -1305,9 +1301,9 @@ | ||
| 1305 | 1301 | wp_mkdir_p($temp_dir); |
| 1306 | 1302 | } |
| 1307 | 1303 | |
| 1308 | 1304 | # Generate unique filename |
| 1309 | - $timestamp = date('Y-m-d_H-i-s'); | |
| 1305 | + $timestamp = gmdate('Y-m-d_H-i-s'); | |
| 1310 | 1306 | $zip_filename = 'metasync-whitelabel-plugin-' . $timestamp . '.zip'; |
| 1311 | 1307 | $json_filename = 'whitelabel-settings.json'; |
| 1312 | 1308 | $zip_path = $temp_dir . '/' . $zip_filename; |
| 1313 | 1309 | |
| @@ -1368,14 +1364,14 @@ | ||
| 1368 | 1364 | header('Pragma: no-cache'); |
| 1369 | 1365 | header('Expires: 0'); |
| 1370 | 1366 | |
| 1371 | 1367 | # Output file and clean up |
| 1372 | - readfile($zip_path); | |
| 1373 | - unlink($zip_path); | |
| 1368 | + readfile($zip_path); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- streams a large file to output without loading it into memory | |
| 1369 | + wp_delete_file($zip_path); | |
| 1374 | 1370 | |
| 1375 | 1371 | # Clean up temp directory if empty |
| 1376 | 1372 | if (is_dir($temp_dir) && count(scandir($temp_dir)) == 2) { |
| 1377 | - rmdir($temp_dir); | |
| 1373 | + rmdir($temp_dir); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- WP_Filesystem would prompt for FTP credentials on non-direct hosts | |
| 1378 | 1374 | } |
| 1379 | 1375 | |
| 1380 | 1376 | # Exit to prevent WordPress from adding anything to the response |
| 1381 | 1377 | exit; |
| @@ -1382,12 +1378,12 @@ | ||
| 1382 | 1378 | |
| 1383 | 1379 | } catch (Exception $e) { |
| 1384 | 1380 | # Log error if debug is enabled |
| 1385 | 1381 | if (defined('WP_DEBUG') && WP_DEBUG) { |
| 1386 | - error_log('MetaSync Whitelabel Export Error: ' . $e->getMessage()); | |
| 1382 | + error_log('MetaSync Whitelabel Export Error: ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- debug-gated, no secrets | |
| 1387 | 1383 | } |
| 1388 | 1384 | |
| 1389 | - wp_die('Failed to export whitelabel settings: ' . $e->getMessage()); | |
| 1385 | + wp_die(esc_html__('Failed to export whitelabel settings.', 'metasync')); | |
| 1390 | 1386 | } |
| 1391 | 1387 | } |
| 1392 | 1388 | |
| 1393 | 1389 | private static function add_directory_to_zip($zip, $dir, $zip_path = '', $exclude_patterns = array()) |
| @@ -1432,10 +1428,10 @@ | ||
| 1432 | 1428 | public function render_html_pages_dashboard_widget() |
| 1433 | 1429 | { |
| 1434 | 1430 | global $wpdb; |
| 1435 | 1431 | |
| 1436 | - $query = " | |
| 1437 | - SELECT p.ID, p.post_title, p.post_modified, p.post_type | |
| 1432 | + $html_pages = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- admin dashboard widget join over posts+postmeta — aggregate view, no WordPress API for the join | |
| 1433 | + "SELECT p.ID, p.post_title, p.post_modified, p.post_type | |
| 1438 | 1434 | FROM {$wpdb->posts} p |
| 1439 | 1435 | INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id |
| 1440 | 1436 | WHERE p.post_status = 'publish' |
| 1441 | 1437 | AND p.post_type IN ('post', 'page') |
| @@ -1442,11 +1438,10 @@ | ||
| 1442 | 1438 | AND (pm.meta_key = '_metasync_raw_html_enabled' OR pm.meta_key = '_metasync_custom_css') |
| 1443 | 1439 | GROUP BY p.ID |
| 1444 | 1440 | ORDER BY p.post_modified DESC |
| 1445 | 1441 | LIMIT 10 |
| 1446 | - "; | |
| 1447 | - | |
| 1448 | - $html_pages = $wpdb->get_results($query); | |
| 1442 | + " | |
| 1443 | + ); | |
| 1449 | 1444 | $total_count = count($html_pages); |
| 1450 | 1445 | |
| 1451 | 1446 | $label = $this->get_html_source_label(); |
| 1452 | 1447 | |
| @@ -1453,13 +1448,14 @@ | ||
| 1453 | 1448 | if (empty($html_pages)) { |
| 1454 | 1449 | echo '<div class="metasync-dashboard-widget-empty">'; |
| 1455 | 1450 | echo '<span class="dashicons dashicons-admin-page" style="font-size: 48px; opacity: 0.3; display: block; margin: 20px auto;"></span>'; |
| 1456 | 1451 | echo '<p style="text-align: center; color: #666;">'; |
| 1452 | + /* translators: %s: HTML source label. */ | |
| 1457 | 1453 | echo sprintf(__('No pages created with %s yet.', 'metasync'), '<strong>' . esc_html($label) . '</strong>'); |
| 1458 | 1454 | echo '</p>'; |
| 1459 | 1455 | echo '<p style="text-align: center;">'; |
| 1460 | - echo '<a href="' . admin_url('admin.php?page=' . Metasync_Admin::$page_slug) . '" class="button button-primary">'; | |
| 1461 | - echo __('Get Started', 'metasync'); | |
| 1456 | + echo '<a href="' . esc_url(admin_url('admin.php?page=' . Metasync_Admin::$page_slug)) . '" class="button button-primary">'; | |
| 1457 | + echo esc_html__('Get Started', 'metasync'); | |
| 1462 | 1458 | echo '</a>'; |
| 1463 | 1459 | echo '</p>'; |
| 1464 | 1460 | echo '</div>'; |
| 1465 | 1461 | return; |
| @@ -1468,15 +1464,15 @@ | ||
| 1468 | 1464 | echo '<div class="metasync-dashboard-widget">'; |
| 1469 | 1465 | |
| 1470 | 1466 | echo '<div class="metasync-widget-stats">'; |
| 1471 | 1467 | echo '<div class="metasync-stat-box">'; |
| 1472 | - echo '<span class="metasync-stat-number">' . $total_count . '</span>'; | |
| 1473 | - echo '<span class="metasync-stat-label">' . __('AI-Generated Pages', 'metasync') . '</span>'; | |
| 1468 | + echo '<span class="metasync-stat-number">' . esc_html((string) $total_count) . '</span>'; | |
| 1469 | + echo '<span class="metasync-stat-label">' . esc_html__('AI-Generated Pages', 'metasync') . '</span>'; | |
| 1474 | 1470 | echo '</div>'; |
| 1475 | 1471 | echo '</div>'; |
| 1476 | 1472 | |
| 1477 | 1473 | echo '<div class="metasync-widget-list">'; |
| 1478 | - echo '<h4>' . __('Recent Pages', 'metasync') . '</h4>'; | |
| 1474 | + echo '<h4>' . esc_html__('Recent Pages', 'metasync') . '</h4>'; | |
| 1479 | 1475 | echo '<ul>'; |
| 1480 | 1476 | |
| 1481 | 1477 | foreach ($html_pages as $page) { |
| 1482 | 1478 | $edit_link = get_edit_post_link($page->ID); |
| @@ -1489,11 +1485,12 @@ | ||
| 1489 | 1485 | echo '<a href="' . esc_url($edit_link) . '" class="metasync-page-title">'; |
| 1490 | 1486 | echo esc_html($page->post_title ?: __('(no title)', 'metasync')); |
| 1491 | 1487 | echo '</a>'; |
| 1492 | 1488 | echo '<span class="metasync-page-meta">'; |
| 1493 | - echo sprintf(__('Updated %s ago', 'metasync'), $time_ago); | |
| 1489 | + /* translators: %s: human-readable time difference. */ | |
| 1490 | + echo esc_html(sprintf(__('Updated %s ago', 'metasync'), $time_ago)); | |
| 1494 | 1491 | echo ' • '; |
| 1495 | - echo '<a href="' . esc_url($view_link) . '" target="_blank">' . __('View', 'metasync') . '</a>'; | |
| 1492 | + echo '<a href="' . esc_url($view_link) . '" target="_blank">' . esc_html__('View', 'metasync') . '</a>'; | |
| 1496 | 1493 | echo '</span>'; |
| 1497 | 1494 | echo '</div>'; |
| 1498 | 1495 | echo '</li>'; |
| 1499 | 1496 | } |
| @@ -1501,10 +1498,10 @@ | ||
| 1501 | 1498 | echo '</ul>'; |
| 1502 | 1499 | echo '</div>'; |
| 1503 | 1500 | |
| 1504 | 1501 | echo '<div class="metasync-widget-footer">'; |
| 1505 | - echo '<a href="' . admin_url('edit.php?post_type=page') . '">'; | |
| 1506 | - echo __('View All Pages', 'metasync') . ' →'; | |
| 1502 | + echo '<a href="' . esc_url(admin_url("edit.php?post_type=page")) . '">'; | |
| 1503 | + echo esc_html__('View All Pages', 'metasync') . ' →'; | |
| 1507 | 1504 | echo '</a>'; |
| 1508 | 1505 | echo '</div>'; |
| 1509 | 1506 | |
| 1510 | 1507 | echo '</div>'; |