| 1 |
<?php |
| 2 |
/** |
| 3 |
* Plugin Name: Child Theme Wizard |
| 4 |
* Plugin URI: https://wpguru.tv |
| 5 |
* Description: Creates a child theme from any installed parent theme. |
| 6 |
* Version: 1.5 |
| 7 |
* Author: Jay Versluis |
| 8 |
* Author URI: https://wpguru.tv |
| 9 |
* License: GPL2 |
| 10 |
* License URI: http://www.gnu.org/licenses/gpl-2.0.html |
| 11 |
* Text Domain: child-theme-wizard |
| 12 |
*/ |
| 13 |
|
| 14 |
/* Copyright 2013-2026 Jay Versluis (email support@wpguru.tv) |
| 15 |
|
| 16 |
This program is free software; you can redistribute it and/or modify |
| 17 |
it under the terms of the GNU General Public License, version 2, as |
| 18 |
published by the Free Software Foundation. |
| 19 |
|
| 20 |
This program is distributed in the hope that it will be useful, |
| 21 |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 22 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 23 |
GNU General Public License for more details. |
| 24 |
|
| 25 |
You should have received a copy of the GNU General Public License |
| 26 |
along with this program; if not, write to the Free Software |
| 27 |
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| 28 |
*/ |
| 29 |
|
| 30 |
if ( ! defined( 'ABSPATH' ) ) { |
| 31 |
exit; |
| 32 |
} |
| 33 |
|
| 34 |
add_action( 'admin_menu', 'ctw_menu' ); |
| 35 |
|
| 36 |
function ctw_menu(): void { |
| 37 |
add_submenu_page( |
| 38 |
'tools.php', |
| 39 |
__( 'Child Theme Wizard', 'child-theme-wizard' ), |
| 40 |
__( 'Child Theme Wizard', 'child-theme-wizard' ), |
| 41 |
'manage_options', |
| 42 |
'ChildThemeWizard', |
| 43 |
'ctw_main_function' |
| 44 |
); |
| 45 |
} |
| 46 |
|
| 47 |
function ctw_main_function(): void { |
| 48 |
|
| 49 |
if ( ! current_user_can( 'manage_options' ) ) { |
| 50 |
wp_die( esc_html__( 'You do not have sufficient privileges to access this page.', 'child-theme-wizard' ) ); |
| 51 |
} |
| 52 |
|
| 53 |
?> |
| 54 |
<div class="wrap"> |
| 55 |
<h1><?php esc_html_e( 'Child Theme Wizard', 'child-theme-wizard' ); ?></h1> |
| 56 |
|
| 57 |
<?php |
| 58 |
|
| 59 |
if ( isset( $_POST['hiddenfield'] ) && $_POST['hiddenfield'] === 'Y' ) { |
| 60 |
|
| 61 |
// Verify nonce — blocks CSRF attacks. |
| 62 |
check_admin_referer( 'ctw_create_child_theme', 'ctw_nonce' ); |
| 63 |
|
| 64 |
// Validate parent against actually-installed parent themes. |
| 65 |
$parent_themes = ctw_giveme_parents(); |
| 66 |
$valid_parents = array_map( fn( $t ) => $t->get_stylesheet(), $parent_themes ); |
| 67 |
$submitted_parent = sanitize_text_field( wp_unslash( $_POST['parent'] ?? '' ) ); |
| 68 |
|
| 69 |
if ( ! in_array( $submitted_parent, $valid_parents, true ) ) { |
| 70 |
wp_die( esc_html__( 'Invalid parent theme selected.', 'child-theme-wizard' ) ); |
| 71 |
} |
| 72 |
|
| 73 |
$newchildtheme = array( |
| 74 |
'parent' => $submitted_parent, |
| 75 |
'title' => sanitize_text_field( wp_unslash( $_POST['title'] ?? '' ) ), |
| 76 |
'description' => sanitize_text_field( wp_unslash( $_POST['description'] ?? '' ) ), |
| 77 |
'url' => esc_url_raw( wp_unslash( $_POST['url'] ?? '' ) ), |
| 78 |
'author' => sanitize_text_field( wp_unslash( $_POST['author'] ?? '' ) ), |
| 79 |
'author-url' => esc_url_raw( wp_unslash( $_POST['author-url'] ?? '' ) ), |
| 80 |
'version' => sanitize_text_field( wp_unslash( $_POST['version'] ?? '' ) ), |
| 81 |
'include-gpl' => ( isset( $_POST['include-gpl'] ) && $_POST['include-gpl'] === 'on' ) ? 'on' : 'off', |
| 82 |
); |
| 83 |
|
| 84 |
$status = ctw_create_theme( $newchildtheme ); |
| 85 |
|
| 86 |
if ( $status['alert'] === -1 ) { |
| 87 |
?> |
| 88 |
<div class="notice notice-error"> |
| 89 |
<p><strong><?php esc_html_e( 'Yikes — something went wrong:', 'child-theme-wizard' ); ?></strong></p> |
| 90 |
<?php echo wp_kses_post( $status['message'] ); ?> |
| 91 |
</div> |
| 92 |
<p><a href="<?php echo esc_url( admin_url( 'tools.php?page=ChildThemeWizard' ) ); ?>"><?php esc_html_e( 'Try again?', 'child-theme-wizard' ); ?></a></p> |
| 93 |
<?php |
| 94 |
} else { |
| 95 |
?> |
| 96 |
<div class="notice notice-success"> |
| 97 |
<?php echo wp_kses_post( $status['message'] ); ?> |
| 98 |
<p><strong><?php esc_html_e( 'Your Child Theme was created successfully!', 'child-theme-wizard' ); ?></strong></p> |
| 99 |
</div> |
| 100 |
<p> |
| 101 |
<?php |
| 102 |
printf( |
| 103 |
/* translators: 1: link to Appearance > Themes, 2: link to Appearance > Editor */ |
| 104 |
esc_html__( 'Head over to %1$s to activate it. Add your custom styles in %2$s.', 'child-theme-wizard' ), |
| 105 |
'<a href="' . esc_url( admin_url( 'themes.php' ) ) . '">' . esc_html__( 'Appearance › Themes', 'child-theme-wizard' ) . '</a>', |
| 106 |
'<a href="' . esc_url( admin_url( 'theme-editor.php' ) ) . '">' . esc_html__( 'Appearance › Editor', 'child-theme-wizard' ) . '</a>' |
| 107 |
); |
| 108 |
?> |
| 109 |
</p> |
| 110 |
<?php |
| 111 |
} |
| 112 |
} else { |
| 113 |
|
| 114 |
$current_user = wp_get_current_user(); |
| 115 |
$parent_themes = ctw_giveme_parents(); |
| 116 |
$current_theme = wp_get_theme(); |
| 117 |
?> |
| 118 |
|
| 119 |
<p> |
| 120 |
<?php |
| 121 |
printf( |
| 122 |
/* translators: %s: link to WordPress child theme documentation */ |
| 123 |
esc_html__( 'This simple wizard will help you generate a new %s with just one click.', 'child-theme-wizard' ), |
| 124 |
'<a href="https://developer.wordpress.org/themes/advanced-topics/child-themes/" target="_blank">' . esc_html__( 'Child Theme', 'child-theme-wizard' ) . '</a>' |
| 125 |
); |
| 126 |
?> |
| 127 |
</p> |
| 128 |
<p><?php esc_html_e( 'Select which theme you want to use as a base, fill in the details and click "Create Child Theme".', 'child-theme-wizard' ); ?></p> |
| 129 |
<hr> |
| 130 |
|
| 131 |
<form name="ctwform" method="post" action=""> |
| 132 |
<?php wp_nonce_field( 'ctw_create_child_theme', 'ctw_nonce' ); ?> |
| 133 |
<input type="hidden" name="hiddenfield" value="Y"> |
| 134 |
|
| 135 |
<table class="form-table" role="presentation"> |
| 136 |
<tr> |
| 137 |
<th scope="row"><label for="ctw-parent"><?php esc_html_e( 'Parent Theme', 'child-theme-wizard' ); ?></label></th> |
| 138 |
<td> |
| 139 |
<select name="parent" id="ctw-parent"> |
| 140 |
<?php foreach ( $parent_themes as $theme ) : |
| 141 |
$stylesheet = $theme->get_stylesheet(); |
| 142 |
$name = $theme->get( 'Name' ); |
| 143 |
$is_active = $current_theme->get( 'Name' ) === $name; |
| 144 |
?> |
| 145 |
<option value="<?php echo esc_attr( $stylesheet ); ?>"<?php selected( $is_active ); ?>> |
| 146 |
<?php |
| 147 |
echo esc_html( $name ); |
| 148 |
if ( $is_active ) { |
| 149 |
echo ' (' . esc_html__( 'currently active', 'child-theme-wizard' ) . ')'; |
| 150 |
} |
| 151 |
?> |
| 152 |
</option> |
| 153 |
<?php endforeach; ?> |
| 154 |
</select> |
| 155 |
</td> |
| 156 |
</tr> |
| 157 |
<tr> |
| 158 |
<th scope="row"><label for="ctw-title"><?php esc_html_e( 'Title', 'child-theme-wizard' ); ?></label></th> |
| 159 |
<td> |
| 160 |
<input type="text" name="title" id="ctw-title" value="" class="regular-text"> |
| 161 |
<p class="description"><?php esc_html_e( "What's your new Child Theme called?", 'child-theme-wizard' ); ?></p> |
| 162 |
</td> |
| 163 |
</tr> |
| 164 |
<tr> |
| 165 |
<th scope="row"><label for="ctw-description"><?php esc_html_e( 'Description', 'child-theme-wizard' ); ?></label></th> |
| 166 |
<td> |
| 167 |
<input type="text" name="description" id="ctw-description" value="" class="regular-text"> |
| 168 |
<p class="description"><?php esc_html_e( 'A few notes about your Child Theme.', 'child-theme-wizard' ); ?></p> |
| 169 |
</td> |
| 170 |
</tr> |
| 171 |
<tr> |
| 172 |
<th scope="row"><label for="ctw-url"><?php esc_html_e( 'Child Theme URL', 'child-theme-wizard' ); ?></label></th> |
| 173 |
<td> |
| 174 |
<input type="url" name="url" id="ctw-url" value="" class="regular-text"> |
| 175 |
<p class="description"><?php esc_html_e( 'Does it have a website or release post?', 'child-theme-wizard' ); ?></p> |
| 176 |
</td> |
| 177 |
</tr> |
| 178 |
<tr> |
| 179 |
<th scope="row"><label for="ctw-author"><?php esc_html_e( 'Author', 'child-theme-wizard' ); ?></label></th> |
| 180 |
<td> |
| 181 |
<input type="text" name="author" id="ctw-author" value="<?php echo esc_attr( $current_user->display_name ); ?>" class="regular-text"> |
| 182 |
<p class="description"><?php esc_html_e( "That's you.", 'child-theme-wizard' ); ?></p> |
| 183 |
</td> |
| 184 |
</tr> |
| 185 |
<tr> |
| 186 |
<th scope="row"><label for="ctw-author-url"><?php esc_html_e( 'Author URL', 'child-theme-wizard' ); ?></label></th> |
| 187 |
<td> |
| 188 |
<input type="url" name="author-url" id="ctw-author-url" value="<?php echo esc_url( $current_user->user_url ); ?>" class="regular-text"> |
| 189 |
<p class="description"><?php esc_html_e( "That's your website.", 'child-theme-wizard' ); ?></p> |
| 190 |
</td> |
| 191 |
</tr> |
| 192 |
<tr> |
| 193 |
<th scope="row"><label for="ctw-version"><?php esc_html_e( 'Version', 'child-theme-wizard' ); ?></label></th> |
| 194 |
<td> |
| 195 |
<input type="text" name="version" id="ctw-version" value="1.0" class="small-text"> |
| 196 |
<p class="description"><?php esc_html_e( 'Start with 1.0 and work your way up.', 'child-theme-wizard' ); ?></p> |
| 197 |
</td> |
| 198 |
</tr> |
| 199 |
<tr> |
| 200 |
<th scope="row"><label for="ctw-include-gpl"><?php esc_html_e( 'Include GPL License', 'child-theme-wizard' ); ?></label></th> |
| 201 |
<td> |
| 202 |
<select name="include-gpl" id="ctw-include-gpl"> |
| 203 |
<option value="on"><?php esc_html_e( 'Yes Please!', 'child-theme-wizard' ); ?></option> |
| 204 |
<option value="off"><?php esc_html_e( 'No Thanks', 'child-theme-wizard' ); ?></option> |
| 205 |
</select> |
| 206 |
</td> |
| 207 |
</tr> |
| 208 |
</table> |
| 209 |
|
| 210 |
<p class="submit"> |
| 211 |
<input type="submit" name="Submit" class="button-primary" value="<?php esc_attr_e( 'Create Child Theme', 'child-theme-wizard' ); ?>"> |
| 212 |
</p> |
| 213 |
</form> |
| 214 |
|
| 215 |
<?php |
| 216 |
} |
| 217 |
?> |
| 218 |
|
| 219 |
<br><hr><br> |
| 220 |
</div><!-- .wrap --> |
| 221 |
|
| 222 |
<p><a href="https://wpguru.co.uk" target="_blank"><img src="<?php echo esc_url( plugins_url( 'images/guru-header-2013.png', __FILE__ ) ); ?>" width="300" alt="WP Guru"></a></p> |
| 223 |
|
| 224 |
<p> |
| 225 |
<a href="https://wpguru.co.uk/2014/03/introducing-child-theme-wizard-for-wordpress/" target="_blank">Plugin by Jay Versluis</a> |
| 226 |
| <a href="https://github.com/versluis/Child-Theme-Wizard" target="_blank">Fork me or Contribute on GitHub</a> |
| 227 |
| <a href="https://patreon.com/versluis" target="_blank">Support me on Patreon</a> |
| 228 |
</p> |
| 229 |
|
| 230 |
<?php |
| 231 |
} |
| 232 |
|
| 233 |
function ctw_create_theme( array $childtheme ): array { |
| 234 |
|
| 235 |
$status = array( |
| 236 |
'alert' => 0, |
| 237 |
'message' => '', |
| 238 |
); |
| 239 |
|
| 240 |
global $wp_filesystem; |
| 241 |
if ( empty( $wp_filesystem ) ) { |
| 242 |
require_once ABSPATH . 'wp-admin/includes/file.php'; |
| 243 |
WP_Filesystem(); |
| 244 |
} |
| 245 |
|
| 246 |
$directory = get_theme_root() . '/' . sanitize_file_name( $childtheme['title'] ); |
| 247 |
|
| 248 |
if ( ! $wp_filesystem->mkdir( $directory ) ) { |
| 249 |
$status['message'] = '<p>' . esc_html__( 'Could not create directory. Does it exist already?', 'child-theme-wizard' ) . '</p>'; |
| 250 |
$status['alert'] = -1; |
| 251 |
return $status; |
| 252 |
} |
| 253 |
|
| 254 |
$status['message'] .= '<p>' . esc_html__( 'Directory created successfully.', 'child-theme-wizard' ) . '</p>'; |
| 255 |
|
| 256 |
// Strip */ so user-supplied values cannot break out of the CSS comment block. |
| 257 |
$safe = array_map( fn( $v ) => str_replace( '*/', '', (string) $v ), $childtheme ); |
| 258 |
|
| 259 |
$style = "/* \n Theme Name: " . $safe['title']; |
| 260 |
$style .= "\n Theme URI: " . $safe['url']; |
| 261 |
$style .= "\n Description: " . $safe['description']; |
| 262 |
$style .= "\n Author: " . $safe['author']; |
| 263 |
$style .= "\n Author URI: " . $safe['author-url']; |
| 264 |
$style .= "\n Template: " . $safe['parent']; |
| 265 |
$style .= "\n Version: " . $safe['version']; |
| 266 |
|
| 267 |
if ( $safe['include-gpl'] === 'on' ) { |
| 268 |
$style .= "\n License: GNU General Public License v2 or later"; |
| 269 |
$style .= "\n License URI: http://www.gnu.org/licenses/gpl-2.0.html"; |
| 270 |
} |
| 271 |
|
| 272 |
$style .= "\n\n /* == Add your own styles below this line =="; |
| 273 |
$style .= "\n--------------------------------------------*/\n\n"; |
| 274 |
|
| 275 |
if ( ! $wp_filesystem->put_contents( $directory . '/style.css', $style, FS_CHMOD_FILE ) ) { |
| 276 |
$status['message'] .= '<p>' . esc_html__( 'There was a problem writing data to style.css.', 'child-theme-wizard' ) . '</p>'; |
| 277 |
$status['alert'] = -1; |
| 278 |
return $status; |
| 279 |
} |
| 280 |
|
| 281 |
$status['message'] .= '<p>' . esc_html__( 'Writing data to style.css…', 'child-theme-wizard' ) . '</p>'; |
| 282 |
|
| 283 |
$functions = "<?php\n/*\n\n This file is part of a child theme called " . $safe['title'] . "."; |
| 284 |
$functions .= "\n Functions in this file will be loaded before the parent theme's functions."; |
| 285 |
$functions .= "\n For more information, please read"; |
| 286 |
$functions .= "\n https://developer.wordpress.org/themes/advanced-topics/child-themes/"; |
| 287 |
$functions .= "\n\n*/\n\n"; |
| 288 |
$functions .= "// Loads the parent stylesheet — leave this in place unless you know what you're doing.\n"; |
| 289 |
$functions .= "function your_theme_enqueue_styles() {\n\n"; |
| 290 |
$functions .= " \$parent_style = 'parent-style';\n\n"; |
| 291 |
$functions .= " wp_enqueue_style( \$parent_style,\n"; |
| 292 |
$functions .= " get_template_directory_uri() . '/style.css'\n"; |
| 293 |
$functions .= " );\n\n"; |
| 294 |
$functions .= " wp_enqueue_style( 'child-style',\n"; |
| 295 |
$functions .= " get_stylesheet_directory_uri() . '/style.css',\n"; |
| 296 |
$functions .= " array( \$parent_style ),\n"; |
| 297 |
$functions .= " wp_get_theme()->get( 'Version' )\n"; |
| 298 |
$functions .= " );\n"; |
| 299 |
$functions .= "}\n"; |
| 300 |
$functions .= "add_action( 'wp_enqueue_scripts', 'your_theme_enqueue_styles' );\n\n"; |
| 301 |
$functions .= "/* Add your own functions below this line.\n"; |
| 302 |
$functions .= " ======================================== */\n\n"; |
| 303 |
|
| 304 |
if ( ! $wp_filesystem->put_contents( $directory . '/functions.php', $functions, FS_CHMOD_FILE ) ) { |
| 305 |
$status['message'] .= '<p>' . esc_html__( 'There was a problem writing data to functions.php.', 'child-theme-wizard' ) . '</p>'; |
| 306 |
$status['alert'] = -1; |
| 307 |
return $status; |
| 308 |
} |
| 309 |
|
| 310 |
$status['message'] .= '<p>' . esc_html__( 'Writing data to functions.php…', 'child-theme-wizard' ) . '</p>'; |
| 311 |
|
| 312 |
$status['message'] .= ctw_make_thumbnail( $directory ); |
| 313 |
|
| 314 |
return $status; |
| 315 |
} |
| 316 |
|
| 317 |
function ctw_giveme_parents(): array { |
| 318 |
|
| 319 |
$parent_themes = array(); |
| 320 |
|
| 321 |
foreach ( wp_get_themes() as $theme ) { |
| 322 |
if ( $theme->parent() === false ) { |
| 323 |
$parent_themes[] = $theme; |
| 324 |
} |
| 325 |
} |
| 326 |
|
| 327 |
return $parent_themes; |
| 328 |
} |
| 329 |
|
| 330 |
function ctw_make_thumbnail( string $childpath ): string { |
| 331 |
|
| 332 |
global $wp_filesystem; |
| 333 |
if ( empty( $wp_filesystem ) ) { |
| 334 |
require_once ABSPATH . 'wp-admin/includes/file.php'; |
| 335 |
WP_Filesystem(); |
| 336 |
} |
| 337 |
|
| 338 |
$source = plugin_dir_path( __FILE__ ) . 'images/screenshot.png'; |
| 339 |
$destination = $childpath . '/screenshot.png'; |
| 340 |
|
| 341 |
if ( ! $wp_filesystem->copy( $source, $destination ) ) { |
| 342 |
return '<p>' . esc_html__( 'Could not copy thumbnail image.', 'child-theme-wizard' ) . '</p>'; |
| 343 |
} |
| 344 |
|
| 345 |
return '<p>' . esc_html__( 'Copied thumbnail file — looking good!', 'child-theme-wizard' ) . '</p>'; |
| 346 |
} |
| 347 |
|