Marcelo Site Connector

Сүрөттөө

Connecting a website to Google Search Console, Google Analytics or Google Tag Manager always ends the same way: a line of code has to go into the site’s <head>.

WordPress can be given permission to write posts, pages and media over its own REST API, but not to write there. So the last step of connecting any of those three is a person being asked to paste a snippet into a theme file or into an SEO plugin’s settings — and the person being asked is usually a business owner who paid somebody to build the site two years ago.

This plugin removes that step. Install it once, and Marcelo can place those tags itself.

What it does

  • Places up to three Google tags in the site’s <head> — a Search Console verification, an Analytics tag and a Tag Manager container — built from the identifier of each rather than from any markup it was handed.
  • Stores a description for a page that has none, and prints it as that page’s meta description — the sentence a search engine shows under the page in its results. WordPress core keeps no such field, and every SEO plugin uses its own hidden one, so a description written into any of those keys is invisible on a site running a different plugin.
  • Stores the title a page should go by in search results and in a browser tab, and hands it to WordPress through pre_get_document_title — the filter core publishes for exactly this, and the one every common SEO plugin uses. This is not the post’s own title: post_title is the name of the page in the admin and usually the heading a visitor reads, and changing it does not change the title a search engine shows on a site running any SEO plugin.
  • Stores a LocalBusiness schema block describing the business, as JSON-LD in the head of every page.
  • Stores redirects for addresses that no longer exist, and sends visitors on to the page that replaced them.
  • Reads and writes the layout of a page built with Elementor, so the text of such a page can be edited at all. WordPress keeps a page’s text in post_content; Elementor keeps its own copy in a protected postmeta field and prints that instead, so a write to post_content on an Elementor page succeeds and changes nothing a visitor reads.
  • Puts a tap-to-call bar in front of visitors: one fixed element carrying the business’s phone number as a tel: link, printed at wp_footer on every page. It is Marcelo’s own element and touches nothing the site already had, so clearing it removes it completely.
  • Draws links Marcelo measured as too small to tap at least 44 pixels tall and wide on phone-sized screens, by a minimum size on each named link in a stylesheet of its own. This is the one thing here that changes how the site’s own elements are drawn, and only their size: nothing is reworded or moved, and clearing it removes every rule.
  • Reports how this site serves robots.txt, and — only where WordPress generates it — lets a named crawler back in that robots.txt was turning away. A robots.txt file sitting in the site’s root is refused rather than touched: the webserver serves that file directly and WordPress is never asked for one, so nothing a plugin does could change what a crawler reads.
  • Points a page’s canonical link back at the page itself, where the one on it names another website. This is the only thing here that displaces markup the site was already printing rather than adding markup of its own — see below for exactly how far that reaches, and where it stops.
  • Adds a translated page’s own link among its hreflang alternates where it is missing one — the one entry that says “this page is itself”. Nothing else in the head is touched: whatever else prints the page’s other alternates is left exactly as it is, and only the language code is stored — the address is always built from the page’s current request, never stored, so it stays correct if the domain ever changes.
  • Saves an oversized picture in the site’s own media library again at a smaller size, keeping the untouched original beside it so it can be put back. This is the only thing here that overwrites a file rather than a value in the database, and it is bounded accordingly: only an attachment this site really owns, only a file inside its own uploads directory, only when the file still weighs what the caller says it weighed, never a GIF or an SVG, and never at all on a host with no image library. A re-encode that came out larger is undone in full.
  • Draws a page that does not exist yet in this site’s own theme — header, navigation, styling and footer — from a title and some text, and hands the finished page back without storing any of it. Nothing is created: no post, no draft, no revision, no row of any kind. The page is assembled in memory for the length of one request, and the database is held read-only underneath it, so anything on the site that would ordinarily write while a page renders is refused rather than trusted.
  • On a page Marcelo created with its own phone number, shows that number wherever the site’s own header and footer print the business’s main one — on that page only. This is the one place the plugin rewrites a page’s finished HTML, and it is bounded accordingly: it opens only on a page carrying the post meta Marcelo sets, it replaces only that one number, in words and in tel: links, and it never touches a script, a style, a comment or any other address.
  • Exposes all of it over the REST API to users who can already manage_options.

