Essiow — AI SEO Suite for WooCommerce

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.

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
  4. Click “Test Connection” to verify
  5. Start optimizing from Essiow > Products or Essiow > Categories

FAQ

Do I need an Essiow account?

Yes. Create a free account at 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.

Reviews

There are no reviews for this plugin.

Contributors & Developers

“Essiow — AI SEO Suite for WooCommerce” is open source software. The following people have contributed to this plugin.

Contributors

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 <img> 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 <img> mĂȘme quand on lui listait les produits dans le prompt. Le sanitizer compte maintenant les <img> 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 <h2>, wrappĂ©es dans des <a> 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 <img> minimum, format explicite avec wrap <a href="PRODUCT_URL"> 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 — <img src="IMAGE_URL"> strip ou fallback, <a href="produit/x"> 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/<id>/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 <img src="IMAGE_URL"> littĂ©raux (placeholder du prompt non substituĂ© par le vrai URL). DĂ©sormais, un post-processing cĂŽtĂ© Flask scanne chaque <img> 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 <a href="produit/widget"> (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 <a href> :
    • 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 <a> 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 <p>, <h2>, <ul>, <table> distincts (plus de gros bloc HTML brut difficile Ă  Ă©diter). Identique Ă  la gĂ©nĂ©ration individuelle.
  • SEO meta complet : Yoast SEO, Rank Math et AIOSEO sont tous les trois renseignĂ©s (titre, description, focus keyword). Avant, seuls titre + description partiels Ă©taient settĂ©s.
  • Excerpt automatique + nettoyage des doublons (h1/h2 du titre, en-tĂȘtes “Introduction” rĂ©siduels).
  • Nouvelle section « Articles gĂ©nĂ©rĂ©s en masse » en bas de la page Bulk : liste paginĂ©e des articles produits par les jobs, avec score SEO (0-100), statut (publiĂ©/brouillon), date, nombre de mots, et boutons Voir / Modifier. SĂ©lection multiple ping IndexNow + demande d’indexation Google, comme dans la liste des articles individuels.
  • Auto-refresh de la liste Ă  chaque tick de polling — les nouveaux articles publiĂ©s apparaissent dans la liste sans recharger la page.
  • Fix accents : les titres de produits et catĂ©gories Ă©taient affichĂ©s avec leurs entitĂ©s HTML brutes (8Sinn eXtraThin HDMI &#8211; Cable au lieu de – Cable). DĂ©codage via html_entity_decode partout : sĂ©lecteur produits bulk, sĂ©lecteur catĂ©gories, contexte envoyĂ© au worker.

1.1.68

  • Refonte UX bulk suivant retours utilisateurs (5 changements majeurs) :
  • 1) Annulation immĂ©diate : annuler un job en queued / awaiting_wp_publish / paused bascule maintenant le job Ă  cancelled instantanĂ©ment cĂŽtĂ© serveur. Avant, seul le flag cancel_requested Ă©tait mis Ă  true, mais comme aucun worker n’Ă©tait en train de tourner, le status restait inchangĂ© — les boutons Annuler persistaient mĂȘme aprĂšs reload. Pareil pour Pause sur un job en attente.
  • 2) Suppression des jauges visuelles : remplacĂ©es par des pourcentages texte clairs et compacts. La modale active affiche đŸ€– GĂ©nĂ©ration IA : 60% (3/5) · 📝 Publication WP : 40% (2/5) sur une seule ligne.
  • 3) GĂ©nĂ©rations en cours dĂ©placĂ©es en bas de la page (sous l’historique), comme une barre de statut discrĂšte. L’utilisateur n’est plus visuellement bloquĂ© par un gros banner en haut quand il configure une nouvelle gĂ©nĂ©ration.
  • 4) Dialog DĂ©tails enrichi : nouvelle colonne « Titre de l’article » qui affiche le titre gĂ©nĂ©rĂ© par l’IA pour chaque mot-clĂ©, plus deux boutons d’action explicites — 👁 Voir (lien public vers l’article) et ✎ Modifier (admin WP). Indispensable pour passer en revue les articles gĂ©nĂ©rĂ©s.
  • 5) Auto-refresh aprĂšs action : pause / reprendre / annuler dĂ©clenchent immĂ©diatement un re-fetch du status + un refresh de l’historique + un refresh de la modale DĂ©tails si elle est ouverte sur le mĂȘme job. Plus jamais d’Ă©tat stale dans l’UI.
  • Auto-fade-out de la barre active 5s aprĂšs complĂ©tion + toast final « ✅ GĂ©nĂ©ration terminĂ©e — N articles ».

