Plugin Directory

Changeset 3701980


Ignore:
Timestamp:
09/18/2026 12:38:57 PM (5 weeks ago)
Author:
jamesmackie
Message:

Release 2.4.0: Meta Titles widget. Generates and serves the title tag per page (SEO-plugin filter overrides for Yoast/AIOSEO/RankMath/SEOPress/TSF/Slim/Squirrly, og/twitter title parity, head-scoped strip that leaves inline SVG titles alone), per-page and global toggle, plugin reports meta_titles capability on connect/verify. Tag 2.4.0.

Location:
ezy-ai
Files:
2 added
1 deleted
12 edited
23 copied

Legend:

Unmodified
Added
Removed
  • ezy-ai/tags/2.4.0/admin/widgets.php

    r3635434 r3701980  
    4141        'book-open-text' => '<path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/><path d="M6 8h2"/><path d="M6 12h2"/><path d="M16 8h2"/><path d="M16 12h2"/>',
    4242        'file-search'    => '<path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M4.3 21a2 2 0 0 1-1.7-1.9V4a2 2 0 0 1 2-2h9l5 5v3"/><circle cx="11.5" cy="16.5" r="2.5"/><path d="m21 21-1.6-1.6"/>',
     43        'app-window'     => '<rect x="2" y="4" width="20" height="16" rx="2"/><path d="M10 4v4"/><path d="M2 8h20"/><path d="M6 4v4"/>',
    4344        'braces'         => '<path d="M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5a2 2 0 0 0 2 2h1"/><path d="M16 21h1a2 2 0 0 0 2-2v-5a2 2 0 0 1 2-2 2 2 0 0 1-2-2V5a2 2 0 0 0-2-2h-1"/>',
    4445        'list'           => '<path d="M3 5h.01"/><path d="M3 12h.01"/><path d="M3 19h.01"/><path d="M8 5h13"/><path d="M8 12h13"/><path d="M8 19h13"/>',
    … …  
    104105    'meta_descriptions' => array(
    105106        'label' => 'META DESCRIPTIONS', 'icon' => 'file-search',
    106         'description' => 'Generate AI-friendly titles and descriptions that improve visibility in search and AI answers.',
     107        'description' => 'Generate AI-friendly descriptions that improve visibility in search and AI answers.',
    107108        'option' => 'ezy_ai_meta_descriptions_updated_at', 'view_path' => null,
    108109        'modal_title' => 'Sharpen your search presence',
    109         'modal_desc'  => 'Generate AI-friendly titles and meta descriptions that improve how your pages surface in search and AI answers.',
     110        'modal_desc'  => 'Generate AI-friendly meta descriptions that improve how your pages surface in search and AI answers.',
     111        'benefits' => array('Higher click-through', 'Better AI visibility', 'Optimised per page'),
     112    ),
     113    'meta_titles' => array(
     114        'label' => 'META TITLES', 'icon' => 'app-window',
     115        'description' => 'Generate AI-friendly title tags that improve how your pages are named in search and AI answers.',
     116        'option' => 'ezy_ai_meta_titles_updated_at', 'view_path' => null,
     117        'modal_title' => 'Name your pages for search',
     118        'modal_desc'  => 'Generate AI-friendly title tags that improve how your pages surface in search and AI answers.',
    110119        'benefits' => array('Higher click-through', 'Better AI visibility', 'Optimised per page'),
    111120    ),
    … …  
    142151 */
    143152$toggle_pages = array();
    144 foreach (array('schemas' => 'ezy_ai_schemas_cache', 'meta_descriptions' => 'ezy_ai_meta_descriptions_cache') as $widget_key => $cache_option) {
     153foreach (array('schemas' => 'ezy_ai_schemas_cache', 'meta_descriptions' => 'ezy_ai_meta_descriptions_cache', 'meta_titles' => 'ezy_ai_meta_titles_cache') as $widget_key => $cache_option) {
    145154    $rows = get_option($cache_option);
    146155    $seen = array();
  • ezy-ai/tags/2.4.0/assets/js/connection.js

    r3598687 r3701980  
    669669        schemas: 'Schema Markup',
    670670        meta_descriptions: 'Meta Descriptions',
     671        meta_titles: 'Meta Titles',
    671672        faqs_html: 'FAQs Page',
    672673        blogs: 'Blog Posts'
  • ezy-ai/tags/2.4.0/ezy-ai-plugin.php

    r3635434 r3701980  
    44 * Plugin URI: https://ezy.ai/cms
    55 * Description: EZY.ai is the first all-in-one AEO plugin for WordPress with built-in AI content and chatbot lead gen. Get more customers from ChatGPT, Claude & Gemini.
    6  * Version: 2.3.2
     6 * Version: 2.4.0
    77 * Requires at least: 5.7
    88 * Requires PHP: 7.4
    … …  
    1818}
    1919
    20 define('EZY_AI_VERSION', '2.3.2');
     20define('EZY_AI_VERSION', '2.4.0');
    2121define('EZY_AI_PLUGIN_DIR', plugin_dir_path(__FILE__));
    2222define('EZY_AI_PLUGIN_URL', plugin_dir_url(__FILE__));
    … …  
    3333require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-schema.php';
    3434require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-meta-descriptions.php';
     35require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-meta-titles.php';
    3536require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-request-path.php';
    3637require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-widget-toggles.php';
    … …  
    5354global $ezy_ai_meta_descriptions;
    5455$ezy_ai_meta_descriptions = null;
     56global $ezy_ai_meta_titles;
     57$ezy_ai_meta_titles = null;
    5558
    5659class Ezy_AI_Plugin {
    … …  
    7982        $this->init_schema();
    8083        $this->init_meta_descriptions();
     84        $this->init_meta_titles();
    8185        $this->init_faqs();
    8286        $this->init_blogs();
    … …  
    401405    private function init_meta_descriptions() {
    402406        new EZY_AI_Meta_Descriptions();
     407    }
     408
     409    // Instantiated AFTER meta descriptions on purpose: both classes buffer
     410    // wp_head, and the titles buffer (opened second = inner) must close first
     411    // (PHP_INT_MAX - 1) so each strip pass runs on its own bytes.
     412    private function init_meta_titles() {
     413        new EZY_AI_Meta_Titles();
    403414    }
    404415
    … …  
    718729            'schemas' => get_option('ezy_ai_schemas_updated_at', null),
    719730            'meta_descriptions' => get_option('ezy_ai_meta_descriptions_updated_at', null),
     731            'meta_titles' => get_option('ezy_ai_meta_titles_updated_at', null),
    720732            'faqs_html' => get_option('ezy_ai_faqs_html_updated_at', null),
    721733            'blogs' => get_option('ezy_ai_blogs_updated_at', null),
    … …  
    761773            'schemas' => array('EZY_AI_Schema', 'sync_schemas'),
    762774            'meta_descriptions' => array('EZY_AI_Meta_Descriptions', 'sync_meta_descriptions'),
     775            'meta_titles' => array('EZY_AI_Meta_Titles', 'sync_meta_titles'),
    763776            'faqs_html' => array('EZY_AI_FAQs', 'sync_faqs_html'),
    764777            'blogs' => array('EZY_AI_Blogs', 'sync_blogs'),
    … …  
    10921105    require_once plugin_dir_path(__FILE__) . 'includes/class-ezy-ai-schema.php';
    10931106    require_once plugin_dir_path(__FILE__) . 'includes/class-ezy-ai-meta-descriptions.php';
     1107    require_once plugin_dir_path(__FILE__) . 'includes/class-ezy-ai-meta-titles.php';
    10941108    require_once plugin_dir_path(__FILE__) . 'includes/class-ezy-ai-faqs.php';
    10951109    require_once plugin_dir_path(__FILE__) . 'includes/class-ezy-ai-blogs.php';
  • ezy-ai/tags/2.4.0/includes/class-ezy-ai-analytics-api.php

    r3635434 r3701980  
    380380            ),
    381381            array(
     382                'key'         => 'meta_titles',
     383                'label'       => 'Optimize Meta Titles',
     384                'description' => 'Improve how AI and Google headline your pages',
     385                'done'        => (bool) get_option('ezy_ai_meta_titles_updated_at'),
     386                'url'         => $ezy_url,
     387                'external'    => true,
     388            ),
     389            array(
    382390                'key'         => 'blogs',
    383391                'label'       => 'Expand content coverage',
    … …  
    526534                'schemas' => $this->toggleable_pages('schemas', 'ezy_ai_schemas_cache'),
    527535                'meta_descriptions' => $this->toggleable_pages('meta_descriptions', 'ezy_ai_meta_descriptions_cache'),
     536                'meta_titles' => $this->toggleable_pages('meta_titles', 'ezy_ai_meta_titles_cache'),
    528537            ),
    529538        ));
  • ezy-ai/tags/2.4.0/includes/class-ezy-ai-api-client.php

    r3635434 r3701980  
    376376    }
    377377
     378    public function upload_meta_titles($integration_id, $meta_titles, $token) {
     379        if (empty($integration_id) || empty($meta_titles) || !is_array($meta_titles) || empty($token)) {
     380            return new WP_Error('invalid_params', 'Integration ID, meta titles array, and token are required');
     381        }
     382
     383        return $this->post(
     384            '/integrations/' . urlencode($integration_id) . '/upload-meta-titles',
     385            array(
     386                'meta_titles' => $meta_titles
     387            ),
     388            $token
     389        );
     390    }
     391
     392    public function get_meta_titles($token) {
     393        if (empty($token)) {
     394            return new WP_Error('invalid_params', 'Connection token is required');
     395        }
     396
     397        return $this->get('/meta-titles', $token);
     398    }
     399
    378400    public function get_faqs_html($token) {
    379401        if (empty($token)) {
  • ezy-ai/tags/2.4.0/includes/class-ezy-ai-auth.php

    r3635434 r3701980  
    104104            'schema_markup' => true,
    105105            'meta_tags' => true,
     106            'meta_titles' => true,
    106107            'custom_scripts' => true,
    107108        );
    … …  
    241242        $schema = new EZY_AI_Schema();
    242243        $meta_descriptions = new EZY_AI_Meta_Descriptions();
     244        $meta_titles = new EZY_AI_Meta_Titles();
    243245        $faqs = new EZY_AI_FAQs();
    244246        $blogs = new EZY_AI_Blogs();
    … …  
    251253        $schema->clear_schemas();
    252254        $meta_descriptions->clear_meta_descriptions();
     255        $meta_titles->clear_meta_titles();
    253256        $faqs->clear_faqs_html();
    254257        $faqs->clear_all_faq_hubs();
  • ezy-ai/tags/2.4.0/includes/class-ezy-ai-webhook.php

    r3635434 r3701980  
    344344                $result = $this->handle_meta_descriptions($data);
    345345                break;
     346
     347            case 'meta_titles':
     348                $result = $this->handle_meta_titles($data);
     349                break;
    346350               
    347351            case 'faqs_html':
    … …  
    602606    }
    603607
     608    private function handle_meta_titles($data) {
     609        if (empty($data) || !isset($data['meta_titles'])) {
     610            return false;
     611        }
     612
     613        global $ezy_ai_meta_titles;
     614        if (!$ezy_ai_meta_titles) {
     615            $ezy_ai_meta_titles = new EZY_AI_Meta_Titles();
     616        }
     617
     618        return $ezy_ai_meta_titles->update_meta_titles($data['meta_titles']);
     619    }
     620
    604621    private function handle_faqs_html($content, $data = null) {
    605622        global $ezy_ai_faqs;
    … …  
    730747            'schemas' => 'ezy_ai_schemas_updated_at',
    731748            'meta_descriptions' => 'ezy_ai_meta_descriptions_updated_at',
     749            'meta_titles' => 'ezy_ai_meta_titles_updated_at',
    732750            'faqs_html' => 'ezy_ai_faqs_html_updated_at',
    733751            'blog' => 'ezy_ai_blogs_updated_at',
    … …  
    767785            'schemas' => null,
    768786            'meta_descriptions' => null,
     787            'meta_titles' => null,
    769788        );
    770789
    … …  
    835854                }
    836855            }
     856        } elseif ($content_type === 'meta_titles') {
     857            $rows = get_option(EZY_AI_Meta_Titles::META_TITLES_OPTION);
     858            if (is_array($rows)) {
     859                foreach ($rows as $row) {
     860                    if (!empty($row['page_url'])) {
     861                        $paths[] = self::path_from_url($row['page_url']);
     862                    }
     863                }
     864            }
    837865        } elseif ($content_type === 'schemas') {
    838866            $rows = get_option(EZY_AI_Schema::SCHEMAS_OPTION);
  • ezy-ai/tags/2.4.0/includes/class-ezy-ai-widget-fetch.php

    r3605598 r3701980  
    2424                    'required' => true,
    2525                    'type' => 'string',
    26                     'enum' => array('robots', 'llms', 'llms-full', 'schema', 'meta', 'sitemap', 'facts'),
     26                    'enum' => array('robots', 'llms', 'llms-full', 'schema', 'meta', 'title', 'sitemap', 'facts'),
    2727                    'validate_callback' => function($param) {
    28                         return in_array($param, array('robots', 'llms', 'llms-full', 'schema', 'meta', 'sitemap', 'facts'));
     28                        return in_array($param, array('robots', 'llms', 'llms-full', 'schema', 'meta', 'title', 'sitemap', 'facts'));
    2929                    }
    3030                ),
    … …  
    114114            case 'meta':
    115115                return $this->fetch_meta($page_urls);
     116
     117            case 'title':
     118                return $this->fetch_title($page_urls);
    116119               
    117120            case 'sitemap':
    … …  
    274277            'total_pages_requested' => $total_pages_requested,
    275278            'pages_with_meta' => $pages_with_meta
     279        ), 200);
     280    }
     281
     282    private function fetch_title($page_urls = null) {
     283        global $ezy_ai_meta_titles;
     284        if (!$ezy_ai_meta_titles) {
     285            $ezy_ai_meta_titles = new EZY_AI_Meta_Titles();
     286        }
     287
     288        $all_cached = get_option(EZY_AI_Meta_Titles::META_TITLES_OPTION);
     289       
     290        $cached_meta = array();
     291        if (is_array($all_cached)) {
     292            foreach ($all_cached as $meta) {
     293                if (!empty($meta['page_url']) && !empty($meta['meta_title'])) {
     294                    $cached_meta[] = array(
     295                        'page_url' => $meta['page_url'],
     296                        'meta_title' => $meta['meta_title'],
     297                        'is_ezy_generated' => true
     298                    );
     299                }
     300            }
     301        }
     302
     303        $total_pages_requested = is_array($page_urls) ? count($page_urls) : 0;
     304        $pages_with_meta = count($cached_meta);
     305
     306        return new WP_REST_Response(array(
     307            'success' => true,
     308            'widget' => 'title',
     309            'meta_titles' => $cached_meta,
     310            'updated_at' => time(),
     311            'exists' => count($cached_meta) > 0,
     312            'total_pages_requested' => $total_pages_requested,
     313            'pages_with_title' => $pages_with_meta
    276314        ), 200);
    277315    }
  • ezy-ai/tags/2.4.0/includes/class-ezy-ai-widget-toggles.php

    r3635434 r3701980  
    3333        'schemas',
    3434        'meta_descriptions',
     35        'meta_titles',
    3536        'blogs',
    3637        'faqs_html',
    … …  
    3839
    3940    /**
    40      * Widgets that additionally support per-page exclusion. These two inject
     41     * Widgets that additionally support per-page exclusion. These three inject
    4142     * into wp_head keyed by page_url, so a page-level decision is meaningful;
    4243     * the rest are whole-site files where it is not.
    4344     */
    44     const PER_PAGE_WIDGETS = array('schemas', 'meta_descriptions');
     45    const PER_PAGE_WIDGETS = array('schemas', 'meta_descriptions', 'meta_titles');
    4546
    4647    public static function is_widget($key)
  • ezy-ai/tags/2.4.0/readme.txt

    r3673755 r3701980  
    44Requires at least: 5.7
    55Tested up to: 7.0
    6 Stable tag: 2.3.2
     6Stable tag: 2.4.0
    77Requires PHP: 7.4
    88License: GPLv2 or later
    … …  
    169169
    170170== Changelog ==
     171
     172= 2.4.0 =
     173* New: Meta titles. EZY can now write and publish the title tag for each of your pages, the same way it already handles meta descriptions. Titles are generated from your EZY dashboard, sent to your site, and served from the page head with a small marker so EZY can confirm they are live.
     174* Detail: When an EZY title exists for a page it replaces the title produced by your theme or SEO plugin (Yoast, All in One SEO, Rank Math, SEOPress, The SEO Framework, Slim SEO and Squirrly are all handled), including the Open Graph and Twitter title variants, so the page shows one consistent title everywhere. Pages without an EZY title are left exactly as they were.
     175* Detail: Meta titles get their own switch on the EZY.ai > Widgets screen and can be turned off per page, like schema and meta descriptions. Switching the feature off hands the title back to your theme or SEO plugin immediately.
     176* Detail: The title tag inside inline SVG icons is never touched, so accessible names on icons keep working.
     177* Change: The site now reports meta title support to your EZY dashboard on connect and on each verification, so the Meta Titles widget unlocks for this site as soon as the plugin is updated.
    171178
    172179= 2.3.2 =
  • ezy-ai/trunk/admin/widgets.php

    r3635434 r3701980  
    4141        'book-open-text' => '<path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/><path d="M6 8h2"/><path d="M6 12h2"/><path d="M16 8h2"/><path d="M16 12h2"/>',
    4242        'file-search'    => '<path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M4.3 21a2 2 0 0 1-1.7-1.9V4a2 2 0 0 1 2-2h9l5 5v3"/><circle cx="11.5" cy="16.5" r="2.5"/><path d="m21 21-1.6-1.6"/>',
     43        'app-window'     => '<rect x="2" y="4" width="20" height="16" rx="2"/><path d="M10 4v4"/><path d="M2 8h20"/><path d="M6 4v4"/>',
    4344        'braces'         => '<path d="M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5a2 2 0 0 0 2 2h1"/><path d="M16 21h1a2 2 0 0 0 2-2v-5a2 2 0 0 1 2-2 2 2 0 0 1-2-2V5a2 2 0 0 0-2-2h-1"/>',
    4445        'list'           => '<path d="M3 5h.01"/><path d="M3 12h.01"/><path d="M3 19h.01"/><path d="M8 5h13"/><path d="M8 12h13"/><path d="M8 19h13"/>',
    … …  
    104105    'meta_descriptions' => array(
    105106        'label' => 'META DESCRIPTIONS', 'icon' => 'file-search',
    106         'description' => 'Generate AI-friendly titles and descriptions that improve visibility in search and AI answers.',
     107        'description' => 'Generate AI-friendly descriptions that improve visibility in search and AI answers.',
    107108        'option' => 'ezy_ai_meta_descriptions_updated_at', 'view_path' => null,
    108109        'modal_title' => 'Sharpen your search presence',
    109         'modal_desc'  => 'Generate AI-friendly titles and meta descriptions that improve how your pages surface in search and AI answers.',
     110        'modal_desc'  => 'Generate AI-friendly meta descriptions that improve how your pages surface in search and AI answers.',
     111        'benefits' => array('Higher click-through', 'Better AI visibility', 'Optimised per page'),
     112    ),
     113    'meta_titles' => array(
     114        'label' => 'META TITLES', 'icon' => 'app-window',
     115        'description' => 'Generate AI-friendly title tags that improve how your pages are named in search and AI answers.',
     116        'option' => 'ezy_ai_meta_titles_updated_at', 'view_path' => null,
     117        'modal_title' => 'Name your pages for search',
     118        'modal_desc'  => 'Generate AI-friendly title tags that improve how your pages surface in search and AI answers.',
    110119        'benefits' => array('Higher click-through', 'Better AI visibility', 'Optimised per page'),
    111120    ),
    … …  
    142151 */
    143152$toggle_pages = array();
    144 foreach (array('schemas' => 'ezy_ai_schemas_cache', 'meta_descriptions' => 'ezy_ai_meta_descriptions_cache') as $widget_key => $cache_option) {
     153foreach (array('schemas' => 'ezy_ai_schemas_cache', 'meta_descriptions' => 'ezy_ai_meta_descriptions_cache', 'meta_titles' => 'ezy_ai_meta_titles_cache') as $widget_key => $cache_option) {
    145154    $rows = get_option($cache_option);
    146155    $seen = array();
  • ezy-ai/trunk/assets/js/connection.js

    r3598687 r3701980  
    669669        schemas: 'Schema Markup',
    670670        meta_descriptions: 'Meta Descriptions',
     671        meta_titles: 'Meta Titles',
    671672        faqs_html: 'FAQs Page',
    672673        blogs: 'Blog Posts'
  • ezy-ai/trunk/ezy-ai-plugin.php

    r3635434 r3701980  
    44 * Plugin URI: https://ezy.ai/cms
    55 * Description: EZY.ai is the first all-in-one AEO plugin for WordPress with built-in AI content and chatbot lead gen. Get more customers from ChatGPT, Claude & Gemini.
    6  * Version: 2.3.2
     6 * Version: 2.4.0
    77 * Requires at least: 5.7
    88 * Requires PHP: 7.4
    … …  
    1818}
    1919
    20 define('EZY_AI_VERSION', '2.3.2');
     20define('EZY_AI_VERSION', '2.4.0');
    2121define('EZY_AI_PLUGIN_DIR', plugin_dir_path(__FILE__));
    2222define('EZY_AI_PLUGIN_URL', plugin_dir_url(__FILE__));
    … …  
    3333require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-schema.php';
    3434require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-meta-descriptions.php';
     35require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-meta-titles.php';
    3536require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-request-path.php';
    3637require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-widget-toggles.php';
    … …  
    5354global $ezy_ai_meta_descriptions;
    5455$ezy_ai_meta_descriptions = null;
     56global $ezy_ai_meta_titles;
     57$ezy_ai_meta_titles = null;
    5558
    5659class Ezy_AI_Plugin {
    … …  
    7982        $this->init_schema();
    8083        $this->init_meta_descriptions();
     84        $this->init_meta_titles();
    8185        $this->init_faqs();
    8286        $this->init_blogs();
    … …  
    401405    private function init_meta_descriptions() {
    402406        new EZY_AI_Meta_Descriptions();
     407    }
     408
     409    // Instantiated AFTER meta descriptions on purpose: both classes buffer
     410    // wp_head, and the titles buffer (opened second = inner) must close first
     411    // (PHP_INT_MAX - 1) so each strip pass runs on its own bytes.
     412    private function init_meta_titles() {
     413        new EZY_AI_Meta_Titles();
    403414    }
    404415
    … …  
    718729            'schemas' => get_option('ezy_ai_schemas_updated_at', null),
    719730            'meta_descriptions' => get_option('ezy_ai_meta_descriptions_updated_at', null),
     731            'meta_titles' => get_option('ezy_ai_meta_titles_updated_at', null),
    720732            'faqs_html' => get_option('ezy_ai_faqs_html_updated_at', null),
    721733            'blogs' => get_option('ezy_ai_blogs_updated_at', null),
    … …  
    761773            'schemas' => array('EZY_AI_Schema', 'sync_schemas'),
    762774            'meta_descriptions' => array('EZY_AI_Meta_Descriptions', 'sync_meta_descriptions'),
     775            'meta_titles' => array('EZY_AI_Meta_Titles', 'sync_meta_titles'),
    763776            'faqs_html' => array('EZY_AI_FAQs', 'sync_faqs_html'),
    764777            'blogs' => array('EZY_AI_Blogs', 'sync_blogs'),
    … …  
    10921105    require_once plugin_dir_path(__FILE__) . 'includes/class-ezy-ai-schema.php';
    10931106    require_once plugin_dir_path(__FILE__) . 'includes/class-ezy-ai-meta-descriptions.php';
     1107    require_once plugin_dir_path(__FILE__) . 'includes/class-ezy-ai-meta-titles.php';
    10941108    require_once plugin_dir_path(__FILE__) . 'includes/class-ezy-ai-faqs.php';
    10951109    require_once plugin_dir_path(__FILE__) . 'includes/class-ezy-ai-blogs.php';
  • ezy-ai/trunk/includes/class-ezy-ai-analytics-api.php

    r3635434 r3701980  
    380380            ),
    381381            array(
     382                'key'         => 'meta_titles',
     383                'label'       => 'Optimize Meta Titles',
     384                'description' => 'Improve how AI and Google headline your pages',
     385                'done'        => (bool) get_option('ezy_ai_meta_titles_updated_at'),
     386                'url'         => $ezy_url,
     387                'external'    => true,
     388            ),
     389            array(
    382390                'key'         => 'blogs',
    383391                'label'       => 'Expand content coverage',
    … …  
    526534                'schemas' => $this->toggleable_pages('schemas', 'ezy_ai_schemas_cache'),
    527535                'meta_descriptions' => $this->toggleable_pages('meta_descriptions', 'ezy_ai_meta_descriptions_cache'),
     536                'meta_titles' => $this->toggleable_pages('meta_titles', 'ezy_ai_meta_titles_cache'),
    528537            ),
    529538        ));
  • ezy-ai/trunk/includes/class-ezy-ai-api-client.php

    r3635434 r3701980  
    376376    }
    377377
     378    public function upload_meta_titles($integration_id, $meta_titles, $token) {
     379        if (empty($integration_id) || empty($meta_titles) || !is_array($meta_titles) || empty($token)) {
     380            return new WP_Error('invalid_params', 'Integration ID, meta titles array, and token are required');
     381        }
     382
     383        return $this->post(
     384            '/integrations/' . urlencode($integration_id) . '/upload-meta-titles',
     385            array(
     386                'meta_titles' => $meta_titles
     387            ),
     388            $token
     389        );
     390    }
     391
     392    public function get_meta_titles($token) {
     393        if (empty($token)) {
     394            return new WP_Error('invalid_params', 'Connection token is required');
     395        }
     396
     397        return $this->get('/meta-titles', $token);
     398    }
     399
    378400    public function get_faqs_html($token) {
    379401        if (empty($token)) {
  • ezy-ai/trunk/includes/class-ezy-ai-auth.php

    r3635434 r3701980  
    104104            'schema_markup' => true,
    105105            'meta_tags' => true,
     106            'meta_titles' => true,
    106107            'custom_scripts' => true,
    107108        );
    … …  
    241242        $schema = new EZY_AI_Schema();
    242243        $meta_descriptions = new EZY_AI_Meta_Descriptions();
     244        $meta_titles = new EZY_AI_Meta_Titles();
    243245        $faqs = new EZY_AI_FAQs();
    244246        $blogs = new EZY_AI_Blogs();
    … …  
    251253        $schema->clear_schemas();
    252254        $meta_descriptions->clear_meta_descriptions();
     255        $meta_titles->clear_meta_titles();
    253256        $faqs->clear_faqs_html();
    254257        $faqs->clear_all_faq_hubs();
  • ezy-ai/trunk/includes/class-ezy-ai-webhook.php

    r3635434 r3701980  
    344344                $result = $this->handle_meta_descriptions($data);
    345345                break;
     346
     347            case 'meta_titles':
     348                $result = $this->handle_meta_titles($data);
     349                break;
    346350               
    347351            case 'faqs_html':
    … …  
    602606    }
    603607
     608    private function handle_meta_titles($data) {
     609        if (empty($data) || !isset($data['meta_titles'])) {
     610            return false;
     611        }
     612
     613        global $ezy_ai_meta_titles;
     614        if (!$ezy_ai_meta_titles) {
     615            $ezy_ai_meta_titles = new EZY_AI_Meta_Titles();
     616        }
     617
     618        return $ezy_ai_meta_titles->update_meta_titles($data['meta_titles']);
     619    }
     620
    604621    private function handle_faqs_html($content, $data = null) {
    605622        global $ezy_ai_faqs;
    … …  
    730747            'schemas' => 'ezy_ai_schemas_updated_at',
    731748            'meta_descriptions' => 'ezy_ai_meta_descriptions_updated_at',
     749            'meta_titles' => 'ezy_ai_meta_titles_updated_at',
    732750            'faqs_html' => 'ezy_ai_faqs_html_updated_at',
    733751            'blog' => 'ezy_ai_blogs_updated_at',
    … …  
    767785            'schemas' => null,
    768786            'meta_descriptions' => null,
     787            'meta_titles' => null,
    769788        );
    770789
    … …  
    835854                }
    836855            }
     856        } elseif ($content_type === 'meta_titles') {
     857            $rows = get_option(EZY_AI_Meta_Titles::META_TITLES_OPTION);
     858            if (is_array($rows)) {
     859                foreach ($rows as $row) {
     860                    if (!empty($row['page_url'])) {
     861                        $paths[] = self::path_from_url($row['page_url']);
     862                    }
     863                }
     864            }
    837865        } elseif ($content_type === 'schemas') {
    838866            $rows = get_option(EZY_AI_Schema::SCHEMAS_OPTION);
  • ezy-ai/trunk/includes/class-ezy-ai-widget-fetch.php

    r3605598 r3701980  
    2424                    'required' => true,
    2525                    'type' => 'string',
    26                     'enum' => array('robots', 'llms', 'llms-full', 'schema', 'meta', 'sitemap', 'facts'),
     26                    'enum' => array('robots', 'llms', 'llms-full', 'schema', 'meta', 'title', 'sitemap', 'facts'),
    2727                    'validate_callback' => function($param) {
    28                         return in_array($param, array('robots', 'llms', 'llms-full', 'schema', 'meta', 'sitemap', 'facts'));
     28                        return in_array($param, array('robots', 'llms', 'llms-full', 'schema', 'meta', 'title', 'sitemap', 'facts'));
    2929                    }
    3030                ),
    … …  
    114114            case 'meta':
    115115                return $this->fetch_meta($page_urls);
     116
     117            case 'title':
     118                return $this->fetch_title($page_urls);
    116119               
    117120            case 'sitemap':
    … …  
    274277            'total_pages_requested' => $total_pages_requested,
    275278            'pages_with_meta' => $pages_with_meta
     279        ), 200);
     280    }
     281
     282    private function fetch_title($page_urls = null) {
     283        global $ezy_ai_meta_titles;
     284        if (!$ezy_ai_meta_titles) {
     285            $ezy_ai_meta_titles = new EZY_AI_Meta_Titles();
     286        }
     287
     288        $all_cached = get_option(EZY_AI_Meta_Titles::META_TITLES_OPTION);
     289       
     290        $cached_meta = array();
     291        if (is_array($all_cached)) {
     292            foreach ($all_cached as $meta) {
     293                if (!empty($meta['page_url']) && !empty($meta['meta_title'])) {
     294                    $cached_meta[] = array(
     295                        'page_url' => $meta['page_url'],
     296                        'meta_title' => $meta['meta_title'],
     297                        'is_ezy_generated' => true
     298                    );
     299                }
     300            }
     301        }
     302
     303        $total_pages_requested = is_array($page_urls) ? count($page_urls) : 0;
     304        $pages_with_meta = count($cached_meta);
     305
     306        return new WP_REST_Response(array(
     307            'success' => true,
     308            'widget' => 'title',
     309            'meta_titles' => $cached_meta,
     310            'updated_at' => time(),
     311            'exists' => count($cached_meta) > 0,
     312            'total_pages_requested' => $total_pages_requested,
     313            'pages_with_title' => $pages_with_meta
    276314        ), 200);
    277315    }
  • ezy-ai/trunk/includes/class-ezy-ai-widget-toggles.php

    r3635434 r3701980  
    3333        'schemas',
    3434        'meta_descriptions',
     35        'meta_titles',
    3536        'blogs',
    3637        'faqs_html',
    … …  
    3839
    3940    /**
    40      * Widgets that additionally support per-page exclusion. These two inject
     41     * Widgets that additionally support per-page exclusion. These three inject
    4142     * into wp_head keyed by page_url, so a page-level decision is meaningful;
    4243     * the rest are whole-site files where it is not.
    4344     */
    44     const PER_PAGE_WIDGETS = array('schemas', 'meta_descriptions');
     45    const PER_PAGE_WIDGETS = array('schemas', 'meta_descriptions', 'meta_titles');
    4546
    4647    public static function is_widget($key)
  • ezy-ai/trunk/readme.txt

    r3673755 r3701980  
    44Requires at least: 5.7
    55Tested up to: 7.0
    6 Stable tag: 2.3.2
     6Stable tag: 2.4.0
    77Requires PHP: 7.4
    88License: GPLv2 or later
    … …  
    169169
    170170== Changelog ==
     171
     172= 2.4.0 =
     173* New: Meta titles. EZY can now write and publish the title tag for each of your pages, the same way it already handles meta descriptions. Titles are generated from your EZY dashboard, sent to your site, and served from the page head with a small marker so EZY can confirm they are live.
     174* Detail: When an EZY title exists for a page it replaces the title produced by your theme or SEO plugin (Yoast, All in One SEO, Rank Math, SEOPress, The SEO Framework, Slim SEO and Squirrly are all handled), including the Open Graph and Twitter title variants, so the page shows one consistent title everywhere. Pages without an EZY title are left exactly as they were.
     175* Detail: Meta titles get their own switch on the EZY.ai > Widgets screen and can be turned off per page, like schema and meta descriptions. Switching the feature off hands the title back to your theme or SEO plugin immediately.
     176* Detail: The title tag inside inline SVG icons is never touched, so accessible names on icons keep working.
     177* Change: The site now reports meta title support to your EZY dashboard on connect and on each verification, so the Meta Titles widget unlocks for this site as soon as the plugin is updated.
    171178
    172179= 2.3.2 =
Note: See TracChangeset for help on using the changeset viewer.