What it does not do

  • It does not phone home. No connection is ever made from this plugin to any server, including ours. Everything travels the other way, over your site’s own authenticated REST API.
  • It contains no tracking, analytics or telemetry of its own.
  • It stores no markup at all. Three identifiers are kept — a verification token, a G- measurement id, a GTM- container id — and nothing else. Google’s scripts are then loaded through wp_enqueue_script, from a URL this plugin builds, so no text a caller sends can reach a visitor’s browser: there is no path by which any is stored.
  • The schema field is held to a far narrower rule than the rest: exactly one <script type="application/ld+json"> whose body parses as JSON, re-encoded from the parsed value rather than stored as received. A field named for structured data that accepted arbitrary script would be a larger plugin than this one.
  • A page description is text and nothing else. Every tag is stripped on the way in and the value is escaped again on the way out. One that is too long is refused rather than cut — a description truncated mid-sentence is worse than the one the page did not have.
  • A page title is held to the same rule, and for a sharper reason: it is printed inside a <title> element, where a surviving < would close the element early and put whatever followed onto the page. It is never written to the post itself — nothing here calls wp_update_post, and no page is renamed.
  • A title element of its own is printed only on a theme that does not declare title-tag support, because on every other theme WordPress prints one itself and a second would give the page two. Where a theme with no such support echoes its own <title> into header.php, nothing here can reach it and the page ends up with two; Marcelo reads the page afterwards, sees the two, and takes its own title off again.
  • An Elementor layout is checked before a byte of it is stored: it has to parse as a JSON list of elements, each naming its own type. One that does not is refused, and one that is too large is refused rather than stored in part — half a layout is a blank page, and a blank page is worse than everything this route exists to fix put together. A page with no Elementor layout on it is refused too, because storing one would switch the builder on and replace what visitors already see.
  • It never redirects an address the site can answer. A stored redirect fires only where WordPress was about to show a 404, so it cannot hide a page that exists — and if a page ever returns at that address, the redirect stops on its own.
  • It cannot send a visitor to another website. Destinations are paths on this site and go through wp_safe_redirect.
  • A canonical override cannot point anywhere but this site. The fault it exists to correct is a page handing its ranking to a different website, so a route that could store one would be able to cause the exact thing it repairs.
  • It does not rewrite your pages to remove a canonical it did not put there, and it will not. Removing a tag that another hook has already printed means buffering and find-and-replacing the whole page’s HTML, which is a far larger and more dangerous mechanism than anything else in this plugin. Instead it removes WordPress core’s own canonical for that request and asks Yoast, Rank Math, SEOPress, All in One SEO and The SEO Framework — through the filters each of them publishes for this purpose — not to print theirs, then prints one. Where the tag comes from a theme with no filter, none of that reaches it and the page ends up with two; Marcelo reads the page afterwards, sees the two, and takes its own override off again.
  • It stores no robots.txt and no line of one. The setting holds crawler names — “somebody decided GPTBot may read this site” — and the change is made to the text WordPress itself generates, as it generates it. There is no path by which text a caller sends reaches that file.
  • It never writes a file. The only file it will ever name is the robots.txt in the site’s own root, worked out from ABSPATH and never taken from a request, and it is named in order to be refused rather than written.
  • It never removes a robots.txt rule that names a crawler nobody asked about. A rule shared between two crawlers is left exactly as it is, and the caller is told which others share it.
  • It never overrides “Discourage search engines from indexing this site” in Settings Reading. That setting is what closes robots.txt to everybody on nearly every WordPress site, it also puts a noindex on every page, and it is a decision somebody made on purpose — on a staging site or a site not ready to launch, the right one.
  • The availability notice and the profile links edit nothing either, and follow every rule below that the call bar does. Neither is pinned: both sit in the flow of the document at the end of the page, so nothing is covered and no space is reserved.
  • The call bar edits nothing. It adds one element of its own at wp_footer, with no JavaScript at all and with every style rule namespaced under .marcelo-call-bar, so it cannot restyle a single element the site already had. It reserves its own height as padding on the body rather than sitting permanently on top of the page’s last line, and it hides itself when the page is printed. The phone number is checked against a strict pattern before it is stored and escaped again on the way out.