1.1.67

  • Refonte complĂšte UI Bulk generation suite aux retours utilisateurs.
  • 2 jauges de progression distinctes : une pour la gĂ©nĂ©ration IA cĂŽtĂ© SaaS, une pour la publication WordPress cĂŽtĂ© local. Avant, le compteur affichait toujours 0 jusqu’Ă  la publication WP — pendant tout le temps de gĂ©nĂ©ration (5-10 min sur des dizaines d’articles), l’utilisateur croyait que rien ne se passait. Maintenant la jauge bleue avance dĂšs qu’un article est gĂ©nĂ©rĂ© cĂŽtĂ© SaaS, puis la jauge verte avance quand le post WP est créé.
  • Boutons d’action fonctionnels avec feedback immĂ©diat : pause / reprendre / annuler envoient maintenant un toast de confirmation, forcent un rafraĂźchissement immĂ©diat du status et de l’historique. Plus jamais d’action « silencieuse ».
  • Dialog « DĂ©tails » sur chaque ligne d’historique + bouton dans la modale active : affiche tous les paramĂštres du job (statut, source, dates, config IA, mots-clĂ©s un par un avec leur statut individuel et lien direct vers l’article WP créé). Indispensable pour vĂ©rifier oĂč en est un job ou consulter ses anciens runs.
  • Bouton « DĂ©tails » dans la modale active Ă©galement, pour consulter la liste des keywords pendant la gĂ©nĂ©ration.
  • Affichage du mot-clĂ© en cours dans la modale : ⚡ En cours : "comment choisir un tracteur" — l’utilisateur sait exactement oĂč en est le gĂ©nĂ©rateur.
  • Toast final « GĂ©nĂ©ration terminĂ©e — N articles » Ă  la complĂ©tion du job (au lieu de la modale qui restait Ă©ternellement).
  • Compteurs propres : on lit maintenant les bons noms de champs (completed_count, total_count, generated_count, published_count) avec alias rĂ©tro-compat. Plus de « undefined / undefined ».

1.1.66

  • Fix critical : sur les sites peu visitĂ©s, WP-Cron ne tournait pas les articles gĂ©nĂ©rĂ©s cĂŽtĂ© SaaS restaient en attente et n’Ă©taient jamais publiĂ©s. Le job passait Ă  awaiting_wp_publish mais le plugin ne pullait jamais les articles.
  • Sync-pull dans le polling : Ă  chaque poll status (toutes les 5s cĂŽtĂ© JS), si le job est running / awaiting_wp_publish / paused, le plugin dĂ©clenche un pull synchrone immĂ©diat — il pull les items prĂȘts, fait wp_insert_post localement, et confirme Ă  Flask (qui dĂ©bite le crĂ©dit). Le navigateur du user devient le moteur de cron, exactement comme on a fait pour l’Automesh sur shared hosting.
  • Boutons actions dans l’historique : nouvelle colonne « Actions » avec ⏞ Pause / ▶ Reprendre / ✕ Annuler pour chaque job actif. Plus besoin d’attendre que le banner du job actif s’affiche pour le piloter.
  • Toast FR direct : « GĂ©nĂ©ration en masse lancĂ©e » au lieu du fallback anglais « Bulk job started » quand les traductions PHP ne sont pas encore chargĂ©es (transition de version).

1.1.65

  • Fix critical (jauge): la barre de progression du bulk apparaissait pleine 5 secondes au refresh avant de revenir Ă  0. Cause : total_count passĂ© en string '
' au render initial calcul (0+0+0)*100/'
' = NaN width: NaN% CSS invalide fallback navigateur Ă  100%. Tous les compteurs sont dĂ©sormais castĂ©s en parseInt(... 10) || 0, et le render initial part avec total_count: 0 (jauge Ă  0%, plus de flash).
  • Traductions FR complĂštes de toute la nouvelle vue Bulk generation. Tous les libellĂ©s sont en français dans la vue PHP, et les fallbacks JS Ă©galement (statuts du job, sources, labels de progression, boutons pause/reprendre/annuler, configuration de gĂ©nĂ©ration, etc.).
  • Format date français dans l’historique (toLocaleDateString + heure HH:MM) au lieu de l’ISO brut.
  • Labels lisibles pour status (queued « En file », running « En cours », awaiting_wp_publish « Publication WordPress », etc.) et source (keyword_list « Liste de mots-clĂ©s », collections « CatĂ©gories produits », etc.) — plus de codes techniques affichĂ©s Ă  l’utilisateur.

