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
- Upload the
essiowfolder to/wp-content/plugins/ - Activate the plugin through the ‚Plugins‘ menu in WordPress
- Go to Essiow > Settings and enter your API key from essiow.com
- Click „Test Connection“ to verify
- 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Übersetze „Essiow — AI SEO Suite for WooCommerce“ in deine Sprache.
Interessiert an der Entwicklung?
Durchstöbere den Code, sieh dir das SVN Repository an oder abonniere das Entwicklungsprotokoll per RSS.
Änderungsprotokoll
1.1.75
- Fix définitif — images vedette + images inline dans les articles bulk :
- Featured image via attachment ID : avant, le plugin recevait une URL
https://site.com/wp-content/uploads/2024/10/widget-1024x768.jpg(taillelarge) et tentaitattachment_url_to_postidsouvent échec car cette fonction n’accepte que l’URL ORIGINALE sans suffixe-WxH. Désormais le plugin envoie l‘image_iddirectement dansfeatured_image_pool; le worker stockefeatured_image_iddansgenerated_payload; le plugin attache viaset_post_thumbnail($post_id, $id)— zéro HTTP, indestructible. - 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. - 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.
- Featured image via attachment ID : avant, le plugin recevait une URL
- 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-Afterquand 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-urlsur 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
cancelAllBulksOnUnloadannulait toujours les bulks au refresh / fermeture d’onglet vianavigator.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 sonsite_urlcomplet (viahome_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
attemptseterrorpour 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 etresponseJSON.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
sessionStoragecô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’appelerwp_insert_postdeux 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, maisurl_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_statesaccumulait une ligne par démarrage de flow OAuth, même ceux abandonnés en route. - Confirms actionnables : avant
confirm('Confirm?'), désormaisconfirm('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 handlersessiow_bulk_opt_*+ cronessiow_cron_bulk_opt_pullqui 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 viaattachment_url_to_postiddirectement — 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/widget404 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é parcapability => '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 – Cableau lieu de– Cable). Décodage viahtml_entity_decodepartout : 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 à
cancelledinstantanément côté serveur. Avant, seul le flagcancel_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_publishmais 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, faitwp_insert_postlocalement, 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_countpassé en string'…'au render initial calcul(0+0+0)*100/'…'= NaNwidth: NaN%CSS invalide fallback navigateur à 100%. Tous les compteurs sont désormais castés enparseInt(... 10) || 0, et le render initial part avectotal_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 inessiow_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 canonicalEssiow_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 viaupdate_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_cachekeyed by title ~5k tokenizations total. Speedup ~100×. - Optim 3 — Faster Jaccard intersection : replaced
array_intersect(O(n×m)) witharray_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_abortedon your host after this update, themax_execution_timeis < 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:
- Anchor registry sampling for large sites (> 2000 pages). The biggest CPU/memory bottleneck of
_automesh_compute_planis_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. - 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
failedwith 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.“ - Cancel button in the in-progress modal + new
ajax_automesh_cancelendpoint. Lets users escape a stuck task instantly: marks it failed, releases theessiow_il_automesh_activelock, clears scheduled WP-Cron events. Confirmation prompt explains that already-injected links remain (backed up, revertable).
- Anchor registry sampling for large sites (> 2000 pages). The biggest CPU/memory bottleneck of
- New: when a task ends in
failedstate, 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:
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.ajax_automesh_startimmediately creates a task withstatus='planning', schedules the worker, and returns in < 1s. No more „Timed out“ on click.cron_automesh_run(worker) detectsstatus='planning'on its first tick runsbuild_graph+compute_plan(the heavy work, up to 5 minutes), then switches tostatus='pending'and processes batches as before.ajax_automesh_statusdistinguishesplanningvsrunning. 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_planwas less protected thanajax_automesh_startdespite doing MORE work (build_graph + compute_plan + simulate_apply_plan + 2× score). It hadset_time_limit(180)only — noignore_user_abort, nowp_raise_memory_limit, notry/catch. Aligned onajax_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_startreuses it instead of recomputing. One-shot cache (consumed on use). - Fix 3 — JS
$.posthad notimeouton the preview. Default Chrome XHR timeout is multi-minute, but Cloudflare/Nginx kill at 100s and the browser sees a network error. Explicittimeout: 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 inwp_options.essiow_il_automesh_plan_cache. Avoids hitting MySQLmax_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 :
- Truncated AJAX response from
ajax_automesh_start: on a 1000+ page site, computing the plan takes 30-60s. PHP’s defaultmax_execution_time(30s) on shared hosting truncates the JSON response mid-write. The plugin received a partial response withsuccess:truebuttotalundefined0 / undefineddisplayed. - WP-Cron broken / disabled : many shared hosts disable
DISABLE_WP_CRONwithout setting up a real cron, or block loopback HTTP (used byspawn_cron()). The workercron_automesh_runwas scheduled but never executed 0 pages processed indefinitely.
- Truncated AJAX response from
- Fix 1 (server-side hardening) :
ajax_automesh_startnow wraps the plan computation intry/catch, setsset_time_limit(300), raises memory viawp_raise_memory_limit('admin'), and returns a clean error with detail if …
