Plugin Directory

Changeset 3360737


Ignore:
Timestamp:
09/12/2025 07:04:50 PM (5 months ago)
Author:
justnaveenthings
Message:

Actual Version 1.0.0

Location:
engineo
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • engineo/tags/1.0.0/engineo.php

    r3360735 r3360737  
    1515 *
    1616 * @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
    1723 */
    1824
     
    7581        add_action('wp_loaded', [$this, 'handle_direct_requests'], 1);
    7682        add_action('template_redirect', [$this, 'handle_direct_requests'], 0);
     83       
     84        // Ensure WordPress sitemaps are enabled
     85        add_action('init', [$this, 'enable_wordpress_sitemaps']);
    7786        add_action('wp_enqueue_scripts', [$this, 'enqueue_scripts']);
    7887       
     
    336345        update_post_meta($post_id, '_engineo_schema_type', $schema_type);
    337346        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');
    338351    }
    339352
     
    513526
    514527    /**
     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    /**
    515542     * Add rewrite rules
    516543     */
     
    597624            echo '<priority>0.8</priority>' . "\n";
    598625            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";
    599636            echo '</url>' . "\n";
    600637        }
     
    628665                echo "Allow: /wp-content/uploads/\n";
    629666                echo "Sitemap: " . esc_url(home_url('/sitemap.xml')) . "\n";
     667                echo "Sitemap: " . esc_url(home_url('/wp-sitemap.xml')) . "\n";
    630668            }
    631669        } else {
     
    638676     */
    639677    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';
    641680        $existing_content = '';
    642681       
     
    710749    public function prevent_elementor_conflicts() {
    711750        // Check if this is one of our custom endpoints
     751        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- GET parameters for custom endpoints, not form data
    712752        if (isset($_GET['engineo_llms']) || isset($_GET['engineo_robots']) || isset($_GET['engineo_sitemap'])) {
    713753            // Remove Elementor's template_redirect action completely
     
    769809    public function admin_dashboard() {
    770810        // 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')) {
    772812            $this->add_rewrite_rules();
    773813            flush_rewrite_rules();
     
    775815        }
    776816       
     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       
    777824        $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;
    778856        ?>
    779857        <div class="wrap">
     
    831909                </ul>
    832910               
    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>
    834915            </div>
    835916
     
    852933     */
    853934    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')) {
    855936            $this->save_seo_settings();
    856937        }
     
    9321013
    9331014        $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'] ?? '')),
    9421023        ];
    9431024
     
    9531034        <div class="wrap">
    9541035            <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>
    9581060        </div>
    9591061        <?php
     
    9641066     */
    9651067    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')) {
    9671069            $this->save_robots_txt();
    9681070        }
     
    9711073        $existing_robots = $this->get_existing_robots_txt();
    9721074       
    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            }
    9761093        }
    9771094       
     
    9921109                <textarea name="robots_content" rows="20" cols="80" class="large-text code" placeholder="User-agent: *
    9931110Disallow: /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>
     1111Allow: <?php echo esc_url(admin_url('admin-ajax.php')); ?>
     1112Sitemap: <?php echo esc_url(home_url('/sitemap.xml')); ?>"><?php echo esc_textarea($robots_content); ?></textarea>
    9961113               
    9971114                <?php submit_button(); ?>
     
    10201137        }
    10211138
    1022         $robots_content = sanitize_textarea_field($_POST['robots_content']);
     1139        $robots_content = sanitize_textarea_field(wp_unslash($_POST['robots_content'] ?? ''));
    10231140        update_option('engineo_robots_txt', $robots_content);
    10241141        add_settings_error('engineo_robots', 'settings_updated', __('Robots.txt saved successfully.', 'engineo'), 'updated');
     
    10291146     */
    10301147    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')) {
    10321149            $this->save_llms_txt();
    10331150        }
    10341151       
    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')) {
    10361153            $this->reset_llms_txt_to_default();
    10371154        }
     
    10561173            $llms_content .= "Description: " . get_bloginfo('description') . "\n";
    10571174            $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";
    10591176            $llms_content .= "# Content Guidelines\n";
    10601177            $llms_content .= "This website contains original content that may be used for AI training purposes.\n";
     
    11061223        }
    11071224
    1108         $llms_content = sanitize_textarea_field($_POST['llms_content']);
     1225        $llms_content = sanitize_textarea_field(wp_unslash($_POST['llms_content'] ?? ''));
    11091226        $result = update_option('engineo_llms_txt', $llms_content);
    11101227       
     
    11751292     */
    11761293    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       
    11771302        $issues = [];
    11781303        $total_posts = wp_count_posts()->publish + wp_count_posts('page')->publish;
    11791304       
    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) {
    11941352            $issues[] = sprintf(
    11951353                /* translators: %d: number of posts/pages */
    11961354                __('%d posts/pages missing meta titles', 'engineo'),
    1197                 count($posts_without_titles)
     1355                $missing_titles
    11981356            );
    11991357        }
    12001358       
    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) {
    12151360            $issues[] = sprintf(
    12161361                /* translators: %d: number of posts/pages */
    12171362                __('%d posts/pages missing meta descriptions', 'engineo'),
    1218                 count($posts_without_descriptions)
     1363                $missing_descriptions
    12191364            );
    12201365        }
    12211366       
    12221367        // Check for empty site title
    1223         if (empty(get_bloginfo('name'))) {
     1368        $site_title = get_bloginfo('name');
     1369        if (empty($site_title)) {
    12241370            $issues[] = __('Site title is empty', 'engineo');
    12251371        }
    12261372       
    12271373        // Check for empty site description
    1228         if (empty(get_bloginfo('description'))) {
     1374        $site_description = get_bloginfo('description');
     1375        if (empty($site_description)) {
    12291376            $issues[] = __('Site tagline/description is empty', 'engineo');
    12301377        }
     
    12331380        $robots_url = home_url('/robots.txt');
    12341381        $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) {
    12361384            $issues[] = __('Robots.txt is not accessible', 'engineo');
    12371385        }
     
    12441392        }
    12451393       
     1394        // Cache the results for 2 hours to improve performance
     1395        set_transient($cache_key, $issues, 2 * HOUR_IN_SECONDS);
     1396       
    12461397        return $issues;
    12471398    }
     
    12511402     */
    12521403    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')) {
    12541405            $this->add_redirect();
    12551406        }
    12561407       
    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')) {
    12581409            $this->delete_redirect();
    12591410        }
     
    13201471                                    <form method="post" style="display: inline;">
    13211472                                        <?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); ?>" />
    13231474                                        <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'); ?>');" />
    13241475                                    </form>
     
    13461497       
    13471498        $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)
    13511502        ];
    13521503       
     
    13651516
    13661517        $redirects = get_option('engineo_redirects', []);
    1367         $index = intval($_POST['redirect_index']);
     1518        $index = intval($_POST['redirect_index'] ?? 0);
    13681519       
    13691520        if (isset($redirects[$index])) {
     
    13811532        $health_issues = $this->get_health_check_issues();
    13821533        $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;
    13841563        $health_score = $total_posts > 0 ? round(($optimized_posts / $total_posts) * 100) : 100;
    13851564        ?>
     
    13911570                <div style="text-align: center; margin: 20px 0;">
    13921571                    <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); ?>%
    13941573                    </div>
    13951574                    <p><?php esc_html_e('Overall SEO Health', 'engineo'); ?></p>
     
    13991578                        <p><?php esc_html_e('The Health Score is based on the percentage of your content that meets SEO best practices:', 'engineo'); ?></p>
    14001579                        <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>
    14041584                        </ul>
    14051585                        <p><strong><?php esc_html_e('Formula:', 'engineo'); ?></strong> <?php esc_html_e('(Optimized Content ÷ Total Content) × 100 = Health Score', 'engineo'); ?></p>
     
    14161596            <div class="card">
    14171597                <h2><?php esc_html_e('Issues Found', 'engineo'); ?></h2>
     1598               
    14181599                <?php if (empty($health_issues)): ?>
    14191600                    <div class="notice notice-success" style="padding: 20px; border-left: 4px solid #00a32a;">
     
    14321613                        </p>
    14331614                    </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>
    14341643                <?php else: ?>
    14351644                    <div class="notice notice-warning">
     
    14391648                            <li><?php echo esc_html($issue); ?></li>
    14401649                            <?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>
    14411660                        </ul>
    14421661                    </div>
  • engineo/trunk/engineo.php

    r3360734 r3360737  
    1515 *
    1616 * @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
    1723 */
    1824
     
    7581        add_action('wp_loaded', [$this, 'handle_direct_requests'], 1);
    7682        add_action('template_redirect', [$this, 'handle_direct_requests'], 0);
     83       
     84        // Ensure WordPress sitemaps are enabled
     85        add_action('init', [$this, 'enable_wordpress_sitemaps']);
    7786        add_action('wp_enqueue_scripts', [$this, 'enqueue_scripts']);
    7887       
     
    336345        update_post_meta($post_id, '_engineo_schema_type', $schema_type);
    337346        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');
    338351    }
    339352
     
    513526
    514527    /**
     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    /**
    515542     * Add rewrite rules
    516543     */
     
    597624            echo '<priority>0.8</priority>' . "\n";
    598625            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";
    599636            echo '</url>' . "\n";
    600637        }
     
    628665                echo "Allow: /wp-content/uploads/\n";
    629666                echo "Sitemap: " . esc_url(home_url('/sitemap.xml')) . "\n";
     667                echo "Sitemap: " . esc_url(home_url('/wp-sitemap.xml')) . "\n";
    630668            }
    631669        } else {
     
    638676     */
    639677    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';
    641680        $existing_content = '';
    642681       
     
    710749    public function prevent_elementor_conflicts() {
    711750        // Check if this is one of our custom endpoints
     751        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- GET parameters for custom endpoints, not form data
    712752        if (isset($_GET['engineo_llms']) || isset($_GET['engineo_robots']) || isset($_GET['engineo_sitemap'])) {
    713753            // Remove Elementor's template_redirect action completely
     
    769809    public function admin_dashboard() {
    770810        // 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')) {
    772812            $this->add_rewrite_rules();
    773813            flush_rewrite_rules();
     
    775815        }
    776816       
     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       
    777824        $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;
    778856        ?>
    779857        <div class="wrap">
     
    831909                </ul>
    832910               
    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>
    834915            </div>
    835916
     
    852933     */
    853934    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')) {
    855936            $this->save_seo_settings();
    856937        }
     
    9321013
    9331014        $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'] ?? '')),
    9421023        ];
    9431024
     
    9531034        <div class="wrap">
    9541035            <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>
    9581060        </div>
    9591061        <?php
     
    9641066     */
    9651067    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')) {
    9671069            $this->save_robots_txt();
    9681070        }
     
    9711073        $existing_robots = $this->get_existing_robots_txt();
    9721074       
    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            }
    9761093        }
    9771094       
     
    9921109                <textarea name="robots_content" rows="20" cols="80" class="large-text code" placeholder="User-agent: *
    9931110Disallow: /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>
     1111Allow: <?php echo esc_url(admin_url('admin-ajax.php')); ?>
     1112Sitemap: <?php echo esc_url(home_url('/sitemap.xml')); ?>"><?php echo esc_textarea($robots_content); ?></textarea>
    9961113               
    9971114                <?php submit_button(); ?>
     
    10201137        }
    10211138
    1022         $robots_content = sanitize_textarea_field($_POST['robots_content']);
     1139        $robots_content = sanitize_textarea_field(wp_unslash($_POST['robots_content'] ?? ''));
    10231140        update_option('engineo_robots_txt', $robots_content);
    10241141        add_settings_error('engineo_robots', 'settings_updated', __('Robots.txt saved successfully.', 'engineo'), 'updated');
     
    10291146     */
    10301147    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')) {
    10321149            $this->save_llms_txt();
    10331150        }
    10341151       
    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')) {
    10361153            $this->reset_llms_txt_to_default();
    10371154        }
     
    10561173            $llms_content .= "Description: " . get_bloginfo('description') . "\n";
    10571174            $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";
    10591176            $llms_content .= "# Content Guidelines\n";
    10601177            $llms_content .= "This website contains original content that may be used for AI training purposes.\n";
     
    11061223        }
    11071224
    1108         $llms_content = sanitize_textarea_field($_POST['llms_content']);
     1225        $llms_content = sanitize_textarea_field(wp_unslash($_POST['llms_content'] ?? ''));
    11091226        $result = update_option('engineo_llms_txt', $llms_content);
    11101227       
     
    11751292     */
    11761293    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       
    11771302        $issues = [];
    11781303        $total_posts = wp_count_posts()->publish + wp_count_posts('page')->publish;
    11791304       
    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) {
    11941352            $issues[] = sprintf(
    11951353                /* translators: %d: number of posts/pages */
    11961354                __('%d posts/pages missing meta titles', 'engineo'),
    1197                 count($posts_without_titles)
     1355                $missing_titles
    11981356            );
    11991357        }
    12001358       
    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) {
    12151360            $issues[] = sprintf(
    12161361                /* translators: %d: number of posts/pages */
    12171362                __('%d posts/pages missing meta descriptions', 'engineo'),
    1218                 count($posts_without_descriptions)
     1363                $missing_descriptions
    12191364            );
    12201365        }
    12211366       
    12221367        // Check for empty site title
    1223         if (empty(get_bloginfo('name'))) {
     1368        $site_title = get_bloginfo('name');
     1369        if (empty($site_title)) {
    12241370            $issues[] = __('Site title is empty', 'engineo');
    12251371        }
    12261372       
    12271373        // Check for empty site description
    1228         if (empty(get_bloginfo('description'))) {
     1374        $site_description = get_bloginfo('description');
     1375        if (empty($site_description)) {
    12291376            $issues[] = __('Site tagline/description is empty', 'engineo');
    12301377        }
     
    12331380        $robots_url = home_url('/robots.txt');
    12341381        $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) {
    12361384            $issues[] = __('Robots.txt is not accessible', 'engineo');
    12371385        }
     
    12441392        }
    12451393       
     1394        // Cache the results for 2 hours to improve performance
     1395        set_transient($cache_key, $issues, 2 * HOUR_IN_SECONDS);
     1396       
    12461397        return $issues;
    12471398    }
     
    12511402     */
    12521403    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')) {
    12541405            $this->add_redirect();
    12551406        }
    12561407       
    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')) {
    12581409            $this->delete_redirect();
    12591410        }
     
    13201471                                    <form method="post" style="display: inline;">
    13211472                                        <?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); ?>" />
    13231474                                        <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'); ?>');" />
    13241475                                    </form>
     
    13461497       
    13471498        $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)
    13511502        ];
    13521503       
     
    13651516
    13661517        $redirects = get_option('engineo_redirects', []);
    1367         $index = intval($_POST['redirect_index']);
     1518        $index = intval($_POST['redirect_index'] ?? 0);
    13681519       
    13691520        if (isset($redirects[$index])) {
     
    13811532        $health_issues = $this->get_health_check_issues();
    13821533        $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;
    13841563        $health_score = $total_posts > 0 ? round(($optimized_posts / $total_posts) * 100) : 100;
    13851564        ?>
     
    13911570                <div style="text-align: center; margin: 20px 0;">
    13921571                    <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); ?>%
    13941573                    </div>
    13951574                    <p><?php esc_html_e('Overall SEO Health', 'engineo'); ?></p>
     
    13991578                        <p><?php esc_html_e('The Health Score is based on the percentage of your content that meets SEO best practices:', 'engineo'); ?></p>
    14001579                        <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>
    14041584                        </ul>
    14051585                        <p><strong><?php esc_html_e('Formula:', 'engineo'); ?></strong> <?php esc_html_e('(Optimized Content ÷ Total Content) × 100 = Health Score', 'engineo'); ?></p>
     
    14161596            <div class="card">
    14171597                <h2><?php esc_html_e('Issues Found', 'engineo'); ?></h2>
     1598               
    14181599                <?php if (empty($health_issues)): ?>
    14191600                    <div class="notice notice-success" style="padding: 20px; border-left: 4px solid #00a32a;">
     
    14321613                        </p>
    14331614                    </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>
    14341643                <?php else: ?>
    14351644                    <div class="notice notice-warning">
     
    14391648                            <li><?php echo esc_html($issue); ?></li>
    14401649                            <?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>
    14411660                        </ul>
    14421661                    </div>
Note: See TracChangeset for help on using the changeset viewer.