=== WPGraft – Copy any web page into your page builder ===
Contributors: sasagugunovic
Tags: page builder, import, clone, copy, block editor
Requires at least: 6.0
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.2.5
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Copy any web page into Elementor, Bricks, Divi, Oxygen, Breakdance, Beaver Builder or Gutenberg as native, editable builder elements.

== Description ==

wpgraft copies a section of any public web page and rebuilds it inside your own
page builder — Elementor, the block editor (Gutenberg), Divi, Bricks, Beaver
Builder, Breakdance or Oxygen — not as an embedded screenshot, not as a block of
pasted HTML, but as the builder's own elements, with the styles written into the
builder's own fields. Once it lands you edit it the way you edit anything else
you built by hand.

This plugin is the WordPress half of that. The copying happens in the wpgraft
browser extension; this plugin receives the result, shows it in a panel inside
the editor, inserts it into the page you have open, and pulls the images it needs into
your Media Library.

**Supported editors**

* Block editor (WordPress core)
* Elementor — both the classic (v3) and the atomic (v4) document models
* Bricks
* Divi 5
* Beaver Builder
* Breakdance
* Oxygen 6 and Oxygen Classic

**What the plugin itself does**

* Adds a wpgraft panel inside the editor listing sections waiting to be inserted.
* Inserts a section into the open document using each builder's own data
  structures, so the result is editable natively — no shortcode, no iframe, no
  "do not edit this block" wrapper.
* Imports the images a section needs into the Media Library and rewrites the
  references to point at your own copies, so the layout does not break when the
  source site changes.
* Loads a copied page's typeface from Google Fonts by name (or the closest Google
  font when the original is not there) instead of copying the source site's font
  files. The exceptions are fonts the copy itself embeds: icon fonts, and the
  embedded faces of a block-editor copy, which are stored in your uploads.
* Registers REST routes under `wpgraft/v1`, each one gated on both a capability
  check and the `wp_rest` nonce.

**What it will never insert**

The plugin has no code editor, no CSS box and no field where anyone types code.
What arrives is a page description the conversion service produced; before any
of it is stored the plugin removes every `script` element, event-handler
attribute and PHP open tag, and empties every JavaScript field a builder element
may have. Every stylesheet in it is parsed and rebuilt from an allowlist: a
declaration is kept only when its property is a standard CSS property and its
value is made of standard CSS values and functions, a selector only when it is
made of selector syntax, an at-rule only when it is one of a short list
(`@media`, `@supports`, `@font-face`, `@keyframes`, …), and `url()` only for
http(s), image and font addresses. Everything else is dropped and reported, and
no markup character survives in a stored stylesheet. That happens for every
user, administrators included, because the content was copied off somebody
else's page. On top of that, page-level CSS is only stored
for users who hold `edit_css` or `unfiltered_html` — the capability WordPress
itself requires for Additional CSS — and raw-HTML builder elements are only
kept for users who hold `unfiltered_html`; everyone else gets the section with
those parts removed and a warning saying so. Styles are written into each
builder's own fields, so you edit them where you edit everything else.

**What it does not do**

No account to connect here, no licence key, no options page to fill in — the
settings screen is a status readout and nothing else. Nothing in this plugin is
metered, counted, time-limited or held back for a paid plan: there is no licence
check anywhere in the code, no usage counter and no upgrade prompt. It inserts
whatever the extension hands it, on any site, for as long as you keep it
installed. It reads no visitor data and writes nothing to your pages that you
did not insert yourself. What it stores: the queue of sections waiting to be
inserted (per user, removed on insert or discard), the images and embedded
fonts a copy brings (Media Library and uploads), and the styling of inserted
copies in post meta and the builder's own settings.

**Requires a paid wpgraft plan (an external service)**