Do I need a Marcelo account?

To use it as intended, yes — the plugin is the site-side half of connecting a website to https://meetmarcelo.ai. The plugin itself is free and GPL, works on its own, and sends nothing anywhere.

External services

This plugin can place Google tags built from identifiers stored in its own settings — a Google Analytics measurement id, a Google Tag Manager container id, a Google Ads conversion id. Wherever one of those is stored, the plugin enqueues Google’s own tag script for it, and that script is fetched by the visitor’s own browser, directly from Google, every time a page loads:

  • https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX — Google Analytics, where a measurement id is stored.
  • https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXXX — Google Tag Manager, where a container id is stored.
  • https://www.googletagmanager.com/gtag/js?id=AW-XXXXXXXXX — Google Ads, where a conversion id is stored.

This is the ordinary way any site places these tags, by hand or through any other plugin — the plugin itself never contacts Google or sends it anything; it only writes the <script src> that causes the visitor’s browser to. A stored Search Console verification token loads nothing at all: it is only ever printed as a <meta> tag.

This service is provided by Google. Terms of service: https://policies.google.com/terms. Privacy policy: https://policies.google.com/privacy.

Орнотуу

  1. Install and activate the plugin.
  2. In Marcelo, connect this website. The tags are placed for you.

FAQ.KG

Does this send my data anywhere?

No. The plugin makes no outbound connections at all — not to Google, and not to Marcelo. Everything travels the other way: your site’s own administrator sends it what to place, over your site’s own login, and it places it.

What happens if I deactivate it?

The tags stop being printed, and the stored snippets are kept — so reactivating restores them without reconnecting. Deleting the plugin removes them.

Does the call bar change my theme?

No. Nothing in your theme is read, edited or replaced — the plugin adds one element of its own at the wp_footer hook, which is the same mechanism it uses for the Google tags. Turning it off removes that element and the page is exactly what it was before.

Does it edit my robots.txt file?

It cannot, and it will not try. If your site has a real robots.txt file in its root, your webserver serves that file and WordPress is never asked for one — so this plugin has no way in, and it says so, with the file’s full path, rather than reporting a change that would never have happened. Where there is no such file, WordPress generates robots.txt on every request and this plugin changes what it generates.

What happens to my redirects if I delete the plugin?

They go with it, and every address that was a 404 before is a 404 again. Deactivating keeps them, like the snippets.

Can it inject anything into my site?

No, and the reason is structural rather than a promise. The plugin does not store what it is sent. It looks for the three things it knows — a Google site-verification token, a G- Analytics measurement id, a GTM- container id — and writes the tag itself from a fixed template with that id in it. Text that is not one of those three is not stored, so there is nothing for it to print.

A caller must also already be able to manage_options, which is an account that could install a plugin doing far more. Both are true; the first is the one that would still hold if the second failed.

Сын-пикирлер

There are no reviews for this plugin.

Contributors & Developers

“Marcelo Site Connector” is open source software. The following people have contributed to this plugin.

Мүчөлөрү

Өзгөртүүлөр

1.15.13

  • Larger buttons on phones. Links Marcelo measured as too small for a thumb are drawn at least 44 pixels tall and wide on screens up to 820 pixels wide, through one stylesheet of the plugin’s own registered with wp_add_inline_style. Each link is named by its own address — a[href="…"] — and a link inside a line of text is drawn as an inline box so the minimum applies. Nothing in the theme is edited, no markup is touched, computers are not reached, and clearing the setting removes every rule.
  • Nothing that arrives becomes CSS. What is stored is a link address and one yes-or-no per link, and the address has to be made only of the characters a web address uses, so no quote, backslash, angle bracket, brace or space reaches the selector the plugin writes around it. Every declaration is a template in the plugin, and every stored address is checked again on the way out.
  • The route takes the same option lock the redirect and title routes take, reads the row back past the cache, and refuses rather than reporting a write the site did not take.

