mirror of
https://github.com/WordPress/create-block-theme.git
synced 2026-07-26 21:42:48 +08:00
* tests: add tiny.png and evil.php.txt fixtures for media validation * docs(agents): update test:php commands to test:unit:php * Add CBT_Theme_Media::is_allowed_media_url() extension allowlist * Add CBT_Theme_Media::is_allowed_media_file() MIME allowlist * Apply media URL + file allowlist in add_media_to_local * Add CBT_Theme_Fonts::is_allowed_font_url() extension allowlist * Add CBT_Theme_Fonts::is_allowed_font_file() MIME allowlist * Apply font URL + file allowlist in copy_font_assets_to_theme * Allow font/sfnt and application/vnd.ms-opentype for TTF/OTF MIME detection * Reject multi-extension polyglots in URL allowlist * Apply media+font allowlist to zip export sinks in theme-zip * Add positive end-to-end tests for media and font sinks * Verify downloaded fonts by magic bytes instead of libmagic MIME * Pass $font_src string to download_url instead of $font_face src array Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Stream downloaded media into zip and clean up tmp file Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Strip query string before deriving folder path from media URL Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Restore braces and is_wp_error check dropped by Copilot suggestions Two recent Copilot suggestion commits accidentally removed structural code while making single-line changes: -c045da7("Pass $font_src to download_url") dropped the `} else {` and the `is_wp_error( $tmp_file )` guard. -617f6ec("Stream downloaded media into zip") dropped the closing brace of the `foreach` in add_media_to_zip(). Both broke PHP parse on every CI matrix. Restoring the missing statements so the file is valid again. * lint: align assignment operators in get_media_folder_path_from_url * Strip query string from filename before renaming downloaded media Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Strip query string from font URL before deriving filename Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Strip query string from font URL before deriving filename in zip Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Restore loop brace and sanitize_title dropped by Copilot suggestions Two more Copilot suggestion commits dropped surrounding context lines: -ad31fe4("Strip query string from filename before renaming downloaded media") removed the closing brace of the foreach loop in add_media_to_local(), causing fatal lint errors. -860f1bc("Strip query string from font URL before deriving filename in zip") removed the `$font_family_dir_name = sanitize_title(...)` assignment, leaving an undefined variable used immediately below. * Remove dead pre-loop assignments in add_activated_fonts_to_zip `$font_filename = basename( $font_face['src'] )` threw TypeError on PHP 8+ when src was already an array (valid per Theme JSON spec) and was redefined inside the inner src loop anyway. The adjacent `$font_dir = wp_get_font_dir()` was likewise redundant — also redefined inside the inner loop. Removing both. * Verify downloaded media by magic bytes instead of libmagic MIME wp_check_filetype_and_ext() was inconsistent with the URL allowlist: SVG isn't in WP Core's default mime registry at all, and WP maps wmv to video/x-ms-wmv / avi to video/avi while the post-check allowlist had only video/x-msvideo. Legitimate SVG/WMV/AVI URLs passed preflight and then silently failed post-download. Switching media to magic-byte verification (matching the font sink) removes the dependency on WP Core's mime registry and libmagic versions. Recognised formats: JPEG, PNG, GIF, WebP, SVG, MP4/M4V/MOV/ 3GP/3G2 (via ftyp), WebM, OGV, WMV (ASF), AVI (RIFF), MPEG. * Add regression tests for media asset validation * Fix media validation bugs found by scruffian Three fixes for issues found via regression tests in8da66ff: 1. is_allowed_media_file() now requires the downloaded bytes to match the URL extension specifically — a .jpg URL with SVG/MP4/anything-else bytes is rejected. Previously any allowed magic was accepted, so a browser MIME-sniffing the on-disk .jpg containing SVG could render it as SVG (with all the script-execution surface that brings). 2. make_relative_media_url() now derives the filename from the parsed URL path instead of raw basename(). A URL like cat.jpg?/evil.php previously produced `/assets/images/evil.php` in exported template markup because basename() treats query-string slashes as path separators. 3. add_media_to_zip() reads the downloaded bytes into memory and unlinks the tmp file BEFORE adding to the archive via addFromStringToTheme(). The previous `addFileToTheme() + unlink` sequence broke because ZipArchive defers reading files added via addFile() until close() — by which time the tmp file was gone. * lint: suppress NoSilencedErrors warning on ZipArchive::close in test * Skip zip tests gracefully when ZipArchive extension is unavailable Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Guard localhost retry against missing host/port keys parse_url() omits the 'port' key when the URL has no explicit port (e.g. `http://localhost/foo`). The retry-on-localhost block in both add_media_to_zip() and add_media_to_local() read $parsed_url['port'] unconditionally, raising an undefined-index notice when the initial download failed for such a URL. Guard with isset() on both keys. * Match font magic bytes against URL extension specifically Previously any recognised font magic was accepted, so a .woff2 URL returning TTF/WOFF/OTF/EOT bytes would pass and be saved under the .woff2 filename. This defeated the extension+MIME allowlist intent and would produce broken assets in the exported theme/zip (browsers would fail to load a WOFF2-named file containing TTF content). The check now switches on the URL extension and validates the magic matches THAT specific format. Mirrors the same fix already applied to is_allowed_media_file(). * Align .mpeg handling across URL, folder, and file-content checks is_allowed_media_file() accepted both .mpg and .mpeg, but is_allowed_media_url() and get_media_folder_path_from_url() only listed .mpg. A template with a .mpeg video would get rewritten to a local asset URL, then skipped before download — leaving the exported theme pointing at a missing file. Add .mpeg to the URL allowlist and the folder mapping so all three lists agree (matching WP Core's wp_get_mime_types which maps mpeg|mpg|mpe to video/mpeg). Also drops a pre-existing duplicate 'ogv' entry in the folder mapping that was noticed while editing. * Stream font bytes into zip before unlinking tmp file Same fix as add_media_to_zip: ZipArchive::addFile() defers reading the file until close() is called, so unlinking the download_url tmp file immediately after addFileToTheme() left an empty entry in the final archive when the zip was finalised. Read the bytes into memory, unlink, then addFromStringToTheme() — mirrors the pattern in add_media_to_zip(). Only the remote-download branch is affected; the local-copy branch points at a permanent file in the font library so its addFileToTheme() call stays. * Tweak comment * Allow AVIF images alongside JPEG/PNG/GIF/WebP/SVG AVIF is a WordPress-supported image format but was missing from the URL allowlist, folder mapping, and magic-byte check. The result: make_template_images_local() rewrote .avif URLs to local references, but the post-rewrite download was rejected — leaving exported themes pointing at missing assets. AVIF uses the ISO BMFF container (like MP4) with 'avif' or 'avis' as the major brand at offset 8. Added the brand-specific magic check alongside the URL/folder allowlist entries, plus tests for both AVIF still-image ('avif') and image-sequence ('avis') brands. * Drop rejected font sources from exported families Previously a font source that failed the URL allowlist or post-download MIME check was skipped via continue, but the original $font_src stayed in $font_face['src']. copy_activated_fonts_to_theme() then merged that family into theme.json — leaving the user's activated font pointing at an uncopied (and possibly disallowed) source while the user custom settings were cleared. Both copy_font_assets_to_theme() and add_activated_fonts_to_zip() now build a fresh srcs list and only retain entries that successfully copied or were already file:./ asset paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Only rewrite media URLs after successful asset validation * Accept AVIF compatible-brands in addition to the major brand Some AVIF files (typically those emitted by HEIF-derived tooling) set the major brand to mif1/miaf and only list avif/avis in the compatible brands. Parsing only the major brand at offset 8 would false-reject them. Scan the full FileTypeBox brand list (major brand plus compatible brands at offsets 16+) for any AVIF brand. HEIC files (no avif/avis anywhere) are still rejected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Magic-byte check the local-copy font branch too Both copy_font_assets_to_theme() and add_activated_fonts_to_zip() treated a font src under the WP user-fonts directory as trusted — copying / zipping the bytes without ever inspecting them. Anything that ended up in that directory through a separate flow (a polyglot upload via another plugin, manual write, etc.) would be promoted to the exported theme as-is just because the URL extension was on the allowlist. Run the same is_allowed_font_file() check on the local source before copying / zipping. If the bytes don't match the URL extension's font format, drop the source from the returned families. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Localize media URLs via the block parser, not str_replace Plain str_replace on the whole template would substring-match a shorter validated URL inside a longer rejected URL when one is a prefix of the other (e.g. `photo.png` inside `photo.png.php`). That silently rewrote the rejected URL to a missing local asset and bypassed the validated-media guard. Walk the parsed block tree instead: WP_HTML_Tag_Processor handles img / video src+poster and inline `background-image:url(...)`, while direct array access handles block-comment JSON attrs. Replacements only touch values that exactly match a validated URL. The rewrites embed literal PHP open/close tags that must end up verbatim in the export, but both set_attribute() and wp_json_encode() (inside serialize_blocks) would escape `<` / `>` / quotes. Route the rewrites through opaque, URL-shaped placeholders so they survive both passes, then strtr() them back to the raw PHP at the end. The placeholder is a root-relative path because set_attribute prefixes schemeless values with `http://`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Ben Dwyer <ben@scruffian.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
382 lines
14 KiB
PHP
382 lines
14 KiB
PHP
<?php
|
|
|
|
require_once( __DIR__ . '/theme-media.php' );
|
|
require_once( __DIR__ . '/theme-patterns.php' );
|
|
|
|
class CBT_Theme_Templates {
|
|
|
|
/**
|
|
* Build a collection of templates and template-parts that should be exported (and modified)
|
|
* based on the given export_type.
|
|
*
|
|
* @param string $export_type The type of export to perform. 'all', 'current', or 'user'.
|
|
* @param array $templates_to_export List of specific templates to export.
|
|
* @return object An object containing the templates and parts that should be exported.
|
|
*/
|
|
public static function get_theme_templates( $export_type, $templates_to_export = null ) {
|
|
|
|
$templates = get_block_templates( array( 'slug__in' => $templates_to_export ) );
|
|
$template_parts = get_block_templates( array( 'slug__in' => $templates_to_export ), 'wp_template_part' );
|
|
$exported_templates = array();
|
|
$exported_parts = array();
|
|
|
|
// build collection of templates/parts in currently activated theme
|
|
$templates_paths = get_block_theme_folders();
|
|
$templates_path = get_stylesheet_directory() . '/' . $templates_paths['wp_template'] . '/';
|
|
$parts_path = get_stylesheet_directory() . '/' . $templates_paths['wp_template_part'] . '/';
|
|
|
|
foreach ( $templates as $template ) {
|
|
if ( self::should_include_template(
|
|
$template,
|
|
$export_type,
|
|
$templates_path
|
|
) ) {
|
|
$exported_templates[] = self::cleanup_template( $template );
|
|
}
|
|
}
|
|
|
|
foreach ( $template_parts as $template ) {
|
|
if ( self::should_include_template(
|
|
$template,
|
|
$export_type,
|
|
$parts_path
|
|
) ) {
|
|
$exported_parts[] = self::cleanup_template( $template );
|
|
}
|
|
}
|
|
|
|
return (object) array(
|
|
'templates' => $exported_templates,
|
|
'parts' => $exported_parts,
|
|
);
|
|
|
|
}
|
|
|
|
/**
|
|
* Filter a template out (return false) based on the export_type expected and the templates origin.
|
|
*
|
|
* @param object $template The template to filter.
|
|
* @param string $export_type The type of export to perform. 'all', 'current', or 'user'.
|
|
* @param string $path The path to the templates folder.
|
|
* @return object|bool The template if it should be included, or false if it should be excluded.
|
|
*/
|
|
static function should_include_template( $template, $export_type, $path ) {
|
|
if ( 'theme' === $template->source && 'user' === $export_type ) {
|
|
return false;
|
|
}
|
|
if (
|
|
'theme' === $template->source &&
|
|
'current' === $export_type &&
|
|
! file_exists( $path . $template->slug . '.html' )
|
|
) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Clean up the template content before exporting.
|
|
* @param object $template The template to clean up.
|
|
* @return object The cleaned up template.
|
|
*/
|
|
private static function cleanup_template( $template ) {
|
|
// NOTE: Dashes are encoded as \u002d in the content that we get (noteably in things like css variables used in templates)
|
|
// This replaces that with dashes again. We should consider decoding the entire string but that is proving difficult.
|
|
$template->content = str_replace( '\u002d', '-', $template->content );
|
|
|
|
return $template;
|
|
}
|
|
|
|
/**
|
|
* Replace the old theme slug with the new theme slug in the template content.
|
|
*
|
|
* @param object $template The template to replace the namespace in.
|
|
* @param string $new_slug The new theme slug.
|
|
* @return object The template with the namespace replaced.
|
|
*/
|
|
public static function replace_template_namespace( $template, $new_slug ) {
|
|
$old_slug = wp_get_theme()->get( 'TextDomain' );
|
|
if ( $new_slug ) {
|
|
$template->content = str_replace( $old_slug, $new_slug, $template->content );
|
|
}
|
|
return $template;
|
|
}
|
|
|
|
/**
|
|
* Clear all user templates customizations.
|
|
* This will remove all user templates from the database.
|
|
*/
|
|
public static function clear_user_templates_customizations() {
|
|
$templates = get_block_templates();
|
|
foreach ( $templates as $template ) {
|
|
if ( 'custom' !== $template->source ) {
|
|
continue;
|
|
}
|
|
wp_delete_post( $template->wp_id, true );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear all user template-parts customizations.
|
|
* This will remove all user template-parts from the database.
|
|
*/
|
|
public static function clear_user_template_parts_customizations() {
|
|
$template_parts = get_block_templates( array(), 'wp_template_part' );
|
|
foreach ( $template_parts as $template ) {
|
|
if ( 'custom' !== $template->source ) {
|
|
continue;
|
|
}
|
|
wp_delete_post( $template->wp_id, true );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extract content from a template that need to be patternized.
|
|
* Return the modified template and the pattern that was created
|
|
*
|
|
* @param object $template The template to extract content from.
|
|
* @return object The template with the patternized content.
|
|
*/
|
|
public static function paternize_template( $template, $slug = null ) {
|
|
// If there is any PHP in the template then paternize
|
|
if ( str_contains( $template->content, '<?php' ) ) {
|
|
$pattern = CBT_Theme_Patterns::pattern_from_template( $template, $slug );
|
|
$pattern_link_attributes = array(
|
|
'slug' => $pattern['slug'],
|
|
);
|
|
$template->content = CBT_Theme_Patterns::create_pattern_link( $pattern_link_attributes );
|
|
$template->pattern = $pattern['content'];
|
|
}
|
|
return $template;
|
|
}
|
|
|
|
/**
|
|
* Prepare a template for export.
|
|
* This will escape text in the template, eliminate environment specific content,
|
|
* make template images local, and paternize the template.
|
|
*
|
|
* @param object $template The template to prepare for export.
|
|
* @param string $slug The slug of the theme.
|
|
* @return object The prepared template.
|
|
*/
|
|
public static function prepare_template_for_export( $template, $slug = null, $options = null ) {
|
|
|
|
if ( ! $options ) {
|
|
$options = array(
|
|
'localizeText' => false,
|
|
'removeNavRefs' => true,
|
|
'localizeImages' => true,
|
|
);
|
|
}
|
|
|
|
// Strip PHP tags from the raw template body BEFORE any
|
|
// trusted-PHP injection (escape_text_in_template below).
|
|
// Sanitising here preserves the legitimate localization output.
|
|
$template->content = CBT_Theme_Patterns::strip_php_tags( $template->content );
|
|
|
|
$template = self::eliminate_environment_specific_content( $template, $options );
|
|
|
|
if ( array_key_exists( 'localizeText', $options ) && $options['localizeText'] ) {
|
|
$template = self::escape_text_in_template( $template );
|
|
}
|
|
|
|
if ( array_key_exists( 'localizeImages', $options ) && $options['localizeImages'] ) {
|
|
$validated_media = array_key_exists( 'validatedMedia', $options ) ? $options['validatedMedia'] : null;
|
|
$template = CBT_Theme_Media::make_template_images_local( $template, $validated_media );
|
|
}
|
|
|
|
if ( $slug ) {
|
|
$template = self::replace_template_namespace( $template, $slug );
|
|
}
|
|
|
|
$template = self::paternize_template( $template, $slug );
|
|
|
|
return $template;
|
|
}
|
|
|
|
private static function should_localize_images( $options ) {
|
|
return ! $options || ( array_key_exists( 'localizeImages', $options ) && $options['localizeImages'] );
|
|
}
|
|
|
|
private static function add_validated_media_to_options( $options, $validated_media ) {
|
|
if ( ! $options ) {
|
|
$options = array(
|
|
'localizeText' => false,
|
|
'removeNavRefs' => true,
|
|
'localizeImages' => true,
|
|
);
|
|
}
|
|
|
|
$options['validatedMedia'] = $validated_media;
|
|
return $options;
|
|
}
|
|
|
|
/**
|
|
* Copy the templates and template-parts (including user customizations)
|
|
* as well as any media to the theme filesystem.
|
|
* If patterns need to be created for media or localizations they will also be added.
|
|
*
|
|
* @param string $export_type The type of export to perform. 'all', 'current', or 'user'.
|
|
* @param string $path The path to the theme folder. If null it is assumed to be the current theme.
|
|
* @param string $slug The slug of the theme. If null it is assumed to be the current theme.
|
|
* @param array $options An array of options to use when exporting the templates.
|
|
* @param array $templates_to_export List of specific templates to export. If null it will be fetched.
|
|
*/
|
|
public static function add_templates_to_local( $export_type, $path = null, $slug = null, $options = null, $templates_to_export = null ) {
|
|
|
|
$theme_templates = self::get_theme_templates( $export_type, $templates_to_export );
|
|
$template_folders = get_block_theme_folders();
|
|
|
|
$base_dir = $path ? $path : get_stylesheet_directory();
|
|
$template_dir = $base_dir . DIRECTORY_SEPARATOR . $template_folders['wp_template'];
|
|
$template_part_dir = $base_dir . DIRECTORY_SEPARATOR . $template_folders['wp_template_part'];
|
|
$patterns_dir = $base_dir . DIRECTORY_SEPARATOR . 'patterns';
|
|
|
|
// If there is no templates folder, and it is needed, create it.
|
|
if ( ! is_dir( $template_dir ) && count( $theme_templates->templates ) > 0 ) {
|
|
wp_mkdir_p( $template_dir );
|
|
}
|
|
|
|
// If there is no parts folder, and it is needed, create it.
|
|
if ( ! is_dir( $template_part_dir ) && count( $theme_templates->parts ) > 0 ) {
|
|
wp_mkdir_p( $template_part_dir );
|
|
}
|
|
|
|
foreach ( $theme_templates->templates as $template ) {
|
|
|
|
$template_options = $options;
|
|
if ( self::should_localize_images( $template_options ) ) {
|
|
$template->media = CBT_Theme_Media::get_media_absolute_urls_from_template( $template );
|
|
$validated_media = ! empty( $template->media ) ? CBT_Theme_Media::add_media_to_local( $template->media ) : array();
|
|
$template_options = self::add_validated_media_to_options( $template_options, $validated_media );
|
|
}
|
|
|
|
$template = self::prepare_template_for_export( $template, $slug, $template_options );
|
|
|
|
// Write the template content
|
|
file_put_contents(
|
|
$template_dir . DIRECTORY_SEPARATOR . $template->slug . '.html',
|
|
$template->content
|
|
);
|
|
|
|
// Write the pattern if it exists
|
|
if ( isset( $template->pattern ) ) {
|
|
// If there is no patterns folder, create it.
|
|
if ( ! is_dir( $patterns_dir ) ) {
|
|
wp_mkdir_p( $patterns_dir );
|
|
}
|
|
file_put_contents(
|
|
$patterns_dir . DIRECTORY_SEPARATOR . $template->slug . '.php',
|
|
$template->pattern
|
|
);
|
|
}
|
|
}
|
|
|
|
foreach ( $theme_templates->parts as $template ) {
|
|
|
|
$template_options = $options;
|
|
if ( self::should_localize_images( $template_options ) ) {
|
|
$template->media = CBT_Theme_Media::get_media_absolute_urls_from_template( $template );
|
|
$validated_media = ! empty( $template->media ) ? CBT_Theme_Media::add_media_to_local( $template->media ) : array();
|
|
$template_options = self::add_validated_media_to_options( $template_options, $validated_media );
|
|
}
|
|
|
|
$template = self::prepare_template_for_export( $template, $slug, $template_options );
|
|
|
|
// Write the template content
|
|
file_put_contents(
|
|
$template_part_dir . DIRECTORY_SEPARATOR . $template->slug . '.html',
|
|
$template->content
|
|
);
|
|
|
|
// Write the pattern if it exists
|
|
if ( isset( $template->pattern ) ) {
|
|
// If there is no patterns folder, create it.
|
|
if ( ! is_dir( $patterns_dir ) ) {
|
|
wp_mkdir_p( $patterns_dir );
|
|
}
|
|
file_put_contents(
|
|
$patterns_dir . DIRECTORY_SEPARATOR . $template->slug . '.php',
|
|
$template->pattern
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Escape text in template content.
|
|
*
|
|
* @param object $template The template to escape text content in.
|
|
* @return object The template with the content escaped.
|
|
*/
|
|
public static function escape_text_in_template( $template ) {
|
|
$template_blocks = parse_blocks( $template->content );
|
|
$localized_blocks = CBT_Theme_Locale::escape_text_content_of_blocks( $template_blocks );
|
|
$updated_template_content = serialize_blocks( $localized_blocks );
|
|
// Process block attributes after serialization to avoid JSON encoding of PHP tags
|
|
$updated_template_content = CBT_Theme_Locale::escape_block_attribute_strings( $updated_template_content );
|
|
$template->content = $updated_template_content;
|
|
return $template;
|
|
}
|
|
|
|
private static function eliminate_environment_specific_content_from_block( $block, $options = null ) {
|
|
|
|
// remove theme attribute from template parts
|
|
if ( 'core/template-part' === $block['blockName'] && isset( $block['attrs']['theme'] ) ) {
|
|
unset( $block['attrs']['theme'] );
|
|
}
|
|
|
|
// (optionally) remove ref attribute from nav blocks
|
|
if ( 'core/navigation' === $block['blockName'] && isset( $block['attrs']['ref'] ) ) {
|
|
if ( ! $options || ( array_key_exists( 'removeNavRefs', $options ) && $options['removeNavRefs'] ) ) {
|
|
unset( $block['attrs']['ref'] );
|
|
}
|
|
}
|
|
|
|
// remove id attributes and classes from image and cover blocks
|
|
if ( in_array( $block['blockName'], array( 'core/image', 'core/cover' ), true ) ) {
|
|
// remove id attribute from image and cover blocks
|
|
if ( isset( $block['attrs']['id'] ) ) {
|
|
$image_id = $block['attrs']['id'];
|
|
unset( $block['attrs']['id'] );
|
|
// remove wp-image-[id] class from inner content
|
|
foreach ( $block['innerContent'] as $inner_key => $inner_content ) {
|
|
if ( is_null( $inner_content ) ) {
|
|
continue;
|
|
}
|
|
$block['innerContent'][ $inner_key ] = str_replace( 'wp-image-' . $image_id, '', $inner_content );
|
|
}
|
|
}
|
|
}
|
|
|
|
// (optionally) remove taxQuery attribute from query blocks
|
|
if ( 'core/query' === $block['blockName'] && isset( $block['attrs']['query']['taxQuery'] ) ) {
|
|
if ( ! $options || ( array_key_exists( 'removeTaxQuery', $options ) && $options['removeTaxQuery'] ) ) {
|
|
unset( $block['attrs']['query']['taxQuery'] );
|
|
}
|
|
}
|
|
|
|
// process any inner blocks
|
|
if ( ! empty( $block['innerBlocks'] ) ) {
|
|
foreach ( $block['innerBlocks'] as $inner_block_key => $inner_block ) {
|
|
$block['innerBlocks'][ $inner_block_key ] = static::eliminate_environment_specific_content_from_block( $inner_block, $options );
|
|
}
|
|
}
|
|
|
|
return $block;
|
|
}
|
|
|
|
public static function eliminate_environment_specific_content( $template, $options = null ) {
|
|
|
|
$template_blocks = parse_blocks( $template->content );
|
|
$parsed_content = '';
|
|
|
|
foreach ( $template_blocks as $block ) {
|
|
$parsed_block = static::eliminate_environment_specific_content_from_block( $block, $options );
|
|
$parsed_content .= serialize_block( $parsed_block );
|
|
}
|
|
|
|
$template->content = $parsed_content;
|
|
return $template;
|
|
}
|
|
}
|