This plugin is free software and always will be, but on its own it does nothing:
sections reach it from the wpgraft browser extension, and the extension converts
a captured page through the wpgraft conversion service. That service is paid:
copying requires a wpgraft account with a paid plan — Pro monthly, Pro yearly or
Lifetime. There is no free tier and no trial. Plans and prices are at
https://wpgraft.com/#pricing. The plan is checked there — in the browser
extension and on our server, before anything reaches WordPress — never by this
plugin. See the section below for exactly what is sent where.

**Not affiliated with** Elementor, Bricks, Elegant Themes (Divi), Beaver
Builder, Soflyy (Oxygen, Breakdance) or any other builder vendor. Those names
appear here to describe compatibility, and they belong to their owners.

**Bundled resources.** The plugin ships the Cooper Hewitt typeface
(`assets/fonts/`), used only for the wordmark on its own settings screen, under
the SIL Open Font License 1.1 — the full text is in
`assets/fonts/LICENSE-OFL.txt`. Everything else in the plugin is GPLv2 or later.

== External services ==

This plugin is a companion to the wpgraft browser extension and the wpgraft
conversion service, operated by wpgraft (https://wpgraft.com).

**1. The wpgraft conversion service — used by the extension, not by this plugin.**
When you copy a section, the browser extension sends the captured HTML and CSS
of the page you chose to `https://wpgraft.com/api/convert`, together with your
account token, and receives the builder document that this plugin then inserts.
That request is made by the extension in your browser. This plugin does not make
it and does not hold your account credentials. Sections picked in the wpgraft
section Library (https://wpgraft.com/library) arrive the same way, through the
extension. The service is paid: it converts only for accounts with a paid
wpgraft plan (Pro monthly, Pro yearly or Lifetime); there is no free tier.

* Plans and prices: https://wpgraft.com/#pricing

* Terms: https://wpgraft.com/terms
* Privacy policy: https://wpgraft.com/privacy

**2. Downloading the assets a copied section uses.** When a section references
images, your WordPress site downloads them from the URLs the
capture recorded — usually the site you copied from — and stores them in your
Media Library. That traffic goes from your server to that site directly; it
does not pass through wpgraft. Only users who can already upload files can
trigger it. A file is accepted by its own bytes, not its name: raster images,
web fonts, and SVG — every SVG is rebuilt by a sanitizer that removes scripts,
event handlers and external references before it is stored. An image that is
not imported (the import was switched off, or it failed) keeps its original
address, so your visitors' browsers load it from the site it was copied from.

**3. The quality rating — only if you fill it in and press the button.** After a
copy is inserted, the panel asks how it turned out: a score from 1 to 5, an
optional comment, and a tick-box that decides whether the copy itself is sent
back to wpgraft so the fault can be reproduced and fixed. Pressing the button
sends the score, the comment, the tick, your site's HOSTNAME and this plugin's
version to `https://wpgraft.com/api/feedback`, from your browser, using a
single-use ticket that came with the copy. No account credential of yours and no
WordPress credential is involved, and the request carries no cookie. The
tick-box is ticked by default and the button says what will be sent ("Send
rating & copy" or "Send rating only"). The reply is a short thank-you message,
and for a 5 it may carry a link to leave a review.

Nothing is sent unless you press that button. Dismissing the card sends nothing.
Unticking the box sends the score and the comment and no page content at all.
The HTML and CSS of the copy do not come from your site — this plugin never had
them — they are uploaded separately by the browser extension, from the account
that made the copy, and only for ratings you consented to.

* Terms: https://wpgraft.com/terms
* Privacy policy, section 5.10: https://wpgraft.com/privacy#what-we-collect

**4. Google Fonts, for the typeface a copied page uses.** When a copy names a
font, the page it was inserted into links a stylesheet from
`https://fonts.googleapis.com`, so your visitors' browsers load that font from
Google. Only Google Fonts URLs are accepted. A site that wants no third-party
requests can drop them with the `wpgraft_capture_font_links` filter.

* Google Fonts terms: https://developers.google.com/fonts/terms
* Google Fonts privacy: https://developers.google.com/fonts/faq/privacy
* Google privacy policy: https://policies.google.com/privacy

**This build of the plugin's PHP contacts wpgraft.com for nothing at all** —
updates come from WordPress.org like any other plugin here, and your server
makes no request to us. The one request above is made by your browser, from the
editor screen, when you press the button on the rating card.

== Installation ==

1. Install and activate this plugin.
2. Install the wpgraft browser extension for Chrome, and sign in.
3. Open the page you want to copy from, pick a section with the extension, and
   choose the site and builder to send it to.
4. Open the target page in your builder. The wpgraft panel lists what is
   waiting; insert it, and edit it like anything else.

The plugin needs no configuration. Settings → wpgraft shows which builder it
detected, whether the in-editor runtime loaded, and the REST base the extension
talks to — useful when something does not arrive.

== Screenshots ==

1. The copied section inside your builder's own editor — a real section with
   real fields, editable like anything you built by hand.
2. How a section gets from their page to yours: point at it, wpgraft rebuilds
   it into your builder's format, this plugin drops it into your open editor.
3. Picking the section on the source page with the browser extension.

== Frequently Asked Questions ==

= Which page builders does it work with? =

Elementor (both the classic v3 and the atomic v4 document models), Bricks,
Divi 5, Beaver Builder, Breakdance, Oxygen 6, Oxygen Classic, and the
WordPress block editor. Each one gets a document written in its own native
format, so a copied section arrives as Elementor widgets, Bricks elements,
Divi modules, Beaver Builder modules or core blocks — whichever builder you
opened.

= Do I need the browser extension? =

Yes. The plugin is the receiving end; nothing is copied without the extension.

= Is a copied section really editable? =

That is the point of it. Each builder gets its own document written in its own
format, with the styles in the builder's own style fields, so you edit, restyle
and delete the elements normally. Where a builder has no field for something the
source page did, the plugin says so rather than faking it with a raw HTML block.

= Does it work without a paid plan? =

No. The plugin itself is free and has no licence check, but every section it
receives is converted by the wpgraft conversion service, and that service needs
a paid wpgraft plan — Pro monthly, Pro yearly or Lifetime. There is no free tier
and no trial. Without a plan the extension copies nothing, so nothing reaches
this plugin. Plans and prices: https://wpgraft.com/#pricing

= Does the plugin phone home? =

No. Your server never contacts wpgraft.com, and updates come from WordPress.org.
The only request to wpgraft.com is the rating card's, made by your browser when
you press its Send button (see External services, 3).

= Which user can use it? =

Inserting requires `edit_post` on the post being edited; importing assets
requires `upload_files`. Anything that writes raw HTML or CSS additionally
requires `unfiltered_html` / `edit_css`, so a contributor cannot smuggle markup
in through a copied section.

= Can a copied page inject JavaScript or PHP into my site? =

No. The plugin never stores or prints JavaScript or PHP for any user. Script
elements, event-handler attributes and PHP open tags are removed from every
copy before it is stored, the JavaScript field of every builder code element
is emptied, and every stylesheet is rebuilt from an allowlist of standard CSS.
Nothing the plugin hands a builder is something that builder executes. Links
that point at executable code (`javascript:` URLs) are disabled. Each of those
removals is reported in the editor panel so you know what was left out.

= Oxygen 6 shows an error or takes minutes when I save a copied page =

Oxygen 6 saves the page itself, in its own request; this plugin is not part of
that request. Oxygen's selectors are site-wide, and whenever a save changes
them (a pasted copy adds its own), Oxygen rebuilds the stylesheet of every
selector on the whole site. The time that takes grows much faster than the
number of selectors: a site with a few thousand saves in seconds, one with tens
of thousands can take minutes. So a site that has received many copies saves
more and more slowly, whatever the size of the copy being saved. When the
rebuild runs past PHP's maximum execution time (often 30 to 120 seconds on
shared hosting) the save fails.

* Raise PHP's `max_execution_time` for the site to 300 seconds (your hosting
  panel, `php.ini` or `.user.ini`, or ask your host).
* Deleting a copied page does not delete its selectors: they stay in Oxygen's
  "wpgraft" selector collection and keep counting toward every save. Other
  pages may use the same selectors, so delete one only when you know that no
  page on the site uses it.

= Will it copy a whole page? =

Yes. Whole-page copies and section copies arrive through the same panel and are
inserted by the same code — this plugin treats them identically and puts no
limit on either. Copying either kind needs a paid wpgraft plan; the plan is
checked by the remote conversion service, not by this plugin.

== Changelog ==

= 1.2.5 =
* WordPress admin files (file, media, image) are loaded only immediately before
  the one function that needs them.
* Fonts and the block-editor stylesheet find the post being edited through
  WordPress (or the open builder) and only for a user who may edit it, instead
  of reading the post ID from the request.
* Breakdance and Oxygen 6 global selectors are written through the builder's own
  option API.
* Removed an unused Divi 5 font-registry writer.

= 1.2.4 =
* Every stylesheet a copy carries is now parsed and rebuilt from an allowlist of
  standard CSS properties, values, functions, selectors and at-rules, instead of
  being screened for known-bad patterns. Over 54,000 real copied stylesheets the
  result is identical; anything that is not standard CSS is dropped and
  reported. The Beaver Builder per-module CSS, Elementor page and per-variant
  CSS and Divi 5 free-form CSS fields are covered too.
* The editor runtime is loaded as a script file (assets/js/wpgraft-editor.js)
  instead of being printed inline, and the admin-ajax address comes from
  WordPress rather than being assumed.
* Bricks: only inline SVG drawings are signed, after the SVG sanitizer rebuilt
  them; a Bricks Code element is never signed.
* Elementor: importing a copy's images into the Media Library requires the
  `upload_files` capability, like every other import path.

= 1.2.3 =
* One-click insert from the wpgraft section Library: a section you pick on
  wpgraft.com/library goes straight into the builder you have open, the same way
  the panel's own Insert button does. Older plugins still receive it in the panel.
* A builder that reloads to show a paste (Divi 5) no longer leaves the same copy
  in the panel a second time.

= 1.2.2 =
* Rating card: closing the panel with the × or the wpgraft button now closes an
  unanswered rating card too. Before, the next copy opened on the previous
  copy's rating card, with its own Insert button hidden underneath.
* The rating card now appears after every insert. The "Don't ask again" link,
  its row on Settings -> wpgraft and the /wpgraft/v1/prefs REST route are gone.
  Closing the card still sends nothing.
* After you rate a copy, the card answers with one short headline and one clear
  next step: a review link for a 5, a support ticket link when the copy was not
  sent, and what happens next for a low score.
* The readme no longer says web fonts are imported: no font files are installed.
  A copied page loads its typeface from Google Fonts by name (listed under
  External services).

= 1.2.1 =
* Elementor 4.3: copies insert again. Elementor 4.3 changed how heading,
  paragraph and button text is stored, and refused every copy with
  "Settings validation failed. title: invalid_value". wpgraft now writes the
  text in whichever form the site's Elementor expects, so 4.2 and 4.3 both work,
  and adding to a page that already holds an earlier copy works too.

= 1.2.0 =
* wpgraft is now a paid service (Pro monthly, Pro yearly or Lifetime); the
  plugin itself is unchanged in that respect and checks nothing.
* Ready for icon fonts that travel with a copy (used once the conversion
  service sends them): icons still drawn by a font, rather than inline SVG, can get
  that one icon face added to the page. New REST route: /wpgraft/v1/icon-faces
  (same permission as /save; only `@font-face` blocks with embedded `data:`
  fonts are accepted).
* Elementor: large copies open in the V4 editor instead of stalling; inserting
  regenerates only the CSS of the page you inserted into, not the whole site's;
  Elementor 3 copies keep their hover styles and icon-list icons; YouTube
  embeds work in Elementor Free.
* Elementor: two copies inserted at the same moment on a full global-class kit
  no longer leave one page unstyled.
* Image import on slow hosts no longer stops at PHP's 120-second limit.
* Very deep Breakdance copies (more than 64 levels) are accepted.

= 1.1.0 =
* The panel asks how each copy turned out once it is inserted: a score from 1
  to 5, a comment, and a tick-box that sends that one copy back to wpgraft so
  the fault can be reproduced. Nothing is sent unless you press the button, and
  no page content is sent while the box is unticked. "Don't ask again" turns
  the card off for your user; Settings -> wpgraft turns it back on.
* Every queued copy shows its size against what this site can store and render
  (the PHP memory limit, the MySQL packet size, the builder's own ceiling)
  before you insert it. A copy that will probably fail is labelled "Insert
  anyway" rather than blocked. New read-only REST route: /wpgraft/v1/limits.
* A copy the database refused to store is now reported instead of acknowledged
  and lost.
* Direct-download build only (not this one): the "Enable auto-updates" link
  appears for the plugin.
* Elementor: inserting on a site with hundreds of Elementor documents and a
  full global-class kit no longer ends in a critical error.
* Oxygen Classic: large copies are inserted in one pass; the editor no longer
  crashes at a few thousand elements.
* The panel talks to the page's own origin, so sites behind a TLS-terminating
  proxy, or opened on www when WordPress is configured without it, no longer
  get 401/403; plain permalinks no longer 404 the queue.
* Images the site already has are reused rather than fetched again.

= 1.0.2 =
* Direct-download build only (not this one): a fix to its update check. Sites
  installed from WordPress.org are updated by WordPress.org and were never
  affected.

= 1.0.1 =
* Every copy is now scrubbed of script elements, event-handler attributes, PHP
  open tags and JavaScript fields before it is stored, for every user, and
  every stylesheet is validated statement by statement before it is saved.
* A copy whose only root element was a code block no longer bypasses the
  `unfiltered_html` gate on Breakdance and Oxygen.
* Oxygen 6's HTML Code and CSS Code elements are now covered by the same gate
  as the Breakdance Code Block.
* Stylesheets the plugin prints on the front end go through the WordPress
  style API instead of a hand-written `style` element; the settings screen's
  styling moved to a stylesheet file.

= 1.0.0 =
* First stable release. All ten builder targets supported: Gutenberg, Elementor
  (v3 and v4), Bricks, Breakdance, Divi 5, Beaver Builder, Oxygen 6 and
  Oxygen Classic.

= 0.1.2 =
* Beaver Builder and Divi 5 support.
* Elementor: both the classic (v3) and atomic (v4) document models are detected
  and written natively.
* Assets are matched by content, so re-importing a section reuses the
  attachments it already created.

= 0.1.1 =
* Block editor support, including per-instance custom CSS.
* Oxygen Classic: stylesheet writes normalised so the editor cannot be left
  hanging on load.

= 0.1.0 =
* First release: Bricks, Breakdance, Oxygen 6, Oxygen Classic and Elementor.

== Upgrade Notice ==

= 1.2.5 =
Code-quality fixes from the WordPress.org review; no change in what a copy looks like.

= 1.2.4 =
Stylesheets in copies are now rebuilt from an allowlist of standard CSS; the editor runtime loads as a script file.

= 1.2.3 =
Adds one-click insert from the wpgraft section Library on wpgraft.com.

= 1.2.2 =
Closing the panel no longer leaves an old rating card in front of the next copy's Insert button.

= 1.2.1 =
Required for Elementor 4.3: without it every copy is refused with "title: invalid_value".

= 1.0.2 =
Direct-download build only.

= 1.0.1 =
Hardening: script, PHP and invalid CSS are stripped from every copy for every user.

= 1.0.0 =
First stable release.

= 0.1.2 =
Adds Beaver Builder and Divi 5, and stops duplicate media on re-import.