1.15.12

  • A page title Marcelo sets on a block theme — Twenty Twenty-Two and every default theme since — no longer arrives as a second <title> beside the theme’s own. A block theme prints its title tag without declaring title-tag support, so the plugin printed one too, read two back, and undid itself every time. The title now goes through the document-title filter alone on those themes, which is where the block theme reads it from.

1.15.11

  • An article Marcelo writes, which goes up as a post in the site’s blog, is previewed as a post: the private preview draws it in the theme’s single-post frame and the template the site’s own posts use, rather than as a page. The preview route takes postType and says what it drew.
  • A post can carry its own phone number across the site’s header and footer, and its own search settings — noindex, out of the sitemap, a title of its own — the way a page Marcelo writes already could. Both are set only by Marcelo’s create, on the post it creates, by somebody who may edit that post. The core, Rank Math and Yoast sitemaps leave such a post out as they leave out such a page.
  • The theme’s second copy of the title comes off a post Marcelo designed whose content carries its title as the H1, as it already did on a page. Posts Marcelo did not write are untouched.

1.15.10

  • A page whose own content already carries its title as its main heading shows the title once even after it is re-saved in a page builder. Elementor’s text widget drops the hidden marker 1.15.8 recognised Marcelo’s pages by, so a page re-saved there showed the theme’s copy of the title again. The theme’s copy is now taken off wherever the page’s own content carries an H1 with exactly the title’s words; a page whose content carries no such heading is untouched.

1.15.9

  • Updating the plugin from Marcelo installs the build WordPress.org publishes, even in the minutes after a release when WordPress’s own update index is still offering the one before it. The press, and Marcelo’s twice-daily update, used to install only what that index offered — so a site asked to update right after a release answered that it was already current, or moved one build short. Now the directory’s own answer for this one plugin is read as well, and where it publishes a newer build the update goes to that build. Only a build newer than the one installed, only this plugin’s own package, and only from downloads.wordpress.org; anything else leaves WordPress’s answer exactly as it was.

1.15.8

  • A page Marcelo designed shows its title once. Where the theme prints a page’s title as something other than its main heading — a small H2 above the content, say — Marcelo puts the title in the page’s own banner as the page’s one H1, and the theme’s copy is now taken off that page: the first heading with a title class whose words are the page’s title, and the empty title wrapper with it. Only on a page whose own content carries the Marcelo design marker and an H1; every other page on the site is untouched, and a failure inside answers with the page exactly as it was drawn.
  • The page preview takes the title off the same way, so the page a reviewer approves is the one that goes up.

1.15.7

  • A page Marcelo creates can carry its own search settings, when the person asked for them for that page: kept out of search results (noindex), left out of the sitemap, and a browser and search title of its own that differs from the headline on the page — a thank-you page is the usual case. The page carries them as post meta Marcelo sets when it creates the page. Where Rank Math or Yoast SEO runs on the site, the page’s own settings in that plugin are written when the page is created, so the box on the page says what the page does and a change made there is obeyed; where neither runs, WordPress’s own robots tag and document title are answered from the meta. Leaving a page out of the sitemap is asked of WordPress’s own sitemap, Rank Math’s and Yoast’s. Every other page on the site is untouched.

1.15.6

  • A page Marcelo creates can carry its own phone number — a campaign’s, say — in place of the business’s main one, and now the whole page shows it. The page’s own sections already did; the theme’s header and footer around it went on printing the main number. The page carries the swap as post meta Marcelo sets when it creates the page, and on that page alone, after WordPress knows which page it is drawing, the finished HTML has the main number — however it is written: brackets, dashes, dots, spaces, a +1 — replaced with the page’s, in the words and in the tel: link it dials. Scripts, styles, text areas, comments and every other address are left exactly as they were, a longer number that merely contains the main one is never touched, and every other page on the site is untouched. A failure inside answers with the page exactly as it was drawn.
  • The page preview takes the same swap, so the header and footer a reviewer approves are the ones that go up.

