BlueTheme Docs

Demo internals

A demo is a complete site in a folder: content, media, menus, widgets, Theme Options and a few site and WooCommerce settings. The code is in inc/Demo. The setup wizard runs these steps from an admin screen; WP-CLI runs them from the command line.

Package format

manifest.json
en/content.json
en/options.json
fa/content.json
fa/options.json
media/            images and videos

manifest.json:

  • format: 1.
  • id, version.
  • title and description, each per language ({ "en": "…", "fa": "…" }).
  • categories, languages.
  • preview: a picture in the package, either one for every language ("preview.webp") or one per language ({ "fa": "preview-fa.webp", "en": "preview-en.webp" }). The wizard shows the one in the site's language, else English.
  • live: preview addresses per language.
  • plugins: { "required": [], "recommended": [] }, as plugin slugs.

content.json:

  • posts: id, type, status, title, slug, content, excerpt, parent, order, terms and meta. Meta stored as JSON strings is decoded and marked json: true, so ids inside it can be rewritten.
  • terms.
  • media: the file in media/, title, alt text, caption and original address.
  • menus: their items and menu locations.
  • widgets: per widget area.
  • site: front page and posts page.
  • woocommerce: store pages, price format and registration.
  • attributes: WooCommerce attribute taxonomies, such as Color and Size.

The source site's and theme's addresses are written as {{site}} and {{theme}}.

The theme's own JSON is used instead of WXR, a choice made at the start of 9c. It keeps builder layouts, template settings and display conditions exactly as they are, needs no importer plugin, and lets every imported item be tagged for removal.

Accepted files: only data and media (json, jpg, png, webp, gif, mp4, webm, txt). SVG is accepted only in the theme's own bundled packages, because SVG can carry scripts. A remote package with any other file, such as .php or .htaccess, is refused and deleted.

Where demos come from

  • Bundled: bluetheme/demos/{id}/, with the theme.
  • Remote: Theme Options → Global → Performance → Demo catalog address, a JSON catalog:
{ "demos": [ { "id": "corporate", "version": "1.0.0",
  "title": { "en": "Corporate", "fa": "شرکتی" }, "description": { … },
  "categories": [ "business" ], "languages": [ "fa", "en" ],
  "preview": { "fa": "https://…/corporate-fa.jpg", "en": "https://…/corporate-en.jpg" }, "live": { "fa": "https://…" },
  "package": "https://…/corporate.zip",
  "plugins": { "required": [], "recommended": [ "contact-form-7" ] } } ] }

The catalog is cached for 12 hours. Package addresses must use https. When a remote and a bundled demo share an id, the bundled one is used. The bluetheme_demo_catalog filter changes the list.

Import

Each step does a little work, so large demos never hit the PHP time limit:

  1. back up
  2. product attributes
  3. media
  4. terms
  5. posts
  6. links
  7. menus
  8. widgets
  9. options
  10. WooCommerce
  11. finish

Parts: content, templates, menus, widgets, Theme Options and WooCommerce settings can each be left out. Media and terms follow the content parts.

Content language: chosen by the site language (Persian on Persian sites, otherwise English) or asked for.

Rewritten references:

  • Media values ({ id, url } anywhere: images, galleries, backgrounds, logos) point at the imported attachments.
  • Builder settings are rewritten from their field types:
    • page → posts;
    • categories and tax:* → terms;
    • templates:* → templates;
    • menus → menu:ID;
    • forms → cf7:ID, wpforms:ID.
  • Template display conditions, Page Options template choices, thumbnails, product galleries, upsells and cross-sells, and variations' parent products are rewritten too.
  • Builder layouts are saved through the builder's own save (Document::save), so they are cleaned with the importing user's permissions.

Pages and terms:

  • New pages that share a slug with an existing page get WordPress's usual "-2" slug.
  • Terms with an existing slug, such as "Uncategorized" or a category the site already has, are reused rather than created.

Theme Options: a demo replaces every option except the site's own:

  • SEO;
  • social profile links;
  • custom code;
  • SMS settings;
  • license settings;
  • any secret (passwords, API keys).

These are never exported either.

