=== StaticPush by Quintaphon ===
Contributors: quintaphon
Donate link: https://buymeacoffee.com/quintaphon
Tags: static site, static site generator, github, cloudflare, jamstack
Requires at least: 5.8
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 2.15.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Turn WordPress into a fast static site. Export to HTML and push to GitHub or Cloudflare Pages automatically, with search, forms and R2 media.

== Description ==

StaticPush by Quintaphon is a static site generator for WordPress. It exports your WordPress site to plain HTML and publishes it to GitHub (for Cloudflare Pages or any Git-based host), straight to Cloudflare Pages, or as a ZIP. After the first full push, every change you make in WordPress is pushed automatically, and only the pages that changed are sent.

Your visitors get a static site: fast, cheap to host and with nothing to hack. You keep editing in WordPress as usual.

Plugin site: [quintaphon.com/wordpress/staticpush](https://quintaphon.com/wordpress/staticpush/)
Documentation: [quintaphon.com/docs/wordpress/staticpush](https://quintaphon.com/docs/wordpress/staticpush/)

= Publish =

* **Destinations:** GitHub (one-click "Connect GitHub", repository picker, create a repository), Cloudflare Pages Direct Upload, or a local folder and ZIP download.
* **Automatic pushes:** on publish, update, unpublish, delete, comments, terms and site-wide changes (menus, widgets, Customizer, theme). Edits made close together become one push.
* **Push only what you need:** pending changes, the full site, the current files, or specific pages (including the 404 page).
* **Fast exports:** several pages are fetched at once, directly from your own server.
* **Safe by design:** a push that would delete most of the static site is refused, and a login wall in front of WordPress stops the push instead of emptying the site.
* **Scheduled posts:** overdue scheduled posts are published and pushed, even though the WordPress install gets almost no visitors.

= Export =

* Full address or root-relative links, with every occurrence rewritten (including inline CSS, scripts and JSON).
* Finds everything through the WordPress database and by following links: posts, pages, custom post types, categories, tags, authors, pagination, feeds, sitemaps, robots.txt and ads.txt, and the 404 page.
* The theme's real 404 page, marked noindex.
* Feeds saved as feed.xml with a browser stylesheet, and old feed addresses redirected.
* Sitemaps with a browser stylesheet, and /sitemap.xml redirected to the sitemap index.
* Additional pages, files and folders; excluded addresses.
* Cloudflare Pages `_redirects` and `_headers` (including rules from the Redirection plugin and Yoast, Rank Math, AIOSEO and SEOPress sitemaps).

= Features for the static site =

* **Site Search:** a search index and an instant search overlay that takes over your theme's search forms. Fuzzy, typo-tolerant matching with Fuse.js, bundled and self-hosted.
* **llms.txt for AI tools:** a Markdown summary of the site at /llms.txt (llmstxt.org format) with your categories, pages and latest posts, rebuilt on every push.
* **Related Posts:** built into every post at export time, matched by shared tags or categories (topped up with the latest posts when few match), in the Jetpack Related Posts design, a simple grid or your own template.
* **Forms:** remove comment forms, or send POST forms to an external form service.
* **HTML Clean-up:** add a main landmark for accessibility when the theme has none; remove HTML comments, sourceURL markers, block editor and emoji styles, Dashicons, admin-bar CSS, Yoast schema, speculation rules and more; resource hints; HTML minify; your own removal rules.
* **Stealth:** Private WordPress mode hides the WordPress hostname from visitors and search engines while exports keep working; generator tags and WordPress API links can be removed.

= Media Offload =

Mirror your media library to a Cloudflare R2 bucket over the S3 API: resumable background sync with parallel uploads, pause and resume, automatic upload of new media, skip rules for sizes you don't need, and media links in exported pages pointed at your media domain. Image sizes kept out of R2 are replaced by the original in exported pages, so no image breaks.

= Monitor =

* Dashboard with live progress, push history and an activity log.
* Health Check: permalinks, loopback requests, background worker, direct fetch, WP-Cron, PHP, disk space, destination connection and more.
* WP-CLI: `wp static-push export [--no-deploy]`, `wp static-push deploy`, `wp static-push status`, `wp static-push cancel`.

== Installation ==

1. Install StaticPush by Quintaphon from Plugins > Add New (or upload the zip) and activate it.
2. Go to **Static Push > Destination**, choose **GitHub** and click **Connect GitHub**. Enter the code shown on github.com and approve.
3. Pick a repository, or click **New** to create a private one.
4. Go to **Export > Link rewriting**, choose **Full address** and enter your public site address (for example https://www.example.com). Save.
5. Click **Push > Full site**.
6. In Cloudflare: Workers & Pages > Create > Pages > **Connect to Git**, pick the repository. Framework preset: None, build command: empty, output directory: `/`.

From now on every change in WordPress becomes a commit, and Cloudflare Pages publishes it.

**Recommended:** move WordPress to its own hostname (for example wp.example.com), point your public domain at the static host, and turn on **Stealth > Private WordPress**.

**Media on Cloudflare R2 (optional):** create a bucket and an R2 API token with Object Read & Write, enter them under **Media Offload**, click **Test connection**, then **Sync media library now**. Connect your media domain to the bucket (R2 > bucket > Settings > Custom Domains) and run a full push.

Keys can also live in wp-config.php instead of the database:

    define( 'CFSP_GITHUB_TOKEN', 'github_pat_...' );  // fine-grained token for one repository
    define( 'CFSP_API_TOKEN', '...' );                // Cloudflare Pages Direct Upload
    define( 'CFSP_ACCOUNT_ID', '...' );
    define( 'CFSP_R2_ACCESS_KEY', '...' );            // Media Offload
    define( 'CFSP_R2_SECRET_KEY', '...' );
    define( 'CFSP_BASIC_AUTH', 'user:password' );     // if WordPress is behind HTTP basic auth

== Frequently Asked Questions ==

= Do I need to keep WordPress running? =

Yes. You keep writing and editing in WordPress; Static Push turns the result into a static site. Visitors only ever see the static copy, so WordPress can live on a private hostname.

= How long does a full push take? =

It depends on the number of addresses (posts, category and tag pages, pagination, feeds, files) and on how fast WordPress builds a page. A site with a few thousand addresses usually takes 10 to 20 minutes. After that, automatic pushes only export the pages that changed.

= Will comments, contact forms and search work? =

Search works through the built-in static search. Forms can be sent to an external form service. WordPress comments need WordPress, so remove the comment form or use an external comments service.

= My scheduled posts are published late. =

WP-Cron only runs when someone visits WordPress. Keep **Automation > Publish missed scheduled posts** on, and have a service such as cron-job.org call the wp-cron.php address shown on that page every minute.

= What happens to my data if I delete the plugin? =

By default, settings, the GitHub connection, media records and exported files are kept, so reinstalling picks up where you left off. Turn on **Tools > Delete all data when the plugin is deleted** first if you want everything removed. Your static site, GitHub repository and R2 bucket are never touched.

= Does it work with page caches? =

Yes. Exports bypass page caches with a query parameter and a no-cache header.

== Screenshots ==

1. Dashboard with live progress, push history and the activity log.
2. Destination: connect GitHub and pick a repository.
3. Export settings: link rewriting and page discovery.
4. Health Check.
5. Media Offload to Cloudflare R2.
6. Site Search on the static site.

== External services ==

StaticPush by Quintaphon connects to the external services below. Each one is used only for a feature you turn on and set up yourself; with none of them set up, the plugin only writes the static files to a folder on your server (Local / ZIP). No data is sent to Quintaphon.

= GitHub =

What it is for: connecting your GitHub account and publishing the static site to a repository you choose (Destination: GitHub).

What is sent and when:

* When you click **Connect GitHub**: the plugin's public OAuth client ID is sent to github.com (`/login/device/code`), and the device code to `/login/oauth/access_token` while you approve access on github.com. Your GitHub user name is then read from api.github.com.
* When you open the Destination page or pick a repository: your access token is sent to api.github.com to list your repositories, or to create one when you click **New**.
* When a push runs: the exported files and their paths, the commit message and your access token are sent to api.github.com.

GitHub [Terms of Service](https://docs.github.com/en/site-policy/github-terms/github-terms-of-service) and [Privacy Statement](https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement).

= Cloudflare Pages =

What it is for: publishing the static site straight to a Cloudflare Pages project (Destination: Cloudflare Pages, Direct Upload).

What is sent and when: when you save or test the Destination settings, create a project, or a push runs, your Cloudflare account ID, API token and project name are sent to api.cloudflare.com. During a push, the exported files, their paths and content hashes are sent to create the deployment.

Cloudflare [Terms of Service](https://www.cloudflare.com/terms/) and [Privacy Policy](https://www.cloudflare.com/privacypolicy/).

= Cloudflare R2 =

What it is for: storing a copy of your media library in your own R2 bucket (Media Offload).

What is sent and when: only when Media Offload is turned on. When you test the connection, run Sync media library, remove skipped files, or upload new media (with automatic upload on), your media files and their paths are sent to your bucket at `<account id>.r2.cloudflarestorage.com` (or the S3 endpoint you enter), signed with your R2 access keys.

Cloudflare [Terms of Service](https://www.cloudflare.com/terms/) and [Privacy Policy](https://www.cloudflare.com/privacypolicy/).

= Form service you choose (optional) =

What it is for: making forms work on the static site, where WordPress can't receive them.

What is sent and when: only if you enter a form endpoint under **Forms**. The exported pages then send the form to that address (for example Formspree) from the visitor's browser when the visitor submits a form. The plugin itself sends nothing. The data is whatever the form contains, and the terms and privacy policy of the service you choose apply (for Formspree: [Terms](https://formspree.io/legal/terms-of-service/) and [Privacy Policy](https://formspree.io/legal/privacy-policy/)).

No other external requests are made. The static site's search script and Fuse.js are served from your own site, and the admin screens load no files from other sites.

== Third-party libraries ==

* **Fuse.js 7.5.0**, Apache License 2.0 (assets/vendor/LICENSE-fuse.txt). The bundled file is the official minified build; the readable source is at https://github.com/krisk/Fuse (tag v7.5.0).
* **Inter** variable font 4.x (latin, latin-ext), SIL Open Font License 1.1 (assets/fonts/OFL.txt). Used by the admin screens only. Source: https://github.com/rsms/inter

== Developer hooks ==

Filters: `cfsp_full_urls`, `cfsp_post_urls`, `cfsp_exclude_url`, `cfsp_rewrite_content`, `cfsp_clean_html`, `cfsp_fetch_args`, `cfsp_post_types`, `cfsp_github_client_id`, `cfsp_github_protected`, `cfsp_github_allow_mass_delete`.

Actions: `cfsp_job_done`, `cfsp_job_failed`, `cfsp_settings_updated`.

== Changelog ==

= 2.15.0 =
* Fix: when a post split into pages with the Page Break block (<!--nextpage-->) was updated, automatic and "Pending changes" pushes re-exported only its first page, so page 2, 3 and onward kept the old content on the static site. All pages of the post are now exported. Run one "Full site" push after updating to refresh pages that are already out of date.

= 2.14.9 =
* First release on WordPress.org.

== Upgrade Notice ==

= 2.15.0 =
Posts split into several pages now update every page on push. Run one "Full site" push after updating.

= 2.14.9 =
First release on WordPress.org.