1.15.5

  • A page preview is drawn for a visitor, the way the page will really be seen. It used to be drawn for the administrator whose application password Marcelo connects with — but WordPress only accepts that password once the request is known to be an API request, which is after every plugin on the site has loaded, so the site’s plugins were loaded for a visitor and then asked to draw for an administrator. A plugin that loads its admin-only half for administrators at start-up, and calls into it later on the page, then failed on a function its own start-up had never loaded, and every preview on a site running one had to be drawn a second time without it, under a note saying the real page might look different. That was never the plugin’s fault. Drawn for a visitor, such a plugin draws normally, and nothing a visitor would never see — an admin toolbar, an edit or duplicate button — appears in a page a reviewer is asked to approve. The user is switched while nothing may be written to the database and switched back before the answer, and the plugin’s recovery for a plugin that is really broken is unchanged.

1.15.4

  • A page preview the site had to draw a second time — because one of its plugins failed the first time — now keeps the page builder’s own design. Elementor prints the CSS of a site’s header, footer and forms through a list of its own that it keeps for the whole request, and the discarded first attempt had spent it: the second attempt was told that CSS was already on a page nobody would see, and the preview came back with the header and footer in no design at all. That list is now put back before the second attempt, along with any inline CSS the first attempt attached, so nothing is printed twice either.
  • A page preview is drawn in the real file of the page template the site’s pages use. 1.15.2 answered the template through the post-meta filter, and WordPress never asks that filter about a post with no id — which is what a preview post is — so the preview went on being drawn in the theme’s default template, with the title printed above the page’s own headline. The template’s file is now found directly, from the theme or from the page builder that owns it, and the page’s body carries that template’s classes for the length of the draw.

1.15.3

  • An enquiry form for the pages Marcelo writes: the shortcode [marcelo_enquiry_form] draws a name, phone, email and message form, and what it receives is mailed from the site itself to the one address Marcelo stored — the business’s own, out of its confirmed details — with the visitor’s address as reply-to. With no address stored the shortcode draws nothing, so a form can never send to nobody. A nonce, a hidden field no person fills and a small per-address ceiling stand between it and a script; nothing of a third party’s runs on the page.
  • The plugin’s own preview draws that form so a reviewer sees the page as it will be, and runs no other shortcode.

1.15.2

  • A page drawn for preview now carries the page template most of the site’s published pages use, so it is drawn in the same frame the page will really be created in. It was drawn by the theme’s default page template, which prints the title above the content, while the page itself would be created in a page builder’s content-only template that prints none — so a page carrying its own headline showed the title twice in the preview and once on the site. The template is answered for the in-memory page through the metadata filter, for the length of the draw and no longer; nothing is stored. The answer names the slug it used.

1.15.1

  • The four routes that store a whole row — the call button, the profile links, the availability line and the guide at /llms.txt — now refuse a write the site did not take, rather than answering that it worked. Each read the row back afterwards, which is right, and each then reported that row as what the press had stored: on a first write that is honestly empty, and on an overwrite it is the previous row, so a caller was told their press landed when the site had refused it. Found by running the routes against a database holding every option write.
  • The refusal says whose side it happened on. A full or read-only database, a crashed table or a plugin holding option writes is the site rather than anything wrong with what was sent, so it is a 500 with a sentence that does not send somebody to rewrite a number, a list or a line that was never the problem.

1.15.0

  • A site can now serve an llms.txt — the plain-text guide the tools that answer questions about a business read, at /llms.txt. WordPress has no route that serves one and no filter to hook, so a site with no file there answers 404; this serves it from the site itself, only where nothing else claimed the request.
  • A real llms.txt file in the site root is refused by name, with its full path. The webserver serves that file directly and WordPress is never asked for it, so anything stored here would be a document no reader ever sees — reported as published and invisible, which is the worst outcome available.
  • Sent as text/plain with nosniff, bounded, and refused outright if it carries a control character rather than repaired: a guide with something taken out of the middle of it is a document nobody approved.