Before the import the theme saves the Theme Options, widget areas, menu locations, front page and WooCommerce settings. Only one demo can be imported at a time.

Removal

Everything the import created is tagged (_bluetheme_demo post or term meta), so removal deletes exactly that:

  • posts of every type;
  • attachments with their files;
  • terms;
  • menus and their items;
  • the widgets the demo added.

It then restores the saved settings exactly. The site's own settings (SEO, SMS, license…) keep their current values, in case they were changed after the import. Content that was there before the import is never touched.

Tested with a full round trip between the dev sites: the English site was exported, the export was imported into the Persian site, and then removed. A snapshot of post counts per type, terms, menus, Theme Options, widgets, menu locations, front page and shop page was identical before and after.

Setup wizard

BlueTheme → Setup wizard (Demo\Wizard, src/admin/wizard). It opens by itself once, the first time an administrator reaches the dashboard after the theme is activated.

  1. Welcome: start, or remove the imported demo (after a confirmation). It also has Export this site as a demo, which downloads a package (.zip) of the site in its current language. The package is built in a temporary folder and deleted after the download, which needs a nonce and manage_options.
  2. Plugins (Demo\Plugins):
    • The list: WooCommerce, Contact Form 7, and on Persian sites also Persian WooCommerce and the ZarinPal gateway.
    • Each shows its state, and the ticked ones are installed from WordPress.org and activated, one after another.
    • Installs use WordPress's own upgrader, so install_plugins / activate_plugin and the server's file permissions apply. A server that needs FTP details gets a message pointing to Plugins → Add New.
    • When an install fails, WordPress's reason is shown, for example a plugin needing a newer PHP.
    • The bluetheme_setup_plugins filter changes the list.
  3. Demo: a gallery of the bundled and remote demos, with:
    • a preview image (or the demo's name);
    • a description and languages;
    • a "Live preview" link when the demo has one;
    • category filters;
    • "Check for new demos", which refreshes the remote catalog.
  4. Import:
    • Choose between everything and the parts you pick, each with a short explanation.
    • When the demo has both languages, choose the content language.
    • Plugins the demo requires that are missing can be installed from here.
    • Progress is shown with a progress bar and a list of steps. On an error you can stop the import, and what was created can then be removed.
    • At the end: links to the site and to Theme Options.

Accessibility: each step's heading takes focus, the step list marks the current step (aria-current), the progress bar is a progressbar, and step messages are announced (aria-live). The layout uses logical properties and an RTL stylesheet, so it mirrors on right-to-left admin screens.

Bundled demos and how they are built

Id Site Languages
corporate "Nikan Consulting", a consulting firm: Home, About, Services, Team, Pricing, Contact, Blog and 6 posts; business header, dark footer, consultation popup fa, en
agency "Studio Arka", a design studio: Home, Work (8 projects with filters, 3 with galleries), Services, Studio, Journal (4 posts), Contact; transparent header, big-statement footer, corner popup fa, en
shop-clothing "Nakh", a clothing store: 12 products (variable ones with color and size swatches, a sale, one out of stock), 4 categories, shop filters, About, FAQ, Contact; shop header with promo bar, shop footer, welcome popup fa, en
shop-decor "Sayeh", home decor: furniture, lamps, ceramics and textiles in color variants; centered-logo header, card-style products fa, en
shop-electronics "Volt", electronics: phones, laptops, audio and wearables in color variants; compact shop header fa, en
shop-cosmetics "Nilou", cosmetics: makeup shades as swatches, skincare, fragrance and bath; shop header with promo bar, round sale badges fa, en
code "Navak", a software engineering company: Home, Services, Work (8 projects with filters, 3 with galleries), Team (8 people), Blog (4 posts), Contact with a project form; dark header with a dark mode toggle, dark mode on, dark footer, exit popup fa, en
ai "Pandar", an AI assistant product: Home, Features, Solutions, Pricing (3 plans), About with the team, Blog (4 posts), Book a demo form; simple header with a demo button, call-to-action footer, corner popup (not on phones) fa, en
hospital "Arman Hospital", a general hospital: Home, Departments, Doctors (8), Patients and visitors (visit steps, visiting hours, insurance, questions), Health news (4 posts), Appointments form with opening hours and a map; business header with an emergency line, three-column footer fa, en
physio "Tavana Physio", a physiotherapy clinic: Home (first-visit steps, session prices), Treatments, Conditions with questions, Therapists, Exercise tips (4 posts), Book a session form with opening hours and a map; stacked header with a green menu bar, contact footer, exit popup fa, en
dental "Labkhand Dental", a dental clinic: Home (visit steps, prices), Treatments (check-up, whitening, implants, braces), Dentists, Dental tips (4 posts), Appointments form with hours, map and questions; centered menu header, four-column footer, exit popup fa, en
beauty "Rose Skin Clinic", a skin and beauty clinic: Home (prices), Treatments (facials, laser, skincare plans, acne), Specialists, Skin journal (4 posts), booking form with hours, map and questions; centered logo header, centered footer, exit popup fa, en
vet "Hamdam Vet Clinic", for dogs and cats: Home (prices, owner reviews with their pets), Services (check-ups, vaccines, dental care, surgery), Vets, Pet care (4 posts), Appointments form; classic header, gradient footer, exit popup fa, en
psychology "Aram Counseling", a counseling center: Home (session prices), Services (individual, couples, children, online), Psychologists, Articles (4 posts), booking form with hours, map and questions; floating header, logo and menu footer fa, en

Each package is about 1 MB; previews are 960 × 720 WebP pictures, one per language.

The shop demos share tools/demos/lib/Shop.php (catalog: Toman in Persian, dollars in English; Color and Size attributes with swatch colors; one picture per color) and lib/ShopSite.php (pages, templates, menus, filters in the shop sidebar, Theme Options). Each shop is a niche description in tools/demos/shop-{niche}/demo.php. Their category and product lists are limited to the demo's own categories, so a site's existing products do not mix in. A demo sets the store currency, which comes back when the demo is removed.

The packages in bluetheme/demos/ are generated, not exported from a hand-made site. A script creates each demo on an empty WordPress through WordPress's and WooCommerce's own functions, and the theme's exporter writes the package. That way every rebuild is identical, and the content goes through the same import path as any exported site.

  • tools/demo-site/: a separate wp-env with an empty Persian site on :8890 and an English site on :8891. It runs PHP 8.3 so Contact Form 7 works. Start it with cd tools/demo-site && npx wp-env start. If wp-env says it is offline and cannot find the WordPress version, copy latestWordPressVersion from ~/.wp-env/wp-env-cache.json into the new folder's wp-env-cache.json. If the site says it is not installed, run wp core install once.
  • tools/demos/{id}/:
    • art.mjs: the demo's pictures, drawn as SVG with art/kit.mjs and art/objects.mjs. Logos use the bundled fonts; no other picture contains words.
    • demo.php and the files it loads: options, templates, menus, pages and posts, with every text in both languages ($site->t( 'English', 'فارسی' )).
    • manifest.json: title, description, categories and plugins.
  • tools/demos/lib/: Site creates pages, posts, terms, templates, menus, pictures and forms; Blocks holds the shared sections; Clinic holds what the clinic demos share (the appointment form, opening hours, the booking band, doctor cards and the contact section with a map), and ClinicSite builds a whole clinic demo (pages, posts, header, footer, popup, menus and options) from a niche description, the way ShopSite does for shops: the dental, beauty, veterinary and psychology demos are such descriptions. art/clinic-art.mjs draws the pictures every clinic needs (team, patients, blog, title band, popup, logo) and art/clinics.mjs the niche objects (tooth, chair, creams, dog, cat, sofa…). art/medical.mjs draws the medical objects (cross, heartbeat line, building, bed, clipboard, stethoscope, exercise ball, dumbbell, treatment table).
  • Build: node tools/demos/build.mjs corporate (or --lang=fa for one language). It:
    1. renders the pictures to WebP in tools/demos/.out/ (git-ignored);
    2. empties each build site and builds the language;
    3. exports it into bluetheme/demos/{id};
    4. takes preview-{lang}.webp, a screenshot of that language's front page, and adds the manifest texts and the previews.

Private demos

A demo made for one client (their brand, staff photos, insurer logos) must not ship inside the theme. Put it in tools/demos/private/{id}/, which git ignores, with the same files as a bundled demo. It can also have:

  • photos/ and photos.mjs: the client's own photos. photos.mjs exports a function that writes them as WebP into the pictures folder before the build, so $site->image( 'name' ) finds them next to the drawings.

node tools/demos/build.mjs {id} builds it the same way, then writes a plugin instead of a theme folder:

tools/demos/private/{id}/dist/
├── bluetheme-demo-{id}/
│   ├── bluetheme-demo-{id}.php   (from tools/demos/lib/demo-plugin.php)
│   └── package/                  (the demo package and its previews)
└── bluetheme-demo-{id}.zip

Install the zip on the client's site under Plugins → Add New → Upload. The plugin adds the demo to the setup wizard through the bluetheme_demo_catalog filter, as a local package, so it imports and removes like a bundled demo. Deactivate or delete the plugin after the import; the imported content stays.

Each build uploads into a new folder, so picture file names are the same in both languages and the package holds each picture once.

Two languages with Polylang or WPML

BlueThemeCompatMultilingual puts Polylang and WPML behind one interface; languages are named by the theme's codes (fa, en) and matched to the plugin's own by locale. It also makes the theme's post types and taxonomies translatable in Polylang.

With lang: both, the importer runs the content steps (attributes, media, terms, posts, links, menus) once per language, each with its own id map, the plugin's default language first. Items get their language as they are created; a term whose slug the other language already has gets the language in its slug, and so does a post WordPress would have made "-2" (services-fa). Pictures with the same file name are imported once and shared. Translations::link() then pairs the two halves (posts by type and slug, templates by type and order, terms by taxonomy and slug, menus by location) and links them. Widgets, Theme Options, the front page and shop settings come from the main language. Each language's menus go to its own locations (Polylang's nav_menus; WPML follows the menu translations), and removal puts Polylang's locations back too.

Theme Options listed in OptionsTranslatable (visible texts, HTML texts and the logos; not slugs, SMS, SEO, map or white-label settings, nor code) are registered for string translation (group "BlueTheme", name bluetheme_{option}; pictures by their address) and translated on the front end inside Options::all(), which caches by language. The import saves the other language's values as their translations (Translations::options()). Template choice follows the language too: Resolver keeps only the current language's templates, which WPML needs as it does not filter the template query. wpml-config.xml declares the theme's post types, taxonomies and meta for WPML.

WPML is driven through its documented hooks only and has not been tested; Polylang 3.9 is (tools/demo-site has it).

Routes and commands

REST (bluetheme/v1, manage_options):

Route Does
GET demo/catalog The demos, the imported demo, and whether an import is running
POST demo/import Starts an import: { demo, lang, parts }; lang is fa, en, or both with Polylang/WPML
POST demo/language Adds Persian or English to Polylang: { lang }; returns the catalog's multilingual state
POST demo/step Runs the next step and returns { done, progress, message }
POST demo/cancel Stops a running import
POST demo/remove Removes the imported demo
POST demo/keep Keeps the imported demo as the site's own content: drops its demo tag, record and settings backup, so another demo can be imported
GET demo/plugins The suggested plugins (and a demo's own, with ?demo=) and their state (activate_plugins)
POST demo/plugins Installs from WordPress.org when missing, then activates: { slug } (activate_plugins; installing also needs install_plugins)

WP-CLI (run as an administrator, for example --user=admin):

wp bluetheme demo list
wp bluetheme demo import corporate --lang=fa --parts=content,templates,menus,widgets,options
wp bluetheme demo import corporate --lang=both
wp bluetheme demo remove
wp bluetheme demo keep
wp bluetheme demo export ./corporate --id=corporate --title="Corporate" --lang=fa --zip=corporate.zip

Exporting the Persian site and then the English one into the same folder gives a package with both languages.

Hooks:

  • bluetheme_demo_imported (demo id, state) and bluetheme_demo_removed (demo id, counts) fire when a demo is imported or removed.