Title: Marcelo Site Connector
Author: itay12222
Published: <strong>August 12, 2026</strong>
Last modified: August 12, 2026

---

Search plugins

![](https://s.w.org/plugins/geopattern-icon/marcelo-site-connector.svg)

# Marcelo Site Connector

 By [itay12222](https://profiles.wordpress.org/itay12222/)

[Download](https://downloads.wordpress.org/plugin/marcelo-site-connector.1.14.4.zip)

 * [Details](https://wordpress.org/plugins/marcelo-site-connector/#description)
 * [Reviews](https://wordpress.org/plugins/marcelo-site-connector/#reviews)
 *  [Installation](https://wordpress.org/plugins/marcelo-site-connector/#installation)
 * [Development](https://wordpress.org/plugins/marcelo-site-connector/#developers)

 [Support](https://wordpress.org/support/plugin/marcelo-site-connector/)

## Description

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.
 * 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.
 * 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.

## Installation

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

## FAQ

### 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.

## Reviews

There are no reviews for this plugin.

## Contributors & Developers

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

Contributors

 *   [ itay12222 ](https://profiles.wordpress.org/itay12222/)

[Translate “Marcelo Site Connector” into your language.](https://translate.wordpress.org/projects/wp-plugins/marcelo-site-connector)

### Interested in development?

[Browse the code](https://plugins.trac.wordpress.org/browser/marcelo-site-connector/),
check out the [SVN repository](https://plugins.svn.wordpress.org/marcelo-site-connector/),
or subscribe to the [development log](https://plugins.trac.wordpress.org/log/marcelo-site-connector/)
by [RSS](https://plugins.trac.wordpress.org/log/marcelo-site-connector/?limit=100&mode=stop_on_copy&format=rss).

## Changelog

#### 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 site can have one and 
   not the other, and answering “your website links to none of your profiles” with
   a block in the head would tick the finding without putting a link anywhere anybody
   could see.
 * No address is ever composed here. Each one is checked for an `http` or `https`
   scheme and a real host and is stored as it arrived; nothing goes looking for 
   a business’s Facebook page, guesses one from a name, or turns a host into an 
   address. One refused address refuses the whole request, because a site linking
   to three of somebody’s four profiles with nothing saying which one was dropped
   is worse than a site linking to none.
 * The words a link reads come from its own host — “facebook.com” reads Facebook—
   and an address on a host nobody recognises is labelled by its bare host rather
   than by a name invented for it.
 * Every link is at least 44 pixels tall, which is Apple’s minimum and is the size
   a row of small footer links usually is not. The row inherits the site’s own colour
   rather than arriving as a stripe of somebody else’s brand, sits in the flow of
   the document so it covers nothing, and reserves no space when the page is printed.
 * Removing them is one request, and it says what it took off.

#### 1.10.0

 * Oversized images: a picture in this site’s own media library can be saved again
   at a smaller size. This is the first thing this plugin does that overwrites a**
   file** rather than a value in the database, and everything below follows from
   that one difference.
 * The original is copied aside before a single byte is written, and a copy that
   fails refuses the whole request. A re-encoded JPEG is the only copy of itself—
   there is no arithmetic that turns one back into the original — so a compression
   with no way back is not one this makes.
 * Only ever an attachment in this site’s own media library, proved by WordPress’s
   own lookup rather than guessed from the address. An image served from a CDN, 
   a stock photo host or another website is refused by name: this plugin will not
   rewrite a file it cannot show belongs to the site it is installed on. An address
   naming a generated size — `photo-1024x768.jpg` — is resolved back to the attachment
   it is a size of, and the original is what gets resaved.
 * Only ever a file inside this site’s uploads directory, checked against the resolved
   real path. That check is the last thing standing between a REST parameter and
   a write to an arbitrary file on the server.
 * The file has to still weigh what the caller says it weighed. A picture somebody
   has replaced since the measurement is a picture nobody asked about, and it is
   refused with both numbers rather than resaved.
 * JPEG, PNG and WebP only. A GIF may be animated and every image library WordPress
   ships flattens one to a single frame — a “compression” that destroys the thing
   the image was for — and an SVG is text with no raster editor at all. Both refused
   by mime type, with the reason.
 * The server is asked whether it can do this at all, through `wp_image_editor_supports()`.
   WordPress resizes with whichever of Imagick or GD the host has and a host can
   have neither, or a build of GD without JPEG support — which answers yes to “is
   GD loaded” and no to the question that matters. Reported as a hosting fact, because
   that is where it is fixed.
 * The sized copies `srcset` points at are rebuilt from the new file. Those are 
   separate files generated at upload time, and a compression that fixed only the
   original would leave every copy a visitor is actually served exactly as heavy
   as it was — the page no faster, and the record saying it was fixed.
 * A re-encode that came out larger is undone in full and refused with both numbers.
   That happens often on an already-optimised image, and making a customer’s picture
   heavier while reporting a fix is the worst outcome this route has available.
 * Putting the original back is one request, and it refuses rather than answering“
   nothing to do” when there is no saved original beside the file. Every other DELETE
   here removes something of the plugin’s, where “it was not there” is the state
   the caller asked for; this one is asked to restore a file, and a 200 saying it
   did not would read as done.

#### 1.9.0

 * The Google Ads conversion tag: installed from the client’s own connected Google
   Ads account, never typed in or guessed. Stored under its own key so writing it
   can never overwrite the Search Console, Analytics or Tag Manager tag stored beside
   it, and validated as an `AW-` id before it is accepted — a Search Console token
   or a bare customer id is refused rather than fired on every page as a tag belonging
   to nobody.

#### 1.8.0

 * Page titles: the line a search engine shows as the name of a page, and the words
   in a browser tab. Stored beside the address it belongs to and handed to WordPress
   through `pre_get_document_title`, registered last so it is the final opinion 
   asked for. That filter is core’s own way for a plugin to own the title and is
   the one Yoast, Rank Math, SEOPress, All in One SEO and The SEO Framework each
   use, so one hook wins against all of them without this plugin knowing anything
   about any of them.
 * It is not the post’s title, and it is never written as one. `post_title` is the
   name of the page in the admin, in the permalink and — on most themes — the heading
   a visitor reads at the top of the page. Every SEO plugin overrides the rendered`
   <title>` independently of it, so writing `post_title` to change a search result
   would change what a visitor reads and change nothing in Google. Nothing here 
   calls `wp_update_post`.
 * The second thing this plugin does that displaces markup the site was already 
   printing, and bounded the same way as the canonical override. A theme that does
   not declare `title-tag` support never calls `wp_get_document_title()`, so the
   filter never fires there; where such a theme prints no title at all — which is
   the finding this most often answers — a title element of its own is printed instead,
   and where it prints one the page ends up with two. Marcelo reads every page back
   after writing and removes its own title from any page that does not come back
   carrying exactly one. Undoing is a normal step here rather than an error path.
 * Text only, refused rather than cut. Every tag is stripped on the way in and the
   value is escaped again on the way out — which matters more here than for a description,
   because the value goes inside a `<title>` element where a surviving `<` closes
   it early.
 * Written in batches, answered per page, so one bad line does not throw away the
   rest and the caller is told which one.
 * Removing one is clearing it. The site goes back to assembling the title exactly
   as it did before, with nothing of the plugin’s left on any page.

#### 1.7.0

 * robots.txt: a named crawler that robots.txt was turning away can be let back 
   in, and a site can be asked which of the two kinds of robots.txt it has — the
   one WordPress generates, or a file in its root. Those two are identical over 
   HTTP and completely different underneath: WordPress’s `robots_txt` filter runs
   for the first and is never called for the second, so a plugin hooking it changes
   the first and does nothing at all to the second.
 * A real file in the root is refused rather than attempted, named by its full path.
   Writing to a file outside WordPress’s own APIs is a far larger surface than this
   plugin has any business opening, and a change reported but not made is worse 
   than no change at all.
 * Nothing is stored in robots.txt. The setting holds crawler names, and the change
   is made to the text WordPress generates, as it generates it — so removing the
   setting puts the file back to exactly what it said, with nothing of the plugin’s
   left in it.
 * A rule that names other crawlers is left alone. One `Disallow: /` under two `
   User-agent` lines is one rule about both of them, and taking it out for one would
   let the other in as a side effect nobody asked for.
 * “Discourage search engines from indexing this site” is never overridden. It is
   what closes robots.txt to everybody on nearly every WordPress site, it also puts
   a `noindex` on every page, and it is a deliberate decision — refused with the
   setting named, so it can be changed in WordPress by whoever made it.
 * The result is read back before it is stored. The file that would be produced 
   is built and parsed the way a crawler parses it, and a crawler still turned away
   after the change is reported as refused rather than recorded as let in.
 * Canonicals: a page whose `canonical` link names another website can be pointed
   back at itself. This is the first thing this plugin does that displaces markup
   the site was already printing, and it is deliberately only half a mechanism —
   it removes WordPress core’s canonical for that request and asks each of the five
   common SEO plugins, through the filter each publishes for exactly this, not to
   print theirs. A canonical a theme echoes directly into `header.php` is reachable
   by nothing, so the page then carries two; Marcelo reads every page back after
   writing, and removes its own override from any page that does not come back carrying
   exactly one. Undoing is a normal step here rather than an error path.
 * A canonical override can only ever name an address on this site. The fault being
   corrected is a page handing its ranking away, so storing an off-site canonical
   is refused — a route that could do it would be able to cause the exact thing 
   it exists to repair.
 * The schema block is finally printed. From 1.2.0 the block was accepted, validated,
   re-encoded, stored — and never output, so a site could hold one, be told it was
   live for search engines, and have nothing on any page. That is the failure this
   plugin is built against, in the plugin itself, and it is the reason this line
   is here rather than in a quiet commit.
 * Structured data is now encoded with `JSON_HEX_TAG`. A JSON string containing 
   an escaped closing script tag survives the check that refuses a literal one, 
   and re-encoding without this flag turns it back into a literal — which was harmless
   only for as long as the block was never printed.

#### 1.6.0

 * A tap-to-call bar. One fixed element carrying the business’s confirmed phone 
   number as a `tel:` link, printed on every page at `wp_footer`, so somebody who
   lands on a page can call without scrolling or hunting for a number.
 * Nothing on the page is read, moved or rewritten, and no theme file is touched.
   The element is the plugin’s own, wrapped in comments naming it, and clearing 
   the setting removes it with nothing left behind.
 * No JavaScript of any kind, and nothing that animates. The styles go through `
   wp_add_inline_style` on a registered handle, and every selector is namespaced
   under `.marcelo-call-bar` so it cannot restyle anything the site already had.
 * It reserves its own height as padding on the body rather than covering the last
   line of the page, keeps clear of the iPhone home indicator and of the WordPress
   admin bar, and hides itself when a page is printed.
 * Phone screens only unless asked otherwise, because that is where the number being
   far down the page actually costs an enquiry.
 * The number is checked against a strict pattern — digits, spaces, brackets, dots,
   hyphens, one optional leading plus, 7 to 15 digits — before it is stored, and
   escaped again on the way out. A number that fails is refused with a reason rather
   than stored empty.

#### 1.5.0

 * Elementor layouts can be read and written. A page built with Elementor keeps 
   its whole content — sections, columns, widgets and every setting — in a protected
   postmeta field, and prints that rather than the text WordPress stores. Until 
   now a write to such a page succeeded and changed nothing anybody could see, which
   is the one kind of failure this plugin exists to make impossible.
 * Nothing composes a layout here. What arrives is checked and stored, and the stored
   value is handed straight back so the caller can verify what is on the page rather
   than assume it.
 * Refused rather than repaired, and refused rather than truncated. A layout that
   does not parse as a JSON list of elements is not stored at all, because a half-
   written layout does not make a page look wrong — it makes it blank.
 * A page that has no Elementor layout is refused. Storing one would switch the 
   builder on for that page and replace what visitors see, as a side effect of a
   text edit nobody connected to it.
 * The generated CSS for a changed page is cleared, so new content arrives styled
   rather than as correct text in a page that looks broken.

#### 1.4.0

 * Nothing but identifiers is stored. Three values are kept — a Search Console verification
   token, a `G-` measurement id and a `GTM-` container id — and no markup of any
   kind. A full tag can still be sent, and only the identifier inside it is taken.
 * Google’s scripts are loaded through `wp_enqueue_script` instead of being written
   into the head. That means they can be deregistered and deferred like any other
   script, and are not loaded a second time when another plugin enqueues the same
   file — a site with Analytics installed twice counts every visit twice, and nobody
   finds that out by looking at the page. The Analytics configuration is attached
   to its own handle, so it can no longer run before the file it configures.
 * Together these replace a rule that refused a list of dangerous strings and accepted
   anything beginning with an approved tag. That is a deny list guarding a hole 
   the size of `script`: any JavaScript passed, and so did `onclick`, which simply
   was not on the list. A deny list is only ever as good as the imagination of whoever
   wrote it.
 * An Analytics snippet that loads one property and configures another is refused
   rather than repaired — it is the shape of a copy-paste error, and a site reporting
   to a property nobody reads looks instrumented and is not.
 * Upgrading needs nothing. A value stored by an earlier version is a full tag, 
   and the identifier is read out of it the first time it is used.

#### 1.3.0

 * Page descriptions: the sentence a search engine shows under a page, stored beside
   the address it belongs to and printed for that page only. WordPress keeps no 
   description of its own and every SEO plugin uses its own hidden field, so writing
   one of those keys on a site running a different plugin stores a value nothing
   reads — it succeeds, changes nothing, and reports success.
 * Text only. Every tag is stripped on the way in and the value is escaped again
   on the way out, because a description is prose by nature and there is no markup
   it could legitimately need.
 * Refused rather than cut when it is too long. A description truncated mid-sentence
   by a plugin nobody asked is worse than the one the page did not have.
 * Written in batches, answered per page, so one bad line does not throw away the
   rest and the caller is told which one.
 * Removing one is clearing it. Nothing is left on the page.

#### 1.2.0

 * Business schema markup: a LocalBusiness block describing the business, printed
   into the head of every page. Held to its own rule rather than the one the tags
   above use — exactly one `script` of type `application/ld+json`, whose contents
   must parse 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 needs to be.
 * Removing it is clearing the field. Nothing is left on any page.

#### 1.1.0

 * Redirects for addresses that no longer exist, stored and served by the plugin.
   Only ever applied where the site would otherwise show a 404, and only ever to
   a path on the same site.
 * The status route now names what this build can do, so Marcelo does not have to
   infer it from a version number.

#### 1.0.0

 * First release.

## Meta

 *  Version **1.14.4**
 *  Last updated **16 hours ago**
 *  Active installations **Fewer than 10**
 *  WordPress version ** 6.0 or higher **
 *  Tested up to **7.0.4**
 *  PHP version ** 7.4 or higher **
 * Tags
 * [analytics](https://wordpress.org/plugins/tags/analytics/)[canonical](https://wordpress.org/plugins/tags/canonical/)
   [meta description](https://wordpress.org/plugins/tags/meta-description/)[redirects](https://wordpress.org/plugins/tags/redirects/)
   [Search Console](https://wordpress.org/plugins/tags/search-console/)
 *  [Advanced View](https://wordpress.org/plugins/marcelo-site-connector/advanced/)

## Ratings

No reviews have been submitted yet.

[Your review](https://wordpress.org/support/plugin/marcelo-site-connector/reviews/#new-post)

[See all reviews](https://wordpress.org/support/plugin/marcelo-site-connector/reviews/)

## Contributors

 *   [ itay12222 ](https://profiles.wordpress.org/itay12222/)

## Support

Got something to say? Need help?

 [View support forum](https://wordpress.org/support/plugin/marcelo-site-connector/)