=== Essiow — AI SEO Suite for WooCommerce === Contributors: boni58 Tags: woocommerce, seo, ai, product-descriptions, chatbot Requires at least: 5.8 Tested up to: 6.9 Requires PHP: 7.4 Stable tag: 1.1.77 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html Get more Google traffic on WooCommerce. AI rewrites your products, categories and articles using your real Google Search Console data — and pings every search engine to index changes instantly. == Description == **Essiow turns your WooCommerce store into a search-traffic machine.** It plugs into Google Search Console, watches what your customers actually search for, and rewrites your product pages, category pages and blog articles to capture every query you nearly rank on. You don't write SEO. You don't pick keywords. You don't guess what works. You click a button and the right pages get fixed. = What Essiow does for you = **1. Auto-rewrites your product pages.** Long description, short pitch, meta title, meta description, focus keyword, image alt texts — all generated from your real GSC queries when connected, in 8 languages, in your store's tone. Compatible with Yoast SEO, Rank Math and All in One SEO. **2. Turns empty category pages into landing pages.** Bare category pages don't rank. Essiow generates 1,500-2,500 words of category content with FAQ, comparison tables and links to your top products — the page Google needs to rank you in position 1 instead of position 30. **3. Writes blog articles that pull traffic to your products.** 1,500-5,000 word articles with internal links to the products mentioned, FAQ schema, automatic featured image. Suggestions based on what your audience already searches. **4. Spots and grabs every "almost-ranking" keyword.** When Google Search Console is connected, Essiow surfaces every query where your store sits at position 11-20 — the closest gains. One click rewrites the matching page targeting that exact query. **5. Resolves cannibalization in two clicks.** Two of your pages competing for the same query? Essiow detects it, picks the strongest one, and consolidates the canonical from the others — without deleting anything. **6. Indexes everything instantly.** Bing, Yandex, Naver, Seznam are pinged the second you publish. Google gets the URL pushed via sitemap re-submit + URL Inspection refresh + a one-click manual indexation request. **7. Builds your internal mesh in a graph view.** See orphan pages (no incoming links), dead-ends (no outgoing), and connect any two pages with a drag — Essiow injects reciprocal anchor links on the strongest shared keyword. A live mesh score / 100 tells you how healthy your site structure is. **8. AI sales agent on your storefront.** A chatbot that knows your full catalog, handles objections, can issue promo codes within your discount limit, and quotes your delivery / returns / payment policy. **9. Exposes your catalog to ChatGPT, Perplexity and Claude.** Toggle on and Essiow serves a clean `/llms.txt` at your root — the standard AI search engines read to find products to recommend. = Why it ranks better = When Search Console is connected, every optimization sees the actual queries the page is already ranking on, the striking-distance keywords just outside page 1, and the CTR alerts when a title is converting poorly. The AI doesn't guess keywords — it gets them from Google itself, and writes around what's already working. = Made for shop owners, not SEOs = * No keyword research needed * No technical setup beyond pasting an API key * Every action shows its credit cost upfront — no surprise billing * Bulk optimize, pause, resume, restore original — your content is always recoverable * 8 languages, 4 writing tones, 3 content lengths = Compatible & safe = * WooCommerce HPOS compatible * Works alongside Yoast SEO / Rank Math / All in One SEO (writes to all three) * GDPR compliant (auto-delete chat data after 90 days) * Original content backed up the first time you optimize — one-click restore = How credits work = * **1 credit** per product optimization * **1 credit** per category optimization * **3 credits** per blog article * **2 credits** per AI Vision alt text generation * All indexation actions, audits, internal-link suggestions, mesh-score, /llms.txt — **free** (no AI involved) * Credits are debited only on success. Failed AI calls don't consume credits. * Purchased credits never expire (free trial credits expire after 30 days) = External Service = This plugin connects to the Essiow API at `https://essiow.com/api/v1` to process AI content generation. Your product data (names, descriptions, prices, categories) is sent to the Essiow servers where it is processed using OpenAI's models. No data is stored beyond what is needed to track your credit usage. * [Essiow Terms of Service](https://essiow.com/terms) * [Essiow Privacy Policy](https://essiow.com/privacy) == Installation == 1. Upload the `essiow` folder to `/wp-content/plugins/` 2. Activate the plugin through the 'Plugins' menu in WordPress 3. Go to **Essiow > Settings** and enter your API key from [essiow.com](https://essiow.com) 4. Click "Test Connection" to verify 5. Start optimizing from **Essiow > Products** or **Essiow > Categories** == Frequently Asked Questions == = Do I need an Essiow account? = Yes. Create a free account at [essiow.com](https://essiow.com) to get your API key and 10 free credits. = Do I need technical skills? = No. If you can install a WordPress plugin, you can use Essiow. Everything is done in a few clicks. = Do I need to know SEO? = No. Essiow does the SEO work : it picks the keywords (from Google Search Console when connected), writes the meta tags, generates the schema, builds the internal links and submits everything to search engines. You just click "Optimize". = Why does connecting Google Search Console matter? = With GSC connected, every optimization is fed with the real queries your page already ranks on. Essiow finds queries where you sit at position 11-20 (just outside page 1) and rewrites the matching page targeting that exact query. Without GSC, optimizations are still good — but generic. With GSC, they're surgical. = Will optimizing break my existing content? = No. The first time a product or category is optimized, the original content is backed up automatically. One click in the preview modal restores it. = Which SEO plugins are supported? = Essiow works with Yoast SEO, Rank Math, and All in One SEO. It writes to all three formats simultaneously, so switching SEO plugin later does not lose your data. = Is my data safe? = Your product data is sent to Essiow servers only during optimization. It is processed in real-time and not stored beyond credit-tracking metadata. Chat conversations are auto-deleted after 90 days per GDPR requirements. = Do credits expire? = Purchased credits never expire. The 10 free credits expire after 30 days. = Can I cancel a bulk optimization? = Yes. Pause / Resume / Cancel buttons appear during a bulk run. Closing the tab also auto-cancels — items already processed remain saved. = Can I try before buying? = Yes. Create a free account and get 10 credits to test all features. No credit card required. == Screenshots == 1. Dashboard with connection status and credit balance 2. Product optimization with SEO score and bulk actions 3. Category optimization with rich content preview 4. Blog article generator with async processing 5. AI Sales Agent configuration 6. Settings page with API connection == Changelog == = 1.1.77 = * **Parité complète bulk ↔ individuel — contexte GSC ajouté au worker bulk** : c'était la dernière différence restante. La génération individuelle d'articles enrichissait son prompt avec les vraies queries Google Search Console matchant le keyword (28 derniers jours, agrégées par impressions), permettant à l'IA de cibler ce que l'audience tape déjà. Le bulk générait "à l'aveugle". Désormais : avant chaque génération bulk, le worker appelle `_build_gsc_context_for_article(site, item.keyword)` et injecte le résultat dans le prompt — exactement comme l'individuel. = 1.1.76 = * **CAUSE RACINE TROUVÉE — toutes les images bulk étaient bloquées par un filtre silencieux côté backend** : depuis 1.1.69, le plugin envoyait soigneusement le `wp_context` (produits avec image_id/image_url, articles, catégories, pool d'images vedette) au backend Flask. Mais `BulkArticleService.create_job` reconstruisait le dict de configuration en ne retenant qu'une **whitelist de clés** (`tone`, `length`, `language`, `blog_id`, etc.) — `wp_context` n'était PAS dans la whitelist, donc **silencieusement éliminé**. Conséquences en cascade : 1. Le worker Celery recevait `cfg.get('wp_context')` → `None` → `products = []`, `blog_posts = []`, `featured_image_pool = []` 2. L'IA recevait un prompt SANS produits → aucune `` générée (l'IA ne pouvait pas inventer des URLs valides) 3. Pas de pool d'images vedette → pas d'image vedette attachée au post 4. Le sanitizer auto-injection ajouté en 1.1.75 s'appuyait sur `article_data.products` (vide aussi) → ne pouvait rien injecter non plus 5. Conclusion : tous les fixes images depuis 1.1.69 étaient **bloqués en amont**, c'est pour cela que les corrections successives ne donnaient rien visible. * **Fix** : ajout de `wp_context` et `site_url` dans la whitelist. Les NOUVEAUX jobs bulk recevront enfin le contexte produit complet, et toute la chaîne d'images (génération IA + auto-injection sanitizer + image vedette via attachment_id) fonctionnera bout en bout. * **Note** : les jobs créés AVANT 1.1.76 ont un `config` immuable en DB sans wp_context — relancer un nouveau bulk job pour bénéficier du fix. = 1.1.75 = * **Fix définitif — images vedette + images inline dans les articles bulk** : 1. **Featured image via attachment ID** : avant, le plugin recevait une URL `https://site.com/wp-content/uploads/2024/10/widget-1024x768.jpg` (taille `large`) et tentait `attachment_url_to_postid` → souvent échec car cette fonction n'accepte que l'URL ORIGINALE sans suffixe `-WxH`. Désormais le plugin envoie l'`image_id` directement dans `featured_image_pool` ; le worker stocke `featured_image_id` dans `generated_payload` ; le plugin attache via `set_post_thumbnail($post_id, $id)` — **zéro HTTP, indestructible**. 2. **Auto-injection des images inline** : l'IA esquivait parfois les `` même quand on lui listait les produits dans le prompt. Le sanitizer compte maintenant les `` valides après génération. Si moins de 3, il injecte automatiquement les images des produits restants, placées après les premières `