1.14.9

  • Page preview: the check on what a caller may ask to be left out was a pattern PHP could not compile, so it rejected nothing and printed a warning every time it ran. On a site that shows warnings, that warning arrives in front of the answer and the preview fails to read on a site where nothing is wrong.

1.14.8

  • Updating the plugin from Marcelo now asks WordPress.org rather than repeating what the site last heard. WordPress keeps the answer to “is there an update?” for up to twelve hours, and the request to refresh it respects that cache — so a site pressed minutes after a release answers “already on the newest published build” about a version that did not exist when it last looked. Seen on a live site an hour after 1.14.7 was published, with the directory and its own API both serving the new version.
  • The stored answer is cleared before asking, which is what the Check again button on the site’s own Updates screen does. One request to WordPress.org, on a press somebody made deliberately.

1.14.7

  • Page preview on a site with a broken plugin: the plugin that broke it is remembered, and the next preview takes it off before drawing rather than after. A site in that state used to draw every preview twice, and the second draw is where its own design went missing — a stylesheet is printed once per request, so the discarded first attempt had already claimed what it managed to print. 1.14.5 gave the second attempt the enqueue lists back and a real site still came back short: nine stylesheet links against forty-two on the live page, the theme’s own among the missing.
  • So the second draw stops being how that site draws. One attempt, from a request that has spent nothing, and the page cannot come up short on its own stylesheets however the recovery behaves. The page still says which plugin it was drawn without.
  • The memory is dated and lasts a week, because a plugin somebody has since fixed has to come back and nothing on the site says it was fixed. After that it is included again, and if it is still broken the ordinary recovery catches it and writes the date forward.

1.14.6

  • The plugin can update itself when Marcelo asks it to. WordPress can install a plugin from outside and has no way at all to update one, so until now every fix in a new build reached a site only when somebody downloaded a ZIP file and replaced it by hand — for a fix they did not ask for, on software they did not choose to install.
  • Only this plugin, and only from the WordPress.org directory: the route takes no plugin name, so it cannot be asked to install anything else. It asks for the update_plugins permission separately from the settings permission, and a site set not to modify its own files (DISALLOW_FILE_MODS) is told so in those words rather than shown a filesystem error — that is usually a managed host doing the updating for you.
  • The version it reports is read back off the file afterwards rather than claimed: the running request is still executing the old copy, so the only honest answer is what is on disk when it has finished. It also checks the plugin is still switched on, because an update that quietly left it off would take every Marcelo feature off this site at once.
  • This arrives in a version, so it cannot update the version that does not have it: this is the last update that has to be done by hand.

1.14.5

  • Page preview: a page that had to be drawn twice now comes back wearing this site’s own design. 1.14.4 added a second attempt when another plugin breaks the first one; a stylesheet is printed once per request, so the first attempt had already claimed every one of them before it fell over, and the second attempt printed none — the page came back as its own words with no theme, no header styling and no colour, on precisely the sites the second attempt exists to rescue.
  • The enqueue registers are read before the first attempt and put back before the second, so attempt two starts where attempt one did and this site’s theme is asked for its stylesheets again. Nothing else about the attempt changes, and a page drawn once — every site where nothing is broken — is untouched.

1.14.4

  • Page preview: one broken plugin no longer takes the whole preview with it. If drawing the page falls over inside another plugin on this site, the page is drawn once more with that plugin’s own code taken off every hook for the length of the attempt — and the answer says which plugin was left out, so nobody is shown a page that is quietly missing something without being told what.
  • This is what WordPress itself does when a plugin breaks a site: work out which one the fault came from and carry on without it. The same idea, for one render, and then it is put straight back — nothing on the site is switched off, and nothing about it is stored.
  • Bounded on purpose. It happens once and never in a loop; only for another plugin, never for this site’s theme, never for WordPress itself, and never for this plugin; and only where that plugin really had code running. If the second attempt fails too, the answer is the first failure’s own message, because that is the one that names the plugin that actually broke.
  • Everything the preview already promised is unchanged and covers both attempts: the database is held read-only for the whole of it, nothing is stored on any path, and a page that still cannot be drawn is reported as “could not draw the page” exactly as before.