1.1.64

  • Fix critical (bulk articles) : audit complet de 1.1.63 — l’implĂ©mentation initiale n’aurait pas pu fonctionner. Trois problĂšmes bloquants corrigĂ©s.
  • Fix 1 — API authentication broken. The bulk handlers used get_option('essiow_api_key') which reads an option that doesn’t exist in clear (the API key is stored AES-encrypted in essiow_api_key_enc). They also missed the HMAC anti-replay signature headers (X-Timestamp, X-Nonce, X-Domain, X-Signature). Every call would have failed with 401 Unauthorized. Refactored all handlers to use the canonical Essiow_API_Client::instance() which handles decryption + HMAC signing transparently.
  • Fix 2 — JSON response unwrapping bug. Essiow_API_Client::handle_response() returns the decoded body directly (so $resp['data'] IS the data), but the bulk code wrapped on a 3rd level ($resp['data']['data']['jobs']), which always evaluated to null. Status / list / create / pending-items / confirm-published — all 7 endpoints affected. Aligned with the canonical client structure.
  • Fix 3 — CSV preview moved to plugin-side parsing. The original implementation tried to POST a multipart upload to Flask without computing the HMAC signature for binary content. Replaced by a self-contained PHP CSV parser (header sniffing for keyword/volume/KD/intent across English/French/German/Spanish aliases, encoding detection with BOM stripping, delimiter auto-detect via comma/semicolon/tab/pipe count). No more round-trip to Flask for previews — faster and avoids the signing problem entirely.

1.1.63

  • New: Bulk article generation for WooCommerce / WordPress. Generate dozens to hundreds of SEO articles in one go from four sources:
    • Product categories: one article per selected WooCommerce category
    • Products: one article per product (up to 500)
    • Keyword list: free-form textarea, comma or line separated
    • SEMrush CSV: upload your export (Keyword Magic Tool / Organic Research / Keyword Gap), preview keywords with volume/KD/intent, select which to keep
  • Server-side orchestration via the Essiow backend. The browser only triggers the job — generation continues even if you close the tab. A WP-Cron worker pulls ready articles every 5 minutes and inserts them as WordPress posts (or via wp_insert_post triggered immediately on first launch).
  • 1 credit = 1 article published: credits are debited only after the WordPress post is successfully inserted (atomic transaction backend-side). If credits run out mid-job, it auto-pauses; refill and resume.
  • Pause, resume, cancel any running job at any time. State is persisted server-side.
  • Crash resilient: if the worker dies mid-generation, the next pull picks up where it left off without re-paying for what was already generated.
  • Drip publishing option: spread articles over N days (1 every N days) instead of all at once — better for SEO patterns and avoids Google seeing a burst.
  • Per-post config: author, post category, publish status (immediate or draft), tone, length, language, fuzzy dedup toggle.
  • SEO meta filled out: Yoast / Rank Math / AIOSEO meta fields are set automatically when the SaaS returns SEO title/description.
  • Hard cap of 500 articles per job (anti-blast-radius).
  • Job history with status per row, restart polling automatically when reopening the page on an active job.

1.1.62

  • Performance overhaul of _automesh_compute_plan — targets sub-30s compute even on 10k-page sites, so the planning phase fits inside the tight PHP-FPM timeouts of shared hosts (EazyWP, low-tier hosts) and stops triggering the 1.1.61 “planning_aborted” abort.
  • Optim 1 — Batch WP term cache : wp_get_post_terms() was called once per product (10000 separate SELECTs on a 10k-product site). Now pre-warms the WP term cache via update_object_term_cache($product_ids, 'product') — 1-2 SELECTs total, the per-product calls become free cache hits.
  • Optim 2 — Token memoization : every _jaccard_titles() call was re-tokenizing both titles from scratch. On a 5000-page site with 100-sibling avg pool, that’s 500k tokenizations. Added an instance-scoped _token_cache keyed by title ~5k tokenizations total. Speedup ~100×.
  • Optim 3 — Faster Jaccard intersection : replaced array_intersect (O(n×m)) with array_flip + isset() lookup (O(n)). 5× cumulated gain.
  • Optim 4 — Pool size cap in _best_topical_match*(). When the candidate pool exceeds 1000 items, sample 500 random instead of scanning all. Statistically same top-K quality, but O(NÂČ) explosion neutralized on massive sites.
  • Optim 5 — Skip anchor registry preload entirely above 5000 pages. The AUTOMESH_EXACT_RATIO_MAX (15%) guard rail already prevents over-using exact anchors, so an empty initial registry produces a balanced mesh from run 1 anyway.
  • Result on a 10k-page site (measured on a dev VM): compute_plan dropped from ~180s to ~22s. Memory footprint divided by ~3.
  • Note: if a 10k+ site still hits planning_aborted on your host after this update, the max_execution_time is < 30s. Ask your host to set it to 60s+, or split into smaller imports.

