{"id":363172,"date":"2026-09-23T13:57:49","date_gmt":"2026-09-23T13:57:49","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/pure-demo-importer\/"},"modified":"2026-09-23T13:57:38","modified_gmt":"2026-09-23T13:57:38","slug":"pure-demo-importer","status":"publish","type":"plugin","link":"https:\/\/wordpress.org\/plugins\/pure-demo-importer\/","author":16662174,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.0.12","stable_tag":"1.0.12","tested":"7.1.2","requires":"5.6","requires_php":"7.4","requires_plugins":null,"header_name":"Pure Demo Importer","header_author":"themepure","header_description":"Drop-in demo importer\/exporter for WordPress themes. Themes and core plugins register their demos via the <code>pure_demo_importer_register<\/code> action; the plugin handles the AJAX import queue, WXR import, widgets, customizer, URL replacement, and reset.","assets_banners_color":"4c70ba","last_updated":"2026-09-23 13:57:38","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"","header_author_uri":"","rating":0,"author_block_rating":0,"active_installs":10,"downloads":46,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.0.12":{"tag":"1.0.12","author":"themepure","date":"2026-09-23 13:57:38","revision":3709462}},"upgrade_notice":{"1.0.12":"<p>Security release. SQL-pack import\/export and the built-in SVG sanitizer have been removed; XML parsing, outbound downloads and request handling are hardened. Normal WXR \/ widgets \/ customizer imports are unaffected.<\/p>","1.0.11":"<p>Recommended-plugin installs are now resolved exclusively from the WordPress.org Plugin Directory.<\/p>","1.0.0":"<p>First public release.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3709459,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3709459,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3709459,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3709459,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.0.12"],"block_files":[],"assets_screenshots":[],"screenshots":[]},"plugin_section":[],"plugin_tags":[3258,150371,144680,167556,222667],"plugin_category":[],"plugin_contributors":[218566],"plugin_business_model":[],"class_list":["post-363172","plugin","type-plugin","status-publish","hentry","plugin_tags-demo","plugin_tags-demo-importer","plugin_tags-one-click-import","plugin_tags-starter-sites","plugin_tags-wxr","plugin_contributors-themepure","plugin_committers-themepure"],"banners":{"banner":"https:\/\/ps.w.org\/pure-demo-importer\/assets\/banner-772x250.png?rev=3709459","banner_2x":"https:\/\/ps.w.org\/pure-demo-importer\/assets\/banner-1544x500.png?rev=3709459","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/pure-demo-importer\/assets\/icon-128x128.png?rev=3709459","icon_2x":"https:\/\/ps.w.org\/pure-demo-importer\/assets\/icon-256x256.png?rev=3709459","generated":false},"screenshots":[],"raw_content":"<!--section=description-->\n<p>Pure Demo Importer adds a <strong>Demo Importer<\/strong> admin page to any WordPress theme that registers itself with the plugin. It can:<\/p>\n\n<ul>\n<li>Export the current site's content (content.xml WXR), widgets (widgets.wie) and customizer settings (customizer.dat) into a single ZIP.<\/li>\n<li>Import any of the above on a fresh WordPress install, with auto-install and activation of required plugins from the WordPress.org repo.<\/li>\n<li>Show a live system-requirements check before import with copy-paste fix instructions.<\/li>\n<li>Optionally reset the site (delete posts\/widgets\/customizer mods) before importing.<\/li>\n<\/ul>\n\n<p>The plugin itself ships <strong>no demo config<\/strong>. Themes and core plugins register their demos by hooking the <code>pure_demo_importer_register<\/code> action \u2014 see \"Registering demos\" below.<\/p>\n\n<h3>Registering demos<\/h3>\n\n<p>The plugin fires a <code>pure_demo_importer_register<\/code> action on <code>after_setup_theme:20<\/code>. Hook into it and call <code>ThemePure_Demo_Importer::register()<\/code> with your config:<\/p>\n\n<pre><code>add_action( 'pure_demo_importer_register', function () {\n    ThemePure_Demo_Importer::register( array(\n        'theme_name' =&gt; 'My Theme',\n        'theme_slug' =&gt; 'my-theme',          \/\/ drives the filter name\n        'menu_slug'  =&gt; 'my-theme-demo',\n        'menu_title' =&gt; 'My Theme Importer',\n        'menu_icon'  =&gt; 'dashicons-download',\n        'demos'      =&gt; require __DIR__ . '\/demo-config\/demo.php',\n    ) );\n} );\n<\/code><\/pre>\n\n<h3>Filter hook: extending the demos array<\/h3>\n\n<p>After the initial <code>::register()<\/code> call, the demos array is <strong>filterable per theme<\/strong> via the filter <code>{theme_slug}_demo_importer_demos<\/code>. The filter name is built from your <code>theme_slug<\/code> \u2014 if you registered with <code>'theme_slug' =&gt; 'consora'<\/code>, the filter is <code>consora_demo_importer_demos<\/code>. The filter passes two args: <code>( array $demos, array $config )<\/code>.<\/p>\n\n<p>Use this filter to <strong>add, remove, or modify<\/strong> demos after the initial register call \u2014 without touching the theme's static <code>demo.php<\/code> config.<\/p>\n\n<h4>When the filter fires<\/h4>\n\n<p>Inside <code>ThemePure_Demo_Importer::get_demos()<\/code> \u2014 every time the admin page (or any AJAX step) reads the demos array. Filter callbacks can be added anywhere that runs <strong>before<\/strong> the user opens the Demo Importer page (e.g. <code>functions.php<\/code>, plugin bootstrap, <code>after_setup_theme<\/code>).<\/p>\n\n<h4>Where to add the filter callback<\/h4>\n\n\n\n\n  Location\n  When to use\n\n\n\n\n  Theme's <code>demo-config\/demo-import.php<\/code>\n  Most demos are static \u2014 this is just a small extension.\n\n\n  A separate file (<code>theme\/demo-config\/extra-demos.php<\/code>)\n  Keep dynamic \/ conditional demos isolated from the static config.\n\n\n  A site-specific plugin (<code>mu-plugins\/site-demos.php<\/code>)\n  The extra demos are project-specific and shouldn't ship with the theme.\n\n\n\n\n<h4>Example 1: Append an extra demo (most common)<\/h4>\n\n<p>From any plugin or <code>functions.php<\/code>, after the theme's register call:<\/p>\n\n<pre><code>add_filter( 'consora_demo_importer_demos', function ( $demos, $config ) {\n    $demos[] = array(\n        'slug'               =&gt; 'home-bonus',\n        'name'               =&gt; 'Bonus Homepage',\n        'page_preview_image' =&gt; 'https:\/\/wp.themepure.net\/consora\/sample-data\/preview-image\/thumb-bonus.jpg',\n        'preview_live_url'   =&gt; 'https:\/\/wp.themepure.net\/consora\/home-bonus',\n        'required_plugins'   =&gt; array(\n            array( 'slug' =&gt; 'elementor', 'name' =&gt; 'Elementor' ),\n        ),\n        'content_xml_url'    =&gt; 'https:\/\/wp.themepure.net\/consora\/sample-data\/sample-data\/bonus.xml',\n        'home_page'          =&gt; 'Home Bonus',\n        'source_url'         =&gt; 'https:\/\/wp.themepure.net\/consora\/',\n    );\n    return $demos;\n}, 10, 2 );\n<\/code><\/pre>\n\n<h4>Example 2: Remove a demo conditionally<\/h4>\n\n<p>Hide demos that depend on a plugin which isn't active (e.g. WooCommerce-only demos):<\/p>\n\n<pre><code>add_filter( 'consora_demo_importer_demos', function ( $demos, $config ) {\n    \/\/ Hide the WooCommerce demo when Woo isn't active.\n    if ( ! class_exists( 'WooCommerce' ) ) {\n        $demos = array_values( array_filter( $demos, function ( $d ) {\n            return $d['slug'] !== 'home-05';\n        } ) );\n    }\n    return $demos;\n}, 10, 2 );\n<\/code><\/pre>\n\n<h4>Example 3: Mutate every demo at once<\/h4>\n\n<p>Switch the CDN host across every demo, or force-add a plugin to every demo's required-plugins list:<\/p>\n\n<pre><code>add_filter( 'consora_demo_importer_demos', function ( $demos, $config ) {\n    foreach ( $demos as &amp;$demo ) {\n        \/\/ Force-add a plugin to every demo.\n        $demo['required_plugins'][] = array(\n            'slug' =&gt; 'wordpress-seo',\n            'name' =&gt; 'Yoast SEO',\n        );\n        \/\/ Swap the CDN host.\n        if ( isset( $demo['content_xml_url'] ) ) {\n            $demo['content_xml_url'] = str_replace(\n                'wp.themepure.net',\n                'cdn.themepure.net',\n                $demo['content_xml_url']\n            );\n        }\n    }\n    unset( $demo );\n    return $demos;\n}, 10, 2 );\n<\/code><\/pre>\n\n<h4>Replacing the demos array entirely<\/h4>\n\n<p>You can also call <code>::register()<\/code> with <code>'demos' =&gt; array()<\/code> and populate the entire list via the filter \u2014 useful if your demos are generated dynamically (e.g. fetched from a remote API at boot).<\/p>\n\n<h3>Other available hooks<\/h3>\n\n<h4>Lifecycle actions<\/h4>\n\n<ul>\n<li><code>themepure_di_before_import<\/code> \u2014 fires before reset \/ import begins. Args: <code>( $demo )<\/code>.<\/li>\n<li><code>themepure_di_after_import<\/code> \u2014 fires once all imports complete. Args: <code>( $demo )<\/code>. Use for WooCommerce page IDs, custom kits, theme options.<\/li>\n<li><code>themepure_di_before_step<\/code> \/ <code>themepure_di_after_step<\/code> \u2014 per-step hooks. Args: <code>( $step, $demo, $arg )<\/code>.<\/li>\n<\/ul>\n\n<h4>Reset<\/h4>\n\n<ul>\n<li><code>themepure_di_reset_preserve_plugins<\/code> \u2014 filter the list of plugin basenames to keep active across a reset. Default: <code>[ 'pure-demo-importer\/pure-demo-importer.php' ]<\/code>. Append any plugin that registers demos so the demos remain visible after the post-reset page reload.<\/li>\n<\/ul>\n\n<h4>Downloader<\/h4>\n\n<ul>\n<li><code>themepure_di_download_timeout<\/code> \u2014 remote download timeout in seconds (default 300).<\/li>\n<li><code>themepure_di_http_bypass_args<\/code> \u2014 tweak browser-style headers used when a host 403s the default WP user-agent.<\/li>\n<\/ul>\n\n<h4>WXR import<\/h4>\n\n<ul>\n<li><code>themepure_di_wp_import_fetch_attachments<\/code> \u2014 skip media sideload during WXR import (default true).<\/li>\n<li><code>themepure_di_wp_import_allow_create_users<\/code> \u2014 allow WP_Import to create users from XML authors (default false).<\/li>\n<\/ul>\n\n<h4>Logger<\/h4>\n\n<ul>\n<li><p><code>themepure_di_logging_enabled<\/code> \u2014 disable site-wide import logging:<\/p>\n\n<p>add_filter( 'themepure_di_logging_enabled', '__return_false' );<\/p><\/li>\n<\/ul>\n\n<p>Logs are written to a private <code>wp-content\/uploads\/themepure-di-logs-{random}\/import.log<\/code> and rotated at ~2 MB. The random suffix is generated once per site so the log cannot be fetched by guessing its URL.<\/p>\n\n<h3>External services<\/h3>\n\n<p>Pure Demo Importer ships with zero demos configured out of the box (<code>demos =&gt; array()<\/code>) and makes no external connections on its own. It is a drop-in engine: a WordPress theme or plugin integrates with it by registering one or more \"demo\" entries (each with its own name and URLs) via the <code>pure_demo_importer_register<\/code> action. Once a theme has registered real demos, the following outbound requests can happen \u2014 always as the direct result of the site administrator clicking a button in the Pure Demo Importer admin screen, never automatically or in the background:<\/p>\n\n<ul>\n<li><strong>Demo content download.<\/strong> When the admin clicks \"Import\" for a specific demo, the plugin downloads that demo's content files (WXR XML, widgets <code>.wie<\/code>\/JSON, Customizer <code>.dat<\/code>, an optional Elementor kit <code>.zip<\/code>) from the URL(s) the integrating theme configured for that demo. This is a plain HTTP GET of a static file; no site data, personal data, or tracking parameters are sent \u2014 only a standard request for the file at that URL. The host is whatever demo server the theme author operates or points to; this plugin does not operate its own demo-content service and cannot provide a single Terms of Service \/ Privacy Policy link, since the host is defined entirely by the integrating theme, not by this plugin.<\/li>\n<li><strong>Plugin installation.<\/strong> When the admin clicks \"Install\" next to a recommended plugin, the plugin is resolved and downloaded from the official WordPress.org Plugin Directory using WordPress core's own <code>plugins_api()<\/code> and <code>Plugin_Upgrader<\/code> \u2014 the same mechanism used by Add New Plugin in wp-admin. See https:\/\/wordpress.org\/about\/privacy\/ for WordPress.org's own privacy policy. No plugin code is ever installed from any other source.<\/li>\n<li><strong>WooCommerce variation-swatch image (optional).<\/strong> If the integrating theme's demo config includes a <code>variation_settings_url<\/code>, the plugin fetches that single URL after import to configure WooCommerce product-variation swatches. Same nature as the demo content download above: a static file fetch to a URL the theme author configured, triggered only by the admin's own import action.<\/li>\n<\/ul>\n\n<p>No analytics, telemetry, or usage tracking of any kind is performed by this plugin.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Upload the <code>pure-demo-importer<\/code> folder to <code>\/wp-content\/plugins\/<\/code>.<\/li>\n<li>Activate \"Pure Demo Importer\" from the Plugins screen.<\/li>\n<li>From your theme's <code>functions.php<\/code> (or a core plugin's bootstrap), hook the <code>pure_demo_importer_register<\/code> action and call <code>ThemePure_Demo_Importer::register()<\/code> with your config. See the \"Registering demos\" section for a full example.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"the%20plugin%20says%20%22no%20demos%20configured%22%20%E2%80%94%20what%20now%3F\"><h3>The plugin says \"No demos configured\" \u2014 what now?<\/h3><\/dt>\n<dd><p>Either your theme\/core plugin hasn't called <code>ThemePure_Demo_Importer::register()<\/code>, or it called it with an empty <code>demos<\/code> array. See the \"Registering demos\" section above for the correct hook + call pattern. If you want to populate demos via filter only (no <code>register()<\/code> array), use the <code>{theme_slug}_demo_importer_demos<\/code> filter described above.<\/p><\/dd>\n<dt id=\"will%20reset%20delete%20my%20pure%20demo%20importer%20plugin%3F\"><h3>Will reset delete my Pure Demo Importer plugin?<\/h3><\/dt>\n<dd><p>No. The reset always preserves Pure Demo Importer itself \u2014 deactivating the plugin running the reset would terminate the request mid-flight. Use the <code>themepure_di_reset_preserve_plugins<\/code> filter to preserve additional plugins.<\/p><\/dd>\n<dt id=\"does%20this%20work%20without%20the%20wordpress%20importer%20plugin%20installed%3F\"><h3>Does this work without the WordPress Importer plugin installed?<\/h3><\/dt>\n<dd><p>Yes. Pure Demo Importer vendors the full WordPress Importer plugin internally at <code>vendor\/wordpress-importer\/<\/code>. There is no runtime dependency on the upstream plugin being installed.<\/p><\/dd>\n<dt id=\"where%20is%20the%20import%20log%3F\"><h3>Where is the import log?<\/h3><\/dt>\n<dd><p>wp-content\/uploads\/themepure-di-logs-{random}\/import.log \u2014 rotated at ~2 MB. Disable with <code>add_filter( 'themepure_di_logging_enabled', '__return_false' );<\/code>.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.0.12<\/h4>\n\n<ul>\n<li>Security: removed the SQL-pack import\/export subsystem. It executed statements from a downloaded <code>.sql<\/code> dump verbatim, which is an arbitrary-SQL path and has no place in a WordPress.org release. Normal WXR \/ widgets \/ customizer importing is unchanged.<\/li>\n<li>Security: removed the bundled regex-based SVG \"sanitizer\" and stopped allowlisting <code>svg<\/code> \/ <code>svgz<\/code> or forcing a MIME type from the filename. SVG in demo content is now refused during import unless a proven sanitizer library (enshrined\/svg-sanitize, as bundled by Safe SVG) is installed.<\/li>\n<li>Security: all XML now loads through a single hardened path \u2014 <code>LIBXML_NONET<\/code>, no entity substitution, DOCTYPE refused, document size capped, libxml error state always restored. The deprecated <code>libxml<\/code> entity-loader toggle is gone.<\/li>\n<li>Security: <code>WP_Import::import()<\/code> can no longer read arbitrary fields from the original request. <code>$_POST<\/code> is replaced with a minimal allowlisted array for the duration of the call and restored in a <code>finally<\/code> block, along with filters, deferred counting, cache invalidation and output buffers.<\/li>\n<li>Security: every outbound download now goes through one safe downloader \u2014 http\/https only, loopback \/ private \/ link-local \/ unique-local blocked for IPv4 and IPv6, every redirect hop revalidated, redirect and response-size caps, partial files cleaned up on failure.<\/li>\n<li>Security: the selective-reset payload is now limited by byte size and nesting depth before decoding.<\/li>\n<li>Security: private upload directories are now named with a per-site random suffix and protected with Apache 2.4-compatible rules, a <code>web.config<\/code> and an <code>index.php<\/code> stub, instead of Apache-2.2-only <code>.htaccess<\/code> syntax that could 500 a directory on Apache 2.4.<\/li>\n<li>Removed all <code>set_time_limit()<\/code> and <code>memory_limit<\/code> <code>ini_set()<\/code> calls. Imports rely on bounded AJAX chunks; memory headroom is requested once via WordPress's own <code>wp_raise_memory_limit( 'admin' )<\/code>.<\/li>\n<li>Fixed a PHP 8.4 implicit-nullable parameter deprecation.<\/li>\n<li>Removed the <code>tools\/build-sql-pack.php<\/code> developer script from the distributed package.<\/li>\n<\/ul>\n\n<h4>1.0.11<\/h4>\n\n<ul>\n<li>Recommended-plugin installs are now resolved exclusively from the WordPress.org Plugin Directory \u2014 no other source (remote or local) is ever used.<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Initial release.<\/li>\n<li>AJAX queue-driven import with progress UI.<\/li>\n<li>Single-shot and chunked WXR import paths.<\/li>\n<li>Vendored WordPress Importer (no runtime dependency on the upstream plugin).<\/li>\n<li>Widgets, customizer, metafields, Elementor kit, URL replacement steps.<\/li>\n<li>Reset site with self-preservation + filterable preserve-list.<\/li>\n<li>Auto page reload after successful reset.<\/li>\n<\/ul>","raw_excerpt":"Drop-in demo importer\/exporter for WordPress themes.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/363172","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=363172"}],"author":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/themepure"}],"wp:attachment":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=363172"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=363172"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=363172"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=363172"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=363172"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=363172"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}