`, wrappées dans des `` vers la page produit pour le SEO. Le plugin reçoit un article qui a TOUJOURS au moins 3 images contextuelles, peu importe ce que l'IA a fait. 3. **Prompt durci** : section IMAGES déplacée en MANDATORY (non-négociable), exige 3-6 `` minimum, format explicite avec wrap `` pour cumuler valeur SEO. * **Fix HTTP 429 sur ping IndexNow** : avant, un 429 (rate limit) cassait l'opération sans recovery. Désormais le retry honore le header `Retry-After` quand IndexNow l'envoie, sinon backoff plus long. Côté UI, le toast affiche un message clair (« IndexNow rate-limited. Try again in a few minutes. ») au lieu d'un cryptique « HTTP 429 ». * **Fix bouton « Indexer » qui ouvrait GSC dans une nouvelle fenêtre** : avant, après IndexNow + sitemap submit, le plugin ouvrait automatiquement Google Search Console sur la page d'inspection de l'URL — Google affichait son texte par défaut « URL is not on Google. Couldn't fetch it... » et l'utilisateur croyait à un échec de l'indexation. Désormais aucune ouverture automatique ; le toast affiche un récap clair des étapes effectuées (« ✓ IndexNow · Sitemap re-submitted · Indexation status refreshed »). L'URL GSC reste accessible si besoin via `data-hint-url` sur le bouton (extensible plus tard pour une UI dédiée). * **Récap d'étapes détaillé** dans `ajax_request_indexing` : chaque sous-action (IndexNow, sitemap, inspection cache) renvoie son statut individuel. Les échecs partiels sont annoncés (`⚠ IndexNow rate-limited`) sans planter l'opération globale. = 1.1.74 = * **Audit de vérification 1.1.73** : aucun handler legacy orphelin, lock transient correct, idempotence du pull garantie, sanitizer appelé avant le débit crédit. 1 seul vrai bug remonté, corrigé ici. * **Fix critique — page-close n'annule plus le job** : avant 1.1.74, le `cancelAllBulksOnUnload` annulait toujours les bulks au refresh / fermeture d'onglet via `navigator.sendBeacon`, ce qui était directement contraire à l'architecture jobs-serveur déployée en 1.1.72-73 (« rien ne s'arrête quand l'utilisateur ferme »). Le handler a été neutralisé : les jobs continuent côté serveur, le polling reprend automatiquement au rechargement. * **Fix sites HTTP + installs en sous-répertoire** : le sanitizer côté Flask reconstruisait `https://{domain}` en ignorant le protocole et le subpath réels (info perdue côté serveur). Les sites en HTTP ou dans `/shop/` voyaient tous leurs liens internes stripés. Désormais le plugin envoie son `site_url` complet (via `home_url('/')`) dans le wp_context et dans le payload de `/optimize/article` — le sanitizer l'utilise comme base de résolution. * **IndexNow retry exponentiel** : avant, un seul shot avec timeout 5s. Un blip réseau ou un 503 transitoire perdait définitivement la soumission. Désormais : jusqu'à 3 tentatives avec backoff 0s / 1s / 3s. Les 4xx (clé / URLs invalides) court-circuitent — pas de retry sur erreurs déterministes. Timeout porté à 10s. Le log historique inclut maintenant `attempts` et `error` pour audit. * **Cleanup auto des cannibalisations dismissed/resolved obsolètes** : à chaque chargement de la page Search Console, on compare les clés stockées en options WP avec les paires (query|primary|secondary) actuellement présentes dans les données GSC live. Toute clé absente du live → entrée stale → supprimée. Évite l'accumulation indéfinie (plusieurs centaines par an sur sites actifs). * **Helper `apiError(xhr, defaultMsg)` côté JS** : les erreurs AJAX étaient toutes affichées comme `'Network error'` quelle que soit la cause. Désormais détection automatique via le code HTTP et `responseJSON.data.code` : * Status 0 → « Cannot reach the server. Check your internet connection. » * 401/403 ou code `invalid_api_key` → « API key invalid or expired. Reconfigure in Settings. » * 402 ou code `insufficient_credits` → « Plan ran out of credits. Upgrade in Settings. » * 429 ou code `rate_limited` → « Too many requests. Try again in a minute. » * 503/504 ou code `overloaded` → « Server overloaded. Try again in 30 seconds. » * 5xx → « Server error. Try again or contact support. » * **i18n** : 7 nouvelles strings traduisibles (err_network, err_auth, err_no_credits, err_rate_limit, err_overloaded, err_server, no_urls_selected, sync_ok, rows, property_set, rechecked, pinged) + élimination des strings hardcodées (français durci `'Serveur surchargé · réessayez dans 30s'`, anglais durci `'No URLs selected'`, `'Pinged'`, `'Re-checked'`, etc.). = 1.1.73 = * **Audit complet du plugin + backend** — 4 axes audités en parallèle (bulk produits/catégories, génération articles, Search Console, UX). 14 bugs et améliorations livrés en une release. * **Phase 2 wirée sur l'UI** : les boutons « Optimiser sélection » des pages Produits et Catégories utilisent désormais le nouveau système jobs serveur (`essiow_bulk_opt_create`). Concrètement : vous lancez, vous pouvez fermer l'onglet, vous revenez 1 heure plus tard — le job a continué côté serveur, le WP-Cron a appliqué les optimisations au fur et à mesure, et l'UI affiche l'état final. * **Auto-resume au chargement de la page** : si un job était en cours quand vous avez quitté, l'UI redémarre automatiquement le polling et affiche la progression (via `sessionStorage` côté navigateur). * **Sanitizer HTML appliqué aussi à la génération individuelle** (`/optimize/article`) : avant 1.1.73, le post-processing des images placeholder et liens relatifs ne tournait que sur le bulk. Les articles générés un par un héritaient des mêmes bugs. Maintenant la même protection s'applique partout — `` → strip ou fallback, `` relatif → URL absolue canonique, sinon unwrap. * **Race condition sync-pull + WP-Cron pull supprimée** : un transient lock par job (`essiow_bulk_pull_lock_{id}`) empêche les deux processus de pull les mêmes items simultanément et d'appeler `wp_insert_post` deux fois → plus de duplicates côté WP. * **Idempotence du pull renforcée** : chaque post WP est marqué `_essiow_bulk_item_id`. Si Flask renvoie le même item après un retry, on détecte le post existant et on re-confirme à Flask au lieu d'insérer un duplicate. * **Subdirectory install supporté** dans `resolve_url_to_local` : si WordPress est installé dans `/wp/` (ou autre sous-répertoire), GSC renvoie l'URL avec le préfixe subdir, mais `url_to_postid()` attend le path relatif. Le résolveur retire maintenant le préfixe et retente. * **Cleanup hourly des state tokens OAuth Google expirés** (Celery beat) — sans ce nettoyage, la table `gsc_oauth_states` accumulait une ligne par démarrage de flow OAuth, même ceux abandonnés en route. * **Confirms actionnables** : avant `confirm('Confirm?')`, désormais `confirm('Optimize 12 products? Each uses 1 credit and continues running even if you close this page.')`. Pareil pour catégories et annulation de job. * **Label « Processing: [item] »** affiché dans la barre de progression — vous voyez en direct quel produit/catégorie est en cours d'optimisation. * **CSS bouton désactivé cohérent** (opacity 0.55 + cursor not-allowed) — fini les thèmes qui rendent les boutons disabled identiques aux activés. * **Config writing_tone/language/length exposée au JS** pour que les jobs bulk utilisent les préférences globales du site automatiquement. = 1.1.72 = * **Phase 2 — Optimisations produits/catégories via jobs serveur** (architecture jumelle des articles bulk). Le plugin POST la liste d'objets WP à optimiser vers Flask, Celery les traite un par un en arrière-plan, le plugin pull les items prêts via WP-Cron (5 min) et les applique localement via `wp_update_post` + `update_post_meta` + `update_term_meta` + métas SEO (Yoast / Rank Math / AIOSEO). Le crédit est débité en transaction atomique côté Flask au moment du confirm. * **Survit à tout** : fermeture d'onglet, perte de connexion, inactivité prolongée, crash navigateur, redémarrage WordPress. Le state du job est en DB SQL côté Flask — le plugin n'a aucun transient critique à perdre. À la reconnexion, le polling reprend où il en était, et même sans reconnexion, le worker Celery continue et le WP-Cron applique au fil de l'eau. * **Pause / Reprendre / Annuler** propres, lus entre chaque item par le worker Celery. Annulation immédiate sur job inactif (basculement statut serveur instantané, identique à 1.1.68 pour les articles). * **Nouveau modèle DB Flask** : `BulkOptimizationJob` + `BulkOptimizationItem`. Endpoints : `/optimize/bulk/create`, `/status`, `/pause`, `/resume`, `/cancel`, `/pending-items`, `/items//applied`. * **Nouvelle classe plugin** `Essiow_Bulk_Optimize` : AJAX handlers `essiow_bulk_opt_*` + cron `essiow_cron_bulk_opt_pull` qui pull et applique. Le code legacy (WP-Cron transient-based) reste en place pour rétro-compatibilité — la migration UI vers les nouveaux endpoints se fait progressivement dans les prochaines releases. = 1.1.71 = * **Fix critique — image vedette absente sur les articles bulk** : `download_url()` échouait silencieusement sur les hosts mutualisés où le loopback HTTP est bloqué (cas très fréquent : mod_security, reverse-proxy hostile, WAF). Résultat : aucune image vedette n'était jamais attachée. Désormais, quand l'URL est locale au site, on retrouve l'attachment via `attachment_url_to_postid` directement — **aucune requête HTTP** — c'est instantané et 100 % fiable. Le téléchargement reste en fallback pour les images distantes (CDN externe). * **Fix critique — images cassées dans le corps d'article** : l'IA produisait régulièrement des `` littéraux (placeholder du prompt non substitué par le vrai URL). Désormais, un post-processing côté Flask scanne chaque `` après génération : ceux qui contiennent un placeholder (`IMAGE_URL`, `PRODUCT_URL`, `example.com`, src vide, etc.) sont soit remplacés par l'image vedette du pool, soit stripés. Le prompt OpenAI a aussi été reformulé pour interdire explicitement les placeholders. * **Fix critique — liens internes renvoient à l'accueil** : l'IA générait fréquemment des `` (relatif), qui une fois publiés sur `/mon-article/`, deviennent `/mon-article/produit/widget` → 404 → souvent redirigé vers l'accueil par les plugins SEO. Le post-processing résout maintenant chaque `` : * Si l'URL correspond à un produit / article / catégorie envoyé en contexte → résolu en URL absolue canonique. * Si l'URL est relative et inconnue → résolue via `site_url + chemin`. * Si l'URL est vide / placeholder / `#` → le `` est unwrappé (le texte reste, le lien disparaît). * **Nouveau service** `article_html_sanitizer.py` : module autonome de post-processing HTML qui répare tous les artefacts d'IA (placeholders, URLs relatives, ancres vides). Logué avec compteurs (`imgs kept=X stripped=Y / links kept=X stripped=Y`) pour audit a posteriori. * **set_featured_image() durci** : * Timeout porté de 5s à 30s. * Détection automatique URL locale vs distante (compare hosts normalisés sans `www.`). * Fallback gracieux si le nom de fichier de l'image est sans extension (déduction du type MIME via `getimagesize`). * Logs explicites en cas d'échec (avant : silencieux). **Phase 2 à venir** : migration de la génération individuelle d'articles, de l'optimisation produits (individuel + bulk), et de l'optimisation catégories (individuel + bulk) vers l'architecture jobs-serveur identique aux articles bulk — fermeture de l'onglet, perte de connexion, inactivité : rien ne s'arrête, l'optimisation reprend où elle s'est arrêtée. Travail de refonte sur plusieurs releases. = 1.1.70 = * **Audit complet des 4 sources de génération bulk** (liste de mots-clés, catégories WC, produits WC, import CSV SEMrush) + 6 bugs corrigés. * **Fix critique — état du sélecteur de source** : taper une liste de mots-clés, puis switcher sur « Produits » (sans rien cocher) puis lancer envoyait le mauvais payload au backend (source=products + des keywords textuels libres). Désormais, le changement d'onglet réinitialise la sélection et l'état visuel — l'utilisateur doit re-sélectionner sur le nouveau mode. * **Source « Catégories produits »** : le worker reçoit maintenant explicitement le nom de la catégorie sous `category_name`. Le prompt active la section « PRODUCT CATEGORY CONTEXT » et rédige l'article comme une page pilier de catégorie au lieu d'un article générique sur la requête. * **Source « Produits »** : l'URL, l'image et le prix du produit sélectionné sont désormais envoyés en contexte au worker. Le produit cible est injecté en tête de la liste des produits disponibles (l'IA le mentionne en priorité) et son image devient l'image vedette par défaut. * **Fix dropdown auteur vide sur WP 5.9+** : `get_users(['who' => 'authors'])` est déprécié depuis WordPress 5.9 et renvoyait un tableau vide → le dropdown auteur de la page bulk était vide, impossible de lancer un job. Remplacé par `capability => 'edit_posts'`, avec fallback sur l'utilisateur courant si vide. * **CPC préservé** dans le pipeline : la valeur Cost-Per-Click parsée depuis les exports SEMrush était parsée puis dropped lors de la sanitisation côté PHP. Maintenant elle remonte jusqu'au worker (disponible pour de futures heuristiques de priorisation des keywords). * **Sanitisation des keywords élargie** côté plugin : accepte les 3 formats — strings (mode liste/catégories), dict SEMrush (`keyword, volume, kd, intent, cpc`), dict produit (`keyword, product_url, product_image_url, product_price`). = 1.1.69 = * **Parité complète entre articles bulk et articles individuels**. Avant 1.1.69, les articles générés en masse étaient pauvres : pas d'images, pas d'image vedette, pas de liens internes vers les produits/catégories, pas de recommandations. Maintenant ils sortent identiques à ceux générés un par un. * **Contexte WordPress envoyé au worker** : à chaque création de job, le plugin collecte et transmet au backend les 30 produits top-ventes (avec URL/prix/image), les 20 articles récents et les 15 catégories produits actives. L'IA dispose donc du même contexte que pour la génération individuelle — elle peut citer les vrais produits, créer des liens internes pertinents, choisir des images réelles. * **Image vedette automatique** : chaque article reçoit en featured image une image produit de votre catalogue (rotation par position pour varier entre articles d'un même batch). * **Conversion en blocs Gutenberg** : le contenu HTML est désormais découpé en blocs `

`, `

`, `