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.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 it crashes — instead of a truncated 200 response.
  • Fix 2 (worker stall detection + sync fallback) : ajax_automesh_status now detects when a task has been pending/running for 20s+ without any tick. When stalled, it (a) re-schedules the WP-Cron event and (b) executes ONE batch synchronously in the status request itself — using the user’s browser as the cron engine. The polling continues to drive progress. No more silent stalls.
  • Fix 3 (JS defensive) : the progress modal handles total = 0/undefined/NaN by showing until the first status poll. Stall detection client-side after 60s shows a hint to the user. AJAX timeout extended to 120s for large-site planning.
  • Fix 4 (resume-on-reload safety) : if the user resumes an existing task, the response now includes the original total (was missing before, which caused the same “undefined” bug on tab reload).

1.1.57

  • Fix: Search Console Overview tab stuck on Clicks: 0 / Impressions: 0 even after a successful sync. The auto-sync trigger logic was buggy.
  • Root cause: the auto-sync only fired when BOTH KPIs and indexation were empty. If indexation was already populated (by an earlier cron, manual sync, or background job), the condition !hasIndex && !hasKpis evaluated to false and the sync never ran — leaving the analytics KPIs forever at 0 even though the database had no analytics rows.
  • Fix: auto-sync now triggers based on KPIs alone (!hasKpis). Indexation status is independent of analytics rows — they come from different backend tables and pipelines, so we can’t infer one from the other. Now any user with empty analytics gets an automatic sync on first page load.

1.1.56

  • Fix: “Could not resolve secondary URL to a local page” when clicking “Set canonical” in the Cannibalization tab. The resolver was relying solely on url_to_postid(), which silently fails for the WooCommerce Shop page (/boutique/, /shop/, etc.), the front page (/), and pages with custom permalinks.
  • New centralized helper resolve_url_to_local() with cascading fallbacks: native url_to_postid front page blog page WooCommerce Shop/Cart/Checkout/My-Account/Terms via wc_get_page_id() get_page_by_path() for slug lookup /product-category/<slug>/, /category/<slug>/, /tag/<slug>/ for terms. Now correctly resolves /boutique/ Shop page ID, / front page ID, etc.
  • Used by set_canonical, set_canonical_bulk, and resolve_url_to_entity (Indexation tab). The cannibalization “Set canonical” button now works for the homepage and the Shop page, which are the two most common cases of cannibalization on a WooCommerce store.

1.1.55

  • Fix critical : after reconnecting Google Search Console, the Overview tab stayed stuck on “Processing…” with empty KPIs (—) even though the server was correctly syncing thousands of rows in the background.
  • Root cause: cache poisoning in the Search Console overview cache. The cache key included the period (essiow_gsc_overview_cache_28, _90, etc.), but the invalidation only deleted the unsuffixed key (essiow_gsc_overview_cache). On reconnect, the first overview fetch returned KPIs at 0 (sync not finished yet) and got cached for 1 hour — every subsequent fetch served the empty cache, never updating despite the data arriving in the database.
  • Fix 1: new invalidate_overview_cache() helper that deletes ALL period-suffixed cache keys (_1, _7, _28, _90, _180, _365, _480). Called by all 4 places that previously did the broken single-key delete: ajax_disconnect, ajax_sync, ajax_select_property, cron_nightly_sync.
  • Fix 2: JS loadScOverview() now accepts a force flag that bypasses the PHP cache. Used after the auto-sync triggered when KPIs are detected as empty, and after the manual “Refresh stats now” button — guarantees the post-sync fetch always sees fresh data, never the old cached zeros.

1.1.54

  • Fix definitively: the GSC quota toast that kept appearing during Product / Category optimizations is now suppressed outside the Search Console tab.
  • Root cause analysis: the auto-inspection background batch (50 URLs) re-fired on every table re-render after an optimization, hit the exhausted Google quota, and showed the toast — even though the user wasn’t on the indexation tab. The 1-hour server cooldown was only set if 100% of the batch errored; in practice GSC often returns 49 errors + 1 success, leaving the cooldown unset and the next run re-triggering the toast.
  • Fix 1: Toast confined to Search Console tab. The auto-inspection still runs in background to populate badges, but the error toast only appears when essiow.current_page === 'essiow-search-console'. On Products / Categories pages, the cooldown is silently applied without the toast.
  • Fix 2: Cooldown threshold lowered to ≥50% errors. Catches the realistic case (49/50 errors) — previously needed exactly 100%.
  • Fix 3: Cooldown duration extended to 6h (vs 1h). Google’s quota resets daily; a 1-hour cooldown was too short and caused the toast to come back after 1 hour of work.
  • Fix 4: Cooldown propagated immediately in the AJAX response (quota_cooldown: true, cooldown_until: ts). The JS applies it without waiting for a page reload, so any subsequent inspection batch in the same session is short-circuited.

1.1.53

  • Major Graph view rewrite. Visual link graph now shows the actual edges (lines) connecting pages — previously you saw nodes but no relationships.
  • New: Persistent edges drawn as SVG lines between every pair of internally-linked pages. Subdued gray when idle, bold blue when a node is focused. Auto-redraws on pan/zoom. Capped at 1500 visible non-focus edges to keep large graphs (1000+ pages) responsive.
  • New: Click an edge to delete it. Wide invisible hitbox (10px) makes 1px lines easy to click. Confirmation prompt, then the <a> wrapping is removed from the source page (anchor text preserved).
  • New: Permanently visible drag handle (🔗). Top-right of every node, opacity 0.55 by default, fully visible on hover or focus. Drag it onto another node to create a reciprocal link.
  • New: Click a node to focus it. Highlights all its edges in blue, shows a status panel with title, URL, edit link, in/out counts and instructions. Re-click to defocus.
  • New: Type filters (Products / Categories / Articles checkboxes) hide entire types — useful on big sites to inspect just the category-product mesh.
  • New: Toggle labels. When zoomed-out on dense graphs, you can hide titles and keep only colored dots — much more readable.
  • New: Reorganize button (🌀). Resets every node position to a fresh Vogel-spiral layout (golden-angle distribution = no visual rays). Preserved manually-dragged positions are wiped.
  • New: Visible legend under the toolbar : 🔴 orphan / 🟢 hub / ⚪ normal + interaction help.
  • New backend AJAX endpoint: essiow_il_unlink_pair (action). Removes <a href="target">…</a> wrappers from a source page’s content, keeping the anchor text intact. Direction can be a_to_b, b_to_a or both. Used by the click-edge-to-delete flow.
  • New: graph cache now exposes a flat edges array [{from, to}, …] (was previously dropped before caching). Allows the frontend to render the full mesh.

1.1.52

  • Polish: rescue caps raised after real-world data (1.1.51 reduced orphans from 380 to 39 on a 1156-page store, score 87 99/100). PASSE 0 cap raised from 8 to 12 (per-source orphan absorption), PASSE B cap raised to 15 (final safety net).
  • New: PASSE B² — last resort. If after PASSE 0 + PASSE B + main loop an orphan is STILL not covered (its entire fallback chain is saturated at 15), the orphan is forced onto the most active category of the site with NO cap. …