1.14.3

  • Page preview: a failure now says where it happened, not only what it said. “Call to undefined function cdp_vars()” alone named a function and nothing about which theme or plugin called it. The same message now carries the file — the part of the path under this site’s own wp-content, nothing about the server above it — and the line number, the same information WordPress’s own fatal-error screen already shows to a signed-in administrator, which is the trust level a request reaching this route already has.
  • No change to what draws the preview or to what it may do. This is the 1.14.2 fix reporting itself more precisely, not a second attempt at it.

1.14.2

  • Page preview: a page is now drawn the way this site draws a page, rather than by loading the theme’s template on its own. WordPress runs two steps between working out what a request is and drawing it — wp and template_redirect — and a request coming in over the REST API never reaches either. A great many themes and plugins start their front-end half on those two steps, including loading the files that define the functions their own templates then call.
  • What that looked like: a site whose pages all work perfectly answering every preview with “This site could not draw the page: Call to undefined function …”, naming a function belonging to some other plugin on the site. Both steps now run before the template does, in the order WordPress runs them.
  • Everything the preview already promised is unchanged and covers the wider surface: the database is still held read-only for the whole render, so a plugin that writes while starting up is refused exactly as one that writes while a page draws; a plugin that falls over while starting up is reported as “could not draw the page” rather than breaking the request; and nothing is stored, on any path.
  • Two things that only make sense for a real visitor are declined for the length of the render. The admin bar is not drawn into a preview — the published page will not have one, and it would otherwise carry the account name of whoever is signed in. And a plugin asking to redirect during a preview is not sent anywhere: there is no visitor to send, only a request asking this site to draw a page.
  • No change to what the plugin can do. Every route, every feature name and every answer is the same as 1.14.0 and 1.14.1.

1.14.1

  • Subdirectory installs: a site living at example.com/blog sent visitors to example.com/blog/blog/the-new-page. Every path this plugin stores is measured from the domain root, and one function turned it back into an address by appending to the site root — so on a root install it was right and on a subdirectory install it doubled the folder. The self-referencing hreflang tag named an address the same way and had the same fault. Both build the address from the site’s own origin now.
  • Worse than a broken link, which is why this is a release of its own: the rule really was stored, so asking the site for its rules again returned exactly what was written, and the fix was recorded as applied. A visitor followed a redirect to a page that does not exist while the record said it landed.
  • Page preview: the read-only hold over the database is let go of after the render is completely finished rather than one step before, so nothing a theme does at the very end of drawing a page can write to a site that was only being looked at.
  • No change to what the plugin can do. Every route, every feature name and every answer is the same as 1.14.0 — so a site on 1.14.0 loses nothing by staying, and gains these two fixes by updating.

