Plugin Directory

Changeset 3445423


Ignore:
Timestamp:
01/23/2026 08:57:16 AM (4 weeks ago)
Author:
jamesmackie
Message:

Remove all comments from codebase (keep only phpcs:ignore and plugin header)

Location:
ezy-ai
Files:
26 edited

Legend:

Unmodified
Added
Removed
  • ezy-ai/tags/1.0.0/admin/connection.php

    r3440881 r3445423  
    11<?php
    2 /**
    3  * EZY AI Connection Settings Page
    4  *
    5  * @package EZY_AI
    6  * @since 1.0.0
    7  */
    82
    93if (!defined('ABSPATH')) {
     
    3125        }
    3226    } catch (Exception $e) {
    33         // Swallow verification errors silently
    3427    } catch (Error $e) {
    35         // Swallow verification fatal errors silently
    3628    }
    3729}
     
    8476}
    8577
    86 // Get progress data
    8778global $ezy_ai_webhook;
    8879if (!$ezy_ai_webhook && $is_connected) {
     
    9788$ezy_score = $progress_percentage;
    9889
    99 // Get site URL for display
    10090$site_url = wp_parse_url(get_site_url(), PHP_URL_HOST);
    10191
    102 // Initialize AI visits data - Always show last 7 days
    10392$ai_visits_data = array();
    10493$ai_visits_labels = array();
     
    11099$chart_title = 'AI visits in the last 7 days';
    111100
    112 // Generate last 7 days labels
    113101$last_7_days_labels = array();
    114102for ($i = 6; $i >= 0; $i--) {
     
    116104}
    117105
    118 // Initialize with zeros for all 7 days
    119106$ai_visits_data = array_fill(0, 7, 0);
    120107$ai_visits_labels = $last_7_days_labels;
    121108
    122 // Only fetch data if connected
    123109if ($is_connected) {
    124110    $token = $ezy_ai_auth->get_connection_token();
    125111   
    126112    if ($token) {
    127         // Fetch agent stats (total visits, unique agents)
    128113        $stats_url = EZY_AI_API_Client::API_BASE_URL . '/integrations/' . $token . '/agent-stats?days=7';
    129114        $stats_response = wp_remote_get($stats_url, array(
     
    145130        }
    146131       
    147         // Fetch agent analytics (hourly distribution for chart) - always 7 days
    148132        $api_url = EZY_AI_API_Client::API_BASE_URL . '/integrations/' . $token . '/agent-analytics?timeRange=7d';
    149133        $response = wp_remote_get($api_url, array(
     
    160144                $hourly = $body['data']['hourlyDistribution'];
    161145               
    162                 // Group hourly data by day and map to our 7-day labels
    163146                $daily_visits = array();
    164147               
     
    176159                }
    177160               
    178                 // Map to our preset 7-day labels
    179161                foreach ($last_7_days_labels as $index => $label) {
    180162                    if (isset($daily_visits[$label])) {
     
    187169}
    188170
    189 // Labels are already set to last 7 days, data defaults to zeros
    190 
    191 // Calculate max value for scaling (minimum 1 to avoid division by zero)
    192171$max_visits = max(1, max($ai_visits_data));
    193172$num_points = count($ai_visits_data);
     
    453432                                    $is_active = $i < $active_segments;
    454433                                   
    455                                     // Tapered segments: slim at center, wide at outer edge
    456434                                    $angle_spread_inner = 3.5;
    457435                                    $angle_spread_outer = 8;
     
    459437                                    $outer_radius = 92;
    460438                                   
    461                                     // Calculate 4 points for tapered blade shape
    462439                                    $inner_start_angle = deg2rad($angle - $angle_spread_inner);
    463440                                    $inner_end_angle = deg2rad($angle + $angle_spread_inner);
  • ezy-ai/tags/1.0.0/ezy-ai-plugin.php

    r3445408 r3445423  
    3535require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-blogs.php';
    3636require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-tracking.php';
    37 
    38 // Activation and deactivation hooks will be registered at the end of the file
    3937
    4038global $ezy_ai_auth;
     
    152150    private function init_tracking() {
    153151        global $ezy_ai_auth;
    154         // Only initialize tracking if connected
    155152        if ($ezy_ai_auth && $ezy_ai_auth->is_connected()) {
    156153            new EZY_AI_Tracking();
     
    159156   
    160157    public function add_admin_menu() {
    161         // Custom SVG icon - pre-encoded base64, uses fill="black" so WP can recolor it
    162         // This is the EZY "E" logo icon
    163158        $icon_base64 = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBmaWxsPSJibGFjayIgZD0iTTAgMGgxMnY0SDR2NGg4djRINHY0aDEydjRIMHoiLz48cGF0aCBmaWxsPSJibGFjayIgZD0iTTEyIDBoNHY4aDR2NEgxNnY4aC00di04SDh2LTRoNHoiLz48L3N2Zz4=';
    164159       
     
    368363            $ezy_ai_auth->verify_connection();
    369364           
    370             // Initialize tracking after successful connection
    371365            $this->init_tracking();
    372366        } elseif ($ezy_ai_auth->get_connection_token()) {
     
    482476register_deactivation_hook(__FILE__, 'ezy_ai_deactivate');
    483477function ezy_ai_deactivate() {
    484     // Deactivate component classes
    485478    EZY_AI_LLMS::deactivate();
    486479    EZY_AI_Facts::deactivate();
    487480    EZY_AI_Robots::deactivate();
    488481   
    489     // Unschedule health check cron
    490482    $timestamp = wp_next_scheduled('ezy_ai_connection_health_check');
    491483    if ($timestamp) {
     
    532524   
    533525    wp_cache_flush();
    534    
    535     // swallow debug log
    536526}
  • ezy-ai/tags/1.0.0/includes/class-ezy-ai-auth.php

    r3440881 r3445423  
    168168        delete_transient('ezy_ai_just_connected');
    169169       
    170         // swallow debug log
    171        
    172170        return true;
    173171    }
  • ezy-ai/tags/1.0.0/includes/class-ezy-ai-blogs.php

    r3444216 r3445423  
    1414    private $auth;
    1515
    16     // allowed tags for full html documents
    1716    private function get_allowed_html_tags()
    1817    {
     
    4948    }
    5049
    51     // extract ld+json scripts before wp_kses so schema is not stripped, then restore after
    5250    private function sanitize_full_html_preserving_ldjson($html)
    5351    {
     
    8280    public function register_blog_routes()
    8381    {
    84         // Single blog post: /ezy/blogs/slug
    8582        add_rewrite_rule(
    8683            '^ezy/blogs/([^/]+)/?$',
     
    8986        );
    9087
    91         // Blog listing: /ezy/blogs
    9288        add_rewrite_rule(
    9389            '^ezy/blogs/?$',
     
    107103        $is_listing = get_query_var('ezy_blog_listing');
    108104
    109         // Handle Listing Page
    110105        if ($is_listing) {
    111106            $this->render_blog_listing();
     
    113108        }
    114109
    115         // Handle Single Blog Page
    116110        if (!$slug) {
    117111            $request_uri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';
     
    172166    private function get_listing_template()
    173167    {
    174         // Check cache first
    175168        $cached_template = get_transient('ezy_ai_listing_template');
    176169        if ($cached_template) {
     
    178171        }
    179172
    180         // Fetch from API
    181173        if (!$this->auth || !$this->auth->is_connected()) {
    182174            return null;
     
    192184
    193185            if (!is_wp_error($response) && !empty($response)) {
    194                 // Cache for 24 hours
    195186                set_transient('ezy_ai_listing_template', $response, 24 * HOUR_IN_SECONDS);
    196187                return $response;
    197188            }
    198189        } catch (Exception $e) {
    199             // swallow debug log
    200190        }
    201191
     
    208198        nocache_headers();
    209199
    210         // Enqueue blog styles
    211200        wp_enqueue_style(
    212201            'ezy-ai-blogs-style',
     
    216205        );
    217206
    218         // Simple, clean listing page
    219207        ?>
    220208        <!DOCTYPE html>
     
    332320        $result = update_option($option_name, $formatted_html, false);
    333321
    334         // Update Index with published_at date
    335322        $this->update_blog_index($slug, $title, $excerpt, $published_at);
    336323
    337324        if ($result) {
    338             // swallow debug log
    339 
    340325            flush_rewrite_rules(false);
    341         } else {
    342             // swallow debug log
    343326        }
    344327
     
    350333        $index = get_option(self::BLOG_INDEX_OPTION, array());
    351334
    352         // Remove existing entry if present
    353335        $index = array_filter($index, function ($item) use ($slug) {
    354336            return $item['slug'] !== $slug;
    355337        });
    356338
    357         // Use provided published_at or fallback to current time
    358         // Convert ISO 8601 format to MySQL datetime if needed
    359339        $date = $published_at;
    360340        if ($date) {
    361             // Convert ISO 8601 to MySQL datetime format
    362341            $timestamp = strtotime($date);
    363342            if ($timestamp !== false) {
     
    370349        }
    371350
    372         // Add new entry
    373351        $index[] = array(
    374352            'slug' => $slug,
     
    410388        $result = delete_option($option_name);
    411389
    412         // Remove from index
    413390        $index = get_option(self::BLOG_INDEX_OPTION, array());
    414391        $index = array_filter($index, function ($item) use ($slug) {
  • ezy-ai/tags/1.0.0/includes/class-ezy-ai-facts.php

    r3440881 r3445423  
    2424    }
    2525
    26     /**
    27      * Setup rewrite rules to ensure facts.json goes through WordPress
    28      */
    2926    public function setup_facts_json_override() {
    3027        add_rewrite_rule('^facts\.json$', 'index.php?ezy_facts_json=1', 'top');
     
    3633    }
    3734
    38     /**
    39      * Remove any physical facts.json files created by other plugins
    40      * Only runs if EZY AI has facts.json content configured
    41      */
    4235    public function cleanup_competing_facts_files() {
    43         // Only cleanup if we have content to serve
    4436        if (!$this->get_facts_json_content()) {
    4537            return;
     
    6860    }
    6961
    70     /**
    71      * Output facts.json content
    72      * Handles both standard requests and rewritten requests
    73      */
    7462    public function output_facts_json() {
    7563        if (get_query_var('ezy_facts_json')) {
     
    8876    }
    8977
    90     /**
    91      * Actually serve the facts.json content
    92      */
    9378    private function serve_facts_json() {
    9479        $facts_content = $this->get_facts_json_content();
     
    172157    }
    173158
    174     /**
    175      * Flush rewrite rules if needed
    176      */
    177159    private function maybe_flush_rewrite_rules() {
    178160        $rules = get_option('rewrite_rules');
     
    182164    }
    183165
    184     /**
    185      * Static method to flush rewrite rules on plugin activation
    186      */
    187166    public static function activate() {
    188167        add_rewrite_rule('^facts\.json$', 'index.php?ezy_facts_json=1', 'top');
     
    190169    }
    191170
    192     /**
    193      * Static method to clean up on plugin deactivation
    194      */
    195171    public static function deactivate() {
    196172        flush_rewrite_rules(false);
  • ezy-ai/tags/1.0.0/includes/class-ezy-ai-faqs.php

    r3440881 r3445423  
    4242        nocache_headers();
    4343       
    44         // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    4544        echo $faqs_content;
    4645       
     
    5958        }
    6059       
    61         // Return content even if empty (for clearing)
    6260        if (isset($response['content'])) {
    6361            return $response['content'];
     
    7977        $content = $this->fetch_faqs_html_from_api($token);
    8078       
    81         // If content is explicitly empty string, clear FAQs
    8279        if ($content === '') {
    8380            return $this->clear_faqs_html();
    8481        }
    8582       
    86         // If content exists, update it
    8783        if ($content) {
    8884            return $this->update_faqs_html($content);
  • ezy-ai/tags/1.0.0/includes/class-ezy-ai-llms.php

    r3445412 r3445423  
    2929    }
    3030
    31     /**
    32      * Block Yoast SEO from creating llms.txt file
    33      * Returns a non-writable path so Yoast fails silently
    34      *
    35      * @param string $path Original path from Yoast
    36      * @return string Modified path that prevents file creation
    37      */
    3831    public function block_yoast_llms_file($path) {
    3932        if ($this->get_llms_txt_content()) {
     
    4336    }
    4437
    45     /**
    46      * Setup rewrite rules to ensure llms.txt goes through WordPress
    47      */
    4838    public function setup_llms_txt_override() {
    4939        add_rewrite_rule('^llms\.txt$', 'index.php?ezy_llms_txt=1', 'top');
     
    5646    }
    5747
    58     /**
    59      * Remove any physical llms.txt files created by other plugins (like Yoast)
    60      * Only runs if EZY AI has llms.txt content configured
    61      */
    6248    public function cleanup_competing_llms_files() {
    6349        if (!$this->get_llms_txt_content()) {
     
    9783    }
    9884
    99     /**
    100      * Detect if site is hosted on WP Engine
    101      */
    10285    private function is_wp_engine() {
    10386        return (defined('WPE_APIKEY') || isset($_SERVER['IS_WPE']) || isset($_SERVER['IS_WPE_SNAPSHOT']));
    10487    }
    10588
    106     /**
    107      * Output llms.txt content
    108      * Handles both standard requests and rewritten requests
    109      * Redirects to trailing slash on WP Engine to bypass Nginx blocking
    110      */
    11189    public function output_llms_txt() {
    11290        if (get_query_var('ezy_llms_txt')) {
     
    143121    }
    144122
    145     /**
    146      * Actually serve the llms.txt content
    147      */
    148123    private function serve_llms_txt() {
    149124        $llms_content = $this->get_llms_txt_content();
     
    167142    }
    168143
    169     /**
    170      * Actually serve the llms-full.txt content
    171      */
    172144    private function serve_llms_full_txt() {
    173145        $llms_full_content = $this->get_llms_full_txt_content();
     
    278250    }
    279251
    280     /**
    281      * Flush rewrite rules if needed
    282      * Only flushes if our rule doesn't exist yet
    283      */
    284252    private function maybe_flush_rewrite_rules() {
    285253        $rules = get_option('rewrite_rules');
     
    289257    }
    290258
    291     /**
    292      * Static method to flush rewrite rules on plugin activation
    293      */
    294259    public static function activate() {
    295260        add_rewrite_rule('^llms\.txt$', 'index.php?ezy_llms_txt=1', 'top');
     
    298263    }
    299264
    300     /**
    301      * Static method to clean up on plugin deactivation
    302      */
    303265    public static function deactivate() {
    304266        flush_rewrite_rules(false);
  • ezy-ai/tags/1.0.0/includes/class-ezy-ai-meta-descriptions.php

    r3440881 r3445423  
    11<?php
    2 /**
    3  * EZY AI Meta Descriptions Handler
    4  *
    5  * Handles meta description injection with guaranteed priority over Yoast SEO.
    6  * Uses multiple strategies to ensure EZY meta descriptions take precedence.
    7  */
    82
    93if (!defined('ABSPATH')) {
     
    1913    private $auth;
    2014   
    21     /**
    22      * Cache for current page meta description to avoid repeated lookups
    23      */
    2415    private $current_page_meta_cache = null;
    2516   
    26     /**
    27      * Flag to track if we've already determined meta for this request
    28      */
    2917    private $meta_checked = false;
    3018
     
    3826    }
    3927
    40     /**
    41      * Inject EZY meta description into wp_head
    42      * Runs at priority 1 to appear before most other meta tags
    43      */
    4428    public function inject_meta_description() {
    4529        $meta_description = $this->get_ezy_meta_for_current_page();
     
    5337    }
    5438
    55     /**
    56      * Comprehensive Yoast meta description blocking
    57      * Uses multiple filters and high priority to ensure EZY meta descriptions take precedence
    58      */
    5939    private function disable_yoast_meta_interference() {
    6040        add_filter('wpseo_metadesc', array($this, 'override_yoast_meta_description'), 1, 2);
     
    6747    }
    6848   
    69     /**
    70      * Get cached EZY meta description for current page
    71      */
    7249    private function get_ezy_meta_for_current_page() {
    7350        if ($this->meta_checked) {
     
    8966    }
    9067   
    91     /**
    92      * Check if EZY has meta description for current page
    93      */
    9468    private function has_ezy_meta_for_current_page() {
    9569        return !empty($this->get_ezy_meta_for_current_page());
    9670    }
    9771
    98     /**
    99      * Primary override: Replace Yoast's meta description with EZY's
    100      */
    10172    public function override_yoast_meta_description($description, $presentation = null) {
    10273        $ezy_meta = $this->get_ezy_meta_for_current_page();
     
    10980    }
    11081   
    111     /**
    112      * Late override: Safety net to ensure EZY meta description is used
    113      */
    11482    public function override_yoast_meta_description_late($description, $presentation = null) {
    11583        return $this->override_yoast_meta_description($description, $presentation);
    11684    }
    11785
    118     /**
    119      * Override Yoast's Open Graph description
    120      */
    12186    public function override_yoast_og_description($description, $presentation = null) {
    12287        $ezy_meta = $this->get_ezy_meta_for_current_page();
     
    12994    }
    13095   
    131     /**
    132      * Late override for Open Graph description
    133      */
    13496    public function override_yoast_og_description_late($description, $presentation = null) {
    13597        return $this->override_yoast_og_description($description, $presentation);
    13698    }
    13799
    138     /**
    139      * Override Yoast's Twitter description
    140      */
    141100    public function override_yoast_twitter_description($description, $presentation = null) {
    142101        $ezy_meta = $this->get_ezy_meta_for_current_page();
     
    149108    }
    150109   
    151     /**
    152      * Late override for Twitter description
    153      */
    154110    public function override_yoast_twitter_description_late($description, $presentation = null) {
    155111        return $this->override_yoast_twitter_description($description, $presentation);
    156112    }
    157113   
    158     /**
    159      * Override Yoast's frontend presentation object
    160      * This ensures the meta description is set at the presentation level
    161      */
    162114    public function override_yoast_presentation($presentation, $context = null) {
    163115        $ezy_meta = $this->get_ezy_meta_for_current_page();
  • ezy-ai/tags/1.0.0/includes/class-ezy-ai-robots.php

    r3445411 r3445423  
    2727    }
    2828
    29     /**
    30      * Block Yoast SEO's robots.txt modifications when EZY has content
    31      * This runs early (priority 1) to capture the original robots.txt
    32      * before Yoast modifies it
    33      *
    34      * @param string $output The robots.txt output
    35      * @param bool $public Whether the site is public
    36      * @return string The robots.txt output
    37      */
    3829    public function block_yoast_robots_output($output, $public) {
    3930        return $output;
    4031    }
    4132
    42     /**
    43      * Filter the robots.txt output - runs AFTER Yoast
    44      * Completely replaces content with EZY's robots.txt if we have content
    45      *
    46      * @param string $output The robots.txt output (possibly modified by Yoast)
    47      * @param bool $public Whether the site is public
    48      * @return string The filtered robots.txt output
    49      */
    5033    public function filter_robots_txt($output, $public) {
    5134        $robots_content = $this->get_robots_txt_content();
     
    5740    }
    5841
    59     /**
    60      * Output robots.txt content via do_robots action
    61      * This is a fallback that runs before WordPress's default output
    62      */
    6342    public function output_robots_txt() {
    6443        $robots_content = $this->get_robots_txt_content();
     
    7756    }
    7857
    79     /**
    80      * Setup rewrite rule for robots.txt to handle physical file override (WP Engine fix)
    81      */
    8258    public function setup_robots_txt_rewrite() {
    8359        add_rewrite_rule('^robots\.txt$', 'index.php?ezy_robots_txt=1', 'top');
    8460    }
    8561
    86     /**
    87      * Add custom query var for robots.txt rewrite
    88      */
    8962    public function add_robots_query_var($vars) {
    9063        $vars[] = 'ezy_robots_txt';
     
    9265    }
    9366
    94     /**
    95      * Serve robots.txt via rewrite rule (bypasses physical file)
    96      */
    9767    public function serve_robots_via_rewrite() {
    9868        if (!get_query_var('ezy_robots_txt')) {
     
    12191    }
    12292
    123     /**
    124      * Remove any physical robots.txt files when EZY has content
    125      * Note: Only removes files that appear to be auto-generated (not manually created)
    126      */
    12793    public function cleanup_competing_robots_files() {
    12894        if (!$this->get_robots_txt_content()) {
     
    147113        foreach ($paths_to_check as $file_path) {
    148114            if (file_exists($file_path) && wp_is_writable($file_path)) {
    149                 // Check if this is a Yoast-generated file or generic auto-generated
    150115                $content = @file_get_contents($file_path);
    151116                if ($content !== false) {
    152                     // Check for Yoast signature or generic WordPress robots
    153117                    $is_auto_generated = (
    154118                        strpos($content, 'Yoast') !== false ||
    155119                        strpos($content, '# START YOAST BLOCK') !== false ||
    156                         // Generic WordPress default robots.txt pattern
    157120                        (strpos($content, 'User-agent: *') !== false &&
    158121                         strpos($content, 'Disallow: /wp-admin/') !== false &&
     
    223186    }
    224187
    225     /**
    226      * Static method for plugin activation
    227      */
    228188    public static function activate() {
    229189        add_rewrite_rule('^robots\.txt$', 'index.php?ezy_robots_txt=1', 'top');
     
    231191    }
    232192
    233     /**
    234      * Static method for plugin deactivation 
    235      */
    236193    public static function deactivate() {
    237         // Nothing special needed for robots.txt
    238194    }
    239195
  • ezy-ai/tags/1.0.0/includes/class-ezy-ai-schema.php

    r3440881 r3445423  
    11<?php
    2 /**
    3  * EZY AI Schema.org Markup Handler
    4  *
    5  * Handles schema.org JSON-LD injection with guaranteed priority over Yoast SEO.
    6  * Uses multiple strategies to ensure EZY schemas take precedence.
    7  */
    82
    93if (!defined('ABSPATH')) {
     
    2014    private $auth;
    2115   
    22     /**
    23      * Cache for current page schemas to avoid repeated lookups
    24      */
    2516    private $current_page_schemas_cache = null;
    2617   
    27     /**
    28      * Flag to track if we've already determined schemas for this request
    29      */
    3018    private $schemas_checked = false;
    3119
     
    3826    }
    3927
    40     /**
    41      * Comprehensive Yoast schema blocking
    42      * Uses multiple filters and strategies to ensure EZY schemas take priority
    43      */
    4428    private function disable_yoast_schema_interference() {
    4529        add_filter('wpseo_json_ld_output', array($this, 'block_yoast_jsonld_output'), 1, 1);
     
    5842    }
    5943
    60     /**
    61      * Primary blocking method: Return FALSE to completely disable Yoast's JSON-LD output
    62      * This is the most effective method based on Yoast's schema-presenter.php
    63      */
    6444    public function block_yoast_jsonld_output($data) {
    6545        if ($this->has_ezy_schemas_for_current_page()) {
     
    7050    }
    7151
    72     /**
    73      * Secondary blocking method: Clear the schema graph
    74      */
    7552    public function block_yoast_schema_graph($graph, $context) {
    7653        if ($this->has_ezy_schemas_for_current_page()) {
     
    8158    }
    8259   
    83     /**
    84      * Block individual schema pieces when EZY has schemas for the page
    85      */
    8660    public function block_yoast_schema_piece($is_needed) {
    8761        if ($this->has_ezy_schemas_for_current_page()) {
     
    9266    }
    9367   
    94     /**
    95      * Block the entire graph pieces array
    96      */
    9768    public function block_yoast_graph_pieces($pieces, $context) {
    9869        if ($this->has_ezy_schemas_for_current_page()) {
     
    10374    }
    10475   
    105     /**
    106      * Check if EZY has schemas for the current page
    107      * Uses caching to avoid repeated database lookups
    108      */
    10976    private function has_ezy_schemas_for_current_page() {
    11077        if ($this->schemas_checked) {
     
    12693    }
    12794
    128     /**
    129      * Inject EZY schema markup into wp_head
    130      * Runs at priority 1 to appear before most other scripts
    131      */
    13295    public function inject_schema_markup() {
    13396        if (!$this->schemas_checked) {
     
    239202        }
    240203
    241         // Use wp_print_inline_script_tag for safe JSON-LD output (WordPress 5.7+)
    242         // This function handles escaping internally and is the recommended approach
    243204        echo "\n<!-- EZY.AI Schema.org Markup -->\n";
    244205        wp_print_inline_script_tag(
  • ezy-ai/tags/1.0.0/includes/class-ezy-ai-tracking.php

    r3440881 r3445423  
    1717        $this->auth = $ezy_ai_auth;
    1818
    19         // Hook into WordPress request lifecycle
    20         // Use template_redirect for frontend requests only
    2119        add_action('template_redirect', array($this, 'track_visit'), 1);
    2220    }
     
    2422    public function track_visit()
    2523    {
    26         // Only track if connected
    2724        if (!$this->auth || !$this->auth->is_connected()) {
    2825            return;
    2926        }
    3027
    31         // Skip tracking for admin, AJAX, cron, and REST API requests
    3228        if (is_admin() || wp_doing_ajax() || wp_doing_cron() || (defined('REST_REQUEST') && REST_REQUEST)) {
    3329            return;
    3430        }
    3531
    36         // Skip tracking for non-GET requests (except HEAD)
    3732        $method = isset($_SERVER['REQUEST_METHOD']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_METHOD'])) : 'GET';
    3833        if ($method !== 'GET' && $method !== 'HEAD') {
     
    4035        }
    4136
    42         // Get connection token
    4337        $token = $this->auth->get_connection_token();
    4438        if (!$token) {
     
    4640        }
    4741
    48         // Extract visit data
    4942        $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '';
    5043        $ip_address = $this->get_client_ip();
    5144        $url = $this->get_current_url();
    5245
    53         // Extract all headers for signature verification
    5446        $headers = $this->get_all_headers();
    5547
    56         // Send tracking request asynchronously (non-blocking)
    5748        $this->send_tracking_request($token, $user_agent, $ip_address, $url, $headers);
    5849    }
     
    7263                $ip = sanitize_text_field(wp_unslash($_SERVER[$key]));
    7364
    74                 // Handle comma-separated IPs (X-Forwarded-For)
    7565                if (strpos($ip, ',') !== false) {
    7666                    $ips = explode(',', $ip);
     
    7868                }
    7969
    80                 // Validate IP address
    8170                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
    8271                    return $ip;
     
    121110        );
    122111
    123         // Get all HTTP headers
    124112        if (function_exists('getallheaders')) {
    125113            $all_headers = getallheaders();
     
    132120            }
    133121        } else {
    134             // Fallback for servers without getallheaders()
    135122            foreach ($_SERVER as $key => $value) {
    136123                if (strpos($key, 'HTTP_') === 0) {
     
    143130        }
    144131
    145         // Ensure signature headers are included (case-insensitive)
    146132        $signature_headers = array('signature-agent', 'signature-input', 'signature');
    147133        foreach ($signature_headers as $sig_header) {
     
    157143    private function send_tracking_request($token, $user_agent, $ip_address, $url, $headers)
    158144    {
    159         // Use wp_remote_post for async request (non-blocking)
    160145        $api_url = EZY_AI_API_Client::API_BASE_URL . '/track';
    161146
     
    174159            )),
    175160            'timeout' => 5,
    176             'blocking' => false, // Non-blocking request
     161            'blocking' => false,
    177162            'sslverify' => true,
    178163        );
    179164
    180         // Fire and forget - don't wait for response
    181165        wp_remote_post($api_url, $request_args);
    182166    }
  • ezy-ai/tags/1.0.0/includes/class-ezy-ai-webhook.php

    r3444216 r3445423  
    123123        $updated_at = $request->get_param('updated_at');
    124124
    125         // swallow debug log
    126 
    127125        $result = false;
    128126       
     
    308306        }
    309307
    310         // If content is explicitly empty string, clear FAQs
    311308        if ($content === '') {
    312309            return $ezy_ai_faqs->clear_faqs_html();
    313310        }
    314311
    315         // If content is null or false, return false
    316312        if (empty($content)) {
    317313            return false;
  • ezy-ai/tags/1.0.0/includes/class-ezy-ai-widget-fetch.php

    r3440881 r3445423  
    124124
    125125    private function fetch_robots() {
    126         // Always fetch live robots.txt from the website
    127126        $site_url = get_site_url();
    128127        $robots_url = rtrim($site_url, '/') . '/robots.txt';
     
    155154
    156155    private function fetch_llms() {
    157         // Always fetch live llms.txt from the website
    158156        $site_url = get_site_url();
    159157        $llms_url = rtrim($site_url, '/') . '/llms.txt';
     
    204202
    205203    private function fetch_schema($page_urls = null) {
    206         // Always fetch live schemas from pages
    207204        $live_schemas = array();
    208205        $total_pages_requested = 0;
     
    239236
    240237    private function fetch_meta($page_urls = null) {
    241         // Return ALL cached meta descriptions - let the backend do the filtering
    242         // This is more reliable than trying to match URLs in WordPress
    243238        global $ezy_ai_meta_descriptions;
    244239        if (!$ezy_ai_meta_descriptions) {
     
    246241        }
    247242
    248         // Get all cached meta descriptions
    249243        $all_cached = get_option(EZY_AI_Meta_Descriptions::META_DESCRIPTIONS_OPTION);
    250244       
     
    277271
    278272    private function fetch_sitemap() {
    279         // Always fetch live sitemap.xml from the website
    280273        $site_url = get_site_url();
    281274        $sitemap_url = rtrim($site_url, '/') . '/sitemap.xml';
     
    316309        $updated_at = get_option('ezy_ai_facts_json_updated_at');
    317310
    318         // If no stored content, try to fetch live facts.json from the website
    319311        if (empty($content)) {
    320312            $site_url = get_site_url();
     
    400392        $schemas = array();
    401393       
    402         // Extract JSON-LD schemas from <script type="application/ld+json">
    403394        preg_match_all('/<script[^>]*type=["\']application\/ld\+json["\'][^>]*>(.*?)<\/script>/is', $html, $matches);
    404395       
     
    431422        }
    432423       
    433         // Extract meta description from <meta name="description">
    434424        $meta_description = '';
    435425        $is_ezy_generated = false;
    436426       
    437         // Try to find meta description tag
    438427        if (preg_match('/<meta[^>]*name=["\']description["\'][^>]*>/i', $html, $matches, PREG_OFFSET_CAPTURE)) {
    439428            $full_match = $matches[0][0];
    440429            $match_index = $matches[0][1];
    441430           
    442             // Extract content value
    443431            if (preg_match('/content=["\']([^"\']*)["\']/i', $full_match, $content_matches)) {
    444432                $meta_description = html_entity_decode($content_matches[1], ENT_QUOTES, 'UTF-8');
    445433               
    446                 // Check for data-ezy-generated attribute
    447434                if (preg_match('/data-ezy-generated\s*=\s*["\']true["\']/i', $full_match)) {
    448435                    $is_ezy_generated = true;
    449436                } else {
    450                     // Check for EZY.AI comment within 200 chars before or after
    451437                    $before_context = substr($html, max(0, $match_index - 200), min(200, $match_index));
    452438                    $after_context = substr($html, $match_index + strlen($full_match), 200);
  • ezy-ai/trunk/admin/connection.php

    r3440881 r3445423  
    11<?php
    2 /**
    3  * EZY AI Connection Settings Page
    4  *
    5  * @package EZY_AI
    6  * @since 1.0.0
    7  */
    82
    93if (!defined('ABSPATH')) {
     
    3125        }
    3226    } catch (Exception $e) {
    33         // Swallow verification errors silently
    3427    } catch (Error $e) {
    35         // Swallow verification fatal errors silently
    3628    }
    3729}
     
    8476}
    8577
    86 // Get progress data
    8778global $ezy_ai_webhook;
    8879if (!$ezy_ai_webhook && $is_connected) {
     
    9788$ezy_score = $progress_percentage;
    9889
    99 // Get site URL for display
    10090$site_url = wp_parse_url(get_site_url(), PHP_URL_HOST);
    10191
    102 // Initialize AI visits data - Always show last 7 days
    10392$ai_visits_data = array();
    10493$ai_visits_labels = array();
     
    11099$chart_title = 'AI visits in the last 7 days';
    111100
    112 // Generate last 7 days labels
    113101$last_7_days_labels = array();
    114102for ($i = 6; $i >= 0; $i--) {
     
    116104}
    117105
    118 // Initialize with zeros for all 7 days
    119106$ai_visits_data = array_fill(0, 7, 0);
    120107$ai_visits_labels = $last_7_days_labels;
    121108
    122 // Only fetch data if connected
    123109if ($is_connected) {
    124110    $token = $ezy_ai_auth->get_connection_token();
    125111   
    126112    if ($token) {
    127         // Fetch agent stats (total visits, unique agents)
    128113        $stats_url = EZY_AI_API_Client::API_BASE_URL . '/integrations/' . $token . '/agent-stats?days=7';
    129114        $stats_response = wp_remote_get($stats_url, array(
     
    145130        }
    146131       
    147         // Fetch agent analytics (hourly distribution for chart) - always 7 days
    148132        $api_url = EZY_AI_API_Client::API_BASE_URL . '/integrations/' . $token . '/agent-analytics?timeRange=7d';
    149133        $response = wp_remote_get($api_url, array(
     
    160144                $hourly = $body['data']['hourlyDistribution'];
    161145               
    162                 // Group hourly data by day and map to our 7-day labels
    163146                $daily_visits = array();
    164147               
     
    176159                }
    177160               
    178                 // Map to our preset 7-day labels
    179161                foreach ($last_7_days_labels as $index => $label) {
    180162                    if (isset($daily_visits[$label])) {
     
    187169}
    188170
    189 // Labels are already set to last 7 days, data defaults to zeros
    190 
    191 // Calculate max value for scaling (minimum 1 to avoid division by zero)
    192171$max_visits = max(1, max($ai_visits_data));
    193172$num_points = count($ai_visits_data);
     
    453432                                    $is_active = $i < $active_segments;
    454433                                   
    455                                     // Tapered segments: slim at center, wide at outer edge
    456434                                    $angle_spread_inner = 3.5;
    457435                                    $angle_spread_outer = 8;
     
    459437                                    $outer_radius = 92;
    460438                                   
    461                                     // Calculate 4 points for tapered blade shape
    462439                                    $inner_start_angle = deg2rad($angle - $angle_spread_inner);
    463440                                    $inner_end_angle = deg2rad($angle + $angle_spread_inner);
  • ezy-ai/trunk/ezy-ai-plugin.php

    r3445408 r3445423  
    3535require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-blogs.php';
    3636require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-tracking.php';
    37 
    38 // Activation and deactivation hooks will be registered at the end of the file
    3937
    4038global $ezy_ai_auth;
     
    152150    private function init_tracking() {
    153151        global $ezy_ai_auth;
    154         // Only initialize tracking if connected
    155152        if ($ezy_ai_auth && $ezy_ai_auth->is_connected()) {
    156153            new EZY_AI_Tracking();
     
    159156   
    160157    public function add_admin_menu() {
    161         // Custom SVG icon - pre-encoded base64, uses fill="black" so WP can recolor it
    162         // This is the EZY "E" logo icon
    163158        $icon_base64 = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBmaWxsPSJibGFjayIgZD0iTTAgMGgxMnY0SDR2NGg4djRINHY0aDEydjRIMHoiLz48cGF0aCBmaWxsPSJibGFjayIgZD0iTTEyIDBoNHY4aDR2NEgxNnY4aC00di04SDh2LTRoNHoiLz48L3N2Zz4=';
    164159       
     
    368363            $ezy_ai_auth->verify_connection();
    369364           
    370             // Initialize tracking after successful connection
    371365            $this->init_tracking();
    372366        } elseif ($ezy_ai_auth->get_connection_token()) {
     
    482476register_deactivation_hook(__FILE__, 'ezy_ai_deactivate');
    483477function ezy_ai_deactivate() {
    484     // Deactivate component classes
    485478    EZY_AI_LLMS::deactivate();
    486479    EZY_AI_Facts::deactivate();
    487480    EZY_AI_Robots::deactivate();
    488481   
    489     // Unschedule health check cron
    490482    $timestamp = wp_next_scheduled('ezy_ai_connection_health_check');
    491483    if ($timestamp) {
     
    532524   
    533525    wp_cache_flush();
    534    
    535     // swallow debug log
    536526}
  • ezy-ai/trunk/includes/class-ezy-ai-auth.php

    r3440881 r3445423  
    168168        delete_transient('ezy_ai_just_connected');
    169169       
    170         // swallow debug log
    171        
    172170        return true;
    173171    }
  • ezy-ai/trunk/includes/class-ezy-ai-blogs.php

    r3444216 r3445423  
    1414    private $auth;
    1515
    16     // allowed tags for full html documents
    1716    private function get_allowed_html_tags()
    1817    {
     
    4948    }
    5049
    51     // extract ld+json scripts before wp_kses so schema is not stripped, then restore after
    5250    private function sanitize_full_html_preserving_ldjson($html)
    5351    {
     
    8280    public function register_blog_routes()
    8381    {
    84         // Single blog post: /ezy/blogs/slug
    8582        add_rewrite_rule(
    8683            '^ezy/blogs/([^/]+)/?$',
     
    8986        );
    9087
    91         // Blog listing: /ezy/blogs
    9288        add_rewrite_rule(
    9389            '^ezy/blogs/?$',
     
    107103        $is_listing = get_query_var('ezy_blog_listing');
    108104
    109         // Handle Listing Page
    110105        if ($is_listing) {
    111106            $this->render_blog_listing();
     
    113108        }
    114109
    115         // Handle Single Blog Page
    116110        if (!$slug) {
    117111            $request_uri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';
     
    172166    private function get_listing_template()
    173167    {
    174         // Check cache first
    175168        $cached_template = get_transient('ezy_ai_listing_template');
    176169        if ($cached_template) {
     
    178171        }
    179172
    180         // Fetch from API
    181173        if (!$this->auth || !$this->auth->is_connected()) {
    182174            return null;
     
    192184
    193185            if (!is_wp_error($response) && !empty($response)) {
    194                 // Cache for 24 hours
    195186                set_transient('ezy_ai_listing_template', $response, 24 * HOUR_IN_SECONDS);
    196187                return $response;
    197188            }
    198189        } catch (Exception $e) {
    199             // swallow debug log
    200190        }
    201191
     
    208198        nocache_headers();
    209199
    210         // Enqueue blog styles
    211200        wp_enqueue_style(
    212201            'ezy-ai-blogs-style',
     
    216205        );
    217206
    218         // Simple, clean listing page
    219207        ?>
    220208        <!DOCTYPE html>
     
    332320        $result = update_option($option_name, $formatted_html, false);
    333321
    334         // Update Index with published_at date
    335322        $this->update_blog_index($slug, $title, $excerpt, $published_at);
    336323
    337324        if ($result) {
    338             // swallow debug log
    339 
    340325            flush_rewrite_rules(false);
    341         } else {
    342             // swallow debug log
    343326        }
    344327
     
    350333        $index = get_option(self::BLOG_INDEX_OPTION, array());
    351334
    352         // Remove existing entry if present
    353335        $index = array_filter($index, function ($item) use ($slug) {
    354336            return $item['slug'] !== $slug;
    355337        });
    356338
    357         // Use provided published_at or fallback to current time
    358         // Convert ISO 8601 format to MySQL datetime if needed
    359339        $date = $published_at;
    360340        if ($date) {
    361             // Convert ISO 8601 to MySQL datetime format
    362341            $timestamp = strtotime($date);
    363342            if ($timestamp !== false) {
     
    370349        }
    371350
    372         // Add new entry
    373351        $index[] = array(
    374352            'slug' => $slug,
     
    410388        $result = delete_option($option_name);
    411389
    412         // Remove from index
    413390        $index = get_option(self::BLOG_INDEX_OPTION, array());
    414391        $index = array_filter($index, function ($item) use ($slug) {
  • ezy-ai/trunk/includes/class-ezy-ai-facts.php

    r3440881 r3445423  
    2424    }
    2525
    26     /**
    27      * Setup rewrite rules to ensure facts.json goes through WordPress
    28      */
    2926    public function setup_facts_json_override() {
    3027        add_rewrite_rule('^facts\.json$', 'index.php?ezy_facts_json=1', 'top');
     
    3633    }
    3734
    38     /**
    39      * Remove any physical facts.json files created by other plugins
    40      * Only runs if EZY AI has facts.json content configured
    41      */
    4235    public function cleanup_competing_facts_files() {
    43         // Only cleanup if we have content to serve
    4436        if (!$this->get_facts_json_content()) {
    4537            return;
     
    6860    }
    6961
    70     /**
    71      * Output facts.json content
    72      * Handles both standard requests and rewritten requests
    73      */
    7462    public function output_facts_json() {
    7563        if (get_query_var('ezy_facts_json')) {
     
    8876    }
    8977
    90     /**
    91      * Actually serve the facts.json content
    92      */
    9378    private function serve_facts_json() {
    9479        $facts_content = $this->get_facts_json_content();
     
    172157    }
    173158
    174     /**
    175      * Flush rewrite rules if needed
    176      */
    177159    private function maybe_flush_rewrite_rules() {
    178160        $rules = get_option('rewrite_rules');
     
    182164    }
    183165
    184     /**
    185      * Static method to flush rewrite rules on plugin activation
    186      */
    187166    public static function activate() {
    188167        add_rewrite_rule('^facts\.json$', 'index.php?ezy_facts_json=1', 'top');
     
    190169    }
    191170
    192     /**
    193      * Static method to clean up on plugin deactivation
    194      */
    195171    public static function deactivate() {
    196172        flush_rewrite_rules(false);
  • ezy-ai/trunk/includes/class-ezy-ai-faqs.php

    r3440881 r3445423  
    4242        nocache_headers();
    4343       
    44         // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    4544        echo $faqs_content;
    4645       
     
    5958        }
    6059       
    61         // Return content even if empty (for clearing)
    6260        if (isset($response['content'])) {
    6361            return $response['content'];
     
    7977        $content = $this->fetch_faqs_html_from_api($token);
    8078       
    81         // If content is explicitly empty string, clear FAQs
    8279        if ($content === '') {
    8380            return $this->clear_faqs_html();
    8481        }
    8582       
    86         // If content exists, update it
    8783        if ($content) {
    8884            return $this->update_faqs_html($content);
  • ezy-ai/trunk/includes/class-ezy-ai-llms.php

    r3445412 r3445423  
    2929    }
    3030
    31     /**
    32      * Block Yoast SEO from creating llms.txt file
    33      * Returns a non-writable path so Yoast fails silently
    34      *
    35      * @param string $path Original path from Yoast
    36      * @return string Modified path that prevents file creation
    37      */
    3831    public function block_yoast_llms_file($path) {
    3932        if ($this->get_llms_txt_content()) {
     
    4336    }
    4437
    45     /**
    46      * Setup rewrite rules to ensure llms.txt goes through WordPress
    47      */
    4838    public function setup_llms_txt_override() {
    4939        add_rewrite_rule('^llms\.txt$', 'index.php?ezy_llms_txt=1', 'top');
     
    5646    }
    5747
    58     /**
    59      * Remove any physical llms.txt files created by other plugins (like Yoast)
    60      * Only runs if EZY AI has llms.txt content configured
    61      */
    6248    public function cleanup_competing_llms_files() {
    6349        if (!$this->get_llms_txt_content()) {
     
    9783    }
    9884
    99     /**
    100      * Detect if site is hosted on WP Engine
    101      */
    10285    private function is_wp_engine() {
    10386        return (defined('WPE_APIKEY') || isset($_SERVER['IS_WPE']) || isset($_SERVER['IS_WPE_SNAPSHOT']));
    10487    }
    10588
    106     /**
    107      * Output llms.txt content
    108      * Handles both standard requests and rewritten requests
    109      * Redirects to trailing slash on WP Engine to bypass Nginx blocking
    110      */
    11189    public function output_llms_txt() {
    11290        if (get_query_var('ezy_llms_txt')) {
     
    143121    }
    144122
    145     /**
    146      * Actually serve the llms.txt content
    147      */
    148123    private function serve_llms_txt() {
    149124        $llms_content = $this->get_llms_txt_content();
     
    167142    }
    168143
    169     /**
    170      * Actually serve the llms-full.txt content
    171      */
    172144    private function serve_llms_full_txt() {
    173145        $llms_full_content = $this->get_llms_full_txt_content();
     
    278250    }
    279251
    280     /**
    281      * Flush rewrite rules if needed
    282      * Only flushes if our rule doesn't exist yet
    283      */
    284252    private function maybe_flush_rewrite_rules() {
    285253        $rules = get_option('rewrite_rules');
     
    289257    }
    290258
    291     /**
    292      * Static method to flush rewrite rules on plugin activation
    293      */
    294259    public static function activate() {
    295260        add_rewrite_rule('^llms\.txt$', 'index.php?ezy_llms_txt=1', 'top');
     
    298263    }
    299264
    300     /**
    301      * Static method to clean up on plugin deactivation
    302      */
    303265    public static function deactivate() {
    304266        flush_rewrite_rules(false);
  • ezy-ai/trunk/includes/class-ezy-ai-meta-descriptions.php

    r3440881 r3445423  
    11<?php
    2 /**
    3  * EZY AI Meta Descriptions Handler
    4  *
    5  * Handles meta description injection with guaranteed priority over Yoast SEO.
    6  * Uses multiple strategies to ensure EZY meta descriptions take precedence.
    7  */
    82
    93if (!defined('ABSPATH')) {
     
    1913    private $auth;
    2014   
    21     /**
    22      * Cache for current page meta description to avoid repeated lookups
    23      */
    2415    private $current_page_meta_cache = null;
    2516   
    26     /**
    27      * Flag to track if we've already determined meta for this request
    28      */
    2917    private $meta_checked = false;
    3018
     
    3826    }
    3927
    40     /**
    41      * Inject EZY meta description into wp_head
    42      * Runs at priority 1 to appear before most other meta tags
    43      */
    4428    public function inject_meta_description() {
    4529        $meta_description = $this->get_ezy_meta_for_current_page();
     
    5337    }
    5438
    55     /**
    56      * Comprehensive Yoast meta description blocking
    57      * Uses multiple filters and high priority to ensure EZY meta descriptions take precedence
    58      */
    5939    private function disable_yoast_meta_interference() {
    6040        add_filter('wpseo_metadesc', array($this, 'override_yoast_meta_description'), 1, 2);
     
    6747    }
    6848   
    69     /**
    70      * Get cached EZY meta description for current page
    71      */
    7249    private function get_ezy_meta_for_current_page() {
    7350        if ($this->meta_checked) {
     
    8966    }
    9067   
    91     /**
    92      * Check if EZY has meta description for current page
    93      */
    9468    private function has_ezy_meta_for_current_page() {
    9569        return !empty($this->get_ezy_meta_for_current_page());
    9670    }
    9771
    98     /**
    99      * Primary override: Replace Yoast's meta description with EZY's
    100      */
    10172    public function override_yoast_meta_description($description, $presentation = null) {
    10273        $ezy_meta = $this->get_ezy_meta_for_current_page();
     
    10980    }
    11081   
    111     /**
    112      * Late override: Safety net to ensure EZY meta description is used
    113      */
    11482    public function override_yoast_meta_description_late($description, $presentation = null) {
    11583        return $this->override_yoast_meta_description($description, $presentation);
    11684    }
    11785
    118     /**
    119      * Override Yoast's Open Graph description
    120      */
    12186    public function override_yoast_og_description($description, $presentation = null) {
    12287        $ezy_meta = $this->get_ezy_meta_for_current_page();
     
    12994    }
    13095   
    131     /**
    132      * Late override for Open Graph description
    133      */
    13496    public function override_yoast_og_description_late($description, $presentation = null) {
    13597        return $this->override_yoast_og_description($description, $presentation);
    13698    }
    13799
    138     /**
    139      * Override Yoast's Twitter description
    140      */
    141100    public function override_yoast_twitter_description($description, $presentation = null) {
    142101        $ezy_meta = $this->get_ezy_meta_for_current_page();
     
    149108    }
    150109   
    151     /**
    152      * Late override for Twitter description
    153      */
    154110    public function override_yoast_twitter_description_late($description, $presentation = null) {
    155111        return $this->override_yoast_twitter_description($description, $presentation);
    156112    }
    157113   
    158     /**
    159      * Override Yoast's frontend presentation object
    160      * This ensures the meta description is set at the presentation level
    161      */
    162114    public function override_yoast_presentation($presentation, $context = null) {
    163115        $ezy_meta = $this->get_ezy_meta_for_current_page();
  • ezy-ai/trunk/includes/class-ezy-ai-robots.php

    r3445411 r3445423  
    2727    }
    2828
    29     /**
    30      * Block Yoast SEO's robots.txt modifications when EZY has content
    31      * This runs early (priority 1) to capture the original robots.txt
    32      * before Yoast modifies it
    33      *
    34      * @param string $output The robots.txt output
    35      * @param bool $public Whether the site is public
    36      * @return string The robots.txt output
    37      */
    3829    public function block_yoast_robots_output($output, $public) {
    3930        return $output;
    4031    }
    4132
    42     /**
    43      * Filter the robots.txt output - runs AFTER Yoast
    44      * Completely replaces content with EZY's robots.txt if we have content
    45      *
    46      * @param string $output The robots.txt output (possibly modified by Yoast)
    47      * @param bool $public Whether the site is public
    48      * @return string The filtered robots.txt output
    49      */
    5033    public function filter_robots_txt($output, $public) {
    5134        $robots_content = $this->get_robots_txt_content();
     
    5740    }
    5841
    59     /**
    60      * Output robots.txt content via do_robots action
    61      * This is a fallback that runs before WordPress's default output
    62      */
    6342    public function output_robots_txt() {
    6443        $robots_content = $this->get_robots_txt_content();
     
    7756    }
    7857
    79     /**
    80      * Setup rewrite rule for robots.txt to handle physical file override (WP Engine fix)
    81      */
    8258    public function setup_robots_txt_rewrite() {
    8359        add_rewrite_rule('^robots\.txt$', 'index.php?ezy_robots_txt=1', 'top');
    8460    }
    8561
    86     /**
    87      * Add custom query var for robots.txt rewrite
    88      */
    8962    public function add_robots_query_var($vars) {
    9063        $vars[] = 'ezy_robots_txt';
     
    9265    }
    9366
    94     /**
    95      * Serve robots.txt via rewrite rule (bypasses physical file)
    96      */
    9767    public function serve_robots_via_rewrite() {
    9868        if (!get_query_var('ezy_robots_txt')) {
     
    12191    }
    12292
    123     /**
    124      * Remove any physical robots.txt files when EZY has content
    125      * Note: Only removes files that appear to be auto-generated (not manually created)
    126      */
    12793    public function cleanup_competing_robots_files() {
    12894        if (!$this->get_robots_txt_content()) {
     
    147113        foreach ($paths_to_check as $file_path) {
    148114            if (file_exists($file_path) && wp_is_writable($file_path)) {
    149                 // Check if this is a Yoast-generated file or generic auto-generated
    150115                $content = @file_get_contents($file_path);
    151116                if ($content !== false) {
    152                     // Check for Yoast signature or generic WordPress robots
    153117                    $is_auto_generated = (
    154118                        strpos($content, 'Yoast') !== false ||
    155119                        strpos($content, '# START YOAST BLOCK') !== false ||
    156                         // Generic WordPress default robots.txt pattern
    157120                        (strpos($content, 'User-agent: *') !== false &&
    158121                         strpos($content, 'Disallow: /wp-admin/') !== false &&
     
    223186    }
    224187
    225     /**
    226      * Static method for plugin activation
    227      */
    228188    public static function activate() {
    229189        add_rewrite_rule('^robots\.txt$', 'index.php?ezy_robots_txt=1', 'top');
     
    231191    }
    232192
    233     /**
    234      * Static method for plugin deactivation 
    235      */
    236193    public static function deactivate() {
    237         // Nothing special needed for robots.txt
    238194    }
    239195
  • ezy-ai/trunk/includes/class-ezy-ai-schema.php

    r3440881 r3445423  
    11<?php
    2 /**
    3  * EZY AI Schema.org Markup Handler
    4  *
    5  * Handles schema.org JSON-LD injection with guaranteed priority over Yoast SEO.
    6  * Uses multiple strategies to ensure EZY schemas take precedence.
    7  */
    82
    93if (!defined('ABSPATH')) {
     
    2014    private $auth;
    2115   
    22     /**
    23      * Cache for current page schemas to avoid repeated lookups
    24      */
    2516    private $current_page_schemas_cache = null;
    2617   
    27     /**
    28      * Flag to track if we've already determined schemas for this request
    29      */
    3018    private $schemas_checked = false;
    3119
     
    3826    }
    3927
    40     /**
    41      * Comprehensive Yoast schema blocking
    42      * Uses multiple filters and strategies to ensure EZY schemas take priority
    43      */
    4428    private function disable_yoast_schema_interference() {
    4529        add_filter('wpseo_json_ld_output', array($this, 'block_yoast_jsonld_output'), 1, 1);
     
    5842    }
    5943
    60     /**
    61      * Primary blocking method: Return FALSE to completely disable Yoast's JSON-LD output
    62      * This is the most effective method based on Yoast's schema-presenter.php
    63      */
    6444    public function block_yoast_jsonld_output($data) {
    6545        if ($this->has_ezy_schemas_for_current_page()) {
     
    7050    }
    7151
    72     /**
    73      * Secondary blocking method: Clear the schema graph
    74      */
    7552    public function block_yoast_schema_graph($graph, $context) {
    7653        if ($this->has_ezy_schemas_for_current_page()) {
     
    8158    }
    8259   
    83     /**
    84      * Block individual schema pieces when EZY has schemas for the page
    85      */
    8660    public function block_yoast_schema_piece($is_needed) {
    8761        if ($this->has_ezy_schemas_for_current_page()) {
     
    9266    }
    9367   
    94     /**
    95      * Block the entire graph pieces array
    96      */
    9768    public function block_yoast_graph_pieces($pieces, $context) {
    9869        if ($this->has_ezy_schemas_for_current_page()) {
     
    10374    }
    10475   
    105     /**
    106      * Check if EZY has schemas for the current page
    107      * Uses caching to avoid repeated database lookups
    108      */
    10976    private function has_ezy_schemas_for_current_page() {
    11077        if ($this->schemas_checked) {
     
    12693    }
    12794
    128     /**
    129      * Inject EZY schema markup into wp_head
    130      * Runs at priority 1 to appear before most other scripts
    131      */
    13295    public function inject_schema_markup() {
    13396        if (!$this->schemas_checked) {
     
    239202        }
    240203
    241         // Use wp_print_inline_script_tag for safe JSON-LD output (WordPress 5.7+)
    242         // This function handles escaping internally and is the recommended approach
    243204        echo "\n<!-- EZY.AI Schema.org Markup -->\n";
    244205        wp_print_inline_script_tag(
  • ezy-ai/trunk/includes/class-ezy-ai-tracking.php

    r3440881 r3445423  
    1717        $this->auth = $ezy_ai_auth;
    1818
    19         // Hook into WordPress request lifecycle
    20         // Use template_redirect for frontend requests only
    2119        add_action('template_redirect', array($this, 'track_visit'), 1);
    2220    }
     
    2422    public function track_visit()
    2523    {
    26         // Only track if connected
    2724        if (!$this->auth || !$this->auth->is_connected()) {
    2825            return;
    2926        }
    3027
    31         // Skip tracking for admin, AJAX, cron, and REST API requests
    3228        if (is_admin() || wp_doing_ajax() || wp_doing_cron() || (defined('REST_REQUEST') && REST_REQUEST)) {
    3329            return;
    3430        }
    3531
    36         // Skip tracking for non-GET requests (except HEAD)
    3732        $method = isset($_SERVER['REQUEST_METHOD']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_METHOD'])) : 'GET';
    3833        if ($method !== 'GET' && $method !== 'HEAD') {
     
    4035        }
    4136
    42         // Get connection token
    4337        $token = $this->auth->get_connection_token();
    4438        if (!$token) {
     
    4640        }
    4741
    48         // Extract visit data
    4942        $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '';
    5043        $ip_address = $this->get_client_ip();
    5144        $url = $this->get_current_url();
    5245
    53         // Extract all headers for signature verification
    5446        $headers = $this->get_all_headers();
    5547
    56         // Send tracking request asynchronously (non-blocking)
    5748        $this->send_tracking_request($token, $user_agent, $ip_address, $url, $headers);
    5849    }
     
    7263                $ip = sanitize_text_field(wp_unslash($_SERVER[$key]));
    7364
    74                 // Handle comma-separated IPs (X-Forwarded-For)
    7565                if (strpos($ip, ',') !== false) {
    7666                    $ips = explode(',', $ip);
     
    7868                }
    7969
    80                 // Validate IP address
    8170                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
    8271                    return $ip;
     
    121110        );
    122111
    123         // Get all HTTP headers
    124112        if (function_exists('getallheaders')) {
    125113            $all_headers = getallheaders();
     
    132120            }
    133121        } else {
    134             // Fallback for servers without getallheaders()
    135122            foreach ($_SERVER as $key => $value) {
    136123                if (strpos($key, 'HTTP_') === 0) {
     
    143130        }
    144131
    145         // Ensure signature headers are included (case-insensitive)
    146132        $signature_headers = array('signature-agent', 'signature-input', 'signature');
    147133        foreach ($signature_headers as $sig_header) {
     
    157143    private function send_tracking_request($token, $user_agent, $ip_address, $url, $headers)
    158144    {
    159         // Use wp_remote_post for async request (non-blocking)
    160145        $api_url = EZY_AI_API_Client::API_BASE_URL . '/track';
    161146
     
    174159            )),
    175160            'timeout' => 5,
    176             'blocking' => false, // Non-blocking request
     161            'blocking' => false,
    177162            'sslverify' => true,
    178163        );
    179164
    180         // Fire and forget - don't wait for response
    181165        wp_remote_post($api_url, $request_args);
    182166    }
  • ezy-ai/trunk/includes/class-ezy-ai-webhook.php

    r3444216 r3445423  
    123123        $updated_at = $request->get_param('updated_at');
    124124
    125         // swallow debug log
    126 
    127125        $result = false;
    128126       
     
    308306        }
    309307
    310         // If content is explicitly empty string, clear FAQs
    311308        if ($content === '') {
    312309            return $ezy_ai_faqs->clear_faqs_html();
    313310        }
    314311
    315         // If content is null or false, return false
    316312        if (empty($content)) {
    317313            return false;
  • ezy-ai/trunk/includes/class-ezy-ai-widget-fetch.php

    r3440881 r3445423  
    124124
    125125    private function fetch_robots() {
    126         // Always fetch live robots.txt from the website
    127126        $site_url = get_site_url();
    128127        $robots_url = rtrim($site_url, '/') . '/robots.txt';
     
    155154
    156155    private function fetch_llms() {
    157         // Always fetch live llms.txt from the website
    158156        $site_url = get_site_url();
    159157        $llms_url = rtrim($site_url, '/') . '/llms.txt';
     
    204202
    205203    private function fetch_schema($page_urls = null) {
    206         // Always fetch live schemas from pages
    207204        $live_schemas = array();
    208205        $total_pages_requested = 0;
     
    239236
    240237    private function fetch_meta($page_urls = null) {
    241         // Return ALL cached meta descriptions - let the backend do the filtering
    242         // This is more reliable than trying to match URLs in WordPress
    243238        global $ezy_ai_meta_descriptions;
    244239        if (!$ezy_ai_meta_descriptions) {
     
    246241        }
    247242
    248         // Get all cached meta descriptions
    249243        $all_cached = get_option(EZY_AI_Meta_Descriptions::META_DESCRIPTIONS_OPTION);
    250244       
     
    277271
    278272    private function fetch_sitemap() {
    279         // Always fetch live sitemap.xml from the website
    280273        $site_url = get_site_url();
    281274        $sitemap_url = rtrim($site_url, '/') . '/sitemap.xml';
     
    316309        $updated_at = get_option('ezy_ai_facts_json_updated_at');
    317310
    318         // If no stored content, try to fetch live facts.json from the website
    319311        if (empty($content)) {
    320312            $site_url = get_site_url();
     
    400392        $schemas = array();
    401393       
    402         // Extract JSON-LD schemas from <script type="application/ld+json">
    403394        preg_match_all('/<script[^>]*type=["\']application\/ld\+json["\'][^>]*>(.*?)<\/script>/is', $html, $matches);
    404395       
     
    431422        }
    432423       
    433         // Extract meta description from <meta name="description">
    434424        $meta_description = '';
    435425        $is_ezy_generated = false;
    436426       
    437         // Try to find meta description tag
    438427        if (preg_match('/<meta[^>]*name=["\']description["\'][^>]*>/i', $html, $matches, PREG_OFFSET_CAPTURE)) {
    439428            $full_match = $matches[0][0];
    440429            $match_index = $matches[0][1];
    441430           
    442             // Extract content value
    443431            if (preg_match('/content=["\']([^"\']*)["\']/i', $full_match, $content_matches)) {
    444432                $meta_description = html_entity_decode($content_matches[1], ENT_QUOTES, 'UTF-8');
    445433               
    446                 // Check for data-ezy-generated attribute
    447434                if (preg_match('/data-ezy-generated\s*=\s*["\']true["\']/i', $full_match)) {
    448435                    $is_ezy_generated = true;
    449436                } else {
    450                     // Check for EZY.AI comment within 200 chars before or after
    451437                    $before_context = substr($html, max(0, $match_index - 200), min(200, $match_index));
    452438                    $after_context = substr($html, $match_index + strlen($full_match), 200);
Note: See TracChangeset for help on using the changeset viewer.