Essiow — AI SEO Suite for WooCommerce

Beschreibung

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.

Rezensionen

There are no reviews for this plugin.

Mitwirkende & Entwickler

„Essiow — AI SEO Suite for WooCommerce“ ist Open-Source-Software. Folgende Menschen haben an diesem Plugin mitgewirkt:

Mitwirkende

Änderungsprotokoll

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 (300s, memory raised, try/catch with clear error detail).
  • Fix 2 — Double-compute bug: clicking preview then „Run automesh now“ recomputed the plan twice (60s + 60s = 120s cumulative — kill on Cloudflare/Nginx at 100s upstream timeout). Now the preview caches its plan for 5 min, and ajax_automesh_start reuses it instead of recomputing. One-shot cache (consumed on use).
  • Fix 3 — JS $.post had no timeout on the preview. Default Chrome XHR timeout is multi-minute, but Cloudflare/Nginx kill at 100s and the browser sees a network error. Explicit timeout: 180000 (3 min) added, with status-specific error messages (504 detected separately to point at the host’s reverse proxy).
  • Fix 4 — Transient compression: large plans (> 100 KB serialized) are now gzcompress‚d before being stored in wp_options.essiow_il_automesh_plan_cache. Avoids hitting MySQL max_allowed_packet (default 4-8 MB on shared hosting), which would silently truncate the row and break the worker.
  • All four fixes also help the original EazyWP / shared-hosting scenarios from 1.1.58 — they apply to the preview stage which 1.1.58 didn’t cover.

1.1.58

  • Fix critical: Automesh stuck on „0 / undefined pages processed“ on shared / low-spec hosts (EazyWP, Hostinger, low-tier OVH, etc).
  • Two combined root causes :
    1. Truncated AJAX response from ajax_automesh_start : on a 1000+ page site, computing the plan takes 30-60s. PHP’s default max_execution_time (30s) on shared hosting truncates the JSON response mid-write. The plugin received a partial response with success:true but total undefined 0 / undefined displayed.
    2. WP-Cron broken / disabled : many shared hosts disable DISABLE_WP_CRON without setting up a real cron, or block loopback HTTP (used by spawn_cron()). The worker cron_automesh_run was scheduled but never executed 0 pages processed indefinitely.
  • Fix 1 (server-side hardening) : ajax_automesh_start now wraps the plan computation in try/catch, sets set_time_limit(300), raises memory via wp_raise_memory_limit('admin'), and returns a clean error with detail if …