1.14.0

  • Page preview: draws a page that does not exist yet, in this site’s own theme, and hands the finished page back. It is the only route here whose answer is a document rather than a record, and the only one that stores nothing at all — the point being that somebody can see what a new page will look like on their own site before agreeing to have it added.
  • Nothing is created and nothing is changed. The page is a WP_Post built in memory for the length of one request, never wp_insert_post, so there is no post, no draft, no auto-draft, no revision and no row in any table. It has no id, because it has no row.
  • The database is held read-only while the page is drawn, at the one filter every write in WordPress passes through. A theme or plugin that counts a view, warms a cache or stamps a “last seen” while a page renders is refused rather than asked nicely, and the answer says how many writes were turned away.
  • The real template hierarchy and the real template_include filter, so what comes back is the page this site would have served rather than a generic frame with some text in it. A site whose theme cannot draw a page it has no row for is told so plainly, and the caller shows no preview at all — a half-drawn page presented as a preview is worse than none.
  • The title is sanitised as text and the content with wp_kses_post, the same rule WordPress core applies to post content from anybody without unfiltered_html, so the preview is drawn from the characters the page would really carry.
  • A statement is sent only when its first word is one that cannot change a row — SELECT, SHOW, DESCRIBE, EXPLAIN, SET and the transaction words. Anything else is turned away, rather than the other way round: the ways of writing to MySQL are not a list this plugin could finish, and the code running while a page draws belongs to somebody else.
  • A shortcode in the text is shown rather than run. Shortcode handlers are ordinary PHP and can send mail or call another service, and the read-only hold does not cover either of those — so a preview of text nobody has agreed to yet displays [the-shortcode] where the published page would display what it produces.
  • The hold is let go of last, after the page is drawn and after every global the render borrowed is put back, and there is a safety net on shutdown for the endings PHP cannot catch — a plugin calling exit, a memory limit, a time limit. A site left read-only by a preview that never finished would be a site where publishing silently does nothing, which is worse than a preview that failed.

1.13.0

  • hreflang self-reference: adds a translated page’s own link among its own alternates, where the set is missing one. A page that lists French and Spanish alternates but not itself breaks the whole set — search engines are told what the other language versions are but never told this page is one of them.
  • Additive, unlike the canonical route beside it: nothing already in the head is displaced. Whatever else on the page prints its other hreflang alternates — a theme, an SEO plugin — is left exactly as it is, and this adds one more tag naming the page itself.
  • Only a language code is stored, never a URL. The address in the tag is always built from the request being served, the same way the canonical route builds its value from the site’s own parts rather than storing what it was sent, so the tag stays correct if the domain ever changes.
  • The code is checked against a loose BCP 47 shape before it is stored — a language subtag, optionally a region or script — and lower-cased on the way in, because every reader that matters compares hreflang values case-insensitively.
  • Batched like descriptions: one request answers for every page in it separately, so one bad line does not throw the rest away, and the caller is told which page it was.

1.12.0

  • Availability notice: one short line at the bottom of every page saying the business answers around the clock. The third thing this plugin puts in front of a visitor, after the tap-to-call bar and the profile links, and built to the same rules: one element of its own, one namespaced class, no JavaScript at all, nothing animating, and nothing on the site read, moved or rewritten. Clearing the option removes it completely and the page is byte for byte what it was.
  • It is only ever sent for a business whose confirmed hours are seven days of 24 hours. That decision is made against a record a person filled in and cannot be made here — this plugin stores and prints words that arrived and refuses the ones that are the wrong shape. A business that closes at five is never sent anything by this route, because a firm advertised as always available on the strength of its opening hours is the worst thing this feature could do.
  • It is not the openingHours in the schema block, and the difference is the whole point. That is markup in the head of the page and no visitor will ever see a word of it; this is a line somebody reads. Answering “the page never says when you answer” with a block in the head would tick the finding without changing a word anybody reads.
  • wp_footer rather than wp_body_open, though the top of the page is where a line like this reads best. wp_body_open is a hook a theme may or may not call, and an element that silently fails to print is worse than one printed lower down: it would be recorded as published and be nowhere on the page.
  • Sixty characters at most, plain text only, refused rather than cut. It sits in the flow of the document so it covers nothing, inherits the site’s own colour rather than arriving as a band of somebody else’s brand, and reserves no thumb-sized height when the page is printed.
  • Removing it is one request, and it says what it took off.

1.11.0

  • Profile links: a short row of links to the accounts a business has confirmed are its own — Facebook, Instagram, Google and the rest — printed at the very bottom of every page. The second thing this plugin puts in front of a visitor, after the tap-to-call bar, and built to the same rules: one element of its own, one namespaced class, no JavaScript at all, nothing animating, and nothing on the site read, moved or rewritten. Clearing the option removes it completely and the page is byte for byte what it was.
  • It is not the sameAs property in the schema block, and the difference is the whole point. That is markup in the head of the page and no visitor will ever see a word of it; this is a link somebody can click. A …