Changeset 3701980
- Timestamp:
- 09/18/2026 12:38:57 PM (5 weeks ago)
- Location:
- ezy-ai
- Files:
-
- 2 added
- 1 deleted
- 12 edited
- 23 copied
-
tags/2.4.0 (copied) (copied from ezy-ai/trunk )
-
tags/2.4.0/README.md (copied) (copied from ezy-ai/trunk/README.md )
-
tags/2.4.0/admin/widgets.php (copied) (copied from ezy-ai/trunk/admin/widgets.php ) (3 diffs)
-
tags/2.4.0/assets/js/connection.js (modified) (1 diff)
-
tags/2.4.0/assets/js/widgets.js (copied) (copied from ezy-ai/trunk/assets/js/widgets.js )
-
tags/2.4.0/ezy-ai-plugin.php (copied) (copied from ezy-ai/trunk/ezy-ai-plugin.php ) (9 diffs)
-
tags/2.4.0/includes/class-ezy-ai-analytics-api.php (copied) (copied from ezy-ai/trunk/includes/class-ezy-ai-analytics-api.php ) (2 diffs)
-
tags/2.4.0/includes/class-ezy-ai-api-client.php (copied) (copied from ezy-ai/trunk/includes/class-ezy-ai-api-client.php ) (1 diff)
-
tags/2.4.0/includes/class-ezy-ai-auth.php (copied) (copied from ezy-ai/trunk/includes/class-ezy-ai-auth.php ) (3 diffs)
-
tags/2.4.0/includes/class-ezy-ai-blogs.php (copied) (copied from ezy-ai/trunk/includes/class-ezy-ai-blogs.php )
-
tags/2.4.0/includes/class-ezy-ai-content-path.php (copied) (copied from ezy-ai/trunk/includes/class-ezy-ai-content-path.php )
-
tags/2.4.0/includes/class-ezy-ai-facts.php (copied) (copied from ezy-ai/trunk/includes/class-ezy-ai-facts.php )
-
tags/2.4.0/includes/class-ezy-ai-faqs.php (copied) (copied from ezy-ai/trunk/includes/class-ezy-ai-faqs.php )
-
tags/2.4.0/includes/class-ezy-ai-file-tracker.php (copied) (copied from ezy-ai/trunk/includes/class-ezy-ai-file-tracker.php )
-
tags/2.4.0/includes/class-ezy-ai-llms.php (copied) (copied from ezy-ai/trunk/includes/class-ezy-ai-llms.php )
-
tags/2.4.0/includes/class-ezy-ai-meta-descriptions.php (copied) (copied from ezy-ai/trunk/includes/class-ezy-ai-meta-descriptions.php )
-
tags/2.4.0/includes/class-ezy-ai-meta-titles.php (added)
-
tags/2.4.0/includes/class-ezy-ai-robots.php (copied) (copied from ezy-ai/trunk/includes/class-ezy-ai-robots.php )
-
tags/2.4.0/includes/class-ezy-ai-schema.php (copied) (copied from ezy-ai/trunk/includes/class-ezy-ai-schema.php )
-
tags/2.4.0/includes/class-ezy-ai-sitemap.php (copied) (copied from ezy-ai/trunk/includes/class-ezy-ai-sitemap.php )
-
tags/2.4.0/includes/class-ezy-ai-uninstall.php (copied) (copied from ezy-ai/trunk/includes/class-ezy-ai-uninstall.php )
-
tags/2.4.0/includes/class-ezy-ai-webhook.php (copied) (copied from ezy-ai/trunk/includes/class-ezy-ai-webhook.php ) (5 diffs)
-
tags/2.4.0/includes/class-ezy-ai-widget-fetch.php (modified) (3 diffs)
-
tags/2.4.0/includes/class-ezy-ai-widget-toggle-applier.php (copied) (copied from ezy-ai/trunk/includes/class-ezy-ai-widget-toggle-applier.php )
-
tags/2.4.0/includes/class-ezy-ai-widget-toggles.php (copied) (copied from ezy-ai/trunk/includes/class-ezy-ai-widget-toggles.php ) (2 diffs)
-
tags/2.4.0/languages (deleted)
-
tags/2.4.0/readme.txt (copied) (copied from ezy-ai/trunk/readme.txt ) (2 diffs)
-
trunk/admin/widgets.php (modified) (3 diffs)
-
trunk/assets/js/connection.js (modified) (1 diff)
-
trunk/ezy-ai-plugin.php (modified) (9 diffs)
-
trunk/includes/class-ezy-ai-analytics-api.php (modified) (2 diffs)
-
trunk/includes/class-ezy-ai-api-client.php (modified) (1 diff)
-
trunk/includes/class-ezy-ai-auth.php (modified) (3 diffs)
-
trunk/includes/class-ezy-ai-meta-titles.php (added)
-
trunk/includes/class-ezy-ai-webhook.php (modified) (5 diffs)
-
trunk/includes/class-ezy-ai-widget-fetch.php (modified) (3 diffs)
-
trunk/includes/class-ezy-ai-widget-toggles.php (modified) (2 diffs)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
ezy-ai/tags/2.4.0/admin/widgets.php
r3635434 r3701980 41 41 '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"/>', 42 42 '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"/>', 43 44 '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"/>', 44 45 '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"/>', … … 104 105 'meta_descriptions' => array( 105 106 'label' => 'META DESCRIPTIONS', 'icon' => 'file-search', 106 'description' => 'Generate AI-friendly titles anddescriptions that improve visibility in search and AI answers.',107 'description' => 'Generate AI-friendly descriptions that improve visibility in search and AI answers.', 107 108 'option' => 'ezy_ai_meta_descriptions_updated_at', 'view_path' => null, 108 109 '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.', 110 119 'benefits' => array('Higher click-through', 'Better AI visibility', 'Optimised per page'), 111 120 ), … … 142 151 */ 143 152 $toggle_pages = array(); 144 foreach (array('schemas' => 'ezy_ai_schemas_cache', 'meta_descriptions' => 'ezy_ai_meta_descriptions_cache' ) as $widget_key => $cache_option) {153 foreach (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) { 145 154 $rows = get_option($cache_option); 146 155 $seen = array(); -
ezy-ai/tags/2.4.0/assets/js/connection.js
r3598687 r3701980 669 669 schemas: 'Schema Markup', 670 670 meta_descriptions: 'Meta Descriptions', 671 meta_titles: 'Meta Titles', 671 672 faqs_html: 'FAQs Page', 672 673 blogs: 'Blog Posts' -
ezy-ai/tags/2.4.0/ezy-ai-plugin.php
r3635434 r3701980 4 4 * Plugin URI: https://ezy.ai/cms 5 5 * 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.26 * Version: 2.4.0 7 7 * Requires at least: 5.7 8 8 * Requires PHP: 7.4 … … 18 18 } 19 19 20 define('EZY_AI_VERSION', '2. 3.2');20 define('EZY_AI_VERSION', '2.4.0'); 21 21 define('EZY_AI_PLUGIN_DIR', plugin_dir_path(__FILE__)); 22 22 define('EZY_AI_PLUGIN_URL', plugin_dir_url(__FILE__)); … … 33 33 require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-schema.php'; 34 34 require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-meta-descriptions.php'; 35 require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-meta-titles.php'; 35 36 require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-request-path.php'; 36 37 require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-widget-toggles.php'; … … 53 54 global $ezy_ai_meta_descriptions; 54 55 $ezy_ai_meta_descriptions = null; 56 global $ezy_ai_meta_titles; 57 $ezy_ai_meta_titles = null; 55 58 56 59 class Ezy_AI_Plugin { … … 79 82 $this->init_schema(); 80 83 $this->init_meta_descriptions(); 84 $this->init_meta_titles(); 81 85 $this->init_faqs(); 82 86 $this->init_blogs(); … … 401 405 private function init_meta_descriptions() { 402 406 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(); 403 414 } 404 415 … … 718 729 'schemas' => get_option('ezy_ai_schemas_updated_at', null), 719 730 'meta_descriptions' => get_option('ezy_ai_meta_descriptions_updated_at', null), 731 'meta_titles' => get_option('ezy_ai_meta_titles_updated_at', null), 720 732 'faqs_html' => get_option('ezy_ai_faqs_html_updated_at', null), 721 733 'blogs' => get_option('ezy_ai_blogs_updated_at', null), … … 761 773 'schemas' => array('EZY_AI_Schema', 'sync_schemas'), 762 774 'meta_descriptions' => array('EZY_AI_Meta_Descriptions', 'sync_meta_descriptions'), 775 'meta_titles' => array('EZY_AI_Meta_Titles', 'sync_meta_titles'), 763 776 'faqs_html' => array('EZY_AI_FAQs', 'sync_faqs_html'), 764 777 'blogs' => array('EZY_AI_Blogs', 'sync_blogs'), … … 1092 1105 require_once plugin_dir_path(__FILE__) . 'includes/class-ezy-ai-schema.php'; 1093 1106 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'; 1094 1108 require_once plugin_dir_path(__FILE__) . 'includes/class-ezy-ai-faqs.php'; 1095 1109 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 380 380 ), 381 381 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( 382 390 'key' => 'blogs', 383 391 'label' => 'Expand content coverage', … … 526 534 'schemas' => $this->toggleable_pages('schemas', 'ezy_ai_schemas_cache'), 527 535 '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'), 528 537 ), 529 538 )); -
ezy-ai/tags/2.4.0/includes/class-ezy-ai-api-client.php
r3635434 r3701980 376 376 } 377 377 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 378 400 public function get_faqs_html($token) { 379 401 if (empty($token)) { -
ezy-ai/tags/2.4.0/includes/class-ezy-ai-auth.php
r3635434 r3701980 104 104 'schema_markup' => true, 105 105 'meta_tags' => true, 106 'meta_titles' => true, 106 107 'custom_scripts' => true, 107 108 ); … … 241 242 $schema = new EZY_AI_Schema(); 242 243 $meta_descriptions = new EZY_AI_Meta_Descriptions(); 244 $meta_titles = new EZY_AI_Meta_Titles(); 243 245 $faqs = new EZY_AI_FAQs(); 244 246 $blogs = new EZY_AI_Blogs(); … … 251 253 $schema->clear_schemas(); 252 254 $meta_descriptions->clear_meta_descriptions(); 255 $meta_titles->clear_meta_titles(); 253 256 $faqs->clear_faqs_html(); 254 257 $faqs->clear_all_faq_hubs(); -
ezy-ai/tags/2.4.0/includes/class-ezy-ai-webhook.php
r3635434 r3701980 344 344 $result = $this->handle_meta_descriptions($data); 345 345 break; 346 347 case 'meta_titles': 348 $result = $this->handle_meta_titles($data); 349 break; 346 350 347 351 case 'faqs_html': … … 602 606 } 603 607 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 604 621 private function handle_faqs_html($content, $data = null) { 605 622 global $ezy_ai_faqs; … … 730 747 'schemas' => 'ezy_ai_schemas_updated_at', 731 748 'meta_descriptions' => 'ezy_ai_meta_descriptions_updated_at', 749 'meta_titles' => 'ezy_ai_meta_titles_updated_at', 732 750 'faqs_html' => 'ezy_ai_faqs_html_updated_at', 733 751 'blog' => 'ezy_ai_blogs_updated_at', … … 767 785 'schemas' => null, 768 786 'meta_descriptions' => null, 787 'meta_titles' => null, 769 788 ); 770 789 … … 835 854 } 836 855 } 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 } 837 865 } elseif ($content_type === 'schemas') { 838 866 $rows = get_option(EZY_AI_Schema::SCHEMAS_OPTION); -
ezy-ai/tags/2.4.0/includes/class-ezy-ai-widget-fetch.php
r3605598 r3701980 24 24 'required' => true, 25 25 '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'), 27 27 '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')); 29 29 } 30 30 ), … … 114 114 case 'meta': 115 115 return $this->fetch_meta($page_urls); 116 117 case 'title': 118 return $this->fetch_title($page_urls); 116 119 117 120 case 'sitemap': … … 274 277 'total_pages_requested' => $total_pages_requested, 275 278 '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 276 314 ), 200); 277 315 } -
ezy-ai/tags/2.4.0/includes/class-ezy-ai-widget-toggles.php
r3635434 r3701980 33 33 'schemas', 34 34 'meta_descriptions', 35 'meta_titles', 35 36 'blogs', 36 37 'faqs_html', … … 38 39 39 40 /** 40 * Widgets that additionally support per-page exclusion. These t woinject41 * Widgets that additionally support per-page exclusion. These three inject 41 42 * into wp_head keyed by page_url, so a page-level decision is meaningful; 42 43 * the rest are whole-site files where it is not. 43 44 */ 44 const PER_PAGE_WIDGETS = array('schemas', 'meta_descriptions' );45 const PER_PAGE_WIDGETS = array('schemas', 'meta_descriptions', 'meta_titles'); 45 46 46 47 public static function is_widget($key) -
ezy-ai/tags/2.4.0/readme.txt
r3673755 r3701980 4 4 Requires at least: 5.7 5 5 Tested up to: 7.0 6 Stable tag: 2. 3.26 Stable tag: 2.4.0 7 7 Requires PHP: 7.4 8 8 License: GPLv2 or later … … 169 169 170 170 == 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. 171 178 172 179 = 2.3.2 = -
ezy-ai/trunk/admin/widgets.php
r3635434 r3701980 41 41 '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"/>', 42 42 '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"/>', 43 44 '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"/>', 44 45 '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"/>', … … 104 105 'meta_descriptions' => array( 105 106 'label' => 'META DESCRIPTIONS', 'icon' => 'file-search', 106 'description' => 'Generate AI-friendly titles anddescriptions that improve visibility in search and AI answers.',107 'description' => 'Generate AI-friendly descriptions that improve visibility in search and AI answers.', 107 108 'option' => 'ezy_ai_meta_descriptions_updated_at', 'view_path' => null, 108 109 '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.', 110 119 'benefits' => array('Higher click-through', 'Better AI visibility', 'Optimised per page'), 111 120 ), … … 142 151 */ 143 152 $toggle_pages = array(); 144 foreach (array('schemas' => 'ezy_ai_schemas_cache', 'meta_descriptions' => 'ezy_ai_meta_descriptions_cache' ) as $widget_key => $cache_option) {153 foreach (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) { 145 154 $rows = get_option($cache_option); 146 155 $seen = array(); -
ezy-ai/trunk/assets/js/connection.js
r3598687 r3701980 669 669 schemas: 'Schema Markup', 670 670 meta_descriptions: 'Meta Descriptions', 671 meta_titles: 'Meta Titles', 671 672 faqs_html: 'FAQs Page', 672 673 blogs: 'Blog Posts' -
ezy-ai/trunk/ezy-ai-plugin.php
r3635434 r3701980 4 4 * Plugin URI: https://ezy.ai/cms 5 5 * 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.26 * Version: 2.4.0 7 7 * Requires at least: 5.7 8 8 * Requires PHP: 7.4 … … 18 18 } 19 19 20 define('EZY_AI_VERSION', '2. 3.2');20 define('EZY_AI_VERSION', '2.4.0'); 21 21 define('EZY_AI_PLUGIN_DIR', plugin_dir_path(__FILE__)); 22 22 define('EZY_AI_PLUGIN_URL', plugin_dir_url(__FILE__)); … … 33 33 require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-schema.php'; 34 34 require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-meta-descriptions.php'; 35 require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-meta-titles.php'; 35 36 require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-request-path.php'; 36 37 require_once EZY_AI_PLUGIN_DIR . 'includes/class-ezy-ai-widget-toggles.php'; … … 53 54 global $ezy_ai_meta_descriptions; 54 55 $ezy_ai_meta_descriptions = null; 56 global $ezy_ai_meta_titles; 57 $ezy_ai_meta_titles = null; 55 58 56 59 class Ezy_AI_Plugin { … … 79 82 $this->init_schema(); 80 83 $this->init_meta_descriptions(); 84 $this->init_meta_titles(); 81 85 $this->init_faqs(); 82 86 $this->init_blogs(); … … 401 405 private function init_meta_descriptions() { 402 406 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(); 403 414 } 404 415 … … 718 729 'schemas' => get_option('ezy_ai_schemas_updated_at', null), 719 730 'meta_descriptions' => get_option('ezy_ai_meta_descriptions_updated_at', null), 731 'meta_titles' => get_option('ezy_ai_meta_titles_updated_at', null), 720 732 'faqs_html' => get_option('ezy_ai_faqs_html_updated_at', null), 721 733 'blogs' => get_option('ezy_ai_blogs_updated_at', null), … … 761 773 'schemas' => array('EZY_AI_Schema', 'sync_schemas'), 762 774 'meta_descriptions' => array('EZY_AI_Meta_Descriptions', 'sync_meta_descriptions'), 775 'meta_titles' => array('EZY_AI_Meta_Titles', 'sync_meta_titles'), 763 776 'faqs_html' => array('EZY_AI_FAQs', 'sync_faqs_html'), 764 777 'blogs' => array('EZY_AI_Blogs', 'sync_blogs'), … … 1092 1105 require_once plugin_dir_path(__FILE__) . 'includes/class-ezy-ai-schema.php'; 1093 1106 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'; 1094 1108 require_once plugin_dir_path(__FILE__) . 'includes/class-ezy-ai-faqs.php'; 1095 1109 require_once plugin_dir_path(__FILE__) . 'includes/class-ezy-ai-blogs.php'; -
ezy-ai/trunk/includes/class-ezy-ai-analytics-api.php
r3635434 r3701980 380 380 ), 381 381 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( 382 390 'key' => 'blogs', 383 391 'label' => 'Expand content coverage', … … 526 534 'schemas' => $this->toggleable_pages('schemas', 'ezy_ai_schemas_cache'), 527 535 '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'), 528 537 ), 529 538 )); -
ezy-ai/trunk/includes/class-ezy-ai-api-client.php
r3635434 r3701980 376 376 } 377 377 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 378 400 public function get_faqs_html($token) { 379 401 if (empty($token)) { -
ezy-ai/trunk/includes/class-ezy-ai-auth.php
r3635434 r3701980 104 104 'schema_markup' => true, 105 105 'meta_tags' => true, 106 'meta_titles' => true, 106 107 'custom_scripts' => true, 107 108 ); … … 241 242 $schema = new EZY_AI_Schema(); 242 243 $meta_descriptions = new EZY_AI_Meta_Descriptions(); 244 $meta_titles = new EZY_AI_Meta_Titles(); 243 245 $faqs = new EZY_AI_FAQs(); 244 246 $blogs = new EZY_AI_Blogs(); … … 251 253 $schema->clear_schemas(); 252 254 $meta_descriptions->clear_meta_descriptions(); 255 $meta_titles->clear_meta_titles(); 253 256 $faqs->clear_faqs_html(); 254 257 $faqs->clear_all_faq_hubs(); -
ezy-ai/trunk/includes/class-ezy-ai-webhook.php
r3635434 r3701980 344 344 $result = $this->handle_meta_descriptions($data); 345 345 break; 346 347 case 'meta_titles': 348 $result = $this->handle_meta_titles($data); 349 break; 346 350 347 351 case 'faqs_html': … … 602 606 } 603 607 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 604 621 private function handle_faqs_html($content, $data = null) { 605 622 global $ezy_ai_faqs; … … 730 747 'schemas' => 'ezy_ai_schemas_updated_at', 731 748 'meta_descriptions' => 'ezy_ai_meta_descriptions_updated_at', 749 'meta_titles' => 'ezy_ai_meta_titles_updated_at', 732 750 'faqs_html' => 'ezy_ai_faqs_html_updated_at', 733 751 'blog' => 'ezy_ai_blogs_updated_at', … … 767 785 'schemas' => null, 768 786 'meta_descriptions' => null, 787 'meta_titles' => null, 769 788 ); 770 789 … … 835 854 } 836 855 } 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 } 837 865 } elseif ($content_type === 'schemas') { 838 866 $rows = get_option(EZY_AI_Schema::SCHEMAS_OPTION); -
ezy-ai/trunk/includes/class-ezy-ai-widget-fetch.php
r3605598 r3701980 24 24 'required' => true, 25 25 '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'), 27 27 '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')); 29 29 } 30 30 ), … … 114 114 case 'meta': 115 115 return $this->fetch_meta($page_urls); 116 117 case 'title': 118 return $this->fetch_title($page_urls); 116 119 117 120 case 'sitemap': … … 274 277 'total_pages_requested' => $total_pages_requested, 275 278 '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 276 314 ), 200); 277 315 } -
ezy-ai/trunk/includes/class-ezy-ai-widget-toggles.php
r3635434 r3701980 33 33 'schemas', 34 34 'meta_descriptions', 35 'meta_titles', 35 36 'blogs', 36 37 'faqs_html', … … 38 39 39 40 /** 40 * Widgets that additionally support per-page exclusion. These t woinject41 * Widgets that additionally support per-page exclusion. These three inject 41 42 * into wp_head keyed by page_url, so a page-level decision is meaningful; 42 43 * the rest are whole-site files where it is not. 43 44 */ 44 const PER_PAGE_WIDGETS = array('schemas', 'meta_descriptions' );45 const PER_PAGE_WIDGETS = array('schemas', 'meta_descriptions', 'meta_titles'); 45 46 46 47 public static function is_widget($key) -
ezy-ai/trunk/readme.txt
r3673755 r3701980 4 4 Requires at least: 5.7 5 5 Tested up to: 7.0 6 Stable tag: 2. 3.26 Stable tag: 2.4.0 7 7 Requires PHP: 7.4 8 8 License: GPLv2 or later … … 169 169 170 170 == 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. 171 178 172 179 = 2.3.2 =
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)