1.1.61

  • Fix critical: automesh planning phase stuck in infinite loop (“Preparing your link plan
 5000s+”). On hosts where PHP-FPM kills requests at 60-120s, each worker attempt died before saving its progress — and every subsequent status poll relaunched a fresh attempt that died again. No exit, no error, just an ever-growing elapsed counter.
  • Three combined fixes:
    1. Anchor registry sampling for large sites (> 2000 pages). The biggest CPU/memory bottleneck of _automesh_compute_plan is _preload_anchor_registry, which fetches and regex-parses the full HTML content of every page. On 10000 pages that’s 10000 SQL + 500 MB of regex. Now sampled to 500 random pages on large sites — enough to estimate existing anchor-type ratios without killing the worker.
    2. Max 5 planning attempts counter on the task. If the worker keeps dying mid-compute (PHP-FPM timeout, OOM), after 5 retries we mark the task as failed with a clear log: “PHP-FPM/host kill shorter than required compute time. Contact your host to raise max_execution_time and memory_limit, or split your site into smaller imports.”
    3. Cancel button in the in-progress modal + new ajax_automesh_cancel endpoint. Lets users escape a stuck task instantly: marks it failed, releases the essiow_il_automesh_active lock, clears scheduled WP-Cron events. Confirmation prompt explains that already-injected links remain (backed up, revertable).
  • New: when a task ends in failed state, the UI fetches the detailed log line from the backend and shows it in the error toast — actionable info instead of “Something went wrong.”
  • New strings: automesh_cancel, automesh_confirm_cancel, automesh_cancelled (i18n).

1.1.60

  • Refactor critical: the Automesh plan computation is now fully deferred to the background worker. On very large sites (10000+ pages), the compute_plan was taking > 3 minutes inside the HTTP request, hitting browser timeouts, Cloudflare 100s upstream limit, and PHP max_execution_time even after our 1.1.59 hardening. No amount of timeout-raising could solve that — the compute had to stop blocking HTTP entirely.
  • Architecture after 1.1.60:
    1. ajax_automesh_plan (preview) returns a fast heuristic estimate based only on the cached graph (orphans count, deadends count, hubs count). ≀ 10s on any site size. No compute_plan, no simulate_apply_plan.
    2. ajax_automesh_start immediately creates a task with status='planning', schedules the worker, and returns in < 1s. No more “Timed out” on click.
    3. cron_automesh_run (worker) detects status='planning' on its first tick runs build_graph + compute_plan (the heavy work, up to 5 minutes), then switches to status='pending' and processes batches as before.
    4. ajax_automesh_status distinguishes planning vs running. The sync-fallback path (shared hosting where WP-Cron is dead) also handles the planning tick — same trick: if no tick in 30s, the status request itself runs the worker.
  • JS UI: shows “Preparing your link plan
” with an indeterminate progress bar during the planning phase, then switches to “X / Y pages processed” once the plan is ready. Elapsed counter visible (so the user knows things are moving). Failed-status detection added to clear the modal if the worker crashes during planning.
  • New strings: automesh_planning, automesh_planning_hint, automesh_failed (i18n).
  • Removed: the 1.1.59 transient cache for the plan — no longer needed since the start endpoint doesn’t compute the plan anymore.

1.1.59

  • Fix: “Erreur rĂ©seau” toast on Automesh when the site has many links (1000+ pages, 4000+ planned links). Four compounding causes audited and patched:
  • Fix 1 — ajax_automesh_plan was less protected than ajax_automesh_start despite doing MORE work (build_graph + compute_plan + simulate_apply_plan + 2× score). It had set_time_limit(180) only — no ignore_user_abort, no wp_raise_memory_limit, no try/catch. Aligned on ajax_automesh_start