Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 57 additions & 1 deletion includes/create-theme/theme-patterns.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,61 @@
<?php

class CBT_Theme_Patterns {
/**
* Strip PHP execution tags from user-supplied pattern body content.
*
* Block patterns are HTML/block markup, not PHP. Any `<?php` (or short/
* legacy variants) in user content is treated as malicious and removed
* before the body is interpolated into the exported `.php` pattern file.
*
* This helper is `public static` because it is invoked from two pipelines:
* 1. `pattern_from_wp_block()` in this class (wp_block patterns), where
* sanitisation happens BEFORE `prepare_pattern_for_export()` injects
* trusted `<?php esc_*_e(...);?>` markers.
* 2. `CBT_Theme_Templates::prepare_template_for_export()` (templates and
* template parts), where sanitisation must happen at the very start —
Comment thread
Copilot marked this conversation as resolved.
* BEFORE `escape_text_in_template()` injects the same trusted markers.
*
* In both cases the rule is: sanitise first, inject trusted PHP second,
* build the heredoc third. Calling this AFTER the trusted-PHP injection
* would strip the plugin's own localization helpers and break the
* "Make text translation-ready" feature.
*
* @param mixed $content User-supplied body content.
* @return mixed Same content with PHP open tags removed (when input is a non-empty string).
*/
public static function strip_php_tags( $content ) {
if ( ! is_string( $content ) || '' === $content ) {
return $content;
}

// Strip ANY `<?` open tag. On hosts with `short_open_tag=1`, PHP parses
// `<?` followed by `$`, `(`, `"`, `//`, `/*`, `;`, or `xml` as an open
// tag — preserving any of them would either re-execute as PHP or
// produce a fatal parse error when the exported `.php` file is loaded.
// Block patterns are HTML/block markup, so there's no legitimate
// `<?xml` content to preserve.
$content = preg_replace( '/<\?/', '', $content );

// Strip legacy `<script language="php">…</script>` blocks. PHP 7+
// removed this parser, but custom SAPIs / polyfills could still
// honour it. Match the entire block (opening tag → closing tag,
// inclusive of inner content).
$content = preg_replace( '#<script\s+language\s*=\s*["\']?php["\']?[^>]*>.*?</script>#is', '', $content );

return $content;
}

/**
* Build a pattern .php file from a template stdClass.
*
* IMPORTANT: this function expects `$template->content` to be already
* sanitised by the caller. The pipeline entry point is
* `CBT_Theme_Templates::prepare_template_for_export`, which strips PHP
* tags from `$template->content` BEFORE the trusted-PHP injection done
* by `escape_text_in_template`. Calling `pattern_from_template` with
* un-sanitised user content would re-introduce the PHP injection bug.
*/
public static function pattern_from_template( $template, $new_slug = null ) {
$theme_slug = $new_slug ? $new_slug : wp_get_theme()->get( 'TextDomain' );
$template_slug = str_replace( '*/', '*&#47;', $template->slug );
Expand Down Expand Up @@ -32,6 +87,7 @@ public static function pattern_from_wp_block( $pattern_post ) {
$pattern->categories = ! empty( $pattern_category_list ) ? join( ', ', wp_list_pluck( $pattern_category_list, 'name' ) ) : '';
$pattern_title = str_replace( '*/', '*&#47;', $pattern->title );
$pattern_categories = str_replace( '*/', '*&#47;', $pattern->categories );
$safe_body = self::strip_php_tags( $pattern_post->post_content );
$pattern->content = <<<PHP
<?php
/**
Expand All @@ -40,7 +96,7 @@ public static function pattern_from_wp_block( $pattern_post ) {
* Categories: {$pattern_categories}
*/
?>
{$pattern_post->post_content}
{$safe_body}
PHP;

return $pattern;
Expand Down
5 changes: 5 additions & 0 deletions includes/create-theme/theme-templates.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ public static function prepare_template_for_export( $template, $slug = null, $op
);
}

// 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'] ) {
Expand Down
Loading
Loading