Files
heretek-control-core/includes/class-child-theme-builder.php
T
Mian Shahzad Raza 0ce420601b style(copy): remove em dashes from plugin copy, fix mcpb display name
Sweeps shipped copy clean of em dashes: readme.txt, README.md,
CHANGELOG.md, the bundled sample prompts, and user-facing PHP strings
(tool descriptions, admin labels). PHP was swept on non-comment lines
only, so developer docblocks and inline comments are untouched.
Internal dev docs (CLAUDE.md, docs/) were deliberately left alone.

Replacements are contextual: colons for label/definition patterns,
commas for asides, en dashes for numeric ranges.

The .mcpb bundle display_name now reads "EMCP Tools - <host>" with a
plain hyphen; its test is updated to match. Fixing CHANGELOG.md also
clears the em dashes from the website's generated changelog on the
next site build, since that file is its source.

Verified: php -l clean on every touched file, pro suite 1559 pass,
public suite 102 pass. Bumps the pro submodule for the Pro-side sweep.
2026-07-21 08:03:19 +05:00

163 lines
4.7 KiB
PHP

<?php
/**
* Scaffolds and activates a child theme of the active parent theme.
*
* This is the framework-agnostic enabler for agent-driven theme file edits: it
* creates `style.css` + a minimal `functions.php` inside a fresh child theme
* directory and switches to it. The agent then performs file writes through the
* existing Filesystem tools (ABSPATH-confined, `edit_files`-gated, backed up,
* audit-logged) targeting the returned child directory.
*
* Deliberately conservative: it only ever creates a child of the CURRENT active
* parent, refuses when the active theme is already a child (no grandchildren),
* never touches the parent or any other theme, and is idempotent (an existing
* child is activated, never overwritten).
*
* @package EMCP_Tools
* @since 3.4.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Child theme scaffolder.
*/
class EMCP_Tools_Child_Theme_Builder {
/**
* Whether the active theme is itself a child (stylesheet != template).
*
* @return bool
*/
public static function is_active_a_child(): bool {
return get_stylesheet() !== get_template();
}
/**
* The active parent (template) slug.
*
* @return string
*/
public static function parent_slug(): string {
return (string) get_template();
}
/**
* The child theme slug (`<parent>-child`).
*
* @return string
*/
public static function child_slug(): string {
return self::parent_slug() . '-child';
}
/**
* The child theme directory path.
*
* @return string
*/
public static function child_dir(): string {
return trailingslashit( get_theme_root() ) . self::child_slug();
}
/**
* Whether the child theme directory already exists on disk.
*
* @return bool
*/
public static function child_exists(): bool {
return is_dir( self::child_dir() );
}
/**
* Create (if needed) and activate the child theme.
*
* @return array|WP_Error { child, parent, directory, activated, created } or error.
*/
public static function create() {
if ( self::is_active_a_child() ) {
return new WP_Error(
'is_child',
__( 'The active theme is already a child theme; refusing to create a grandchild.', 'emcp-tools' )
);
}
$parent = self::parent_slug();
$slug = self::child_slug();
$dir = self::child_dir();
$existed = self::child_exists();
if ( ! $existed ) {
if ( ! wp_mkdir_p( $dir ) ) {
return new WP_Error( 'mkdir_failed', __( 'Could not create the child theme directory.', 'emcp-tools' ) );
}
$parent_theme = wp_get_theme( $parent );
$parent_name = ( $parent_theme && '' !== (string) $parent_theme->get( 'Name' ) )
? (string) $parent_theme->get( 'Name' )
: ucfirst( $parent );
$wrote_style = self::write_file( $dir . '/style.css', self::style_css( $parent, $parent_name ) );
$wrote_funcs = self::write_file( $dir . '/functions.php', self::functions_php( $parent_name ) );
if ( false === $wrote_style || false === $wrote_funcs ) {
return new WP_Error( 'write_failed', __( 'Could not write the child theme files.', 'emcp-tools' ) );
}
}
switch_theme( $slug );
return array(
'child' => $slug,
'parent' => $parent,
'directory' => $dir,
'activated' => true,
'created' => ! $existed,
);
}
/**
* Write a file, preferring WP_Filesystem when initialized, else direct
* (matching the Filesystem tools' direct-FS posture). The target is always
* inside wp-content/themes/<child>/.
*
* @param string $path Absolute path.
* @param string $contents File contents.
* @return int|false Bytes written, or false on failure.
*/
private static function write_file( string $path, string $contents ) {
global $wp_filesystem;
if ( $wp_filesystem && is_object( $wp_filesystem ) && method_exists( $wp_filesystem, 'put_contents' ) ) {
return $wp_filesystem->put_contents( $path, $contents ) ? strlen( $contents ) : false;
}
return file_put_contents( $path, $contents );
}
/**
* @param string $parent Parent template slug.
* @param string $parent_name Parent display name.
* @return string
*/
private static function style_css( string $parent, string $parent_name ): string {
return "/*\n"
. 'Theme Name: ' . $parent_name . " Child\n"
. 'Template: ' . $parent . "\n"
. "Version: 1.0.0\n"
. 'Description: Child theme of ' . $parent_name . ", generated by EMCP Tools.\n"
. "*/\n";
}
/**
* @param string $parent_name Parent display name.
* @return string
*/
private static function functions_php( string $parent_name ): string {
return "<?php\n"
. '// ' . $parent_name . " child theme, generated by EMCP Tools.\n"
. "if ( ! defined( 'ABSPATH' ) ) { exit; }\n\n"
. "add_action( 'wp_enqueue_scripts', function () {\n"
. "\twp_enqueue_style( 'emcp-parent-style', get_template_directory_uri() . '/style.css' );\n"
. "} );\n";
}
}