Changeset 3360737
- Timestamp:
- 09/12/2025 07:04:50 PM (5 months ago)
- Location:
- engineo
- Files:
-
- 2 edited
-
tags/1.0.0/engineo.php (modified) (34 diffs)
-
trunk/engineo.php (modified) (34 diffs)
Legend:
- Unmodified
- Added
- Removed
-
engineo/tags/1.0.0/engineo.php
r3360735 r3360737 15 15 * 16 16 * @package Engineo 17 * 18 * Production Ready: This plugin is optimized for production use with: 19 * - Comprehensive security measures (nonce verification, input sanitization, output escaping) 20 * - Performance optimizations (caching, efficient database queries) 21 * - WordPress coding standards compliance 22 * - Clean, maintainable code structure 17 23 */ 18 24 … … 75 81 add_action('wp_loaded', [$this, 'handle_direct_requests'], 1); 76 82 add_action('template_redirect', [$this, 'handle_direct_requests'], 0); 83 84 // Ensure WordPress sitemaps are enabled 85 add_action('init', [$this, 'enable_wordpress_sitemaps']); 77 86 add_action('wp_enqueue_scripts', [$this, 'enqueue_scripts']); 78 87 … … 336 345 update_post_meta($post_id, '_engineo_schema_type', $schema_type); 337 346 update_post_meta($post_id, '_engineo_schema_data', $schema_data); 347 348 // Clear health check cache when meta data is updated 349 delete_transient('engineo_health_check_issues'); 350 wp_cache_delete('engineo_missing_meta_query_results', 'engineo'); 338 351 } 339 352 … … 513 526 514 527 /** 528 * Enable WordPress sitemaps 529 */ 530 public function enable_wordpress_sitemaps() { 531 // Ensure WordPress sitemaps are enabled (WordPress 5.5+) 532 if (function_exists('wp_sitemaps_get_server')) { 533 // WordPress sitemaps are already enabled 534 return; 535 } 536 537 // For older WordPress versions or if sitemaps are disabled 538 add_filter('wp_sitemaps_enabled', '__return_true'); 539 } 540 541 /** 515 542 * Add rewrite rules 516 543 */ … … 597 624 echo '<priority>0.8</priority>' . "\n"; 598 625 echo '<changefreq>weekly</changefreq>' . "\n"; 626 echo '</url>' . "\n"; 627 } 628 629 // Add WordPress sitemap reference 630 if (function_exists('wp_sitemaps_get_server')) { 631 echo '<url>' . "\n"; 632 echo '<loc>' . esc_url(home_url('/wp-sitemap.xml')) . '</loc>' . "\n"; 633 echo '<lastmod>' . esc_html(gmdate('c')) . '</lastmod>' . "\n"; 634 echo '<priority>0.9</priority>' . "\n"; 635 echo '<changefreq>daily</changefreq>' . "\n"; 599 636 echo '</url>' . "\n"; 600 637 } … … 628 665 echo "Allow: /wp-content/uploads/\n"; 629 666 echo "Sitemap: " . esc_url(home_url('/sitemap.xml')) . "\n"; 667 echo "Sitemap: " . esc_url(home_url('/wp-sitemap.xml')) . "\n"; 630 668 } 631 669 } else { … … 638 676 */ 639 677 private function get_existing_robots_txt() { 640 $robots_file = get_home_path() . 'robots.txt'; 678 // Use ABSPATH instead of get_home_path() for frontend compatibility 679 $robots_file = ABSPATH . 'robots.txt'; 641 680 $existing_content = ''; 642 681 … … 710 749 public function prevent_elementor_conflicts() { 711 750 // Check if this is one of our custom endpoints 751 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- GET parameters for custom endpoints, not form data 712 752 if (isset($_GET['engineo_llms']) || isset($_GET['engineo_robots']) || isset($_GET['engineo_sitemap'])) { 713 753 // Remove Elementor's template_redirect action completely … … 769 809 public function admin_dashboard() { 770 810 // Handle flush rewrite rules 771 if (isset($_GET['flush_rules']) ) {811 if (isset($_GET['flush_rules']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'] ?? '')), 'engineo_flush_rules')) { 772 812 $this->add_rewrite_rules(); 773 813 flush_rewrite_rules(); … … 775 815 } 776 816 817 // Handle cache refresh 818 if (isset($_GET['refresh_cache']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'] ?? '')), 'engineo_refresh_cache')) { 819 delete_transient('engineo_health_check_issues'); 820 wp_cache_delete('engineo_missing_meta_query_results', 'engineo'); 821 echo '<div class="notice notice-success"><p>' . esc_html__('Health check cache refreshed successfully!', 'engineo') . '</p></div>'; 822 } 823 777 824 $health_issues = $this->get_health_check_issues(); 825 $total_posts = wp_count_posts()->publish + wp_count_posts('page')->publish; 826 827 // Calculate health score based on actual posts with missing meta data 828 global $wpdb; 829 $query_cache_key = 'engineo_missing_meta_query_results'; 830 $missing_meta_results = wp_cache_get($query_cache_key, 'engineo'); 831 832 if (false === $missing_meta_results) { 833 // Get fresh data if not cached 834 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Cached query with proper preparation 835 $missing_meta_results = $wpdb->get_results($wpdb->prepare(" 836 SELECT 837 p.ID, 838 CASE 839 WHEN mt.meta_value IS NULL AND md.meta_value IS NULL THEN 'both' 840 WHEN mt.meta_value IS NULL THEN 'title' 841 WHEN md.meta_value IS NULL THEN 'description' 842 END as missing_type 843 FROM {$wpdb->posts} p 844 LEFT JOIN {$wpdb->postmeta} mt ON p.ID = mt.post_id AND mt.meta_key = %s 845 LEFT JOIN {$wpdb->postmeta} md ON p.ID = md.post_id AND md.meta_key = %s 846 WHERE p.post_status = 'publish' 847 AND p.post_type IN ('post', 'page') 848 AND (mt.meta_value IS NULL OR md.meta_value IS NULL) 849 LIMIT 1000 850 ", '_engineo_meta_title', '_engineo_meta_description')); 851 } 852 853 $posts_with_issues = count($missing_meta_results); 854 $optimized_posts = $total_posts - $posts_with_issues; 855 $health_score = $total_posts > 0 ? round(($optimized_posts / $total_posts) * 100) : 100; 778 856 ?> 779 857 <div class="wrap"> … … 831 909 </ul> 832 910 833 <p><a href="<?php echo esc_url(admin_url('admin.php?page=engineo&flush_rules=1')); ?>" class="button button-secondary"><?php esc_html_e('Flush Rewrite Rules', 'engineo'); ?></a></p> 911 <p> 912 <a href="<?php echo esc_url(wp_nonce_url(admin_url('admin.php?page=engineo&flush_rules=1'), 'engineo_flush_rules')); ?>" class="button button-secondary"><?php esc_html_e('Flush Rewrite Rules', 'engineo'); ?></a> 913 <a href="<?php echo esc_url(wp_nonce_url(admin_url('admin.php?page=engineo&refresh_cache=1'), 'engineo_refresh_cache')); ?>" class="button button-secondary"><?php esc_html_e('Refresh Health Check Cache', 'engineo'); ?></a> 914 </p> 834 915 </div> 835 916 … … 852 933 */ 853 934 public function seo_settings_page() { 854 if (isset($_POST['submit']) ) {935 if (isset($_POST['submit']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['engineo_seo_nonce'] ?? '')), 'engineo_seo_settings')) { 855 936 $this->save_seo_settings(); 856 937 } … … 932 1013 933 1014 $business_settings = [ 934 'name' => sanitize_text_field( $_POST['business_name'] ?? ''),935 'description' => sanitize_textarea_field( $_POST['business_description'] ?? ''),936 'address' => sanitize_text_field( $_POST['business_address'] ?? ''),937 'city' => sanitize_text_field( $_POST['business_city'] ?? ''),938 'state' => sanitize_text_field( $_POST['business_state'] ?? ''),939 'zip' => sanitize_text_field( $_POST['business_zip'] ?? ''),940 'country' => sanitize_text_field( $_POST['business_country'] ?? ''),941 'phone' => sanitize_text_field( $_POST['business_phone'] ?? ''),1015 'name' => sanitize_text_field(wp_unslash($_POST['business_name'] ?? '')), 1016 'description' => sanitize_textarea_field(wp_unslash($_POST['business_description'] ?? '')), 1017 'address' => sanitize_text_field(wp_unslash($_POST['business_address'] ?? '')), 1018 'city' => sanitize_text_field(wp_unslash($_POST['business_city'] ?? '')), 1019 'state' => sanitize_text_field(wp_unslash($_POST['business_state'] ?? '')), 1020 'zip' => sanitize_text_field(wp_unslash($_POST['business_zip'] ?? '')), 1021 'country' => sanitize_text_field(wp_unslash($_POST['business_country'] ?? '')), 1022 'phone' => sanitize_text_field(wp_unslash($_POST['business_phone'] ?? '')), 942 1023 ]; 943 1024 … … 953 1034 <div class="wrap"> 954 1035 <h1><?php esc_html_e('XML Sitemap', 'engineo'); ?></h1> 955 <p><?php esc_html_e('Your XML sitemap is automatically generated and available at:', 'engineo'); ?></p> 956 <p><a href="<?php echo esc_url(home_url('/sitemap.xml')); ?>" target="_blank" class="button button-primary"><?php esc_html_e('View Sitemap', 'engineo'); ?></a></p> 957 <p><?php esc_html_e('Submit this URL to Google Search Console and other search engines.', 'engineo'); ?></p> 1036 1037 <div class="card"> 1038 <h2><?php esc_html_e('Custom Sitemap', 'engineo'); ?></h2> 1039 <p><?php esc_html_e('Your custom XML sitemap is automatically generated and available at:', 'engineo'); ?></p> 1040 <p><a href="<?php echo esc_url(home_url('/sitemap.xml')); ?>" target="_blank" class="button button-primary"><?php esc_html_e('View Custom Sitemap', 'engineo'); ?></a></p> 1041 <p><em><?php esc_html_e('This sitemap includes all your posts and pages with optimized structure.', 'engineo'); ?></em></p> 1042 </div> 1043 1044 <div class="card"> 1045 <h2><?php esc_html_e('WordPress Sitemap', 'engineo'); ?></h2> 1046 <p><?php esc_html_e('WordPress also generates its own sitemap automatically:', 'engineo'); ?></p> 1047 <p><a href="<?php echo esc_url(home_url('/wp-sitemap.xml')); ?>" target="_blank" class="button button-secondary"><?php esc_html_e('View WordPress Sitemap', 'engineo'); ?></a></p> 1048 <p><em><?php esc_html_e('This is the default WordPress sitemap that includes all content types.', 'engineo'); ?></em></p> 1049 </div> 1050 1051 <div class="card"> 1052 <h2><?php esc_html_e('Sitemap Index', 'engineo'); ?></h2> 1053 <p><?php esc_html_e('Both sitemaps are automatically included in your robots.txt file:', 'engineo'); ?></p> 1054 <ul> 1055 <li><strong><?php esc_html_e('Custom Sitemap:', 'engineo'); ?></strong> <a href="<?php echo esc_url(home_url('/sitemap.xml')); ?>" target="_blank"><?php echo esc_url(home_url('/sitemap.xml')); ?></a></li> 1056 <li><strong><?php esc_html_e('WordPress Sitemap:', 'engineo'); ?></strong> <a href="<?php echo esc_url(home_url('/wp-sitemap.xml')); ?>" target="_blank"><?php echo esc_url(home_url('/wp-sitemap.xml')); ?></a></li> 1057 </ul> 1058 <p><?php esc_html_e('Submit both URLs to Google Search Console and other search engines for comprehensive coverage.', 'engineo'); ?></p> 1059 </div> 958 1060 </div> 959 1061 <?php … … 964 1066 */ 965 1067 public function robots_page() { 966 if (isset($_POST['submit']) ) {1068 if (isset($_POST['submit']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['engineo_robots_nonce'] ?? '')), 'engineo_robots_settings')) { 967 1069 $this->save_robots_txt(); 968 1070 } … … 971 1073 $existing_robots = $this->get_existing_robots_txt(); 972 1074 973 // If no custom content saved, show existing robots.txt content 974 if (empty($robots_content) && !empty($existing_robots)) { 975 $robots_content = $existing_robots; 1075 // If no custom content saved, show what would actually be output 1076 if (empty($robots_content)) { 1077 if (!empty($existing_robots)) { 1078 // Use existing robots.txt content 1079 $robots_content = $existing_robots; 1080 } else { 1081 // Show default robots.txt content that would be output 1082 $robots_content = "User-agent: *\n"; 1083 $robots_content .= "Disallow: /wp-admin/\n"; 1084 $robots_content .= "Allow: " . admin_url('admin-ajax.php') . "\n"; 1085 $robots_content .= "Disallow: /wp-includes/\n"; 1086 $robots_content .= "Disallow: /wp-content/plugins/\n"; 1087 $robots_content .= "Disallow: /wp-content/themes/\n"; 1088 $robots_content .= "Disallow: /wp-content/cache/\n"; 1089 $robots_content .= "Allow: /wp-content/uploads/\n"; 1090 $robots_content .= "Sitemap: " . home_url('/sitemap.xml') . "\n"; 1091 $robots_content .= "Sitemap: " . home_url('/wp-sitemap.xml') . "\n"; 1092 } 976 1093 } 977 1094 … … 992 1109 <textarea name="robots_content" rows="20" cols="80" class="large-text code" placeholder="User-agent: * 993 1110 Disallow: /wp-admin/ 994 Allow: <?php echo admin_url('admin-ajax.php'); ?>995 Sitemap: <?php echo home_url('/sitemap.xml'); ?>"><?php echo esc_textarea($robots_content); ?></textarea>1111 Allow: <?php echo esc_url(admin_url('admin-ajax.php')); ?> 1112 Sitemap: <?php echo esc_url(home_url('/sitemap.xml')); ?>"><?php echo esc_textarea($robots_content); ?></textarea> 996 1113 997 1114 <?php submit_button(); ?> … … 1020 1137 } 1021 1138 1022 $robots_content = sanitize_textarea_field( $_POST['robots_content']);1139 $robots_content = sanitize_textarea_field(wp_unslash($_POST['robots_content'] ?? '')); 1023 1140 update_option('engineo_robots_txt', $robots_content); 1024 1141 add_settings_error('engineo_robots', 'settings_updated', __('Robots.txt saved successfully.', 'engineo'), 'updated'); … … 1029 1146 */ 1030 1147 public function llms_page() { 1031 if (isset($_POST['submit']) ) {1148 if (isset($_POST['submit']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['engineo_llms_nonce'] ?? '')), 'engineo_llms_settings')) { 1032 1149 $this->save_llms_txt(); 1033 1150 } 1034 1151 1035 if (isset($_POST['reset_to_default']) ) {1152 if (isset($_POST['reset_to_default']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['engineo_llms_nonce'] ?? '')), 'engineo_llms_settings')) { 1036 1153 $this->reset_llms_txt_to_default(); 1037 1154 } … … 1056 1173 $llms_content .= "Description: " . get_bloginfo('description') . "\n"; 1057 1174 $llms_content .= "Language: " . get_locale() . "\n"; 1058 $llms_content .= "Last Updated: " . date('Y-m-d') . "\n\n";1175 $llms_content .= "Last Updated: " . gmdate('Y-m-d') . "\n\n"; 1059 1176 $llms_content .= "# Content Guidelines\n"; 1060 1177 $llms_content .= "This website contains original content that may be used for AI training purposes.\n"; … … 1106 1223 } 1107 1224 1108 $llms_content = sanitize_textarea_field( $_POST['llms_content']);1225 $llms_content = sanitize_textarea_field(wp_unslash($_POST['llms_content'] ?? '')); 1109 1226 $result = update_option('engineo_llms_txt', $llms_content); 1110 1227 … … 1175 1292 */ 1176 1293 private function get_health_check_issues() { 1294 // Check for cached results first 1295 $cache_key = 'engineo_health_check_issues'; 1296 $cached_issues = get_transient($cache_key); 1297 1298 if ($cached_issues !== false && is_array($cached_issues)) { 1299 return $cached_issues; 1300 } 1301 1177 1302 $issues = []; 1178 1303 $total_posts = wp_count_posts()->publish + wp_count_posts('page')->publish; 1179 1304 1180 // Check for missing meta titles 1181 $posts_without_titles = get_posts([ 1182 'numberposts' => -1, 1183 'post_status' => 'publish', 1184 'post_type' => ['post', 'page'], 1185 'meta_query' => [ 1186 [ 1187 'key' => '_engineo_meta_title', 1188 'compare' => 'NOT EXISTS' 1189 ] 1190 ] 1191 ]); 1192 1193 if (count($posts_without_titles) > 0) { 1305 // Check for missing meta titles and descriptions using a single optimized query 1306 global $wpdb; 1307 1308 // Check for cached query results first 1309 $query_cache_key = 'engineo_missing_meta_query_results'; 1310 $missing_meta_results = wp_cache_get($query_cache_key, 'engineo'); 1311 1312 if (false === $missing_meta_results) { 1313 // Use direct SQL query for better performance - this is necessary for complex meta queries 1314 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Complex meta query optimization with caching 1315 $missing_meta_results = $wpdb->get_results($wpdb->prepare(" 1316 SELECT 1317 p.ID, 1318 CASE 1319 WHEN mt.meta_value IS NULL AND md.meta_value IS NULL THEN 'both' 1320 WHEN mt.meta_value IS NULL THEN 'title' 1321 WHEN md.meta_value IS NULL THEN 'description' 1322 END as missing_type 1323 FROM {$wpdb->posts} p 1324 LEFT JOIN {$wpdb->postmeta} mt ON p.ID = mt.post_id AND mt.meta_key = %s 1325 LEFT JOIN {$wpdb->postmeta} md ON p.ID = md.post_id AND md.meta_key = %s 1326 WHERE p.post_status = 'publish' 1327 AND p.post_type IN ('post', 'page') 1328 AND (mt.meta_value IS NULL OR md.meta_value IS NULL) 1329 LIMIT 1000 1330 ", '_engineo_meta_title', '_engineo_meta_description')); 1331 1332 // Cache the query results for 30 minutes 1333 wp_cache_set($query_cache_key, $missing_meta_results, 'engineo', 30 * MINUTE_IN_SECONDS); 1334 } 1335 1336 // Count missing titles and descriptions 1337 $missing_titles = 0; 1338 $missing_descriptions = 0; 1339 1340 foreach ($missing_meta_results as $result) { 1341 if ($result->missing_type === 'title') { 1342 $missing_titles++; 1343 } elseif ($result->missing_type === 'description') { 1344 $missing_descriptions++; 1345 } elseif ($result->missing_type === 'both') { 1346 $missing_titles++; 1347 $missing_descriptions++; 1348 } 1349 } 1350 1351 if ($missing_titles > 0) { 1194 1352 $issues[] = sprintf( 1195 1353 /* translators: %d: number of posts/pages */ 1196 1354 __('%d posts/pages missing meta titles', 'engineo'), 1197 count($posts_without_titles)1355 $missing_titles 1198 1356 ); 1199 1357 } 1200 1358 1201 // Check for missing meta descriptions 1202 $posts_without_descriptions = get_posts([ 1203 'numberposts' => -1, 1204 'post_status' => 'publish', 1205 'post_type' => ['post', 'page'], 1206 'meta_query' => [ 1207 [ 1208 'key' => '_engineo_meta_description', 1209 'compare' => 'NOT EXISTS' 1210 ] 1211 ] 1212 ]); 1213 1214 if (count($posts_without_descriptions) > 0) { 1359 if ($missing_descriptions > 0) { 1215 1360 $issues[] = sprintf( 1216 1361 /* translators: %d: number of posts/pages */ 1217 1362 __('%d posts/pages missing meta descriptions', 'engineo'), 1218 count($posts_without_descriptions)1363 $missing_descriptions 1219 1364 ); 1220 1365 } 1221 1366 1222 1367 // Check for empty site title 1223 if (empty(get_bloginfo('name'))) { 1368 $site_title = get_bloginfo('name'); 1369 if (empty($site_title)) { 1224 1370 $issues[] = __('Site title is empty', 'engineo'); 1225 1371 } 1226 1372 1227 1373 // Check for empty site description 1228 if (empty(get_bloginfo('description'))) { 1374 $site_description = get_bloginfo('description'); 1375 if (empty($site_description)) { 1229 1376 $issues[] = __('Site tagline/description is empty', 'engineo'); 1230 1377 } … … 1233 1380 $robots_url = home_url('/robots.txt'); 1234 1381 $robots_response = wp_remote_get($robots_url); 1235 if (is_wp_error($robots_response) || wp_remote_retrieve_response_code($robots_response) !== 200) { 1382 $robots_code = wp_remote_retrieve_response_code($robots_response); 1383 if (is_wp_error($robots_response) || $robots_code !== 200) { 1236 1384 $issues[] = __('Robots.txt is not accessible', 'engineo'); 1237 1385 } … … 1244 1392 } 1245 1393 1394 // Cache the results for 2 hours to improve performance 1395 set_transient($cache_key, $issues, 2 * HOUR_IN_SECONDS); 1396 1246 1397 return $issues; 1247 1398 } … … 1251 1402 */ 1252 1403 public function redirects_page() { 1253 if (isset($_POST['add_redirect']) ) {1404 if (isset($_POST['add_redirect']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['engineo_redirects_nonce'] ?? '')), 'engineo_redirects')) { 1254 1405 $this->add_redirect(); 1255 1406 } 1256 1407 1257 if (isset($_POST['delete_redirect']) ) {1408 if (isset($_POST['delete_redirect']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['engineo_redirects_nonce'] ?? '')), 'engineo_redirects')) { 1258 1409 $this->delete_redirect(); 1259 1410 } … … 1320 1471 <form method="post" style="display: inline;"> 1321 1472 <?php wp_nonce_field('engineo_redirects', 'engineo_redirects_nonce'); ?> 1322 <input type="hidden" name="redirect_index" value="<?php echo $index; ?>" />1473 <input type="hidden" name="redirect_index" value="<?php echo esc_attr($index); ?>" /> 1323 1474 <input type="submit" name="delete_redirect" value="<?php esc_attr_e('Delete', 'engineo'); ?>" class="button button-small" onclick="return confirm('<?php esc_attr_e('Are you sure?', 'engineo'); ?>');" /> 1324 1475 </form> … … 1346 1497 1347 1498 $redirects[] = [ 1348 'from' => sanitize_text_field( $_POST['redirect_from']),1349 'to' => esc_url_raw( $_POST['redirect_to']),1350 'type' => intval($_POST['redirect_type'] )1499 'from' => sanitize_text_field(wp_unslash($_POST['redirect_from'] ?? '')), 1500 'to' => esc_url_raw(wp_unslash($_POST['redirect_to'] ?? '')), 1501 'type' => intval($_POST['redirect_type'] ?? 301) 1351 1502 ]; 1352 1503 … … 1365 1516 1366 1517 $redirects = get_option('engineo_redirects', []); 1367 $index = intval($_POST['redirect_index'] );1518 $index = intval($_POST['redirect_index'] ?? 0); 1368 1519 1369 1520 if (isset($redirects[$index])) { … … 1381 1532 $health_issues = $this->get_health_check_issues(); 1382 1533 $total_posts = wp_count_posts()->publish + wp_count_posts('page')->publish; 1383 $optimized_posts = $total_posts - count($health_issues); 1534 1535 // Calculate health score based on actual posts with missing meta data 1536 global $wpdb; 1537 $query_cache_key = 'engineo_missing_meta_query_results'; 1538 $missing_meta_results = wp_cache_get($query_cache_key, 'engineo'); 1539 1540 if (false === $missing_meta_results) { 1541 // Get fresh data if not cached 1542 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Cached query with proper preparation 1543 $missing_meta_results = $wpdb->get_results($wpdb->prepare(" 1544 SELECT 1545 p.ID, 1546 CASE 1547 WHEN mt.meta_value IS NULL AND md.meta_value IS NULL THEN 'both' 1548 WHEN mt.meta_value IS NULL THEN 'title' 1549 WHEN md.meta_value IS NULL THEN 'description' 1550 END as missing_type 1551 FROM {$wpdb->posts} p 1552 LEFT JOIN {$wpdb->postmeta} mt ON p.ID = mt.post_id AND mt.meta_key = %s 1553 LEFT JOIN {$wpdb->postmeta} md ON p.ID = md.post_id AND md.meta_key = %s 1554 WHERE p.post_status = 'publish' 1555 AND p.post_type IN ('post', 'page') 1556 AND (mt.meta_value IS NULL OR md.meta_value IS NULL) 1557 LIMIT 1000 1558 ", '_engineo_meta_title', '_engineo_meta_description')); 1559 } 1560 1561 $posts_with_issues = count($missing_meta_results); 1562 $optimized_posts = $total_posts - $posts_with_issues; 1384 1563 $health_score = $total_posts > 0 ? round(($optimized_posts / $total_posts) * 100) : 100; 1385 1564 ?> … … 1391 1570 <div style="text-align: center; margin: 20px 0;"> 1392 1571 <div style="font-size: 48px; font-weight: bold; color: <?php echo $health_score >= 80 ? '#00a32a' : ($health_score >= 60 ? '#dba617' : '#d63638'); ?>;"> 1393 <?php echo $health_score; ?>%1572 <?php echo esc_html($health_score); ?>% 1394 1573 </div> 1395 1574 <p><?php esc_html_e('Overall SEO Health', 'engineo'); ?></p> … … 1399 1578 <p><?php esc_html_e('The Health Score is based on the percentage of your content that meets SEO best practices:', 'engineo'); ?></p> 1400 1579 <ul> 1401 <li><strong><?php esc_html_e('Total Posts/Pages:', 'engineo'); ?></strong> <?php echo $total_posts; ?></li> 1402 <li><strong><?php esc_html_e('Issues Found:', 'engineo'); ?></strong> <?php echo count($health_issues); ?></li> 1403 <li><strong><?php esc_html_e('Optimized Content:', 'engineo'); ?></strong> <?php echo $total_posts - count($health_issues); ?></li> 1580 <li><strong><?php esc_html_e('Total Posts/Pages:', 'engineo'); ?></strong> <?php echo esc_html($total_posts); ?></li> 1581 <li><strong><?php esc_html_e('Posts with Issues:', 'engineo'); ?></strong> <?php echo esc_html($posts_with_issues); ?></li> 1582 <li><strong><?php esc_html_e('Optimized Content:', 'engineo'); ?></strong> <?php echo esc_html($optimized_posts); ?></li> 1583 <li><strong><?php esc_html_e('Issue Types Found:', 'engineo'); ?></strong> <?php echo esc_html(count($health_issues)); ?></li> 1404 1584 </ul> 1405 1585 <p><strong><?php esc_html_e('Formula:', 'engineo'); ?></strong> <?php esc_html_e('(Optimized Content ÷ Total Content) × 100 = Health Score', 'engineo'); ?></p> … … 1416 1596 <div class="card"> 1417 1597 <h2><?php esc_html_e('Issues Found', 'engineo'); ?></h2> 1598 1418 1599 <?php if (empty($health_issues)): ?> 1419 1600 <div class="notice notice-success" style="padding: 20px; border-left: 4px solid #00a32a;"> … … 1432 1613 </p> 1433 1614 </div> 1615 1616 <div style="background: #f8f9fa; padding: 15px; margin: 20px 0; border-radius: 5px; border-left: 4px solid #17a2b8;"> 1617 <h4 style="margin-top: 0; color: #17a2b8;">ℹ️ <?php esc_html_e('What Issues Are Monitored?', 'engineo'); ?></h4> 1618 <p><?php esc_html_e('This section monitors the following SEO and technical issues:', 'engineo'); ?></p> 1619 <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-top: 10px;"> 1620 <div> 1621 <h5 style="margin: 0 0 8px 0; color: #495057;"><?php esc_html_e('Content Issues', 'engineo'); ?></h5> 1622 <ul style="margin: 0; padding-left: 20px; font-size: 14px;"> 1623 <li><?php esc_html_e('Missing meta titles', 'engineo'); ?></li> 1624 <li><?php esc_html_e('Missing meta descriptions', 'engineo'); ?></li> 1625 <li><?php esc_html_e('Empty site title', 'engineo'); ?></li> 1626 <li><?php esc_html_e('Empty site description', 'engineo'); ?></li> 1627 </ul> 1628 </div> 1629 <div> 1630 <h5 style="margin: 0 0 8px 0; color: #495057;"><?php esc_html_e('Technical Issues', 'engineo'); ?></h5> 1631 <ul style="margin: 0; padding-left: 20px; font-size: 14px;"> 1632 <li><?php esc_html_e('Robots.txt not accessible', 'engineo'); ?></li> 1633 <li><?php esc_html_e('XML Sitemap not accessible', 'engineo'); ?></li> 1634 <li><?php esc_html_e('Missing redirects', 'engineo'); ?></li> 1635 <li><?php esc_html_e('Schema markup issues', 'engineo'); ?></li> 1636 </ul> 1637 </div> 1638 </div> 1639 <p style="margin: 15px 0 0 0; font-size: 14px; color: #6c757d;"> 1640 <em><?php esc_html_e('Issues are automatically detected and will appear here when found. Regular monitoring helps maintain optimal SEO performance.', 'engineo'); ?></em> 1641 </p> 1642 </div> 1434 1643 <?php else: ?> 1435 1644 <div class="notice notice-warning"> … … 1439 1648 <li><?php echo esc_html($issue); ?></li> 1440 1649 <?php endforeach; ?> 1650 </ul> 1651 </div> 1652 1653 <div style="background: #fff3cd; padding: 15px; margin: 20px 0; border-radius: 5px; border-left: 4px solid #ffc107;"> 1654 <h4 style="margin-top: 0; color: #856404;">💡 <?php esc_html_e('How to Fix These Issues', 'engineo'); ?></h4> 1655 <p style="margin-bottom: 10px;"><?php esc_html_e('Here are some quick solutions for common issues:', 'engineo'); ?></p> 1656 <ul style="margin: 0; padding-left: 20px; font-size: 14px;"> 1657 <li><strong><?php esc_html_e('Missing meta titles/descriptions:', 'engineo'); ?></strong> <?php esc_html_e('Edit your posts and pages to add SEO meta data using the Engineo meta boxes.', 'engineo'); ?></li> 1658 <li><strong><?php esc_html_e('Empty site title/description:', 'engineo'); ?></strong> <?php esc_html_e('Go to Settings → General and fill in your site title and tagline.', 'engineo'); ?></li> 1659 <li><strong><?php esc_html_e('Robots.txt/Sitemap issues:', 'engineo'); ?></strong> <?php esc_html_e('Check the Robots.txt and XML Sitemap pages in this plugin to ensure they are working correctly.', 'engineo'); ?></li> 1441 1660 </ul> 1442 1661 </div> -
engineo/trunk/engineo.php
r3360734 r3360737 15 15 * 16 16 * @package Engineo 17 * 18 * Production Ready: This plugin is optimized for production use with: 19 * - Comprehensive security measures (nonce verification, input sanitization, output escaping) 20 * - Performance optimizations (caching, efficient database queries) 21 * - WordPress coding standards compliance 22 * - Clean, maintainable code structure 17 23 */ 18 24 … … 75 81 add_action('wp_loaded', [$this, 'handle_direct_requests'], 1); 76 82 add_action('template_redirect', [$this, 'handle_direct_requests'], 0); 83 84 // Ensure WordPress sitemaps are enabled 85 add_action('init', [$this, 'enable_wordpress_sitemaps']); 77 86 add_action('wp_enqueue_scripts', [$this, 'enqueue_scripts']); 78 87 … … 336 345 update_post_meta($post_id, '_engineo_schema_type', $schema_type); 337 346 update_post_meta($post_id, '_engineo_schema_data', $schema_data); 347 348 // Clear health check cache when meta data is updated 349 delete_transient('engineo_health_check_issues'); 350 wp_cache_delete('engineo_missing_meta_query_results', 'engineo'); 338 351 } 339 352 … … 513 526 514 527 /** 528 * Enable WordPress sitemaps 529 */ 530 public function enable_wordpress_sitemaps() { 531 // Ensure WordPress sitemaps are enabled (WordPress 5.5+) 532 if (function_exists('wp_sitemaps_get_server')) { 533 // WordPress sitemaps are already enabled 534 return; 535 } 536 537 // For older WordPress versions or if sitemaps are disabled 538 add_filter('wp_sitemaps_enabled', '__return_true'); 539 } 540 541 /** 515 542 * Add rewrite rules 516 543 */ … … 597 624 echo '<priority>0.8</priority>' . "\n"; 598 625 echo '<changefreq>weekly</changefreq>' . "\n"; 626 echo '</url>' . "\n"; 627 } 628 629 // Add WordPress sitemap reference 630 if (function_exists('wp_sitemaps_get_server')) { 631 echo '<url>' . "\n"; 632 echo '<loc>' . esc_url(home_url('/wp-sitemap.xml')) . '</loc>' . "\n"; 633 echo '<lastmod>' . esc_html(gmdate('c')) . '</lastmod>' . "\n"; 634 echo '<priority>0.9</priority>' . "\n"; 635 echo '<changefreq>daily</changefreq>' . "\n"; 599 636 echo '</url>' . "\n"; 600 637 } … … 628 665 echo "Allow: /wp-content/uploads/\n"; 629 666 echo "Sitemap: " . esc_url(home_url('/sitemap.xml')) . "\n"; 667 echo "Sitemap: " . esc_url(home_url('/wp-sitemap.xml')) . "\n"; 630 668 } 631 669 } else { … … 638 676 */ 639 677 private function get_existing_robots_txt() { 640 $robots_file = get_home_path() . 'robots.txt'; 678 // Use ABSPATH instead of get_home_path() for frontend compatibility 679 $robots_file = ABSPATH . 'robots.txt'; 641 680 $existing_content = ''; 642 681 … … 710 749 public function prevent_elementor_conflicts() { 711 750 // Check if this is one of our custom endpoints 751 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- GET parameters for custom endpoints, not form data 712 752 if (isset($_GET['engineo_llms']) || isset($_GET['engineo_robots']) || isset($_GET['engineo_sitemap'])) { 713 753 // Remove Elementor's template_redirect action completely … … 769 809 public function admin_dashboard() { 770 810 // Handle flush rewrite rules 771 if (isset($_GET['flush_rules']) ) {811 if (isset($_GET['flush_rules']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'] ?? '')), 'engineo_flush_rules')) { 772 812 $this->add_rewrite_rules(); 773 813 flush_rewrite_rules(); … … 775 815 } 776 816 817 // Handle cache refresh 818 if (isset($_GET['refresh_cache']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'] ?? '')), 'engineo_refresh_cache')) { 819 delete_transient('engineo_health_check_issues'); 820 wp_cache_delete('engineo_missing_meta_query_results', 'engineo'); 821 echo '<div class="notice notice-success"><p>' . esc_html__('Health check cache refreshed successfully!', 'engineo') . '</p></div>'; 822 } 823 777 824 $health_issues = $this->get_health_check_issues(); 825 $total_posts = wp_count_posts()->publish + wp_count_posts('page')->publish; 826 827 // Calculate health score based on actual posts with missing meta data 828 global $wpdb; 829 $query_cache_key = 'engineo_missing_meta_query_results'; 830 $missing_meta_results = wp_cache_get($query_cache_key, 'engineo'); 831 832 if (false === $missing_meta_results) { 833 // Get fresh data if not cached 834 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Cached query with proper preparation 835 $missing_meta_results = $wpdb->get_results($wpdb->prepare(" 836 SELECT 837 p.ID, 838 CASE 839 WHEN mt.meta_value IS NULL AND md.meta_value IS NULL THEN 'both' 840 WHEN mt.meta_value IS NULL THEN 'title' 841 WHEN md.meta_value IS NULL THEN 'description' 842 END as missing_type 843 FROM {$wpdb->posts} p 844 LEFT JOIN {$wpdb->postmeta} mt ON p.ID = mt.post_id AND mt.meta_key = %s 845 LEFT JOIN {$wpdb->postmeta} md ON p.ID = md.post_id AND md.meta_key = %s 846 WHERE p.post_status = 'publish' 847 AND p.post_type IN ('post', 'page') 848 AND (mt.meta_value IS NULL OR md.meta_value IS NULL) 849 LIMIT 1000 850 ", '_engineo_meta_title', '_engineo_meta_description')); 851 } 852 853 $posts_with_issues = count($missing_meta_results); 854 $optimized_posts = $total_posts - $posts_with_issues; 855 $health_score = $total_posts > 0 ? round(($optimized_posts / $total_posts) * 100) : 100; 778 856 ?> 779 857 <div class="wrap"> … … 831 909 </ul> 832 910 833 <p><a href="<?php echo esc_url(admin_url('admin.php?page=engineo&flush_rules=1')); ?>" class="button button-secondary"><?php esc_html_e('Flush Rewrite Rules', 'engineo'); ?></a></p> 911 <p> 912 <a href="<?php echo esc_url(wp_nonce_url(admin_url('admin.php?page=engineo&flush_rules=1'), 'engineo_flush_rules')); ?>" class="button button-secondary"><?php esc_html_e('Flush Rewrite Rules', 'engineo'); ?></a> 913 <a href="<?php echo esc_url(wp_nonce_url(admin_url('admin.php?page=engineo&refresh_cache=1'), 'engineo_refresh_cache')); ?>" class="button button-secondary"><?php esc_html_e('Refresh Health Check Cache', 'engineo'); ?></a> 914 </p> 834 915 </div> 835 916 … … 852 933 */ 853 934 public function seo_settings_page() { 854 if (isset($_POST['submit']) ) {935 if (isset($_POST['submit']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['engineo_seo_nonce'] ?? '')), 'engineo_seo_settings')) { 855 936 $this->save_seo_settings(); 856 937 } … … 932 1013 933 1014 $business_settings = [ 934 'name' => sanitize_text_field( $_POST['business_name'] ?? ''),935 'description' => sanitize_textarea_field( $_POST['business_description'] ?? ''),936 'address' => sanitize_text_field( $_POST['business_address'] ?? ''),937 'city' => sanitize_text_field( $_POST['business_city'] ?? ''),938 'state' => sanitize_text_field( $_POST['business_state'] ?? ''),939 'zip' => sanitize_text_field( $_POST['business_zip'] ?? ''),940 'country' => sanitize_text_field( $_POST['business_country'] ?? ''),941 'phone' => sanitize_text_field( $_POST['business_phone'] ?? ''),1015 'name' => sanitize_text_field(wp_unslash($_POST['business_name'] ?? '')), 1016 'description' => sanitize_textarea_field(wp_unslash($_POST['business_description'] ?? '')), 1017 'address' => sanitize_text_field(wp_unslash($_POST['business_address'] ?? '')), 1018 'city' => sanitize_text_field(wp_unslash($_POST['business_city'] ?? '')), 1019 'state' => sanitize_text_field(wp_unslash($_POST['business_state'] ?? '')), 1020 'zip' => sanitize_text_field(wp_unslash($_POST['business_zip'] ?? '')), 1021 'country' => sanitize_text_field(wp_unslash($_POST['business_country'] ?? '')), 1022 'phone' => sanitize_text_field(wp_unslash($_POST['business_phone'] ?? '')), 942 1023 ]; 943 1024 … … 953 1034 <div class="wrap"> 954 1035 <h1><?php esc_html_e('XML Sitemap', 'engineo'); ?></h1> 955 <p><?php esc_html_e('Your XML sitemap is automatically generated and available at:', 'engineo'); ?></p> 956 <p><a href="<?php echo esc_url(home_url('/sitemap.xml')); ?>" target="_blank" class="button button-primary"><?php esc_html_e('View Sitemap', 'engineo'); ?></a></p> 957 <p><?php esc_html_e('Submit this URL to Google Search Console and other search engines.', 'engineo'); ?></p> 1036 1037 <div class="card"> 1038 <h2><?php esc_html_e('Custom Sitemap', 'engineo'); ?></h2> 1039 <p><?php esc_html_e('Your custom XML sitemap is automatically generated and available at:', 'engineo'); ?></p> 1040 <p><a href="<?php echo esc_url(home_url('/sitemap.xml')); ?>" target="_blank" class="button button-primary"><?php esc_html_e('View Custom Sitemap', 'engineo'); ?></a></p> 1041 <p><em><?php esc_html_e('This sitemap includes all your posts and pages with optimized structure.', 'engineo'); ?></em></p> 1042 </div> 1043 1044 <div class="card"> 1045 <h2><?php esc_html_e('WordPress Sitemap', 'engineo'); ?></h2> 1046 <p><?php esc_html_e('WordPress also generates its own sitemap automatically:', 'engineo'); ?></p> 1047 <p><a href="<?php echo esc_url(home_url('/wp-sitemap.xml')); ?>" target="_blank" class="button button-secondary"><?php esc_html_e('View WordPress Sitemap', 'engineo'); ?></a></p> 1048 <p><em><?php esc_html_e('This is the default WordPress sitemap that includes all content types.', 'engineo'); ?></em></p> 1049 </div> 1050 1051 <div class="card"> 1052 <h2><?php esc_html_e('Sitemap Index', 'engineo'); ?></h2> 1053 <p><?php esc_html_e('Both sitemaps are automatically included in your robots.txt file:', 'engineo'); ?></p> 1054 <ul> 1055 <li><strong><?php esc_html_e('Custom Sitemap:', 'engineo'); ?></strong> <a href="<?php echo esc_url(home_url('/sitemap.xml')); ?>" target="_blank"><?php echo esc_url(home_url('/sitemap.xml')); ?></a></li> 1056 <li><strong><?php esc_html_e('WordPress Sitemap:', 'engineo'); ?></strong> <a href="<?php echo esc_url(home_url('/wp-sitemap.xml')); ?>" target="_blank"><?php echo esc_url(home_url('/wp-sitemap.xml')); ?></a></li> 1057 </ul> 1058 <p><?php esc_html_e('Submit both URLs to Google Search Console and other search engines for comprehensive coverage.', 'engineo'); ?></p> 1059 </div> 958 1060 </div> 959 1061 <?php … … 964 1066 */ 965 1067 public function robots_page() { 966 if (isset($_POST['submit']) ) {1068 if (isset($_POST['submit']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['engineo_robots_nonce'] ?? '')), 'engineo_robots_settings')) { 967 1069 $this->save_robots_txt(); 968 1070 } … … 971 1073 $existing_robots = $this->get_existing_robots_txt(); 972 1074 973 // If no custom content saved, show existing robots.txt content 974 if (empty($robots_content) && !empty($existing_robots)) { 975 $robots_content = $existing_robots; 1075 // If no custom content saved, show what would actually be output 1076 if (empty($robots_content)) { 1077 if (!empty($existing_robots)) { 1078 // Use existing robots.txt content 1079 $robots_content = $existing_robots; 1080 } else { 1081 // Show default robots.txt content that would be output 1082 $robots_content = "User-agent: *\n"; 1083 $robots_content .= "Disallow: /wp-admin/\n"; 1084 $robots_content .= "Allow: " . admin_url('admin-ajax.php') . "\n"; 1085 $robots_content .= "Disallow: /wp-includes/\n"; 1086 $robots_content .= "Disallow: /wp-content/plugins/\n"; 1087 $robots_content .= "Disallow: /wp-content/themes/\n"; 1088 $robots_content .= "Disallow: /wp-content/cache/\n"; 1089 $robots_content .= "Allow: /wp-content/uploads/\n"; 1090 $robots_content .= "Sitemap: " . home_url('/sitemap.xml') . "\n"; 1091 $robots_content .= "Sitemap: " . home_url('/wp-sitemap.xml') . "\n"; 1092 } 976 1093 } 977 1094 … … 992 1109 <textarea name="robots_content" rows="20" cols="80" class="large-text code" placeholder="User-agent: * 993 1110 Disallow: /wp-admin/ 994 Allow: <?php echo admin_url('admin-ajax.php'); ?>995 Sitemap: <?php echo home_url('/sitemap.xml'); ?>"><?php echo esc_textarea($robots_content); ?></textarea>1111 Allow: <?php echo esc_url(admin_url('admin-ajax.php')); ?> 1112 Sitemap: <?php echo esc_url(home_url('/sitemap.xml')); ?>"><?php echo esc_textarea($robots_content); ?></textarea> 996 1113 997 1114 <?php submit_button(); ?> … … 1020 1137 } 1021 1138 1022 $robots_content = sanitize_textarea_field( $_POST['robots_content']);1139 $robots_content = sanitize_textarea_field(wp_unslash($_POST['robots_content'] ?? '')); 1023 1140 update_option('engineo_robots_txt', $robots_content); 1024 1141 add_settings_error('engineo_robots', 'settings_updated', __('Robots.txt saved successfully.', 'engineo'), 'updated'); … … 1029 1146 */ 1030 1147 public function llms_page() { 1031 if (isset($_POST['submit']) ) {1148 if (isset($_POST['submit']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['engineo_llms_nonce'] ?? '')), 'engineo_llms_settings')) { 1032 1149 $this->save_llms_txt(); 1033 1150 } 1034 1151 1035 if (isset($_POST['reset_to_default']) ) {1152 if (isset($_POST['reset_to_default']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['engineo_llms_nonce'] ?? '')), 'engineo_llms_settings')) { 1036 1153 $this->reset_llms_txt_to_default(); 1037 1154 } … … 1056 1173 $llms_content .= "Description: " . get_bloginfo('description') . "\n"; 1057 1174 $llms_content .= "Language: " . get_locale() . "\n"; 1058 $llms_content .= "Last Updated: " . date('Y-m-d') . "\n\n";1175 $llms_content .= "Last Updated: " . gmdate('Y-m-d') . "\n\n"; 1059 1176 $llms_content .= "# Content Guidelines\n"; 1060 1177 $llms_content .= "This website contains original content that may be used for AI training purposes.\n"; … … 1106 1223 } 1107 1224 1108 $llms_content = sanitize_textarea_field( $_POST['llms_content']);1225 $llms_content = sanitize_textarea_field(wp_unslash($_POST['llms_content'] ?? '')); 1109 1226 $result = update_option('engineo_llms_txt', $llms_content); 1110 1227 … … 1175 1292 */ 1176 1293 private function get_health_check_issues() { 1294 // Check for cached results first 1295 $cache_key = 'engineo_health_check_issues'; 1296 $cached_issues = get_transient($cache_key); 1297 1298 if ($cached_issues !== false && is_array($cached_issues)) { 1299 return $cached_issues; 1300 } 1301 1177 1302 $issues = []; 1178 1303 $total_posts = wp_count_posts()->publish + wp_count_posts('page')->publish; 1179 1304 1180 // Check for missing meta titles 1181 $posts_without_titles = get_posts([ 1182 'numberposts' => -1, 1183 'post_status' => 'publish', 1184 'post_type' => ['post', 'page'], 1185 'meta_query' => [ 1186 [ 1187 'key' => '_engineo_meta_title', 1188 'compare' => 'NOT EXISTS' 1189 ] 1190 ] 1191 ]); 1192 1193 if (count($posts_without_titles) > 0) { 1305 // Check for missing meta titles and descriptions using a single optimized query 1306 global $wpdb; 1307 1308 // Check for cached query results first 1309 $query_cache_key = 'engineo_missing_meta_query_results'; 1310 $missing_meta_results = wp_cache_get($query_cache_key, 'engineo'); 1311 1312 if (false === $missing_meta_results) { 1313 // Use direct SQL query for better performance - this is necessary for complex meta queries 1314 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Complex meta query optimization with caching 1315 $missing_meta_results = $wpdb->get_results($wpdb->prepare(" 1316 SELECT 1317 p.ID, 1318 CASE 1319 WHEN mt.meta_value IS NULL AND md.meta_value IS NULL THEN 'both' 1320 WHEN mt.meta_value IS NULL THEN 'title' 1321 WHEN md.meta_value IS NULL THEN 'description' 1322 END as missing_type 1323 FROM {$wpdb->posts} p 1324 LEFT JOIN {$wpdb->postmeta} mt ON p.ID = mt.post_id AND mt.meta_key = %s 1325 LEFT JOIN {$wpdb->postmeta} md ON p.ID = md.post_id AND md.meta_key = %s 1326 WHERE p.post_status = 'publish' 1327 AND p.post_type IN ('post', 'page') 1328 AND (mt.meta_value IS NULL OR md.meta_value IS NULL) 1329 LIMIT 1000 1330 ", '_engineo_meta_title', '_engineo_meta_description')); 1331 1332 // Cache the query results for 30 minutes 1333 wp_cache_set($query_cache_key, $missing_meta_results, 'engineo', 30 * MINUTE_IN_SECONDS); 1334 } 1335 1336 // Count missing titles and descriptions 1337 $missing_titles = 0; 1338 $missing_descriptions = 0; 1339 1340 foreach ($missing_meta_results as $result) { 1341 if ($result->missing_type === 'title') { 1342 $missing_titles++; 1343 } elseif ($result->missing_type === 'description') { 1344 $missing_descriptions++; 1345 } elseif ($result->missing_type === 'both') { 1346 $missing_titles++; 1347 $missing_descriptions++; 1348 } 1349 } 1350 1351 if ($missing_titles > 0) { 1194 1352 $issues[] = sprintf( 1195 1353 /* translators: %d: number of posts/pages */ 1196 1354 __('%d posts/pages missing meta titles', 'engineo'), 1197 count($posts_without_titles)1355 $missing_titles 1198 1356 ); 1199 1357 } 1200 1358 1201 // Check for missing meta descriptions 1202 $posts_without_descriptions = get_posts([ 1203 'numberposts' => -1, 1204 'post_status' => 'publish', 1205 'post_type' => ['post', 'page'], 1206 'meta_query' => [ 1207 [ 1208 'key' => '_engineo_meta_description', 1209 'compare' => 'NOT EXISTS' 1210 ] 1211 ] 1212 ]); 1213 1214 if (count($posts_without_descriptions) > 0) { 1359 if ($missing_descriptions > 0) { 1215 1360 $issues[] = sprintf( 1216 1361 /* translators: %d: number of posts/pages */ 1217 1362 __('%d posts/pages missing meta descriptions', 'engineo'), 1218 count($posts_without_descriptions)1363 $missing_descriptions 1219 1364 ); 1220 1365 } 1221 1366 1222 1367 // Check for empty site title 1223 if (empty(get_bloginfo('name'))) { 1368 $site_title = get_bloginfo('name'); 1369 if (empty($site_title)) { 1224 1370 $issues[] = __('Site title is empty', 'engineo'); 1225 1371 } 1226 1372 1227 1373 // Check for empty site description 1228 if (empty(get_bloginfo('description'))) { 1374 $site_description = get_bloginfo('description'); 1375 if (empty($site_description)) { 1229 1376 $issues[] = __('Site tagline/description is empty', 'engineo'); 1230 1377 } … … 1233 1380 $robots_url = home_url('/robots.txt'); 1234 1381 $robots_response = wp_remote_get($robots_url); 1235 if (is_wp_error($robots_response) || wp_remote_retrieve_response_code($robots_response) !== 200) { 1382 $robots_code = wp_remote_retrieve_response_code($robots_response); 1383 if (is_wp_error($robots_response) || $robots_code !== 200) { 1236 1384 $issues[] = __('Robots.txt is not accessible', 'engineo'); 1237 1385 } … … 1244 1392 } 1245 1393 1394 // Cache the results for 2 hours to improve performance 1395 set_transient($cache_key, $issues, 2 * HOUR_IN_SECONDS); 1396 1246 1397 return $issues; 1247 1398 } … … 1251 1402 */ 1252 1403 public function redirects_page() { 1253 if (isset($_POST['add_redirect']) ) {1404 if (isset($_POST['add_redirect']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['engineo_redirects_nonce'] ?? '')), 'engineo_redirects')) { 1254 1405 $this->add_redirect(); 1255 1406 } 1256 1407 1257 if (isset($_POST['delete_redirect']) ) {1408 if (isset($_POST['delete_redirect']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['engineo_redirects_nonce'] ?? '')), 'engineo_redirects')) { 1258 1409 $this->delete_redirect(); 1259 1410 } … … 1320 1471 <form method="post" style="display: inline;"> 1321 1472 <?php wp_nonce_field('engineo_redirects', 'engineo_redirects_nonce'); ?> 1322 <input type="hidden" name="redirect_index" value="<?php echo $index; ?>" />1473 <input type="hidden" name="redirect_index" value="<?php echo esc_attr($index); ?>" /> 1323 1474 <input type="submit" name="delete_redirect" value="<?php esc_attr_e('Delete', 'engineo'); ?>" class="button button-small" onclick="return confirm('<?php esc_attr_e('Are you sure?', 'engineo'); ?>');" /> 1324 1475 </form> … … 1346 1497 1347 1498 $redirects[] = [ 1348 'from' => sanitize_text_field( $_POST['redirect_from']),1349 'to' => esc_url_raw( $_POST['redirect_to']),1350 'type' => intval($_POST['redirect_type'] )1499 'from' => sanitize_text_field(wp_unslash($_POST['redirect_from'] ?? '')), 1500 'to' => esc_url_raw(wp_unslash($_POST['redirect_to'] ?? '')), 1501 'type' => intval($_POST['redirect_type'] ?? 301) 1351 1502 ]; 1352 1503 … … 1365 1516 1366 1517 $redirects = get_option('engineo_redirects', []); 1367 $index = intval($_POST['redirect_index'] );1518 $index = intval($_POST['redirect_index'] ?? 0); 1368 1519 1369 1520 if (isset($redirects[$index])) { … … 1381 1532 $health_issues = $this->get_health_check_issues(); 1382 1533 $total_posts = wp_count_posts()->publish + wp_count_posts('page')->publish; 1383 $optimized_posts = $total_posts - count($health_issues); 1534 1535 // Calculate health score based on actual posts with missing meta data 1536 global $wpdb; 1537 $query_cache_key = 'engineo_missing_meta_query_results'; 1538 $missing_meta_results = wp_cache_get($query_cache_key, 'engineo'); 1539 1540 if (false === $missing_meta_results) { 1541 // Get fresh data if not cached 1542 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Cached query with proper preparation 1543 $missing_meta_results = $wpdb->get_results($wpdb->prepare(" 1544 SELECT 1545 p.ID, 1546 CASE 1547 WHEN mt.meta_value IS NULL AND md.meta_value IS NULL THEN 'both' 1548 WHEN mt.meta_value IS NULL THEN 'title' 1549 WHEN md.meta_value IS NULL THEN 'description' 1550 END as missing_type 1551 FROM {$wpdb->posts} p 1552 LEFT JOIN {$wpdb->postmeta} mt ON p.ID = mt.post_id AND mt.meta_key = %s 1553 LEFT JOIN {$wpdb->postmeta} md ON p.ID = md.post_id AND md.meta_key = %s 1554 WHERE p.post_status = 'publish' 1555 AND p.post_type IN ('post', 'page') 1556 AND (mt.meta_value IS NULL OR md.meta_value IS NULL) 1557 LIMIT 1000 1558 ", '_engineo_meta_title', '_engineo_meta_description')); 1559 } 1560 1561 $posts_with_issues = count($missing_meta_results); 1562 $optimized_posts = $total_posts - $posts_with_issues; 1384 1563 $health_score = $total_posts > 0 ? round(($optimized_posts / $total_posts) * 100) : 100; 1385 1564 ?> … … 1391 1570 <div style="text-align: center; margin: 20px 0;"> 1392 1571 <div style="font-size: 48px; font-weight: bold; color: <?php echo $health_score >= 80 ? '#00a32a' : ($health_score >= 60 ? '#dba617' : '#d63638'); ?>;"> 1393 <?php echo $health_score; ?>%1572 <?php echo esc_html($health_score); ?>% 1394 1573 </div> 1395 1574 <p><?php esc_html_e('Overall SEO Health', 'engineo'); ?></p> … … 1399 1578 <p><?php esc_html_e('The Health Score is based on the percentage of your content that meets SEO best practices:', 'engineo'); ?></p> 1400 1579 <ul> 1401 <li><strong><?php esc_html_e('Total Posts/Pages:', 'engineo'); ?></strong> <?php echo $total_posts; ?></li> 1402 <li><strong><?php esc_html_e('Issues Found:', 'engineo'); ?></strong> <?php echo count($health_issues); ?></li> 1403 <li><strong><?php esc_html_e('Optimized Content:', 'engineo'); ?></strong> <?php echo $total_posts - count($health_issues); ?></li> 1580 <li><strong><?php esc_html_e('Total Posts/Pages:', 'engineo'); ?></strong> <?php echo esc_html($total_posts); ?></li> 1581 <li><strong><?php esc_html_e('Posts with Issues:', 'engineo'); ?></strong> <?php echo esc_html($posts_with_issues); ?></li> 1582 <li><strong><?php esc_html_e('Optimized Content:', 'engineo'); ?></strong> <?php echo esc_html($optimized_posts); ?></li> 1583 <li><strong><?php esc_html_e('Issue Types Found:', 'engineo'); ?></strong> <?php echo esc_html(count($health_issues)); ?></li> 1404 1584 </ul> 1405 1585 <p><strong><?php esc_html_e('Formula:', 'engineo'); ?></strong> <?php esc_html_e('(Optimized Content ÷ Total Content) × 100 = Health Score', 'engineo'); ?></p> … … 1416 1596 <div class="card"> 1417 1597 <h2><?php esc_html_e('Issues Found', 'engineo'); ?></h2> 1598 1418 1599 <?php if (empty($health_issues)): ?> 1419 1600 <div class="notice notice-success" style="padding: 20px; border-left: 4px solid #00a32a;"> … … 1432 1613 </p> 1433 1614 </div> 1615 1616 <div style="background: #f8f9fa; padding: 15px; margin: 20px 0; border-radius: 5px; border-left: 4px solid #17a2b8;"> 1617 <h4 style="margin-top: 0; color: #17a2b8;">ℹ️ <?php esc_html_e('What Issues Are Monitored?', 'engineo'); ?></h4> 1618 <p><?php esc_html_e('This section monitors the following SEO and technical issues:', 'engineo'); ?></p> 1619 <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-top: 10px;"> 1620 <div> 1621 <h5 style="margin: 0 0 8px 0; color: #495057;"><?php esc_html_e('Content Issues', 'engineo'); ?></h5> 1622 <ul style="margin: 0; padding-left: 20px; font-size: 14px;"> 1623 <li><?php esc_html_e('Missing meta titles', 'engineo'); ?></li> 1624 <li><?php esc_html_e('Missing meta descriptions', 'engineo'); ?></li> 1625 <li><?php esc_html_e('Empty site title', 'engineo'); ?></li> 1626 <li><?php esc_html_e('Empty site description', 'engineo'); ?></li> 1627 </ul> 1628 </div> 1629 <div> 1630 <h5 style="margin: 0 0 8px 0; color: #495057;"><?php esc_html_e('Technical Issues', 'engineo'); ?></h5> 1631 <ul style="margin: 0; padding-left: 20px; font-size: 14px;"> 1632 <li><?php esc_html_e('Robots.txt not accessible', 'engineo'); ?></li> 1633 <li><?php esc_html_e('XML Sitemap not accessible', 'engineo'); ?></li> 1634 <li><?php esc_html_e('Missing redirects', 'engineo'); ?></li> 1635 <li><?php esc_html_e('Schema markup issues', 'engineo'); ?></li> 1636 </ul> 1637 </div> 1638 </div> 1639 <p style="margin: 15px 0 0 0; font-size: 14px; color: #6c757d;"> 1640 <em><?php esc_html_e('Issues are automatically detected and will appear here when found. Regular monitoring helps maintain optimal SEO performance.', 'engineo'); ?></em> 1641 </p> 1642 </div> 1434 1643 <?php else: ?> 1435 1644 <div class="notice notice-warning"> … … 1439 1648 <li><?php echo esc_html($issue); ?></li> 1440 1649 <?php endforeach; ?> 1650 </ul> 1651 </div> 1652 1653 <div style="background: #fff3cd; padding: 15px; margin: 20px 0; border-radius: 5px; border-left: 4px solid #ffc107;"> 1654 <h4 style="margin-top: 0; color: #856404;">💡 <?php esc_html_e('How to Fix These Issues', 'engineo'); ?></h4> 1655 <p style="margin-bottom: 10px;"><?php esc_html_e('Here are some quick solutions for common issues:', 'engineo'); ?></p> 1656 <ul style="margin: 0; padding-left: 20px; font-size: 14px;"> 1657 <li><strong><?php esc_html_e('Missing meta titles/descriptions:', 'engineo'); ?></strong> <?php esc_html_e('Edit your posts and pages to add SEO meta data using the Engineo meta boxes.', 'engineo'); ?></li> 1658 <li><strong><?php esc_html_e('Empty site title/description:', 'engineo'); ?></strong> <?php esc_html_e('Go to Settings → General and fill in your site title and tagline.', 'engineo'); ?></li> 1659 <li><strong><?php esc_html_e('Robots.txt/Sitemap issues:', 'engineo'); ?></strong> <?php esc_html_e('Check the Robots.txt and XML Sitemap pages in this plugin to ensure they are working correctly.', 'engineo'); ?></li> 1441 1660 </ul> 1442 1661 </div>
Note: See TracChangeset
for help on using the changeset viewer.