@lynxflow/seo-engine 1.6.9 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
  <?php
2
2
  /**
3
- * Plugin Name: LynxSEO Studio — Ultimate Programmatic SEO, Schema & AEO Engine
3
+ * Plugin Name: LynxSEO Studio — Complete Enterprise SEO & Programmatic AI Suite
4
4
  * Plugin URI: https://lynxseo.studio
5
- * Description: The ultimate WordPress SEO & Programmatic AI engine. In-memory URL matrix (< 0.05ms), Rank Math / Yoast On-Page parity score (0-100), Google Places verified reviews sync, 28 JSON-LD schemas, instant IndexNow pings, and /llms.txt for ChatGPT & Perplexity.
6
- * Version: 3.0.0
5
+ * Description: The ultimate all-in-one WordPress SEO plugin. Parity with Rank Math Pro, Yoast Premium & SEOPress + In-Memory Programmatic Matrix (< 0.05ms), 30 On-Page audit tests, 14+ Schema.org JSON-LD graphs, 404 Monitor, 301 Redirects, IndexNow auto-pings, Google Places Reviews sync, and /llms.txt AI search feed.
6
+ * Version: 3.2.0
7
7
  * Author: LynxSEO / LynxFlow Technologies
8
8
  * Author URI: https://lynxseo.studio
9
9
  * Text Domain: lynxseo
@@ -12,63 +12,53 @@
12
12
 
13
13
  if (!defined('ABSPATH')) exit;
14
14
 
15
- class LynxSeoWordPressMasterPlugin {
16
- private $option_name = 'lynxseo_settings';
17
- private $version = '3.0.0';
15
+ class LynxSeoMasterEnterprisePlugin {
16
+ private $option_name = 'lynxseo_enterprise_settings';
17
+ private $redirects_option = 'lynxseo_301_redirects';
18
+ private $logs_option = 'lynxseo_404_logs';
19
+ private $version = '3.2.0';
18
20
 
19
21
  public function __construct() {
20
- // Rewrite Rules & Template Routing
21
- add_action('init', array($this, 'register_rewrite_rules'));
22
- add_action('template_redirect', array($this, 'handle_programmatic_routing'));
23
-
24
- // Head injection (JSON-LD & Meta Tags)
25
- add_action('wp_head', array($this, 'inject_seo_head_tags'), 1);
26
-
27
- // Admin & Gutenberg Meta Boxes (Rank Math / Yoast On-Page Parity)
28
- add_action('add_meta_boxes', array($this, 'register_seo_metabox'));
29
- add_action('save_post', array($this, 'save_seo_metabox'));
30
- add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets'));
31
-
32
- // Admin Menus & Settings
33
- add_action('admin_menu', array($this, 'register_admin_menu'));
22
+ // Core Hooks
23
+ add_action('init', array($this, 'init_plugin'));
24
+ add_action('wp_head', array($this, 'inject_seo_header_metadata'), 1);
25
+ add_action('template_redirect', array($this, 'handle_routing_and_redirects'), 1);
26
+
27
+ // Admin & Meta Boxes (Rank Math / Yoast On-Page Parity)
28
+ add_action('add_meta_boxes', array($this, 'register_seo_metaboxes'));
29
+ add_action('save_post', array($this, 'save_seo_metabox_data'));
30
+ add_action('admin_menu', array($this, 'register_admin_menus'));
34
31
  add_action('admin_init', array($this, 'register_settings'));
35
32
 
36
- // Instant IndexNow Pinging on Post Update
37
- add_action('wp_after_insert_post', array($this, 'trigger_indexnow_ping'), 10, 2);
33
+ // Instant IndexNow & Search Engine Pinging
34
+ add_action('wp_after_insert_post', array($this, 'trigger_instant_indexing'), 10, 2);
38
35
 
39
- // Interactive Shortcodes
40
- add_shortcode('lynxseo_roi_calculator', array($this, 'render_roi_calculator_shortcode'));
41
- add_shortcode('lynxseo_serp_simulator', array($this, 'render_serp_simulator_shortcode'));
42
- add_shortcode('lynxseo_reviews', array($this, 'render_reviews_shortcode'));
43
- add_shortcode('lynxseo_breadcrumbs', array($this, 'render_breadcrumbs_shortcode'));
44
- add_shortcode('lynxseo_geolinks', array($this, 'render_geolinks_shortcode'));
36
+ // Shortcodes
37
+ add_shortcode('lynxseo_roi_calculator', array($this, 'sc_roi_calculator'));
38
+ add_shortcode('lynxseo_serp_simulator', array($this, 'sc_serp_simulator'));
39
+ add_shortcode('lynxseo_reviews', array($this, 'sc_reviews_badge'));
40
+ add_shortcode('lynxseo_breadcrumbs', array($this, 'sc_breadcrumbs'));
41
+ add_shortcode('lynxseo_geolinks', array($this, 'sc_geolinks'));
42
+ add_shortcode('lynxseo_density_analyzer', array($this, 'sc_density_analyzer'));
45
43
  }
46
44
 
47
- /* ────────────────────────────────────────────────────────────────────────
48
- * 1. REWRITE RULES & ROUTING (Programmatic Matrices & /llms.txt & Sitemaps)
49
- * ──────────────────────────────────────────────────────────────────────── */
50
- public function register_rewrite_rules() {
51
- // XML Sitemaps
52
- add_rewrite_rule('^sitemap-pseo\.xml$', 'index.php?lynx_sitemap=1', 'top');
53
- add_rewrite_rule('^sitemap-solutions\.xml$', 'index.php?lynx_sitemap=1', 'top');
54
-
55
- // AI Search Feeds (/llms.txt & /llms-full.txt)
45
+ public function init_plugin() {
46
+ // Sitemaps & Feeds rewrite rules
47
+ add_rewrite_rule('^sitemap_index\.xml$', 'index.php?lynx_sitemap=index', 'top');
48
+ add_rewrite_rule('^sitemap-pseo\.xml$', 'index.php?lynx_sitemap=pseo', 'top');
56
49
  add_rewrite_rule('^llms\.txt$', 'index.php?lynx_llms=1', 'top');
57
- add_rewrite_rule('^llms-full\.txt$', 'index.php?lynx_llms=full', 'top');
58
50
 
59
- // 8 Programmatic Intent Matrices
51
+ // Programmatic Matrices rewrite rules
60
52
  add_rewrite_rule('^solutions/([^/]+)/([^/]+)/([^/]+)/?', 'index.php?lynx_seo=1&lynx_type=geo&lynx_p1=$matches[1]&lynx_p2=$matches[2]&lynx_p3=$matches[3]', 'top');
61
53
  add_rewrite_rule('^comparatif/([^/]+)/?', 'index.php?lynx_seo=1&lynx_type=vs&lynx_p1=$matches[1]', 'top');
62
54
  add_rewrite_rule('^vs/([^/]+)/?', 'index.php?lynx_seo=1&lynx_type=vs&lynx_p1=$matches[1]', 'top');
63
55
  add_rewrite_rule('^alternatives/([^/]+)/?', 'index.php?lynx_seo=1&lynx_type=alt&lynx_p1=$matches[1]', 'top');
64
56
  add_rewrite_rule('^secteurs/([^/]+)/?', 'index.php?lynx_seo=1&lynx_type=industry&lynx_p1=$matches[1]', 'top');
65
- add_rewrite_rule('^industries/([^/]+)/?', 'index.php?lynx_seo=1&lynx_type=industry&lynx_p1=$matches[1]', 'top');
66
57
  add_rewrite_rule('^integrations/([^/]+)/?', 'index.php?lynx_seo=1&lynx_type=integration&lynx_p1=$matches[1]', 'top');
67
58
  add_rewrite_rule('^roles/([^/]+)/?', 'index.php?lynx_seo=1&lynx_type=role&lynx_p1=$matches[1]', 'top');
68
59
  add_rewrite_rule('^cas-usage/([^/]+)/?', 'index.php?lynx_seo=1&lynx_type=usecase&lynx_p1=$matches[1]', 'top');
69
60
  add_rewrite_rule('^outils/([^/]+)/?', 'index.php?lynx_seo=1&lynx_type=tool&lynx_p1=$matches[1]', 'top');
70
61
 
71
- // Rewrite tags
72
62
  add_rewrite_tag('%lynx_sitemap%', '([^&]+)');
73
63
  add_rewrite_tag('%lynx_llms%', '([^&]+)');
74
64
  add_rewrite_tag('%lynx_seo%', '([^&]+)');
@@ -78,236 +68,61 @@ class LynxSeoWordPressMasterPlugin {
78
68
  add_rewrite_tag('%lynx_p3%', '([^&]+)');
79
69
  }
80
70
 
81
- public function handle_programmatic_routing() {
82
- // Sitemap XML
83
- if (get_query_var('lynx_sitemap') == '1') {
84
- $this->render_dynamic_sitemap();
85
- exit;
86
- }
71
+ /* ────────────────────────────────────────────────────────────────────────
72
+ * 1. 301 REDIRECTS & 404 LOGGING & ROUTING
73
+ * ──────────────────────────────────────────────────────────────────────── */
74
+ public function handle_routing_and_redirects() {
75
+ $requested_path = trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/');
87
76
 
88
- // /llms.txt AI Search Feed
89
- if (get_query_var('lynx_llms')) {
90
- $this->render_llms_feed();
77
+ // Check 301 Redirects Table
78
+ $redirects = get_option($this->redirects_option, array());
79
+ if (!empty($redirects[$requested_path])) {
80
+ wp_redirect($redirects[$requested_path]['target'], $redirects[$requested_path]['code'] ?: 301);
91
81
  exit;
92
82
  }
93
83
 
94
- // Programmatic Page Resolution
95
- if (get_query_var('lynx_seo') != '1') return;
96
-
97
- $type = get_query_var('lynx_type');
98
- $p1 = sanitize_text_field(get_query_var('lynx_p1'));
99
- $p2 = sanitize_text_field(get_query_var('lynx_p2'));
100
- $p3 = sanitize_text_field(get_query_var('lynx_p3'));
101
-
102
- $settings = get_option($this->option_name, array());
103
- $brand_name = !empty($settings['brand_name']) ? $settings['brand_name'] : get_bloginfo('name');
104
- $site_url = get_site_url();
105
-
106
- $clean_service = ucwords(str_replace('-', ' ', $p1));
107
- $clean_city = ucwords(str_replace('-', ' ', $p3));
108
- $clean_country = strtoupper($p2 ?: 'FR');
109
- $year = date('Y');
110
-
111
- $page_data = $this->build_programmatic_page_data($type, $clean_service, $clean_city, $clean_country, $brand_name, $year);
112
-
113
- // Render page with Theme Template or Clean Standalone
114
- $use_theme = !empty($settings['use_theme_header_footer']);
115
- status_header(200);
116
- header('Content-Type: text/html; charset=utf-8');
117
-
118
- if ($use_theme) {
119
- get_header();
120
- echo '<div class="lynxseo-container" style="max-width:960px;margin:40px auto;padding:0 20px;">';
121
- $this->render_page_content($page_data, $brand_name, $site_url);
122
- echo '</div>';
123
- get_footer();
124
- } else {
125
- $this->render_standalone_html($page_data, $brand_name, $site_url);
84
+ // Handle 404 Logging
85
+ if (is_404()) {
86
+ $logs = get_option($this->logs_option, array());
87
+ $uri = sanitize_text_field($_SERVER['REQUEST_URI']);
88
+ $logs[$uri] = array(
89
+ 'uri' => $uri,
90
+ 'hits' => isset($logs[$uri]['hits']) ? $logs[$uri]['hits'] + 1 : 1,
91
+ 'last_time' => current_time('mysql'),
92
+ 'referrer' => isset($_SERVER['HTTP_REFERER']) ? sanitize_text_field($_SERVER['HTTP_REFERER']) : ''
93
+ );
94
+ if (count($logs) > 200) $logs = array_slice($logs, -200, 200, true);
95
+ update_option($this->logs_option, $logs);
126
96
  }
127
- exit;
128
- }
129
97
 
130
- private function build_programmatic_page_data($type, $service, $city, $country, $brand, $year) {
131
- switch ($type) {
132
- case 'geo':
133
- return array(
134
- 'title' => "{$service} à {$city} ({$country}) — {$brand}",
135
- 'h1' => "{$service} à {$city}",
136
- 'desc' => "Découvrez la solution de {$service} à {$city}. Automatisez vos opérations, optimisez vos flux et boostez votre croissance.",
137
- 'direct_answer' => "{$brand} est la solution de référence pour {$service} à {$city} ({$country}). Déploiement en 10 minutes avec avis clients vérifiés et support dédié.",
138
- 'schema_type' => 'LocalBusiness'
139
- );
140
- case 'vs':
141
- return array(
142
- 'title' => "{$brand} vs {$service} : Comparatif Complet & Tarifs {$year}",
143
- 'h1' => "Comparatif : {$brand} vs {$service}",
144
- 'desc' => "Pourquoi choisir {$brand} plutôt que {$service} ? Découvrez le comparatif des fonctionnalités, prix sans engagement et retours d'expérience.",
145
- 'direct_answer' => "{$brand} se distingue de {$service} par une tarification transparente sans engagement, une automatisation IA native 24/7 et un support réactif.",
146
- 'schema_type' => 'Product'
147
- );
148
- case 'alt':
149
- return array(
150
- 'title' => "Meilleure Alternative à {$service} en {$year} — {$brand}",
151
- 'h1' => "Alternative moderne à {$service}",
152
- 'desc' => "Vous cherchez une alternative moderne à {$service} ? Découvrez pourquoi les entreprises migrent vers {$brand}.",
153
- 'direct_answer' => "La meilleure alternative à {$service} est {$brand}, offrant une flexibilité totale, des fonctionnalités IA avancées et jusqu'à 60% d'économies.",
154
- 'schema_type' => 'SoftwareApplication'
155
- );
156
- case 'industry':
157
- return array(
158
- 'title' => "Solution {$service} pour le secteur {$city} — {$brand}",
159
- 'h1' => "{$service} conçu pour {$city}",
160
- 'desc' => "Logiciel métier adapté aux exigences des professionnels du secteur {$city}.",
161
- 'direct_answer' => "{$brand} fournit une infrastructure complète de {$service} pour le secteur {$city}, conforme RGPD et déployable sans code.",
162
- 'schema_type' => 'BusinessAudience'
163
- );
164
- case 'integration':
165
- return array(
166
- 'title' => "Intégration {$service} & {$brand} — Connexion en 1 Clic",
167
- 'h1' => "Connectez {$service} à votre compte {$brand}",
168
- 'desc' => "Synchronisation bidirectionnelle instantanée entre {$service} et {$brand}.",
169
- 'direct_answer' => "L'intégration native entre {$service} et {$brand} permet de synchroniser contacts et données en temps réel sans code.",
170
- 'schema_type' => 'SoftwareApplication'
171
- );
172
- default:
173
- return array(
174
- 'title' => "{$service} — Solution {$brand}",
175
- 'h1' => $service,
176
- 'desc' => "Découvrez la plateforme de {$service} avec {$brand}. Automatisation intelligente et gain de temps mesurable.",
177
- 'direct_answer' => "{$brand} automatise {$service} en moins de 10 minutes avec synchronisation de vos données.",
178
- 'schema_type' => 'SoftwareApplication'
179
- );
98
+ // Sitemaps Render
99
+ if (get_query_var('lynx_sitemap')) {
100
+ $this->render_xml_sitemaps();
101
+ exit;
180
102
  }
181
- }
182
-
183
- private function render_page_content($data, $brand, $site_url) {
184
- $settings = get_option($this->option_name, array());
185
- $rating = !empty($settings['google_rating']) ? floatval($settings['google_rating']) : 4.9;
186
- $review_count = !empty($settings['google_reviews_count']) ? intval($settings['google_reviews_count']) : 128;
187
-
188
- echo '<div style="background:#eff6ff;color:#1e40af;padding:6px 14px;border-radius:999px;display:inline-block;font-size:12px;font-weight:700;margin-bottom:16px;">⚡ Solution Certifiée & Synchro Directe</div>';
189
- echo '<h1 style="font-size:2.25rem;font-weight:800;color:#0f172a;margin-bottom:12px;">' . esc_html($data['h1']) . '</h1>';
190
- echo '<p style="font-size:1.15rem;color:#475569;margin-bottom:24px;">' . esc_html($data['desc']) . '</p>';
191
-
192
- // AEO / Search IA Direct Answer Callout
193
- echo '<div style="background:#f8fafc;border-left:4px solid #2563eb;padding:20px;border-radius:8px;margin:24px 0;">';
194
- echo '<strong style="color:#0f172a;">🤖 En Résumé (Réponse Directe IA) :</strong>';
195
- echo '<p style="margin:8px 0 0;color:#334155;">' . esc_html($data['direct_answer']) . '</p>';
196
- echo '</div>';
197
103
 
198
- // Verified Reviews Trust Badge (if configured)
199
- if (!empty($settings['enable_verified_reviews'])) {
200
- echo '<div style="display:flex;align-items:center;gap:8px;margin:20px 0;font-size:14px;color:#d97706;font-weight:600;">';
201
- echo '<span>★ ★ ★ ★ ★</span> <span style="color:#0f172a;">' . esc_html($rating) . '/5</span> <span style="color:#64748b;font-weight:normal;">(' . esc_html($review_count) . ' avis vérifiés Google Places)</span>';
202
- echo '</div>';
104
+ // /llms.txt AI Search Feed Render
105
+ if (get_query_var('lynx_llms')) {
106
+ $this->render_llms_txt();
107
+ exit;
203
108
  }
204
109
 
205
- // Call to action box
206
- echo '<div style="background:#f1f5f9;border-radius:12px;padding:28px;text-align:center;margin-top:32px;">';
207
- echo '<h2 style="font-size:1.5rem;font-weight:700;color:#0f172a;margin:0 0 8px;">Prêt à déployer ' . esc_html($brand) . ' ?</h2>';
208
- echo '<p style="color:#64748b;margin:0 0 16px;">Mise en place immédiate en moins de 10 minutes sans engagement.</p>';
209
- echo '<a href="' . esc_url($site_url) . '" style="display:inline-block;background:#2563eb;color:#fff;font-weight:600;padding:12px 28px;border-radius:8px;text-decoration:none;">Découvrir l\'Offre &rarr;</a>';
210
- echo '</div>';
211
- }
212
-
213
- private function render_standalone_html($data, $brand, $site_url) {
214
- $settings = get_option($this->option_name, array());
215
- $rating = !empty($settings['google_rating']) ? floatval($settings['google_rating']) : 4.9;
216
- $review_count = !empty($settings['google_reviews_count']) ? intval($settings['google_reviews_count']) : 128;
217
-
218
- $json_ld = array(
219
- "@context" => "https://schema.org",
220
- "@type" => $data['schema_type'],
221
- "name" => $data['h1'],
222
- "description" => $data['desc'],
223
- "aggregateRating" => array(
224
- "@type" => "AggregateRating",
225
- "ratingValue" => strval($rating),
226
- "reviewCount" => strval($review_count),
227
- "bestRating" => "5"
228
- ),
229
- "offers" => array(
230
- "@type" => "Offer",
231
- "priceCurrency" => "EUR",
232
- "price" => "99.00",
233
- "availability" => "https://schema.org/InStock"
234
- )
235
- );
236
-
237
- echo '<!DOCTYPE html><html lang="fr"><head>';
238
- echo '<meta charset="UTF-8">';
239
- echo '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
240
- echo '<title>' . esc_html($data['title']) . '</title>';
241
- echo '<meta name="description" content="' . esc_attr($data['desc']) . '">';
242
- echo '<script type="application/ld+json">' . json_encode($json_ld, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . '</script>';
243
- echo '<style>
244
- body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; line-height: 1.6; color: #1e293b; max-width: 900px; margin: 40px auto; padding: 0 20px; }
245
- h1 { font-size: 2.25rem; font-weight: 800; color: #0f172a; margin-bottom: 12px; }
246
- .desc { font-size: 1.15rem; color: #475569; margin-bottom: 24px; }
247
- .direct-answer { background: #eff6ff; border-left: 4px solid #2563eb; padding: 20px; border-radius: 8px; margin: 24px 0; font-size: 1.05rem; color: #1e3a8a; }
248
- .cta-box { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; padding: 28px; text-align: center; margin-top: 32px; }
249
- .cta-btn { display: inline-block; background: #2563eb; color: #ffffff; padding: 12px 28px; border-radius: 8px; font-weight: 600; text-decoration: none; margin-top: 12px; }
250
- </style>';
251
- echo '</head><body>';
252
- $this->render_page_content($data, $brand, $site_url);
253
- echo '</body></html>';
254
- }
255
-
256
- private function render_dynamic_sitemap() {
257
- header('Content-Type: application/xml; charset=utf-8');
258
- $site_url = get_site_url();
259
- $urls = array(
260
- '/solutions/crm/fr/paris',
261
- '/solutions/crm/fr/lyon',
262
- '/solutions/crm/fr/marseille',
263
- '/solutions/facturation/fr/paris',
264
- '/comparatif/hubspot',
265
- '/comparatif/salesforce',
266
- '/alternatives/alternative-a-aircall',
267
- '/secteurs/avocats',
268
- '/secteurs/agences-immobilieres',
269
- '/integrations/shopify',
270
- '/integrations/woocommerce',
271
- '/roles/directeur-commercial',
272
- '/cas-usage/relance-devis',
273
- '/outils/simulateur-roi'
274
- );
275
-
276
- echo '<?xml version="1.0" encoding="UTF-8"?>';
277
- echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
278
- foreach ($urls as $u) {
279
- echo '<url><loc>' . esc_url($site_url . $u) . '</loc><changefreq>weekly</changefreq><priority>0.8</priority></url>';
110
+ // Programmatic Routing
111
+ if (get_query_var('lynx_seo') == '1') {
112
+ $this->render_programmatic_page();
113
+ exit;
280
114
  }
281
- echo '</urlset>';
282
- }
283
-
284
- private function render_llms_feed() {
285
- header('Content-Type: text/plain; charset=utf-8');
286
- $settings = get_option($this->option_name, array());
287
- $brand_name = !empty($settings['brand_name']) ? $settings['brand_name'] : get_bloginfo('name');
288
- $site_url = get_site_url();
289
-
290
- echo "# " . $brand_name . " — AI Search & LLM Index\n\n";
291
- echo "> " . get_bloginfo('description') . "\n\n";
292
- echo "## Core Capabilities\n";
293
- echo "- Platform URL: " . $site_url . "\n";
294
- echo "- SEO Architecture: In-Memory Programmatic Matrix with LynxSEO Engine\n";
295
- echo "- Verified Reviews: Google Places & Trustpilot Synchronized\n";
296
- echo "- API & Docs: " . $site_url . "/llms.txt\n\n";
297
- echo "## Key Topics & Solutions\n";
298
- echo "- Solutions Matrix: " . $site_url . "/sitemap-pseo.xml\n";
299
115
  }
300
116
 
301
117
  /* ────────────────────────────────────────────────────────────────────────
302
- * 2. ON-PAGE META BOX (Rank Math & Yoast Parity Score 0-100)
118
+ * 2. ON-PAGE META BOX (30 Algorithmic Checks: Rank Math & Yoast Parity)
303
119
  * ──────────────────────────────────────────────────────────────────────── */
304
- public function register_seo_metabox() {
305
- $screens = array('post', 'page');
306
- foreach ($screens as $screen) {
120
+ public function register_seo_metaboxes() {
121
+ foreach (array('post', 'page', 'product') as $screen) {
307
122
  add_meta_box(
308
- 'lynxseo_metabox',
309
- '⚡ LynxSEO Studio — Score On-Page & Audit IA (Parité Rank Math/Yoast)',
310
- array($this, 'render_seo_metabox'),
123
+ 'lynxseo_full_metabox',
124
+ '⚡ LynxSEO Studio — Audit On-Page & Simulateur SERP (Parité Rank Math/Yoast)',
125
+ array($this, 'render_metabox_ui'),
311
126
  $screen,
312
127
  'normal',
313
128
  'high'
@@ -315,208 +130,338 @@ class LynxSeoWordPressMasterPlugin {
315
130
  }
316
131
  }
317
132
 
318
- public function render_seo_metabox($post) {
319
- wp_nonce_field('lynxseo_save_meta', 'lynxseo_meta_nonce');
320
- $focus_keyword = get_post_meta($post->ID, '_lynxseo_focus_keyword', true);
321
- $custom_title = get_post_meta($post->ID, '_lynxseo_custom_title', true);
322
- $custom_desc = get_post_meta($post->ID, '_lynxseo_custom_desc', true);
323
- $canonical_url = get_post_meta($post->ID, '_lynxseo_canonical_url', true);
324
- $schema_type = get_post_meta($post->ID, '_lynxseo_schema_type', true) ?: 'Article';
325
-
326
- // Calculate On-Page Score (0-100)
327
- $score = $this->calculate_onpage_score($post, $focus_keyword, $custom_title, $custom_desc);
328
- $score_color = $score >= 80 ? '#16a34a' : ($score >= 50 ? '#d97706' : '#dc2626');
133
+ public function render_metabox_ui($post) {
134
+ wp_nonce_field('lynxseo_meta_action', 'lynxseo_nonce');
135
+ $kw = get_post_meta($post->ID, '_lynxseo_focus_kw', true);
136
+ $title = get_post_meta($post->ID, '_lynxseo_title', true);
137
+ $desc = get_post_meta($post->ID, '_lynxseo_desc', true);
138
+ $canonical = get_post_meta($post->ID, '_lynxseo_canonical', true);
139
+ $noindex = get_post_meta($post->ID, '_lynxseo_noindex', true);
140
+ $schema = get_post_meta($post->ID, '_lynxseo_schema', true) ?: 'Article';
141
+
142
+ $analysis = $this->run_30_point_seo_audit($post, $kw, $title, $desc);
143
+ $score = $analysis['score'];
144
+ $score_bg = $score >= 80 ? '#16a34a' : ($score >= 50 ? '#d97706' : '#dc2626');
329
145
  ?>
330
- <div class="lynxseo-metabox-wrap" style="padding: 12px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
331
- <!-- Score Header -->
332
- <div style="display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #e2e8f0; padding-bottom: 16px; margin-bottom: 20px;">
333
- <div style="display: flex; align-items: center; gap: 12px;">
334
- <div style="background: <?php echo $score_color; ?>; color: #fff; width: 44px; height: 44px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 16px;">
146
+ <div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: #1e293b;">
147
+ <!-- Header with Live Score -->
148
+ <div style="display:flex;align-items:center;justify-content:space-between;background:#f8fafc;border:1px solid #e2e8f0;border-radius:12px;padding:16px 20px;margin-bottom:20px;">
149
+ <div style="display:flex;align-items:center;gap:14px;">
150
+ <div style="background:<?php echo $score_bg; ?>;color:#fff;width:48px;height:48px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:800;">
335
151
  <?php echo $score; ?>
336
152
  </div>
337
153
  <div>
338
- <strong style="font-size: 15px; color: #0f172a;">Score On-Page Global (0-100)</strong>
339
- <div style="font-size: 12px; color: #64748b;">Parité algorithmique Rank Math (30 critères) & Yoast Flesch Score</div>
154
+ <strong style="font-size:16px;color:#0f172a;">Score d'Optimisation On-Page (0-100)</strong>
155
+ <div style="font-size:12px;color:#64748b;">30 critères Google Rank Math & Lisibilité Flesch Yoast</div>
340
156
  </div>
341
157
  </div>
342
- <div style="font-size: 12px; font-weight: 600; color: <?php echo $score_color; ?>;">
343
- <?php echo $score >= 80 ? '🟢 Excellent pour Google & Search IA' : ($score >= 50 ? '🟡 Optimisations requises' : '🔴 Score insuffisant'); ?>
158
+ <div style="font-size:13px;font-weight:700;color:<?php echo $score_bg; ?>;">
159
+ <?php echo $score >= 80 ? '🟢 Parfaitement optimisé pour Google & Search IA' : ($score >= 50 ? '🟡 Optimisations conseillées' : '🔴 Action requise'); ?>
344
160
  </div>
345
161
  </div>
346
162
 
347
- <!-- Focus Keyword Field -->
348
- <div style="margin-bottom: 16px;">
349
- <label style="display: block; font-weight: 600; font-size: 13px; color: #0f172a; margin-bottom: 6px;">Mot-Clé Principal Cible (Focus Keyword) :</label>
350
- <input type="text" name="lynxseo_focus_keyword" value="<?php echo esc_attr($focus_keyword); ?>" placeholder="ex: logiciel de facturation en ligne" style="width: 100%; padding: 8px 12px; border-radius: 6px; border: 1px solid #cbd5e1;" />
163
+ <!-- Tab Buttons -->
164
+ <div style="display:flex;gap:8px;border-bottom:1px solid #e2e8f0;padding-bottom:8px;margin-bottom:16px;">
165
+ <button type="button" class="lynx-tab-btn" onclick="openLynxTab(event, 'lynx_tab_general')" style="background:#2563eb;color:#fff;border:0;padding:6px 16px;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">Général & SERP</button>
166
+ <button type="button" class="lynx-tab-btn" onclick="openLynxTab(event, 'lynx_tab_checklist')" style="background:#f1f5f9;color:#475569;border:0;padding:6px 16px;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">Règles On-Page (<?php echo count($analysis['checks']); ?>)</button>
167
+ <button type="button" class="lynx-tab-btn" onclick="openLynxTab(event, 'lynx_tab_schema')" style="background:#f1f5f9;color:#475569;border:0;padding:6px 16px;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">Schémas Schema.org</button>
168
+ <button type="button" class="lynx-tab-btn" onclick="openLynxTab(event, 'lynx_tab_advanced')" style="background:#f1f5f9;color:#475569;border:0;padding:6px 16px;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">Indexation & Canonical</button>
351
169
  </div>
352
170
 
353
- <!-- SERP Snippet Preview Box -->
354
- <div style="background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 16px; margin-bottom: 20px;">
355
- <div style="font-size: 11px; font-weight: 700; color: #64748b; text-transform: uppercase; margin-bottom: 8px;">Aperçu SERP Google (Pixel Width 600px) :</div>
356
- <div style="font-size: 12px; color: #202124; margin-bottom: 2px;"><?php echo esc_url(get_permalink($post->ID)); ?></div>
357
- <div style="font-size: 18px; color: #1a0dab; font-weight: 500; text-decoration: none; margin-bottom: 4px;"><?php echo esc_html($custom_title ?: get_the_title($post->ID)); ?></div>
358
- <div style="font-size: 13px; color: #4d5156; line-height: 1.4;"><?php echo esc_html($custom_desc ?: wp_strip_all_tags($post->post_excerpt ?: wp_trim_words($post->post_content, 25))); ?></div>
171
+ <!-- Tab 1: General & SERP Preview -->
172
+ <div id="lynx_tab_general" class="lynx-tab-content">
173
+ <div style="margin-bottom:16px;">
174
+ <label style="display:block;font-weight:600;font-size:13px;margin-bottom:4px;">Mot-Clé Principal (Focus Keyword) :</label>
175
+ <input type="text" name="lynxseo_focus_kw" value="<?php echo esc_attr($kw); ?>" placeholder="ex: logiciel de facturation sans engagement" style="width:100%;padding:8px 12px;border-radius:6px;border:1px solid #cbd5e1;" />
176
+ </div>
177
+
178
+ <!-- Google SERP Snippet Preview (600px Pixel Width) -->
179
+ <div style="background:#fff;border:1px solid #dadce0;border-radius:8px;padding:16px;margin-bottom:16px;">
180
+ <div style="font-size:11px;font-weight:700;color:#70757a;text-transform:uppercase;margin-bottom:6px;">Aperçu SERP Google (Pixel Width 600px) :</div>
181
+ <div style="font-size:12px;color:#202124;"><?php echo esc_url(get_permalink($post->ID)); ?></div>
182
+ <div style="font-size:18px;color:#1a0dab;font-weight:500;margin:2px 0 4px;"><?php echo esc_html($title ?: get_the_title($post->ID)); ?></div>
183
+ <div style="font-size:13px;color:#4d5156;line-height:1.4;"><?php echo esc_html($desc ?: wp_strip_all_tags($post->post_excerpt ?: wp_trim_words($post->post_content, 25))); ?></div>
184
+ </div>
185
+
186
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
187
+ <div>
188
+ <label style="display:block;font-weight:600;font-size:13px;margin-bottom:4px;">Titre SEO Personnalisé :</label>
189
+ <input type="text" name="lynxseo_title" value="<?php echo esc_attr($title); ?>" placeholder="Laissez vide pour le titre WordPress" style="width:100%;padding:8px 12px;border-radius:6px;border:1px solid #cbd5e1;" />
190
+ </div>
191
+ <div>
192
+ <label style="display:block;font-weight:600;font-size:13px;margin-bottom:4px;">Meta Description :</label>
193
+ <textarea name="lynxseo_desc" rows="2" placeholder="Description entre 120 et 160 caractères..." style="width:100%;padding:8px 12px;border-radius:6px;border:1px solid #cbd5e1;"><?php echo esc_textarea($desc); ?></textarea>
194
+ </div>
195
+ </div>
359
196
  </div>
360
197
 
361
- <!-- Custom SEO Title & Meta Desc -->
362
- <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px;">
363
- <div>
364
- <label style="display: block; font-weight: 600; font-size: 13px; color: #0f172a; margin-bottom: 4px;">Titre SEO Personnalisé :</label>
365
- <input type="text" name="lynxseo_custom_title" value="<?php echo esc_attr($custom_title); ?>" placeholder="Laissez vide pour utiliser le titre WordPress" style="width: 100%; padding: 8px 12px; border-radius: 6px; border: 1px solid #cbd5e1;" />
198
+ <!-- Tab 2: On-Page Checklist Analysis -->
199
+ <div id="lynx_tab_checklist" class="lynx-tab-content" style="display:none;">
200
+ <div style="display:grid;gap:8px;">
201
+ <?php foreach ($analysis['checks'] as $c): ?>
202
+ <div style="display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-radius:8px;background:<?php echo $c['pass'] ? '#f0fdf4' : '#fef2f2'; ?>;border:1px solid <?php echo $c['pass'] ? '#bbf7d0' : '#fecaca'; ?>;">
203
+ <span style="font-size:13px;color:<?php echo $c['pass'] ? '#166534' : '#991b1b'; ?>;">
204
+ <?php echo $c['pass'] ? '✅' : '❌'; ?> <?php echo esc_html($c['label']); ?>
205
+ </span>
206
+ <strong style="font-size:12px;color:<?php echo $c['pass'] ? '#15803d' : '#b91c1c'; ?>;"><?php echo $c['pass'] ? '+ ' . $c['points'] . ' pts' : '0 pt'; ?></strong>
207
+ </div>
208
+ <?php endforeach; ?>
366
209
  </div>
367
- <div>
368
- <label style="display: block; font-weight: 600; font-size: 13px; color: #0f172a; margin-bottom: 4px;">Schéma Schema.org JSON-LD :</label>
369
- <select name="lynxseo_schema_type" style="width: 100%; padding: 8px 12px; border-radius: 6px; border: 1px solid #cbd5e1;">
370
- <option value="Article" <?php selected($schema_type, 'Article'); ?>>Article / Blog Post</option>
371
- <option value="SoftwareApplication" <?php selected($schema_type, 'SoftwareApplication'); ?>>Software Application</option>
372
- <option value="Product" <?php selected($schema_type, 'Product'); ?>>Product (avec Avis)</option>
373
- <option value="LocalBusiness" <?php selected($schema_type, 'LocalBusiness'); ?>>Local Business</option>
374
- <option value="FAQPage" <?php selected($schema_type, 'FAQPage'); ?>>FAQ Page Accordéon</option>
210
+ </div>
211
+
212
+ <!-- Tab 3: Schema.org -->
213
+ <div id="lynx_tab_schema" class="lynx-tab-content" style="display:none;">
214
+ <div style="margin-bottom:16px;">
215
+ <label style="display:block;font-weight:600;font-size:13px;margin-bottom:4px;">Type de Schéma JSON-LD pour cette page :</label>
216
+ <select name="lynxseo_schema" style="width:100%;padding:8px 12px;border-radius:6px;border:1px solid #cbd5e1;">
217
+ <option value="Article" <?php selected($schema, 'Article'); ?>>Article / Blog Post</option>
218
+ <option value="SoftwareApplication" <?php selected($schema, 'SoftwareApplication'); ?>>Software Application (SaaS)</option>
219
+ <option value="Product" <?php selected($schema, 'Product'); ?>>Produit avec Avis Clients</option>
220
+ <option value="LocalBusiness" <?php selected($schema, 'LocalBusiness'); ?>>Entreprise Locale (LocalBusiness)</option>
221
+ <option value="FAQPage" <?php selected($schema, 'FAQPage'); ?>>FAQPage (Questions / Réponses)</option>
222
+ <option value="HowTo" <?php selected($schema, 'HowTo'); ?>>HowTo (Tutoriel étape par étape)</option>
375
223
  </select>
376
224
  </div>
377
225
  </div>
378
226
 
379
- <div style="margin-bottom: 16px;">
380
- <label style="display: block; font-weight: 600; font-size: 13px; color: #0f172a; margin-bottom: 4px;">Meta Description Ciblée :</label>
381
- <textarea name="lynxseo_custom_desc" rows="2" placeholder="Description attractive de 140 à 160 caractères..." style="width: 100%; padding: 8px 12px; border-radius: 6px; border: 1px solid #cbd5e1;"><?php echo esc_textarea($custom_desc); ?></textarea>
227
+ <!-- Tab 4: Advanced Indexing -->
228
+ <div id="lynx_tab_advanced" class="lynx-tab-content" style="display:none;">
229
+ <div style="margin-bottom:16px;">
230
+ <label style="display:block;font-weight:600;font-size:13px;margin-bottom:4px;">URL Canonique Personnalisée :</label>
231
+ <input type="text" name="lynxseo_canonical" value="<?php echo esc_attr($canonical); ?>" placeholder="<?php echo esc_url(get_permalink($post->ID)); ?>" style="width:100%;padding:8px 12px;border-radius:6px;border:1px solid #cbd5e1;" />
232
+ </div>
233
+ <div>
234
+ <label style="font-size:13px;color:#0f172a;display:flex;align-items:center;gap:8px;">
235
+ <input type="checkbox" name="lynxseo_noindex" value="1" <?php checked($noindex, 1); ?> />
236
+ Activer <code>noindex, nofollow</code> pour masquer cette page des moteurs de recherche
237
+ </label>
238
+ </div>
382
239
  </div>
383
240
  </div>
241
+
242
+ <script>
243
+ function openLynxTab(evt, tabId) {
244
+ var contents = document.getElementsByClassName('lynx-tab-content');
245
+ for (var i = 0; i < contents.length; i++) contents[i].style.display = 'none';
246
+ var btns = document.getElementsByClassName('lynx-tab-btn');
247
+ for (var i = 0; i < btns.length; i++) {
248
+ btns[i].style.background = '#f1f5f9';
249
+ btns[i].style.color = '#475569';
250
+ }
251
+ document.getElementById(tabId).style.display = 'block';
252
+ evt.currentTarget.style.background = '#2563eb';
253
+ evt.currentTarget.style.color = '#ffffff';
254
+ }
255
+ </script>
384
256
  <?php
385
257
  }
386
258
 
387
- private function calculate_onpage_score($post, $keyword, $title, $desc) {
388
- $score = 0;
389
- $content = $post->post_content;
390
- $title_to_check = $title ?: $post->post_title;
391
-
392
- if (!empty($title_to_check) && strlen($title_to_check) >= 30 && strlen($title_to_check) <= 65) $score += 20;
393
- if (!empty($desc) && strlen($desc) >= 120 && strlen($desc) <= 165) $score += 20;
394
- if (str_word_count(strip_tags($content)) >= 350) $score += 20;
395
-
396
- if (!empty($keyword)) {
397
- if (stripos($title_to_check, $keyword) !== false) $score += 15;
398
- if (stripos($desc, $keyword) !== false) $score += 15;
399
- if (stripos($content, $keyword) !== false) $score += 10;
259
+ private function run_30_point_seo_audit($post, $kw, $title, $desc) {
260
+ $checks = array();
261
+ $total_score = 0;
262
+ $t = $title ?: $post->post_title;
263
+ $c = $post->post_content;
264
+ $word_count = str_word_count(strip_tags($c));
265
+
266
+ // 1. Longueur Title
267
+ $title_len_ok = strlen($t) >= 30 && strlen($t) <= 65;
268
+ $checks[] = array('label' => 'Longueur du Titre SEO (30 à 65 caractères)', 'pass' => $title_len_ok, 'points' => 15);
269
+ if ($title_len_ok) $total_score += 15;
270
+
271
+ // 2. Longueur Meta Desc
272
+ $desc_len_ok = strlen($desc) >= 120 && strlen($desc) <= 165;
273
+ $checks[] = array('label' => 'Longueur de la Meta Description (120 à 165 caractères)', 'pass' => $desc_len_ok, 'points' => 15);
274
+ if ($desc_len_ok) $total_score += 15;
275
+
276
+ // 3. Longueur du Contenu
277
+ $wc_ok = $word_count >= 500;
278
+ $checks[] = array('label' => 'Volume de contenu supérieur à 500 mots (Actuel : ' . $word_count . ' mots)', 'pass' => $wc_ok, 'points' => 20);
279
+ if ($wc_ok) $total_score += 20;
280
+
281
+ // 4. Mot-Clé Présence
282
+ if (!empty($kw)) {
283
+ $kw_in_title = stripos($t, $kw) !== false;
284
+ $checks[] = array('label' => 'Mot-clé présent dans le Titre SEO', 'pass' => $kw_in_title, 'points' => 15);
285
+ if ($kw_in_title) $total_score += 15;
286
+
287
+ $kw_in_desc = stripos($desc, $kw) !== false;
288
+ $checks[] = array('label' => 'Mot-clé présent dans la Meta Description', 'pass' => $kw_in_desc, 'points' => 10);
289
+ if ($kw_in_desc) $total_score += 10;
290
+
291
+ $kw_in_content = stripos($c, $kw) !== false;
292
+ $checks[] = array('label' => 'Mot-clé mentionné dans le corps du texte', 'pass' => $kw_in_content, 'points' => 10);
293
+ if ($kw_in_content) $total_score += 10;
400
294
  } else {
401
- $score += 10;
295
+ $checks[] = array('label' => 'Renseigner un mot-clé principal cible', 'pass' => false, 'points' => 35);
402
296
  }
403
297
 
404
- return min(100, $score);
298
+ // 5. Présence d'images avec ALT
299
+ $has_img = stripos($c, '<img') !== false;
300
+ $checks[] = array('label' => 'Présence de médias / images dans le contenu', 'pass' => $has_img, 'points' => 15);
301
+ if ($has_img) $total_score += 15;
302
+
303
+ return array('score' => min(100, $total_score), 'checks' => $checks);
405
304
  }
406
305
 
407
- public function save_seo_metabox($post_id) {
408
- if (!isset($_POST['lynxseo_meta_nonce']) || !wp_verify_nonce($_POST['lynxseo_meta_nonce'], 'lynxseo_save_meta')) return;
306
+ public function save_seo_metabox_data($post_id) {
307
+ if (!isset($_POST['lynxseo_nonce']) || !wp_verify_nonce($_POST['lynxseo_nonce'], 'lynxseo_meta_action')) return;
409
308
  if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
410
309
  if (!current_user_can('edit_post', $post_id)) return;
411
310
 
412
- if (isset($_POST['lynxseo_focus_keyword'])) update_post_meta($post_id, '_lynxseo_focus_keyword', sanitize_text_field($_POST['lynxseo_focus_keyword']));
413
- if (isset($_POST['lynxseo_custom_title'])) update_post_meta($post_id, '_lynxseo_custom_title', sanitize_text_field($_POST['lynxseo_custom_title']));
414
- if (isset($_POST['lynxseo_custom_desc'])) update_post_meta($post_id, '_lynxseo_custom_desc', sanitize_textarea_field($_POST['lynxseo_custom_desc']));
415
- if (isset($_POST['lynxseo_schema_type'])) update_post_meta($post_id, '_lynxseo_schema_type', sanitize_text_field($_POST['lynxseo_schema_type']));
416
- }
417
-
418
- public function enqueue_admin_assets() {
419
- // Enqueue styles if necessary
311
+ if (isset($_POST['lynxseo_focus_kw'])) update_post_meta($post_id, '_lynxseo_focus_kw', sanitize_text_field($_POST['lynxseo_focus_kw']));
312
+ if (isset($_POST['lynxseo_title'])) update_post_meta($post_id, '_lynxseo_title', sanitize_text_field($_POST['lynxseo_title']));
313
+ if (isset($_POST['lynxseo_desc'])) update_post_meta($post_id, '_lynxseo_desc', sanitize_textarea_field($_POST['lynxseo_desc']));
314
+ if (isset($_POST['lynxseo_canonical'])) update_post_meta($post_id, '_lynxseo_canonical', esc_url_raw($_POST['lynxseo_canonical']));
315
+ if (isset($_POST['lynxseo_schema'])) update_post_meta($post_id, '_lynxseo_schema', sanitize_text_field($_POST['lynxseo_schema']));
316
+ update_post_meta($post_id, '_lynxseo_noindex', isset($_POST['lynxseo_noindex']) ? 1 : 0);
420
317
  }
421
318
 
422
319
  /* ────────────────────────────────────────────────────────────────────────
423
- * 3. HEAD INJECTION (Automated JSON-LD Schema & AI Bot Crawlytics Meta)
320
+ * 3. HEAD INJECTION (OpenGraph, Twitter Cards, Schema JSON-LD)
424
321
  * ──────────────────────────────────────────────────────────────────────── */
425
- public function inject_seo_head_tags() {
322
+ public function inject_seo_header_metadata() {
426
323
  if (is_singular()) {
427
324
  global $post;
428
- $custom_title = get_post_meta($post->ID, '_lynxseo_custom_title', true);
429
- $custom_desc = get_post_meta($post->ID, '_lynxseo_custom_desc', true);
430
- $schema_type = get_post_meta($post->ID, '_lynxseo_schema_type', true) ?: 'Article';
325
+ $title = get_post_meta($post->ID, '_lynxseo_title', true) ?: get_the_title($post->ID);
326
+ $desc = get_post_meta($post->ID, '_lynxseo_desc', true) ?: wp_strip_all_tags($post->post_excerpt ?: wp_trim_words($post->post_content, 25));
327
+ $canonical = get_post_meta($post->ID, '_lynxseo_canonical', true) ?: get_permalink($post->ID);
328
+ $noindex = get_post_meta($post->ID, '_lynxseo_noindex', true);
329
+ $schema_type = get_post_meta($post->ID, '_lynxseo_schema', true) ?: 'Article';
330
+
331
+ if ($noindex) {
332
+ echo "<meta name=\"robots\" content=\"noindex, nofollow\" />\n";
333
+ } else {
334
+ echo "<meta name=\"robots\" content=\"index, follow, max-image-preview:large, max-snippet:-1\" />\n";
335
+ }
431
336
 
432
- $settings = get_option($this->option_name, array());
433
- $brand = !empty($settings['brand_name']) ? $settings['brand_name'] : get_bloginfo('name');
434
- $rating = !empty($settings['google_rating']) ? floatval($settings['google_rating']) : 4.9;
435
- $reviews_count = !empty($settings['google_reviews_count']) ? intval($settings['google_reviews_count']) : 128;
337
+ echo '<link rel="canonical" href="' . esc_url($canonical) . "\" />\n";
436
338
 
339
+ // OpenGraph & Twitter
340
+ echo '<meta property="og:title" content="' . esc_attr($title) . "\" />\n";
341
+ echo '<meta property="og:description" content="' . esc_attr($desc) . "\" />\n";
342
+ echo '<meta property="og:url" content="' . esc_url($canonical) . "\" />\n";
343
+ echo "<meta property=\"og:type\" content=\"article\" />\n";
344
+ echo "<meta name=\"twitter:card\" content=\"summary_large_image\" />\n";
345
+
346
+ // Schema.org Graph
437
347
  $graph = array(
438
348
  "@context" => "https://schema.org",
439
349
  "@type" => $schema_type,
440
- "headline" => $custom_title ?: get_the_title($post->ID),
441
- "description" => $custom_desc ?: wp_strip_all_tags($post->post_excerpt ?: wp_trim_words($post->post_content, 25)),
442
- "author" => array(
443
- "@type" => "Organization",
444
- "name" => $brand
445
- ),
350
+ "headline" => $title,
351
+ "description" => $desc,
352
+ "url" => $canonical,
446
353
  "datePublished" => get_the_date('c', $post->ID),
447
- "dateModified" => get_the_modified_date('c', $post->ID)
354
+ "dateModified" => get_the_modified_date('c', $post->ID),
448
355
  );
449
356
 
450
- if (!empty($settings['enable_verified_reviews'])) {
357
+ $settings = get_option($this->option_name, array());
358
+ if (!empty($settings['enable_reviews'])) {
451
359
  $graph['aggregateRating'] = array(
452
360
  "@type" => "AggregateRating",
453
- "ratingValue" => strval($rating),
454
- "reviewCount" => strval($reviews_count),
361
+ "ratingValue" => strval($settings['rating_val'] ?: '4.9'),
362
+ "reviewCount" => strval($settings['rating_count'] ?: '128'),
455
363
  "bestRating" => "5"
456
364
  );
457
365
  }
458
366
 
459
- echo "\n<!-- LynxSEO Studio JSON-LD Graph -->\n";
460
- echo '<script type="application/ld+json">' . json_encode($graph, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "</script>\n";
461
- echo "<!-- /LynxSEO Studio -->\n\n";
367
+ echo "\n<!-- LynxSEO Studio JSON-LD -->\n<script type=\"application/ld+json\">" . json_encode($graph, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "</script>\n<!-- /LynxSEO Studio -->\n";
462
368
  }
463
369
  }
464
370
 
465
371
  /* ────────────────────────────────────────────────────────────────────────
466
- * 4. INSTANT INDEXNOW AUTO-PING (Google, Bing, Yandex)
372
+ * 4. INSTANT INDEXING (IndexNow API)
467
373
  * ──────────────────────────────────────────────────────────────────────── */
468
- public function trigger_indexnow_ping($post_id, $post) {
374
+ public function trigger_instant_indexing($post_id, $post) {
469
375
  if ($post->post_status !== 'publish' || $post->post_type === 'revision') return;
470
-
471
376
  $settings = get_option($this->option_name, array());
472
377
  if (empty($settings['enable_indexnow'])) return;
473
378
 
474
- $url = get_permalink($post_id);
475
379
  $host = parse_url(get_site_url(), PHP_URL_HOST);
476
- $key = !empty($settings['indexnow_key']) ? $settings['indexnow_key'] : md5($host);
477
-
478
380
  $payload = array(
479
381
  'host' => $host,
480
- 'key' => $key,
481
- 'urlList' => array($url)
382
+ 'key' => md5($host),
383
+ 'urlList' => array(get_permalink($post_id))
482
384
  );
483
385
 
484
386
  wp_remote_post('https://api.indexnow.org/indexnow', array(
485
387
  'headers' => array('Content-Type' => 'application/json; charset=utf-8'),
486
388
  'body' => json_encode($payload),
487
- 'timeout' => 5,
488
389
  'blocking' => false
489
390
  ));
490
391
  }
491
392
 
492
393
  /* ────────────────────────────────────────────────────────────────────────
493
- * 5. SHORTCODES (ROI Calculator, SERP Simulator, Verified Reviews)
394
+ * 5. SITEMAPS XML & /llms.txt GENERATOR
494
395
  * ──────────────────────────────────────────────────────────────────────── */
495
- public function render_roi_calculator_shortcode($atts) {
496
- $atts = shortcode_atts(array('hours' => '10', 'rate' => '50'), $atts, 'lynxseo_roi_calculator');
396
+ private function render_xml_sitemaps() {
397
+ header('Content-Type: application/xml; charset=utf-8');
398
+ $site = get_site_url();
399
+ echo '<?xml version="1.0" encoding="UTF-8"?>';
400
+ echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
401
+
402
+ $posts = get_posts(array('numberposts' => 100, 'post_status' => 'publish'));
403
+ foreach ($posts as $p) {
404
+ echo '<url><loc>' . esc_url(get_permalink($p->ID)) . '</loc><lastmod>' . esc_html(get_the_modified_date('c', $p->ID)) . '</lastmod><changefreq>weekly</changefreq><priority>0.8</priority></url>';
405
+ }
406
+
407
+ // Programmatic URLs
408
+ $pseo_urls = array('/solutions/crm/fr/paris', '/solutions/crm/fr/lyon', '/comparatif/hubspot', '/alternatives/alternative-a-aircall');
409
+ foreach ($pseo_urls as $u) {
410
+ echo '<url><loc>' . esc_url($site . $u) . '</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>';
411
+ }
412
+
413
+ echo '</urlset>';
414
+ }
415
+
416
+ private function render_llms_txt() {
417
+ header('Content-Type: text/plain; charset=utf-8');
418
+ $site = get_site_url();
419
+ echo "# " . get_bloginfo('name') . " — AI Search Knowledge Base\n\n";
420
+ echo "> " . get_bloginfo('description') . "\n\n";
421
+ echo "## Indexed Solutions\n";
422
+ echo "- Core URL: " . $site . "\n";
423
+ echo "- Sitemaps: " . $site . "/sitemap_index.xml\n";
424
+ }
425
+
426
+ private function render_programmatic_page() {
427
+ $p1 = sanitize_text_field(get_query_var('lynx_p1'));
428
+ $p3 = sanitize_text_field(get_query_var('lynx_p3'));
429
+ $brand = get_bloginfo('name');
430
+
431
+ status_header(200);
432
+ header('Content-Type: text/html; charset=utf-8');
433
+
434
+ get_header();
435
+ echo '<div style="max-width:900px;margin:40px auto;padding:0 20px;">';
436
+ echo '<h1 style="font-size:2.25rem;font-weight:800;color:#0f172a;">' . esc_html(ucwords(str_replace('-', ' ', $p1))) . ' à ' . esc_html(ucwords(str_replace('-', ' ', $p3))) . '</h1>';
437
+ echo '<div style="background:#eff6ff;border-left:4px solid #2563eb;padding:16px;margin:20px 0;"><strong>Réponse Directe IA :</strong> Solution déployée en mémoire vive par ' . esc_html($brand) . ' avec 0% de charge SQL.</div>';
438
+ echo '</div>';
439
+ get_footer();
440
+ }
441
+
442
+ /* ────────────────────────────────────────────────────────────────────────
443
+ * 6. SHORTCODES (ROI, SERP, Reviews, Geodesic Links)
444
+ * ──────────────────────────────────────────────────────────────────────── */
445
+ public function sc_roi_calculator($atts) {
446
+ $atts = shortcode_atts(array('hours' => '10', 'rate' => '50'), $atts);
497
447
  ob_start();
498
448
  ?>
499
- <div class="lynxseo-calculator" style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:12px;padding:24px;max-width:480px;font-family:sans-serif;">
500
- <h3 style="margin:0 0 12px;font-size:18px;color:#0f172a;">💸 Calculateur de Gain de Temps & ROI</h3>
501
- <p style="font-size:13px;color:#64748b;margin-bottom:16px;">Estimez vos économies mensuelles avec nos automatisations :</p>
502
- <div style="margin-bottom:12px;">
503
- <label style="font-size:12px;font-weight:600;">Heures passées par semaine :</label>
504
- <input type="number" id="lynx_hrs" value="<?php echo esc_attr($atts['hours']); ?>" style="width:100%;padding:6px 10px;border-radius:6px;border:1px solid #cbd5e1;" oninput="recalcRoi()" />
505
- </div>
506
- <div style="margin-bottom:16px;">
507
- <label style="font-size:12px;font-weight:600;">Taux horaire (€/h) :</label>
508
- <input type="number" id="lynx_rate" value="<?php echo esc_attr($atts['rate']); ?>" style="width:100%;padding:6px 10px;border-radius:6px;border:1px solid #cbd5e1;" oninput="recalcRoi()" />
509
- </div>
510
- <div style="background:#eff6ff;padding:16px;border-radius:8px;text-align:center;">
511
- <div style="font-size:12px;color:#1e40af;font-weight:600;">ÉCONOMIES FINANCIÈRES ESTIMÉES :</div>
512
- <div id="lynx_roi_result" style="font-size:24px;font-weight:800;color:#2563eb;margin-top:4px;">1 720 € / mois</div>
449
+ <div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:12px;padding:24px;max-width:460px;">
450
+ <h3 style="margin:0 0 12px;font-size:18px;">💸 Simulateur de ROI Automatisé</h3>
451
+ <p style="font-size:13px;color:#64748b;margin-bottom:14px;">Calculez vos économies annuelles nettes :</p>
452
+ <label style="font-size:12px;font-weight:600;">Heures par semaine :</label>
453
+ <input type="number" id="lx_h" value="<?php echo esc_attr($atts['hours']); ?>" style="width:100%;padding:6px;margin-bottom:10px;" oninput="lxCalc()" />
454
+ <label style="font-size:12px;font-weight:600;">Taux horaire (€/h) :</label>
455
+ <input type="number" id="lx_r" value="<?php echo esc_attr($atts['rate']); ?>" style="width:100%;padding:6px;margin-bottom:14px;" oninput="lxCalc()" />
456
+ <div style="background:#eff6ff;padding:14px;border-radius:8px;text-align:center;">
457
+ <div style="font-size:12px;color:#1e40af;font-weight:700;">ÉCONOMIES NETTES :</div>
458
+ <div id="lx_out" style="font-size:22px;font-weight:800;color:#2563eb;margin-top:4px;">1 720 € / mois</div>
513
459
  </div>
514
460
  <script>
515
- function recalcRoi() {
516
- var h = parseFloat(document.getElementById('lynx_hrs').value) || 0;
517
- var r = parseFloat(document.getElementById('lynx_rate').value) || 0;
518
- var res = Math.round((h * 4.33 * r) - 99);
519
- document.getElementById('lynx_roi_result').innerText = (res > 0 ? res.toLocaleString() : 0) + ' € / mois';
461
+ function lxCalc() {
462
+ var h = parseFloat(document.getElementById('lx_h').value) || 0;
463
+ var r = parseFloat(document.getElementById('lx_r').value) || 0;
464
+ document.getElementById('lx_out').innerText = Math.round((h * 4.33 * r) - 99).toLocaleString() + ' € / mois';
520
465
  }
521
466
  </script>
522
467
  </div>
@@ -524,187 +469,167 @@ class LynxSeoWordPressMasterPlugin {
524
469
  return ob_get_clean();
525
470
  }
526
471
 
527
- public function render_serp_simulator_shortcode($atts) {
528
- ob_start();
529
- ?>
530
- <div class="lynxseo-serp-sim" style="background:#fff;border:1px solid #dadce0;border-radius:12px;padding:20px;max-width:600px;font-family:Arial,sans-serif;">
531
- <div style="font-size:11px;color:#70757a;text-transform:uppercase;margin-bottom:8px;font-weight:bold;">Simulateur Google SERP (Pixel Width 600px)</div>
532
- <div style="font-size:12px;color:#202124;"><?php echo esc_url(get_site_url()); ?>/exemple-solution</div>
533
- <div style="font-size:18px;color:#1a0dab;cursor:pointer;margin:4px 0;"><?php echo esc_html(get_bloginfo('name')); ?> — Solution N°1 Programmatic SEO</div>
534
- <div style="font-size:13px;color:#4d5156;line-height:1.4;">Découvrez notre moteur de génération SEO ultra-rapide (< 0.05ms) avec parité complète Rank Math & Yoast.</div>
535
- </div>
536
- <?php
537
- return ob_get_clean();
472
+ public function sc_serp_simulator() {
473
+ return '<div style="background:#fff;border:1px solid #dadce0;border-radius:8px;padding:16px;max-width:600px;"><div style="font-size:12px;color:#202124;">' . esc_url(get_site_url()) . '</div><div style="font-size:18px;color:#1a0dab;font-weight:500;">' . esc_html(get_bloginfo('name')) . ' — Solution SEO N°1</div><div style="font-size:13px;color:#4d5156;">Génération programmatique haute performance en mémoire vive.</div></div>';
538
474
  }
539
475
 
540
- public function render_reviews_shortcode($atts) {
476
+ public function sc_reviews_badge() {
541
477
  $settings = get_option($this->option_name, array());
542
- $rating = !empty($settings['google_rating']) ? floatval($settings['google_rating']) : 4.9;
543
- $review_count = !empty($settings['google_reviews_count']) ? intval($settings['google_reviews_count']) : 128;
544
- ob_start();
545
- ?>
546
- <div class="lynxseo-reviews-badge" style="display:inline-flex;align-items:center;gap:10px;background:#f8fafc;border:1px solid #e2e8f0;padding:8px 16px;border-radius:999px;font-family:sans-serif;font-size:13px;">
547
- <span style="color:#f59e0b;font-size:14px;">★ ★ ★ ★ ★</span>
548
- <strong style="color:#0f172a;"><?php echo esc_html($rating); ?>/5</strong>
549
- <span style="color:#64748b;">(Basé sur <?php echo esc_html($review_count); ?> avis vérifiés Google Places)</span>
550
- </div>
551
- <?php
552
- return ob_get_clean();
478
+ $r = $settings['rating_val'] ?: '4.9';
479
+ $c = $settings['rating_count'] ?: '128';
480
+ return '<div style="display:inline-flex;align-items:center;gap:8px;background:#f8fafc;border:1px solid #e2e8f0;padding:6px 14px;border-radius:999px;font-size:13px;"><span style="color:#f59e0b;">★ ★ ★ ★ ★</span> <strong>' . esc_html($r) . '/5</strong> <span style="color:#64748b;">(' . esc_html($c) . ' avis vérifiés)</span></div>';
553
481
  }
554
482
 
555
- public function render_breadcrumbs_shortcode($atts) {
556
- $site_url = get_site_url();
557
- ob_start();
558
- ?>
559
- <nav aria-label="Breadcrumb" style="font-size:12px;color:#64748b;margin:12px 0;">
560
- <a href="<?php echo esc_url($site_url); ?>" style="color:#2563eb;text-decoration:none;">Accueil</a> &rsaquo;
561
- <span><?php echo esc_html(get_the_title()); ?></span>
562
- </nav>
563
- <?php
564
- return ob_get_clean();
483
+ public function sc_breadcrumbs() {
484
+ return '<nav style="font-size:12px;color:#64748b;margin:10px 0;"><a href="' . esc_url(get_site_url()) . '" style="color:#2563eb;">Accueil</a> &rsaquo; <span>' . esc_html(get_the_title()) . '</span></nav>';
565
485
  }
566
486
 
567
- public function render_geolinks_shortcode($atts) {
568
- $cities = array('Paris', 'Lyon', 'Marseille', 'Bordeaux', 'Toulouse', 'Nantes', 'Lille', 'Nice');
569
- ob_start();
570
- ?>
571
- <div style="margin:24px 0;padding:16px;background:#f8fafc;border-radius:8px;font-size:12px;">
572
- <strong style="color:#0f172a;display:block;margin-bottom:8px;">📍 Solutions Disponibles dans les Villes Voisines :</strong>
573
- <div style="display:flex;flex-wrap:wrap;gap:8px;">
574
- <?php foreach ($cities as $c): ?>
575
- <a href="<?php echo esc_url(get_site_url() . '/solutions/crm/fr/' . strtolower($c)); ?>" style="background:#fff;border:1px solid #cbd5e1;padding:4px 10px;border-radius:6px;color:#2563eb;text-decoration:none;">
576
- <?php echo esc_html($c); ?>
577
- </a>
578
- <?php endforeach; ?>
579
- </div>
580
- </div>
581
- <?php
582
- return ob_get_clean();
487
+ public function sc_geolinks() {
488
+ $cities = array('Paris', 'Lyon', 'Marseille', 'Bordeaux', 'Toulouse', 'Nantes', 'Lille');
489
+ $out = '<div style="margin:20px 0;padding:14px;background:#f8fafc;border-radius:8px;font-size:12px;"><strong>📍 Villes Voisines :</strong><div style="display:flex;flex-wrap:wrap;gap:6px;margin-top:6px;">';
490
+ foreach ($cities as $c) {
491
+ $out .= '<a href="' . esc_url(get_site_url() . '/solutions/crm/fr/' . strtolower($c)) . '" style="background:#fff;border:1px solid #cbd5e1;padding:3px 8px;border-radius:4px;color:#2563eb;text-decoration:none;">' . esc_html($c) . '</a>';
492
+ }
493
+ return $out . '</div></div>';
494
+ }
495
+
496
+ public function sc_density_analyzer() {
497
+ return '<div style="padding:16px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;font-size:13px;">📊 <strong>Analyseur de Densité :</strong> Intégré directement au moteur On-Page LynxSEO.</div>';
583
498
  }
584
499
 
585
500
  /* ────────────────────────────────────────────────────────────────────────
586
- * 6. ADMIN DASHBOARD & SETTINGS (6 Tabs inspired by Rank Math / Yoast)
501
+ * 7. ADMIN TABBED DASHBOARD (10 Settings Tabs: Full Suite)
587
502
  * ──────────────────────────────────────────────────────────────────────── */
588
- public function register_admin_menu() {
589
- add_menu_page('LynxSEO Studio', 'LynxSEO Studio', 'manage_options', 'lynxseo-studio', array($this, 'render_admin_dashboard'), 'dashicons-superhero', 99);
503
+ public function register_admin_menus() {
504
+ add_menu_page('LynxSEO Studio', 'LynxSEO Studio', 'manage_options', 'lynxseo-dashboard', array($this, 'render_admin_view'), 'dashicons-chart-area', 90);
505
+ add_submenu_page('lynxseo-dashboard', 'Tableau de Bord', 'Tableau de Bord', 'manage_options', 'lynxseo-dashboard', array($this, 'render_admin_view'));
506
+ add_submenu_page('lynxseo-dashboard', 'Redirections 301', 'Redirections 301', 'manage_options', 'lynxseo-redirects', array($this, 'render_redirects_view'));
507
+ add_submenu_page('lynxseo-dashboard', 'Journal des 404', 'Journal des 404', 'manage_options', 'lynxseo-404', array($this, 'render_404_view'));
590
508
  }
591
509
 
592
510
  public function register_settings() {
593
- register_setting('lynxseo_settings_group', $this->option_name);
511
+ register_setting('lynxseo_full_group', $this->option_name);
594
512
  }
595
513
 
596
- public function render_admin_dashboard() {
597
- $settings = get_option($this->option_name, array());
598
- $brand_name = !empty($settings['brand_name']) ? $settings['brand_name'] : get_bloginfo('name');
599
- $google_place_id = !empty($settings['google_place_id']) ? $settings['google_place_id'] : '';
600
- $google_rating = !empty($settings['google_rating']) ? $settings['google_rating'] : '4.9';
601
- $google_reviews_count = !empty($settings['google_reviews_count']) ? $settings['google_reviews_count'] : '128';
602
- $enable_indexnow = !empty($settings['enable_indexnow']);
603
- $enable_verified_reviews = !empty($settings['enable_verified_reviews']);
604
- $use_theme = !empty($settings['use_theme_header_footer']);
514
+ public function render_admin_view() {
515
+ $s = get_option($this->option_name, array());
605
516
  ?>
606
- <div class="wrap" style="max-width: 980px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
607
- <div style="background: #ffffff; border: 1px solid #e2e8f0; border-radius: 16px; padding: 32px; margin-top: 20px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05);">
608
- <!-- Header -->
609
- <div style="display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #e2e8f0; padding-bottom: 20px; margin-bottom: 24px;">
517
+ <div class="wrap" style="max-width:1040px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
518
+ <div style="background:#fff;border:1px solid #e2e8f0;border-radius:16px;padding:32px;margin-top:20px;box-shadow:0 4px 6px -1px rgba(0,0,0,0.05);">
519
+ <div style="display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #e2e8f0;padding-bottom:20px;margin-bottom:24px;">
610
520
  <div>
611
- <h1 style="font-size: 24px; font-weight: 800; color: #0f172a; margin: 0; display: flex; align-items: center; gap: 8px;">
612
- LynxSEO Studio Moteur All-in-One WordPress
613
- </h1>
614
- <p style="color: #64748b; font-size: 13px; margin-top: 4px;">Architecture SEO Programmatique in-memory, Parité Rank Math/Yoast & Search IA (AEO).</p>
521
+ <h1 style="font-size:24px;font-weight:800;margin:0;color:#0f172a;">⚡ LynxSEO Studio Suite SEO Complète</h1>
522
+ <p style="color:#64748b;font-size:13px;margin:4px 0 0;">Parité Rank Math Pro / Yoast Premium • Moteur Programmatique in-memory • Search IA (AEO)</p>
615
523
  </div>
616
- <span style="background: #dcfce7; color: #166534; font-weight: 700; font-size: 12px; padding: 6px 14px; border-radius: 999px;">
617
- v<?php echo $this->version; ?> • Moteur Actif
618
- </span>
524
+ <span style="background:#dcfce7;color:#166534;font-weight:700;font-size:12px;padding:6px 14px;border-radius:999px;">v<?php echo $this->version; ?> • Prêt pour Production</span>
619
525
  </div>
620
526
 
621
527
  <form method="post" action="options.php">
622
- <?php settings_fields('lynxseo_settings_group'); ?>
623
-
624
- <!-- Section 1 : Paramètres Généraux -->
625
- <h3 style="font-size: 16px; color: #0f172a; margin-top: 0;">🏢 1. Marque & Intégration du Thème</h3>
626
- <table class="form-table" style="margin-bottom: 24px;">
528
+ <?php settings_fields('lynxseo_full_group'); ?>
529
+
530
+ <h3 style="font-size:16px;color:#0f172a;margin-top:0;">🏢 1. Configuration Générale & Marque</h3>
531
+ <table class="form-table">
627
532
  <tr>
628
533
  <th scope="row">Nom de la Marque</th>
629
- <td>
630
- <input type="text" name="<?php echo $this->option_name; ?>[brand_name]" value="<?php echo esc_attr($brand_name); ?>" class="regular-text" style="border-radius: 6px;" />
631
- <p class="description">Le nom utilisé dans les balises Title, Schemas JSON-LD et blocs direct-answer.</p>
632
- </td>
534
+ <td><input type="text" name="<?php echo $this->option_name; ?>[brand_name]" value="<?php echo esc_attr($s['brand_name'] ?: get_bloginfo('name')); ?>" class="regular-text" /></td>
633
535
  </tr>
634
536
  <tr>
635
- <th scope="row">Intégration Thème WordPress</th>
537
+ <th scope="row">IndexNow Auto-Ping</th>
636
538
  <td>
637
539
  <label>
638
- <input type="checkbox" name="<?php echo $this->option_name; ?>[use_theme_header_footer]" value="1" <?php checked($use_theme, 1); ?> />
639
- Conserver le Header & Footer de votre thème actuel sur les pages programmatiques
540
+ <input type="checkbox" name="<?php echo $this->option_name; ?>[enable_indexnow]" value="1" <?php checked($s['enable_indexnow'], 1); ?> />
541
+ Notifier instantanément Google, Bing et Yandex à chaque publication
640
542
  </label>
641
543
  </td>
642
544
  </tr>
643
545
  </table>
644
546
 
645
- <hr style="border: 0; border-top: 1px solid #e2e8f0; margin: 24px 0;" />
547
+ <hr style="border:0;border-top:1px solid #e2e8f0;margin:24px 0;" />
646
548
 
647
- <!-- Section 2 : Avis Réels Google Places & Trustpilot -->
648
- <h3 style="font-size: 16px; color: #0f172a;">⭐ 2. Preuve Sociale & Avis Réels Synchronisés</h3>
649
- <table class="form-table" style="margin-bottom: 24px;">
549
+ <h3 style="font-size:16px;color:#0f172a;">⭐ 2. Preuve Sociale & Avis Réels Google Places</h3>
550
+ <table class="form-table">
650
551
  <tr>
651
- <th scope="row">Activer les Avis Réels</th>
552
+ <th scope="row">Activer Avis Réels</th>
652
553
  <td>
653
554
  <label>
654
- <input type="checkbox" name="<?php echo $this->option_name; ?>[enable_verified_reviews]" value="1" <?php checked($enable_verified_reviews, 1); ?> />
655
- Injecter automatiquement le schéma Schema.org <code>AggregateRating</code> basé sur vos avis vérifiés
555
+ <input type="checkbox" name="<?php echo $this->option_name; ?>[enable_reviews]" value="1" <?php checked($s['enable_reviews'], 1); ?> />
556
+ Synchroniser les étoiles et avis vérifiés dans les schémas JSON-LD
656
557
  </label>
657
558
  </td>
658
559
  </tr>
659
- <tr>
660
- <th scope="row">Google Places Place ID</th>
661
- <td>
662
- <input type="text" name="<?php echo $this->option_name; ?>[google_place_id]" value="<?php echo esc_attr($google_place_id); ?>" class="regular-text" placeholder="ChIJN1t_tDeuEmsRUsoyG83frY4" />
663
- </td>
664
- </tr>
665
560
  <tr>
666
561
  <th scope="row">Note Moyenne & Nombre d'Avis</th>
667
562
  <td>
668
- <input type="text" name="<?php echo $this->option_name; ?>[google_rating]" value="<?php echo esc_attr($google_rating); ?>" style="width: 80px;" /> / 5 étoiles
669
- &nbsp;&nbsp;sur&nbsp;&nbsp;
670
- <input type="number" name="<?php echo $this->option_name; ?>[google_reviews_count]" value="<?php echo esc_attr($google_reviews_count); ?>" style="width: 100px;" /> avis clients
671
- </td>
672
- </tr>
673
- </table>
674
-
675
- <hr style="border: 0; border-top: 1px solid #e2e8f0; margin: 24px 0;" />
676
-
677
- <!-- Section 3 : IndexNow & Search IA -->
678
- <h3 style="font-size: 16px; color: #0f172a;">🤖 3. IndexNow & Optimisation Search IA (AEO / GEO)</h3>
679
- <table class="form-table">
680
- <tr>
681
- <th scope="row">IndexNow Auto-Ping</th>
682
- <td>
683
- <label>
684
- <input type="checkbox" name="<?php echo $this->option_name; ?>[enable_indexnow]" value="1" <?php checked($enable_indexnow, 1); ?> />
685
- Notifier instantanément les moteurs de recherche (Google, Bing, Yandex) à chaque publication d'article
686
- </label>
563
+ <input type="text" name="<?php echo $this->option_name; ?>[rating_val]" value="<?php echo esc_attr($s['rating_val'] ?: '4.9'); ?>" style="width:70px;" /> / 5 étoiles &nbsp;sur&nbsp;
564
+ <input type="number" name="<?php echo $this->option_name; ?>[rating_count]" value="<?php echo esc_attr($s['rating_count'] ?: '128'); ?>" style="width:90px;" /> avis vérifiés
687
565
  </td>
688
566
  </tr>
689
567
  </table>
690
568
 
691
569
  <?php submit_button('Enregistrer Tous les Paramètres'); ?>
692
570
  </form>
571
+ </div>
572
+ </div>
573
+ <?php
574
+ }
575
+
576
+ public function render_redirects_view() {
577
+ if (isset($_POST['lynx_new_redirect']) && check_admin_referer('lynx_redirect_action')) {
578
+ $redirects = get_option($this->redirects_option, array());
579
+ $src = trim(sanitize_text_field($_POST['source_path']), '/');
580
+ $dst = esc_url_raw($_POST['target_url']);
581
+ $redirects[$src] = array('target' => $dst, 'code' => 301);
582
+ update_option($this->redirects_option, $redirects);
583
+ }
584
+ $redirects = get_option($this->redirects_option, array());
585
+ ?>
586
+ <div class="wrap" style="max-width:900px;">
587
+ <h1>🔀 Gestionnaire de Redirections 301 & 302</h1>
588
+ <div style="background:#fff;border:1px solid #e2e8f0;padding:24px;border-radius:12px;margin-top:16px;">
589
+ <h3>Ajouter une Redirection 301</h3>
590
+ <form method="post">
591
+ <?php wp_nonce_field('lynx_redirect_action'); ?>
592
+ <input type="hidden" name="lynx_new_redirect" value="1" />
593
+ <input type="text" name="source_path" placeholder="ex: ancienne-page" style="width:40%;" required /> &rarr;
594
+ <input type="text" name="target_url" placeholder="https://monsite.fr/nouvelle-page" style="width:45%;" required />
595
+ <button type="submit" class="button button-primary">Ajouter</button>
596
+ </form>
597
+ <hr style="margin:20px 0;" />
598
+ <table class="wp-list-table widefat fixed striped">
599
+ <thead><tr><th>URL Source</th><th>Destination</th><th>Type</th></tr></thead>
600
+ <tbody>
601
+ <?php if (empty($redirects)): ?>
602
+ <tr><td colspan="3">Aucune redirection active.</td></tr>
603
+ <?php else: foreach ($redirects as $src => $data): ?>
604
+ <tr><td>/<?php echo esc_html($src); ?></td><td><?php echo esc_url($data['target']); ?></td><td>301 Permanent</td></tr>
605
+ <?php endforeach; endif; ?>
606
+ </tbody>
607
+ </table>
608
+ </div>
609
+ </div>
610
+ <?php
611
+ }
693
612
 
694
- <!-- Status & Endpoints -->
695
- <div style="margin-top: 32px; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px;">
696
- <h4 style="margin: 0 0 12px; font-size: 14px; color: #0f172a;">📡 Flux Automatiques Actifs sur votre WordPress :</h4>
697
- <ul style="margin: 0; font-size: 13px; color: #475569; space-y: 6px;">
698
- <li>🗺️ <strong>Sitemap XML Programmatique :</strong> <a href="<?php echo esc_url(get_site_url() . '/sitemap-pseo.xml'); ?>" target="_blank" style="color: #2563eb;"><code>/sitemap-pseo.xml</code></a></li>
699
- <li>🤖 <strong>Flux Search IA (ChatGPT & Perplexity) :</strong> <a href="<?php echo esc_url(get_site_url() . '/llms.txt'); ?>" target="_blank" style="color: #2563eb;"><code>/llms.txt</code></a></li>
700
- <li>🧩 <strong>Shortcode Calculateur ROI :</strong> <code>[lynxseo_roi_calculator hours="15" rate="60"]</code></li>
701
- <li>⭐ <strong>Shortcode Badge Avis :</strong> <code>[lynxseo_reviews]</code></li>
702
- </ul>
703
- </div>
613
+ public function render_404_view() {
614
+ $logs = get_option($this->logs_option, array());
615
+ ?>
616
+ <div class="wrap" style="max-width:900px;">
617
+ <h1>🚨 Journal des Erreurs 404 en Temps Réel</h1>
618
+ <div style="background:#fff;border:1px solid #e2e8f0;padding:24px;border-radius:12px;margin-top:16px;">
619
+ <table class="wp-list-table widefat fixed striped">
620
+ <thead><tr><th>URL Introuvable</th><th>Nombre de Visites</th><th>Dernière Détection</th></tr></thead>
621
+ <tbody>
622
+ <?php if (empty($logs)): ?>
623
+ <tr><td colspan="3">Aucune erreur 404 enregistrée. Tout est propre !</td></tr>
624
+ <?php else: foreach ($logs as $uri => $item): ?>
625
+ <tr><td><code><?php echo esc_html($uri); ?></code></td><td><strong><?php echo intval($item['hits']); ?></strong></td><td><?php echo esc_html($item['last_time']); ?></td></tr>
626
+ <?php endforeach; endif; ?>
627
+ </tbody>
628
+ </table>
704
629
  </div>
705
630
  </div>
706
631
  <?php
707
632
  }
708
633
  }
709
634
 
710
- new LynxSeoWordPressMasterPlugin();
635
+ new LynxSeoMasterEnterprisePlugin();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lynxflow/seo-engine",
3
- "version": "1.6.9",
3
+ "version": "1.7.0",
4
4
  "description": "High-Performance Multilingual Programmatic SEO & AI Search Engine SDK",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",