run:R W Run
7.85 KB
2026-03-11 16:18:51
R W Run
3.54 KB
2026-03-11 16:18:51
R W Run
148.33 KB
2026-03-11 16:18:51
R W Run
11.45 KB
2026-03-11 16:18:51
R W Run
3.58 KB
2026-03-11 16:18:51
R W Run
2.53 KB
2026-03-11 16:18:51
R W Run
2.6 KB
2026-03-11 16:18:51
R W Run
6.59 KB
2026-03-11 16:18:51
R W Run
14.83 KB
2026-03-11 16:18:51
R W Run
21.18 KB
2026-03-11 16:18:51
R W Run
48.13 KB
2026-03-11 16:18:51
R W Run
4.07 KB
2026-03-11 16:18:51
R W Run
5.3 KB
2026-03-11 16:18:51
R W Run
8.28 KB
2026-03-11 16:18:51
R W Run
26.73 KB
2026-03-11 16:18:51
R W Run
2.8 KB
2026-03-11 16:18:51
R W Run
15.2 KB
2026-03-11 16:18:51
R W Run
192.08 KB
2026-03-11 16:18:51
R W Run
11.77 KB
2026-03-11 16:18:51
R W Run
3.2 KB
2026-03-11 16:18:51
R W Run
22.89 KB
2026-03-11 16:18:51
R W Run
12.77 KB
2026-03-11 16:18:51
R W Run
4.08 KB
2026-03-11 16:18:51
R W Run
26.27 KB
2026-03-11 16:18:51
R W Run
4.97 KB
2026-03-11 16:18:51
R W Run
5.57 KB
2026-03-11 16:18:51
R W Run
13.93 KB
2026-03-11 16:18:51
R W Run
4.09 KB
2026-03-11 16:18:51
R W Run
6.79 KB
2026-03-11 16:18:51
R W Run
60.45 KB
2026-03-11 16:18:51
R W Run
32.4 KB
2026-03-11 16:18:51
R W Run
18.24 KB
2026-03-11 16:18:51
R W Run
66.01 KB
2026-03-11 16:18:51
R W Run
23.84 KB
2026-03-11 16:18:51
R W Run
17.72 KB
2026-03-11 16:18:51
R W Run
22.71 KB
2026-03-11 16:18:51
R W Run
18.05 KB
2026-03-11 16:18:51
R W Run
22.76 KB
2026-03-11 16:18:51
R W Run
7.34 KB
2026-03-11 16:18:51
R W Run
4.51 KB
2026-03-11 16:18:51
R W Run
9.02 KB
2026-03-11 16:18:51
R W Run
1.46 KB
2026-03-11 16:18:51
R W Run
51.76 KB
2026-03-11 16:18:51
R W Run
25.29 KB
2026-03-11 16:18:51
R W Run
21.61 KB
2026-03-11 16:18:51
R W Run
27.77 KB
2026-03-11 16:18:51
R W Run
15.35 KB
2026-03-11 16:18:51
R W Run
24.54 KB
2026-03-11 16:18:51
R W Run
56.44 KB
2026-03-11 16:18:51
R W Run
1.42 KB
2026-03-11 16:18:51
R W Run
63.66 KB
2026-03-11 16:18:51
R W Run
31.9 KB
2026-03-11 16:18:51
R W Run
14.44 KB
2026-03-11 16:18:51
R W Run
36.47 KB
2026-03-11 16:18:51
R W Run
14 KB
2026-03-11 16:18:51
R W Run
121.89 KB
2026-03-11 16:18:51
R W Run
6.26 KB
2026-03-11 16:18:51
R W Run
20.73 KB
2026-03-11 16:18:51
R W Run
15.23 KB
2026-03-11 16:18:51
R W Run
10.14 KB
2026-03-11 16:18:51
R W Run
6.94 KB
2026-03-11 16:18:51
R W Run
1.44 KB
2026-03-11 16:18:51
R W Run
46.85 KB
2026-03-11 16:18:51
R W Run
18.61 KB
2026-03-11 16:18:51
R W Run
6.08 KB
2026-03-11 16:18:51
R W Run
20.06 KB
2026-03-11 16:18:51
R W Run
5.73 KB
2026-03-11 16:18:51
R W Run
68.18 KB
2026-03-11 16:18:51
R W Run
40.8 KB
2026-03-11 16:18:51
R W Run
1.44 KB
2026-03-11 16:18:51
R W Run
25.26 KB
2026-03-11 16:18:51
R W Run
95.94 KB
2026-03-11 16:18:51
R W Run
43.12 KB
2026-03-11 16:18:51
R W Run
41.73 KB
2026-03-11 16:18:51
R W Run
6.46 KB
2026-03-11 16:18:51
R W Run
3.71 KB
2026-03-11 16:18:51
R W Run
116.31 KB
2026-03-11 16:18:51
R W Run
9.39 KB
2026-03-11 16:18:51
R W Run
64.34 KB
2026-03-11 16:18:51
R W Run
44.73 KB
2026-03-11 16:18:51
R W Run
1.27 KB
2026-03-11 16:18:51
R W Run
3.68 KB
2026-03-11 16:18:51
R W Run
33.53 KB
2026-03-11 16:18:51
R W Run
48.84 KB
2026-03-11 16:18:51
R W Run
26.35 KB
2026-03-11 16:18:51
R W Run
1.12 KB
2026-03-11 16:18:51
R W Run
4.19 KB
2026-03-11 16:18:51
R W Run
38.19 KB
2026-03-11 16:18:51
R W Run
91.33 KB
2026-03-11 16:18:51
R W Run
80.39 KB
2026-03-11 16:18:51
R W Run
32.67 KB
2026-03-11 16:18:51
R W Run
16.18 KB
2026-03-11 16:18:51
R W Run
44.46 KB
2026-03-11 16:18:51
R W Run
6.23 KB
2026-03-11 16:18:51
R W Run
8.23 KB
2026-03-11 16:18:51
R W Run
96.96 KB
2026-03-11 16:18:51
R W Run
6.83 KB
2026-03-11 16:18:51
R W Run
46.62 KB
2026-03-11 16:18:51
R W Run
10.82 KB
2026-03-11 16:18:51
R W Run
68.86 KB
2026-03-11 16:18:51
R W Run
33.63 KB
2026-03-11 16:18:51
R W Run
113.3 KB
2026-03-11 16:18:51
R W Run
22.98 KB
2026-03-11 16:18:51
R W Run
10.66 KB
2026-03-11 16:18:51
R W Run
error_log
📄theme.php
1<?php
2/**
3 * WordPress Theme Administration API
4 *
5 * @package WordPress
6 * @subpackage Administration
7 */
8
9/**
10 * Removes a theme.
11 *
12 * @since 2.8.0
13 *
14 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
15 *
16 * @param string $stylesheet Stylesheet of the theme to delete.
17 * @param string $redirect Redirect to page when complete.
18 * @return bool|null|WP_Error True on success, false if `$stylesheet` is empty, WP_Error on failure.
19 * Null if filesystem credentials are required to proceed.
20 */
21function delete_theme( $stylesheet, $redirect = '' ) {
22 global $wp_filesystem;
23
24 if ( empty( $stylesheet ) ) {
25 return false;
26 }
27
28 if ( empty( $redirect ) ) {
29 $redirect = wp_nonce_url( 'themes.php?action=delete&stylesheet=' . urlencode( $stylesheet ), 'delete-theme_' . $stylesheet );
30 }
31
32 ob_start();
33 $credentials = request_filesystem_credentials( $redirect );
34 $data = ob_get_clean();
35
36 if ( false === $credentials ) {
37 if ( ! empty( $data ) ) {
38 require_once ABSPATH . 'wp-admin/admin-header.php';
39 echo $data;
40 require_once ABSPATH . 'wp-admin/admin-footer.php';
41 exit;
42 }
43 return;
44 }
45
46 if ( ! WP_Filesystem( $credentials ) ) {
47 ob_start();
48 // Failed to connect. Error and request again.
49 request_filesystem_credentials( $redirect, '', true );
50 $data = ob_get_clean();
51
52 if ( ! empty( $data ) ) {
53 require_once ABSPATH . 'wp-admin/admin-header.php';
54 echo $data;
55 require_once ABSPATH . 'wp-admin/admin-footer.php';
56 exit;
57 }
58 return;
59 }
60
61 if ( ! is_object( $wp_filesystem ) ) {
62 return new WP_Error( 'fs_unavailable', __( 'Could not access filesystem.' ) );
63 }
64
65 if ( is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
66 return new WP_Error( 'fs_error', __( 'Filesystem error.' ), $wp_filesystem->errors );
67 }
68
69 // Get the base theme folder.
70 $themes_dir = $wp_filesystem->wp_themes_dir();
71 if ( empty( $themes_dir ) ) {
72 return new WP_Error( 'fs_no_themes_dir', __( 'Unable to locate WordPress theme directory.' ) );
73 }
74
75 /**
76 * Fires immediately before a theme deletion attempt.
77 *
78 * @since 5.8.0
79 *
80 * @param string $stylesheet Stylesheet of the theme to delete.
81 */
82 do_action( 'delete_theme', $stylesheet );
83
84 $theme = wp_get_theme( $stylesheet );
85
86 $themes_dir = trailingslashit( $themes_dir );
87 $theme_dir = trailingslashit( $themes_dir . $stylesheet );
88 $deleted = $wp_filesystem->delete( $theme_dir, true );
89
90 /**
91 * Fires immediately after a theme deletion attempt.
92 *
93 * @since 5.8.0
94 *
95 * @param string $stylesheet Stylesheet of the theme to delete.
96 * @param bool $deleted Whether the theme deletion was successful.
97 */
98 do_action( 'deleted_theme', $stylesheet, $deleted );
99
100 if ( ! $deleted ) {
101 return new WP_Error(
102 'could_not_remove_theme',
103 /* translators: %s: Theme name. */
104 sprintf( __( 'Could not fully remove the theme %s.' ), $stylesheet )
105 );
106 }
107
108 $theme_translations = wp_get_installed_translations( 'themes' );
109
110 // Remove language files, silently.
111 if ( ! empty( $theme_translations[ $stylesheet ] ) ) {
112 $translations = $theme_translations[ $stylesheet ];
113
114 foreach ( $translations as $translation => $data ) {
115 $wp_filesystem->delete( WP_LANG_DIR . '/themes/' . $stylesheet . '-' . $translation . '.po' );
116 $wp_filesystem->delete( WP_LANG_DIR . '/themes/' . $stylesheet . '-' . $translation . '.mo' );
117 $wp_filesystem->delete( WP_LANG_DIR . '/themes/' . $stylesheet . '-' . $translation . '.l10n.php' );
118
119 $json_translation_files = glob( WP_LANG_DIR . '/themes/' . $stylesheet . '-' . $translation . '-*.json' );
120 if ( $json_translation_files ) {
121 array_map( array( $wp_filesystem, 'delete' ), $json_translation_files );
122 }
123 }
124 }
125
126 // Remove the theme from allowed themes on the network.
127 if ( is_multisite() ) {
128 WP_Theme::network_disable_theme( $stylesheet );
129 }
130
131 // Clear theme caches.
132 $theme->cache_delete();
133
134 // Force refresh of theme update information.
135 delete_site_transient( 'update_themes' );
136
137 return true;
138}
139
140/**
141 * Gets the page templates available in this theme.
142 *
143 * @since 1.5.0
144 * @since 4.7.0 Added the `$post_type` parameter.
145 *
146 * @param WP_Post|null $post Optional. The post being edited, provided for context.
147 * @param string $post_type Optional. Post type to get the templates for. Default 'page'.
148 * @return string[] Array of template file names keyed by the template header name.
149 */
150function get_page_templates( $post = null, $post_type = 'page' ) {
151 return array_flip( wp_get_theme()->get_page_templates( $post, $post_type ) );
152}
153
154/**
155 * Tidies a filename for url display by the theme file editor.
156 *
157 * @since 2.9.0
158 * @access private
159 *
160 * @param string $fullpath Full path to the theme file
161 * @param string $containingfolder Path of the theme parent folder
162 * @return string
163 */
164function _get_template_edit_filename( $fullpath, $containingfolder ) {
165 return str_replace( dirname( $containingfolder, 2 ), '', $fullpath );
166}
167
168/**
169 * Check if there is an update for a theme available.
170 *
171 * Will display link, if there is an update available.
172 *
173 * @since 2.7.0
174 *
175 * @see get_theme_update_available()
176 *
177 * @param WP_Theme $theme Theme data object.
178 */
179function theme_update_available( $theme ) {
180 echo get_theme_update_available( $theme );
181}
182
183/**
184 * Retrieves the update link if there is a theme update available.
185 *
186 * Will return a link if there is an update available.
187 *
188 * @since 3.8.0
189 *
190 * @param WP_Theme $theme WP_Theme object.
191 * @return string|false HTML for the update link, or false if invalid info was passed.
192 */
193function get_theme_update_available( $theme ) {
194 static $themes_update = null;
195
196 if ( ! current_user_can( 'update_themes' ) ) {
197 return false;
198 }
199
200 if ( ! isset( $themes_update ) ) {
201 $themes_update = get_site_transient( 'update_themes' );
202 }
203
204 if ( ! ( $theme instanceof WP_Theme ) ) {
205 return false;
206 }
207
208 $stylesheet = $theme->get_stylesheet();
209
210 $html = '';
211
212 if ( isset( $themes_update->response[ $stylesheet ] ) ) {
213 $update = $themes_update->response[ $stylesheet ];
214 $theme_name = $theme->display( 'Name' );
215 $details_url = add_query_arg(
216 array(
217 'TB_iframe' => 'true',
218 'width' => 1024,
219 'height' => 800,
220 ),
221 $update['url']
222 ); // Theme browser inside WP? Replace this. Also, theme preview JS will override this on the available list.
223 $update_url = wp_nonce_url( admin_url( 'update.php?action=upgrade-theme&amp;theme=' . urlencode( $stylesheet ) ), 'upgrade-theme_' . $stylesheet );
224
225 if ( ! is_multisite() ) {
226 if ( ! current_user_can( 'update_themes' ) ) {
227 $html = sprintf(
228 /* translators: 1: Theme name, 2: Theme details URL, 3: Additional link attributes, 4: Version number. */
229 '<p><strong>' . __( 'There is a new version of %1$s available. <a href="%2$s" %3$s>View version %4$s details</a>.' ) . '</strong></p>',
230 $theme_name,
231 esc_url( $details_url ),
232 sprintf(
233 'class="thickbox open-plugin-details-modal" aria-label="%s"',
234 /* translators: 1: Theme name, 2: Version number. */
235 esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $theme_name, $update['new_version'] ) )
236 ),
237 $update['new_version']
238 );
239 } elseif ( empty( $update['package'] ) ) {
240 $html = sprintf(
241 /* translators: 1: Theme name, 2: Theme details URL, 3: Additional link attributes, 4: Version number. */
242 '<p><strong>' . __( 'There is a new version of %1$s available. <a href="%2$s" %3$s>View version %4$s details</a>. <em>Automatic update is unavailable for this theme.</em>' ) . '</strong></p>',
243 $theme_name,
244 esc_url( $details_url ),
245 sprintf(
246 'class="thickbox open-plugin-details-modal" aria-label="%s"',
247 /* translators: 1: Theme name, 2: Version number. */
248 esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $theme_name, $update['new_version'] ) )
249 ),
250 $update['new_version']
251 );
252 } else {
253 $html = sprintf(
254 /* translators: 1: Theme name, 2: Theme details URL, 3: Additional link attributes, 4: Version number, 5: Update URL, 6: Additional link attributes. */
255 '<p><strong>' . __( 'There is a new version of %1$s available. <a href="%2$s" %3$s>View version %4$s details</a> or <a href="%5$s" %6$s>update now</a>.' ) . '</strong></p>',
256 $theme_name,
257 esc_url( $details_url ),
258 sprintf(
259 'class="thickbox open-plugin-details-modal" aria-label="%s"',
260 /* translators: 1: Theme name, 2: Version number. */
261 esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $theme_name, $update['new_version'] ) )
262 ),
263 $update['new_version'],
264 $update_url,
265 sprintf(
266 'aria-label="%s" id="update-theme" data-slug="%s"',
267 /* translators: %s: Theme name. */
268 esc_attr( sprintf( _x( 'Update %s now', 'theme' ), $theme_name ) ),
269 $stylesheet
270 )
271 );
272 }
273 }
274 }
275
276 return $html;
277}
278
279/**
280 * Retrieves list of WordPress theme features (aka theme tags).
281 *
282 * @since 3.1.0
283 * @since 3.2.0 Added 'Gray' color and 'Featured Image Header', 'Featured Images',
284 * 'Full Width Template', and 'Post Formats' features.
285 * @since 3.5.0 Added 'Flexible Header' feature.
286 * @since 3.8.0 Renamed 'Width' filter to 'Layout'.
287 * @since 3.8.0 Renamed 'Fixed Width' and 'Flexible Width' options
288 * to 'Fixed Layout' and 'Fluid Layout'.
289 * @since 3.8.0 Added 'Accessibility Ready' feature and 'Responsive Layout' option.
290 * @since 3.9.0 Combined 'Layout' and 'Columns' filters.
291 * @since 4.6.0 Removed 'Colors' filter.
292 * @since 4.6.0 Added 'Grid Layout' option.
293 * Removed 'Fixed Layout', 'Fluid Layout', and 'Responsive Layout' options.
294 * @since 4.6.0 Added 'Custom Logo' and 'Footer Widgets' features.
295 * Removed 'Blavatar' feature.
296 * @since 4.6.0 Added 'Blog', 'E-Commerce', 'Education', 'Entertainment', 'Food & Drink',
297 * 'Holiday', 'News', 'Photography', and 'Portfolio' subjects.
298 * Removed 'Photoblogging' and 'Seasonal' subjects.
299 * @since 4.9.0 Reordered the filters from 'Layout', 'Features', 'Subject'
300 * to 'Subject', 'Features', 'Layout'.
301 * @since 4.9.0 Removed 'BuddyPress', 'Custom Menu', 'Flexible Header',
302 * 'Front Page Posting', 'Microformats', 'RTL Language Support',
303 * 'Threaded Comments', and 'Translation Ready' features.
304 * @since 5.5.0 Added 'Block Editor Patterns', 'Block Editor Styles',
305 * and 'Full Site Editing' features.
306 * @since 5.5.0 Added 'Wide Blocks' layout option.
307 * @since 5.8.1 Added 'Template Editing' feature.
308 * @since 6.1.1 Replaced 'Full Site Editing' feature name with 'Site Editor'.
309 * @since 6.2.0 Added 'Style Variations' feature.
310 *
311 * @param bool $api Optional. Whether try to fetch tags from the WordPress.org API. Defaults to true.
312 * @return array Array of features keyed by category with translations keyed by slug.
313 */
314function get_theme_feature_list( $api = true ) {
315 // Hard-coded list is used if API is not accessible.
316 $features = array(
317
318 __( 'Subject' ) => array(
319 'blog' => __( 'Blog' ),
320 'e-commerce' => __( 'E-Commerce' ),
321 'education' => __( 'Education' ),
322 'entertainment' => __( 'Entertainment' ),
323 'food-and-drink' => __( 'Food & Drink' ),
324 'holiday' => __( 'Holiday' ),
325 'news' => __( 'News' ),
326 'photography' => __( 'Photography' ),
327 'portfolio' => __( 'Portfolio' ),
328 ),
329
330 __( 'Features' ) => array(
331 'accessibility-ready' => __( 'Accessibility Ready' ),
332 'block-patterns' => __( 'Block Editor Patterns' ),
333 'block-styles' => __( 'Block Editor Styles' ),
334 'custom-background' => __( 'Custom Background' ),
335 'custom-colors' => __( 'Custom Colors' ),
336 'custom-header' => __( 'Custom Header' ),
337 'custom-logo' => __( 'Custom Logo' ),
338 'editor-style' => __( 'Editor Style' ),
339 'featured-image-header' => __( 'Featured Image Header' ),
340 'featured-images' => __( 'Featured Images' ),
341 'footer-widgets' => __( 'Footer Widgets' ),
342 'full-site-editing' => __( 'Site Editor' ),
343 'full-width-template' => __( 'Full Width Template' ),
344 'post-formats' => __( 'Post Formats' ),
345 'sticky-post' => __( 'Sticky Post' ),
346 'style-variations' => __( 'Style Variations' ),
347 'template-editing' => __( 'Template Editing' ),
348 'theme-options' => __( 'Theme Options' ),
349 ),
350
351 __( 'Layout' ) => array(
352 'grid-layout' => __( 'Grid Layout' ),
353 'one-column' => __( 'One Column' ),
354 'two-columns' => __( 'Two Columns' ),
355 'three-columns' => __( 'Three Columns' ),
356 'four-columns' => __( 'Four Columns' ),
357 'left-sidebar' => __( 'Left Sidebar' ),
358 'right-sidebar' => __( 'Right Sidebar' ),
359 'wide-blocks' => __( 'Wide Blocks' ),
360 ),
361
362 );
363
364 if ( ! $api || ! current_user_can( 'install_themes' ) ) {
365 return $features;
366 }
367
368 $feature_list = get_site_transient( 'wporg_theme_feature_list' );
369 if ( ! $feature_list ) {
370 set_site_transient( 'wporg_theme_feature_list', array(), 3 * HOUR_IN_SECONDS );
371 }
372
373 if ( ! $feature_list ) {
374 $feature_list = themes_api( 'feature_list', array() );
375 if ( is_wp_error( $feature_list ) ) {
376 return $features;
377 }
378 }
379
380 if ( ! $feature_list ) {
381 return $features;
382 }
383
384 set_site_transient( 'wporg_theme_feature_list', $feature_list, 3 * HOUR_IN_SECONDS );
385
386 $category_translations = array(
387 'Layout' => __( 'Layout' ),
388 'Features' => __( 'Features' ),
389 'Subject' => __( 'Subject' ),
390 );
391
392 $wporg_features = array();
393
394 // Loop over the wp.org canonical list and apply translations.
395 foreach ( (array) $feature_list as $feature_category => $feature_items ) {
396 if ( isset( $category_translations[ $feature_category ] ) ) {
397 $feature_category = $category_translations[ $feature_category ];
398 }
399
400 $wporg_features[ $feature_category ] = array();
401
402 foreach ( $feature_items as $feature ) {
403 if ( isset( $features[ $feature_category ][ $feature ] ) ) {
404 $wporg_features[ $feature_category ][ $feature ] = $features[ $feature_category ][ $feature ];
405 } else {
406 $wporg_features[ $feature_category ][ $feature ] = $feature;
407 }
408 }
409 }
410
411 return $wporg_features;
412}
413
414/**
415 * Retrieves theme installer pages from the WordPress.org Themes API.
416 *
417 * It is possible for a theme to override the Themes API result with three
418 * filters. Assume this is for themes, which can extend on the Theme Info to
419 * offer more choices. This is very powerful and must be used with care, when
420 * overriding the filters.
421 *
422 * The first filter, {@see 'themes_api_args'}, is for the args and gives the action
423 * as the second parameter. The hook for {@see 'themes_api_args'} must ensure that
424 * an object is returned.
425 *
426 * The second filter, {@see 'themes_api'}, allows a plugin to override the WordPress.org
427 * Theme API entirely. If `$action` is 'query_themes', 'theme_information', or 'feature_list',
428 * an object MUST be passed. If `$action` is 'hot_tags', an array should be passed.
429 *
430 * Finally, the third filter, {@see 'themes_api_result'}, makes it possible to filter the
431 * response object or array, depending on the `$action` type.
432 *
433 * Supported arguments per action:
434 *
435 * | Argument Name | 'query_themes' | 'theme_information' | 'hot_tags' | 'feature_list' |
436 * | -------------------| :------------: | :-----------------: | :--------: | :--------------: |
437 * | `$slug` | No | Yes | No | No |
438 * | `$per_page` | Yes | No | No | No |
439 * | `$page` | Yes | No | No | No |
440 * | `$number` | No | No | Yes | No |
441 * | `$search` | Yes | No | No | No |
442 * | `$tag` | Yes | No | No | No |
443 * | `$author` | Yes | No | No | No |
444 * | `$user` | Yes | No | No | No |
445 * | `$browse` | Yes | No | No | No |
446 * | `$locale` | Yes | Yes | No | No |
447 * | `$fields` | Yes | Yes | No | No |
448 *
449 * @since 2.8.0
450 *
451 * @param string $action API action to perform: Accepts 'query_themes', 'theme_information',
452 * 'hot_tags' or 'feature_list'.
453 * @param array|object $args {
454 * Optional. Array or object of arguments to serialize for the Themes API. Default empty array.
455 *
456 * @type string $slug The theme slug. Default empty.
457 * @type int $per_page Number of themes per page. Default 24.
458 * @type int $page Number of current page. Default 1.
459 * @type int $number Number of tags to be queried.
460 * @type string $search A search term. Default empty.
461 * @type string $tag Tag to filter themes. Default empty.
462 * @type string $author Username of an author to filter themes. Default empty.
463 * @type string $user Username to query for their favorites. Default empty.
464 * @type string $browse Browse view: 'featured', 'popular', 'updated', 'favorites'.
465 * @type string $locale Locale to provide context-sensitive results. Default is the value of get_locale().
466 * @type array $fields {
467 * Array of fields which should or should not be returned.
468 *
469 * @type bool $description Whether to return the theme full description. Default false.
470 * @type bool $sections Whether to return the theme readme sections: description, installation,
471 * FAQ, screenshots, other notes, and changelog. Default false.
472 * @type bool $rating Whether to return the rating in percent and total number of ratings.
473 * Default false.
474 * @type bool $ratings Whether to return the number of rating for each star (1-5). Default false.
475 * @type bool $downloaded Whether to return the download count. Default false.
476 * @type bool $downloadlink Whether to return the download link for the package. Default false.
477 * @type bool $last_updated Whether to return the date of the last update. Default false.
478 * @type bool $tags Whether to return the assigned tags. Default false.
479 * @type bool $homepage Whether to return the theme homepage link. Default false.
480 * @type bool $screenshots Whether to return the screenshots. Default false.
481 * @type int $screenshot_count Number of screenshots to return. Default 1.
482 * @type bool $screenshot_url Whether to return the URL of the first screenshot. Default false.
483 * @type bool $photon_screenshots Whether to return the screenshots via Photon. Default false.
484 * @type bool $template Whether to return the slug of the parent theme. Default false.
485 * @type bool $parent Whether to return the slug, name and homepage of the parent theme. Default false.
486 * @type bool $versions Whether to return the list of all available versions. Default false.
487 * @type bool $theme_url Whether to return theme's URL. Default false.
488 * @type bool $extended_author Whether to return nicename or nicename and display name. Default false.
489 * }
490 * }
491 * @return object|array|WP_Error Response object or array on success, WP_Error on failure. See the
492 * {@link https://developer.wordpress.org/reference/functions/themes_api/ function reference article}
493 * for more information on the make-up of possible return objects depending on the value of `$action`.
494 */
495function themes_api( $action, $args = array() ) {
496 if ( is_array( $args ) ) {
497 $args = (object) $args;
498 }
499
500 if ( 'query_themes' === $action ) {
501 if ( ! isset( $args->per_page ) ) {
502 $args->per_page = 24;
503 }
504 }
505
506 if ( ! isset( $args->locale ) ) {
507 $args->locale = get_user_locale();
508 }
509
510 if ( ! isset( $args->wp_version ) ) {
511 $args->wp_version = substr( wp_get_wp_version(), 0, 3 ); // x.y
512 }
513
514 /**
515 * Filters arguments used to query for installer pages from the WordPress.org Themes API.
516 *
517 * Important: An object MUST be returned to this filter.
518 *
519 * @since 2.8.0
520 *
521 * @param object $args Arguments used to query for installer pages from the WordPress.org Themes API.
522 * @param string $action Requested action. Likely values are 'theme_information',
523 * 'feature_list', or 'query_themes'.
524 */
525 $args = apply_filters( 'themes_api_args', $args, $action );
526
527 /**
528 * Filters whether to override the WordPress.org Themes API.
529 *
530 * Returning a non-false value will effectively short-circuit the WordPress.org API request.
531 *
532 * If `$action` is 'query_themes', 'theme_information', or 'feature_list', an object MUST
533 * be passed. If `$action` is 'hot_tags', an array should be passed.
534 *
535 * @since 2.8.0
536 *
537 * @param false|object|array $override Whether to override the WordPress.org Themes API. Default false.
538 * @param string $action Requested action. Likely values are 'theme_information',
539 * 'feature_list', or 'query_themes'.
540 * @param object $args Arguments used to query for installer pages from the Themes API.
541 */
542 $res = apply_filters( 'themes_api', false, $action, $args );
543
544 if ( ! $res ) {
545 $url = 'http://api.wordpress.org/themes/info/1.2/';
546 $url = add_query_arg(
547 array(
548 'action' => $action,
549 'request' => $args,
550 ),
551 $url
552 );
553
554 $http_url = $url;
555 $ssl = wp_http_supports( array( 'ssl' ) );
556 if ( $ssl ) {
557 $url = set_url_scheme( $url, 'https' );
558 }
559
560 $http_args = array(
561 'timeout' => 15,
562 'user-agent' => 'WordPress/' . wp_get_wp_version() . '; ' . home_url( '/' ),
563 );
564 $request = wp_remote_get( $url, $http_args );
565
566 if ( $ssl && is_wp_error( $request ) ) {
567 if ( ! wp_doing_ajax() ) {
568 wp_trigger_error(
569 __FUNCTION__,
570 sprintf(
571 /* translators: %s: Support forums URL. */
572 __( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server&#8217;s configuration. If you continue to have problems, please try the <a href="%s">support forums</a>.' ),
573 __( 'https://wordpress.org/support/forums/' )
574 ) . ' ' . __( '(WordPress could not establish a secure connection to WordPress.org. Please contact your server administrator.)' ),
575 headers_sent() || WP_DEBUG ? E_USER_WARNING : E_USER_NOTICE
576 );
577 }
578 $request = wp_remote_get( $http_url, $http_args );
579 }
580
581 if ( is_wp_error( $request ) ) {
582 $res = new WP_Error(
583 'themes_api_failed',
584 sprintf(
585 /* translators: %s: Support forums URL. */
586 __( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server&#8217;s configuration. If you continue to have problems, please try the <a href="%s">support forums</a>.' ),
587 __( 'https://wordpress.org/support/forums/' )
588 ),
589 $request->get_error_message()
590 );
591 } else {
592 $res = json_decode( wp_remote_retrieve_body( $request ), true );
593 if ( is_array( $res ) ) {
594 // Object casting is required in order to match the info/1.0 format.
595 $res = (object) $res;
596 } elseif ( null === $res ) {
597 $res = new WP_Error(
598 'themes_api_failed',
599 sprintf(
600 /* translators: %s: Support forums URL. */
601 __( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server&#8217;s configuration. If you continue to have problems, please try the <a href="%s">support forums</a>.' ),
602 __( 'https://wordpress.org/support/forums/' )
603 ),
604 wp_remote_retrieve_body( $request )
605 );
606 }
607
608 if ( isset( $res->error ) ) {
609 $res = new WP_Error( 'themes_api_failed', $res->error );
610 }
611 }
612
613 if ( ! is_wp_error( $res ) ) {
614 // Back-compat for info/1.2 API, upgrade the theme objects in query_themes to objects.
615 if ( 'query_themes' === $action ) {
616 foreach ( $res->themes as $i => $theme ) {
617 $res->themes[ $i ] = (object) $theme;
618 }
619 }
620
621 // Back-compat for info/1.2 API, downgrade the feature_list result back to an array.
622 if ( 'feature_list' === $action ) {
623 $res = (array) $res;
624 }
625 }
626 }
627
628 /**
629 * Filters the returned WordPress.org Themes API response.
630 *
631 * @since 2.8.0
632 *
633 * @param array|stdClass|WP_Error $res WordPress.org Themes API response.
634 * @param string $action Requested action. Likely values are 'theme_information',
635 * 'feature_list', or 'query_themes'.
636 * @param stdClass $args Arguments used to query for installer pages from the WordPress.org Themes API.
637 */
638 return apply_filters( 'themes_api_result', $res, $action, $args );
639}
640
641/**
642 * Prepares themes for JavaScript.
643 *
644 * @since 3.8.0
645 *
646 * @param WP_Theme[] $themes Optional. Array of theme objects to prepare.
647 * Defaults to all allowed themes.
648 *
649 * @return array An associative array of theme data, sorted by name.
650 */
651function wp_prepare_themes_for_js( $themes = null ) {
652 $current_theme = get_stylesheet();
653
654 /**
655 * Filters theme data before it is prepared for JavaScript.
656 *
657 * Passing a non-empty array will result in wp_prepare_themes_for_js() returning
658 * early with that value instead.
659 *
660 * @since 4.2.0
661 *
662 * @param array $prepared_themes An associative array of theme data. Default empty array.
663 * @param WP_Theme[]|null $themes An array of theme objects to prepare, if any.
664 * @param string $current_theme The active theme slug.
665 */
666 $prepared_themes = (array) apply_filters( 'pre_prepare_themes_for_js', array(), $themes, $current_theme );
667
668 if ( ! empty( $prepared_themes ) ) {
669 return $prepared_themes;
670 }
671
672 // Make sure the active theme is listed first.
673 $prepared_themes[ $current_theme ] = array();
674
675 if ( null === $themes ) {
676 $themes = wp_get_themes( array( 'allowed' => true ) );
677 if ( ! isset( $themes[ $current_theme ] ) ) {
678 $themes[ $current_theme ] = wp_get_theme();
679 }
680 }
681
682 $updates = array();
683 $no_updates = array();
684 if ( ! is_multisite() && current_user_can( 'update_themes' ) ) {
685 $updates_transient = get_site_transient( 'update_themes' );
686 if ( isset( $updates_transient->response ) ) {
687 $updates = $updates_transient->response;
688 }
689 if ( isset( $updates_transient->no_update ) ) {
690 $no_updates = $updates_transient->no_update;
691 }
692 }
693
694 WP_Theme::sort_by_name( $themes );
695
696 $parents = array();
697
698 $auto_updates = (array) get_site_option( 'auto_update_themes', array() );
699
700 foreach ( $themes as $theme ) {
701 $slug = $theme->get_stylesheet();
702 $encoded_slug = urlencode( $slug );
703
704 $parent = false;
705 if ( $theme->parent() ) {
706 $parent = $theme->parent();
707 $parents[ $slug ] = $parent->get_stylesheet();
708 $parent = $parent->display( 'Name' );
709 }
710
711 $customize_action = null;
712
713 $can_edit_theme_options = current_user_can( 'edit_theme_options' );
714 $can_customize = current_user_can( 'customize' );
715 $is_block_theme = $theme->is_block_theme();
716
717 if ( $is_block_theme && $can_edit_theme_options ) {
718 $customize_action = admin_url( 'site-editor.php' );
719 if ( $current_theme !== $slug ) {
720 $customize_action = add_query_arg( 'wp_theme_preview', $slug, $customize_action );
721 }
722 } elseif ( ! $is_block_theme && $can_customize && $can_edit_theme_options ) {
723 $customize_action = wp_customize_url( $slug );
724 }
725 if ( null !== $customize_action ) {
726 $customize_action = add_query_arg(
727 array(
728 'return' => urlencode( sanitize_url( remove_query_arg( wp_removable_query_args(), wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) ),
729 ),
730 $customize_action
731 );
732 $customize_action = esc_url( $customize_action );
733 }
734
735 $update_requires_wp = isset( $updates[ $slug ]['requires'] ) ? $updates[ $slug ]['requires'] : null;
736 $update_requires_php = isset( $updates[ $slug ]['requires_php'] ) ? $updates[ $slug ]['requires_php'] : null;
737
738 $auto_update = in_array( $slug, $auto_updates, true );
739 $auto_update_action = $auto_update ? 'disable-auto-update' : 'enable-auto-update';
740
741 if ( isset( $updates[ $slug ] ) ) {
742 $auto_update_supported = true;
743 $auto_update_filter_payload = (object) $updates[ $slug ];
744 } elseif ( isset( $no_updates[ $slug ] ) ) {
745 $auto_update_supported = true;
746 $auto_update_filter_payload = (object) $no_updates[ $slug ];
747 } else {
748 $auto_update_supported = false;
749 /*
750 * Create the expected payload for the auto_update_theme filter, this is the same data
751 * as contained within $updates or $no_updates but used when the Theme is not known.
752 */
753 $auto_update_filter_payload = (object) array(
754 'theme' => $slug,
755 'new_version' => $theme->get( 'Version' ),
756 'url' => '',
757 'package' => '',
758 'requires' => $theme->get( 'RequiresWP' ),
759 'requires_php' => $theme->get( 'RequiresPHP' ),
760 );
761 }
762
763 $auto_update_forced = wp_is_auto_update_forced_for_item( 'theme', null, $auto_update_filter_payload );
764
765 $prepared_themes[ $slug ] = array(
766 'id' => $slug,
767 'name' => $theme->display( 'Name' ),
768 'screenshot' => array( $theme->get_screenshot() ), // @todo Multiple screenshots.
769 'description' => $theme->display( 'Description' ),
770 'author' => $theme->display( 'Author', false, true ),
771 'authorAndUri' => $theme->display( 'Author' ),
772 'tags' => $theme->display( 'Tags' ),
773 'version' => $theme->get( 'Version' ),
774 'compatibleWP' => is_wp_version_compatible( $theme->get( 'RequiresWP' ) ),
775 'compatiblePHP' => is_php_version_compatible( $theme->get( 'RequiresPHP' ) ),
776 'updateResponse' => array(
777 'compatibleWP' => is_wp_version_compatible( $update_requires_wp ),
778 'compatiblePHP' => is_php_version_compatible( $update_requires_php ),
779 ),
780 'parent' => $parent,
781 'active' => $slug === $current_theme,
782 'hasUpdate' => isset( $updates[ $slug ] ),
783 'hasPackage' => isset( $updates[ $slug ] ) && ! empty( $updates[ $slug ]['package'] ),
784 'update' => get_theme_update_available( $theme ),
785 'autoupdate' => array(
786 'enabled' => $auto_update || $auto_update_forced,
787 'supported' => $auto_update_supported,
788 'forced' => $auto_update_forced,
789 ),
790 'actions' => array(
791 'activate' => current_user_can( 'switch_themes' ) ? wp_nonce_url( admin_url( 'themes.php?action=activate&amp;stylesheet=' . $encoded_slug ), 'switch-theme_' . $slug ) : null,
792 'customize' => $customize_action,
793 'delete' => ( ! is_multisite() && current_user_can( 'delete_themes' ) ) ? wp_nonce_url( admin_url( 'themes.php?action=delete&amp;stylesheet=' . $encoded_slug ), 'delete-theme_' . $slug ) : null,
794 'autoupdate' => wp_is_auto_update_enabled_for_type( 'theme' ) && ! is_multisite() && current_user_can( 'update_themes' )
795 ? wp_nonce_url( admin_url( 'themes.php?action=' . $auto_update_action . '&amp;stylesheet=' . $encoded_slug ), 'updates' )
796 : null,
797 ),
798 'blockTheme' => $theme->is_block_theme(),
799 );
800 }
801
802 // Remove 'delete' action if theme has an active child.
803 if ( ! empty( $parents ) && array_key_exists( $current_theme, $parents ) ) {
804 unset( $prepared_themes[ $parents[ $current_theme ] ]['actions']['delete'] );
805 }
806
807 /**
808 * Filters the themes prepared for JavaScript, for themes.php.
809 *
810 * Could be useful for changing the order, which is by name by default.
811 *
812 * @since 3.8.0
813 *
814 * @param array $prepared_themes Array of theme data.
815 */
816 $prepared_themes = apply_filters( 'wp_prepare_themes_for_js', $prepared_themes );
817 $prepared_themes = array_values( $prepared_themes );
818 return array_filter( $prepared_themes );
819}
820
821/**
822 * Prints JS templates for the theme-browsing UI in the Customizer.
823 *
824 * @since 4.2.0
825 */
826function customize_themes_print_templates() {
827 ?>
828 <script type="text/html" id="tmpl-customize-themes-details-view">
829 <div class="theme-backdrop"></div>
830 <div class="theme-wrap wp-clearfix" role="document">
831 <div class="theme-header">
832 <button type="button" class="left dashicons dashicons-no"><span class="screen-reader-text">
833 <?php
834 /* translators: Hidden accessibility text. */
835 _e( 'Show previous theme' );
836 ?>
837 </span></button>
838 <button type="button" class="right dashicons dashicons-no"><span class="screen-reader-text">
839 <?php
840 /* translators: Hidden accessibility text. */
841 _e( 'Show next theme' );
842 ?>
843 </span></button>
844 <button type="button" class="close dashicons dashicons-no"><span class="screen-reader-text">
845 <?php
846 /* translators: Hidden accessibility text. */
847 _e( 'Close details dialog' );
848 ?>
849 </span></button>
850 </div>
851 <div class="theme-about wp-clearfix">
852 <div class="theme-screenshots">
853 <# if ( data.screenshot && data.screenshot[0] ) { #>
854 <div class="screenshot"><img src="{{ data.screenshot[0] }}?ver={{ data.version }}" alt="" /></div>
855 <# } else { #>
856 <div class="screenshot blank"></div>
857 <# } #>
858 </div>
859
860 <div class="theme-info">
861 <# if ( data.active ) { #>
862 <span class="current-label"><?php _e( 'Active Theme' ); ?></span>
863 <# } #>
864 <h2 class="theme-name">{{{ data.name }}}<span class="theme-version">
865 <?php
866 /* translators: %s: Theme version. */
867 printf( __( 'Version: %s' ), '{{ data.version }}' );
868 ?>
869 </span></h2>
870 <h3 class="theme-author">
871 <?php
872 /* translators: %s: Theme author link. */
873 printf( __( 'By %s' ), '{{{ data.authorAndUri }}}' );
874 ?>
875 </h3>
876
877 <# if ( data.stars && 0 != data.num_ratings ) { #>
878 <div class="theme-rating">
879 {{{ data.stars }}}
880 <a class="num-ratings" target="_blank" href="{{ data.reviews_url }}">
881 <?php
882 printf(
883 '%1$s <span class="screen-reader-text">%2$s</span>',
884 /* translators: %s: Number of ratings. */
885 sprintf( __( '(%s ratings)' ), '{{ data.num_ratings }}' ),
886 /* translators: Hidden accessibility text. */
887 __( '(opens in a new tab)' )
888 );
889 ?>
890 </a>
891 </div>
892 <# } #>
893
894 <# if ( data.hasUpdate ) { #>
895 <# if ( data.updateResponse.compatibleWP && data.updateResponse.compatiblePHP ) { #>
896 <div class="notice notice-warning notice-alt notice-large" data-slug="{{ data.id }}">
897 <h3 class="notice-title"><?php _e( 'Update Available' ); ?></h3>
898 {{{ data.update }}}
899 </div>
900 <# } else { #>
901 <div class="notice notice-error notice-alt notice-large" data-slug="{{ data.id }}">
902 <h3 class="notice-title"><?php _e( 'Update Incompatible' ); ?></h3>
903 <p>
904 <# if ( ! data.updateResponse.compatibleWP && ! data.updateResponse.compatiblePHP ) { #>
905 <?php
906 printf(
907 /* translators: %s: Theme name. */
908 __( 'There is a new version of %s available, but it does not work with your versions of WordPress and PHP.' ),
909 '{{{ data.name }}}'
910 );
911 if ( current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) {
912 printf(
913 /* translators: 1: URL to WordPress Updates screen, 2: URL to Update PHP page. */
914 ' ' . __( '<a href="%1$s">Please update WordPress</a>, and then <a href="%2$s">learn more about updating PHP</a>.' ),
915 self_admin_url( 'update-core.php' ),
916 esc_url( wp_get_update_php_url() )
917 );
918 wp_update_php_annotation( '</p><p><em>', '</em>' );
919 } elseif ( current_user_can( 'update_core' ) ) {
920 printf(
921 /* translators: %s: URL to WordPress Updates screen. */
922 ' ' . __( '<a href="%s">Please update WordPress</a>.' ),
923 self_admin_url( 'update-core.php' )
924 );
925 } elseif ( current_user_can( 'update_php' ) ) {
926 printf(
927 /* translators: %s: URL to Update PHP page. */
928 ' ' . __( '<a href="%s">Learn more about updating PHP</a>.' ),
929 esc_url( wp_get_update_php_url() )
930 );
931 wp_update_php_annotation( '</p><p><em>', '</em>' );
932 }
933 ?>
934 <# } else if ( ! data.updateResponse.compatibleWP ) { #>
935 <?php
936 printf(
937 /* translators: %s: Theme name. */
938 __( 'There is a new version of %s available, but it does not work with your version of WordPress.' ),
939 '{{{ data.name }}}'
940 );
941 if ( current_user_can( 'update_core' ) ) {
942 printf(
943 /* translators: %s: URL to WordPress Updates screen. */
944 ' ' . __( '<a href="%s">Please update WordPress</a>.' ),
945 self_admin_url( 'update-core.php' )
946 );
947 }
948 ?>
949 <# } else if ( ! data.updateResponse.compatiblePHP ) { #>
950 <?php
951 printf(
952 /* translators: %s: Theme name. */
953 __( 'There is a new version of %s available, but it does not work with your version of PHP.' ),
954 '{{{ data.name }}}'
955 );
956 if ( current_user_can( 'update_php' ) ) {
957 printf(
958 /* translators: %s: URL to Update PHP page. */
959 ' ' . __( '<a href="%s">Learn more about updating PHP</a>.' ),
960 esc_url( wp_get_update_php_url() )
961 );
962 wp_update_php_annotation( '</p><p><em>', '</em>' );
963 }
964 ?>
965 <# } #>
966 </p>
967 </div>
968 <# } #>
969 <# } #>
970
971 <# if ( data.parent ) { #>
972 <p class="parent-theme">
973 <?php
974 printf(
975 /* translators: %s: Theme name. */
976 __( 'This is a child theme of %s.' ),
977 '<strong>{{{ data.parent }}}</strong>'
978 );
979 ?>
980 </p>
981 <# } #>
982
983 <# if ( ! data.compatibleWP || ! data.compatiblePHP ) { #>
984 <div class="notice notice-error notice-alt notice-large"><p>
985 <# if ( ! data.compatibleWP && ! data.compatiblePHP ) { #>
986 <?php
987 _e( 'This theme does not work with your versions of WordPress and PHP.' );
988 if ( current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) {
989 printf(
990 /* translators: 1: URL to WordPress Updates screen, 2: URL to Update PHP page. */
991 ' ' . __( '<a href="%1$s">Please update WordPress</a>, and then <a href="%2$s">learn more about updating PHP</a>.' ),
992 self_admin_url( 'update-core.php' ),
993 esc_url( wp_get_update_php_url() )
994 );
995 wp_update_php_annotation( '</p><p><em>', '</em>' );
996 } elseif ( current_user_can( 'update_core' ) ) {
997 printf(
998 /* translators: %s: URL to WordPress Updates screen. */
999 ' ' . __( '<a href="%s">Please update WordPress</a>.' ),
1000 self_admin_url( 'update-core.php' )
1001 );
1002 } elseif ( current_user_can( 'update_php' ) ) {
1003 printf(
1004 /* translators: %s: URL to Update PHP page. */
1005 ' ' . __( '<a href="%s">Learn more about updating PHP</a>.' ),
1006 esc_url( wp_get_update_php_url() )
1007 );
1008 wp_update_php_annotation( '</p><p><em>', '</em>' );
1009 }
1010 ?>
1011 <# } else if ( ! data.compatibleWP ) { #>
1012 <?php
1013 _e( 'This theme does not work with your version of WordPress.' );
1014 if ( current_user_can( 'update_core' ) ) {
1015 printf(
1016 /* translators: %s: URL to WordPress Updates screen. */
1017 ' ' . __( '<a href="%s">Please update WordPress</a>.' ),
1018 self_admin_url( 'update-core.php' )
1019 );
1020 }
1021 ?>
1022 <# } else if ( ! data.compatiblePHP ) { #>
1023 <?php
1024 _e( 'This theme does not work with your version of PHP.' );
1025 if ( current_user_can( 'update_php' ) ) {
1026 printf(
1027 /* translators: %s: URL to Update PHP page. */
1028 ' ' . __( '<a href="%s">Learn more about updating PHP</a>.' ),
1029 esc_url( wp_get_update_php_url() )
1030 );
1031 wp_update_php_annotation( '</p><p><em>', '</em>' );
1032 }
1033 ?>
1034 <# } #>
1035 </p></div>
1036 <# } else if ( ! data.active && data.blockTheme ) { #>
1037 <div class="notice notice-error notice-alt notice-large"><p>
1038 <?php
1039 _e( 'This theme doesn\'t support Customizer.' );
1040 ?>
1041 <# if ( data.actions.activate ) { #>
1042 <?php
1043 printf(
1044 /* translators: %s: URL to the themes page (also it activates the theme). */
1045 ' ' . __( 'However, you can still <a href="%s">activate this theme</a>, and use the Site Editor to customize it.' ),
1046 '{{{ data.actions.activate }}}'
1047 );
1048 ?>
1049 <# } #>
1050 </p></div>
1051 <# } #>
1052
1053 <p class="theme-description">{{{ data.description }}}</p>
1054
1055 <# if ( data.tags ) { #>
1056 <p class="theme-tags"><span><?php _e( 'Tags:' ); ?></span> {{{ data.tags }}}</p>
1057 <# } #>
1058 </div>
1059 </div>
1060
1061 <div class="theme-actions">
1062 <# if ( data.active ) { #>
1063 <button type="button" class="button button-primary customize-theme"><?php _e( 'Customize' ); ?></button>
1064 <# } else if ( 'installed' === data.type ) { #>
1065 <div class="theme-inactive-actions">
1066 <# if ( data.blockTheme ) { #>
1067 <?php
1068 /* translators: %s: Theme name. */
1069 $aria_label = sprintf( _x( 'Activate %s', 'theme' ), '{{ data.name }}' );
1070 ?>
1071 <# if ( data.compatibleWP && data.compatiblePHP && data.actions.activate ) { #>
1072 <a href="{{{ data.actions.activate }}}" class="button button-primary activate" aria-label="<?php echo esc_attr( $aria_label ); ?>"><?php _e( 'Activate' ); ?></a>
1073 <# } #>
1074 <# } else { #>
1075 <# if ( data.compatibleWP && data.compatiblePHP ) { #>
1076 <button type="button" class="button button-primary preview-theme" data-slug="{{ data.id }}"><?php _e( 'Live Preview' ); ?></button>
1077 <# } else { #>
1078 <button class="button button-primary disabled"><?php _e( 'Live Preview' ); ?></button>
1079 <# } #>
1080 <# } #>
1081 </div>
1082 <?php if ( current_user_can( 'delete_themes' ) ) { ?>
1083 <# if ( data.actions && data.actions['delete'] ) { #>
1084 <a href="{{{ data.actions['delete'] }}}" data-slug="{{ data.id }}" class="button button-secondary delete-theme"><?php _e( 'Delete' ); ?></a>
1085 <# } #>
1086 <?php } ?>
1087 <# } else { #>
1088 <# if ( data.compatibleWP && data.compatiblePHP ) { #>
1089 <button type="button" class="button theme-install" data-slug="{{ data.id }}"><?php _e( 'Install' ); ?></button>
1090 <button type="button" class="button button-primary theme-install preview" data-slug="{{ data.id }}"><?php _e( 'Install &amp; Preview' ); ?></button>
1091 <# } else { #>
1092 <button type="button" class="button disabled"><?php _ex( 'Cannot Install', 'theme' ); ?></button>
1093 <button type="button" class="button button-primary disabled"><?php _e( 'Install &amp; Preview' ); ?></button>
1094 <# } #>
1095 <# } #>
1096 </div>
1097 </div>
1098 </script>
1099 <?php
1100}
1101
1102/**
1103 * Determines whether a theme is technically active but was paused while
1104 * loading.
1105 *
1106 * For more information on this and similar theme functions, check out
1107 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
1108 * Conditional Tags} article in the Theme Developer Handbook.
1109 *
1110 * @since 5.2.0
1111 *
1112 * @global WP_Paused_Extensions_Storage $_paused_themes
1113 *
1114 * @param string $theme Path to the theme directory relative to the themes directory.
1115 * @return bool True, if in the list of paused themes. False, not in the list.
1116 */
1117function is_theme_paused( $theme ) {
1118 if ( ! isset( $GLOBALS['_paused_themes'] ) ) {
1119 return false;
1120 }
1121
1122 if ( get_stylesheet() !== $theme && get_template() !== $theme ) {
1123 return false;
1124 }
1125
1126 return array_key_exists( $theme, $GLOBALS['_paused_themes'] );
1127}
1128
1129/**
1130 * Gets the error that was recorded for a paused theme.
1131 *
1132 * @since 5.2.0
1133 *
1134 * @global WP_Paused_Extensions_Storage $_paused_themes
1135 *
1136 * @param string $theme Path to the theme directory relative to the themes
1137 * directory.
1138 * @return array|false Array of error information as it was returned by
1139 * `error_get_last()`, or false if none was recorded.
1140 */
1141function wp_get_theme_error( $theme ) {
1142 if ( ! isset( $GLOBALS['_paused_themes'] ) ) {
1143 return false;
1144 }
1145
1146 if ( ! array_key_exists( $theme, $GLOBALS['_paused_themes'] ) ) {
1147 return false;
1148 }
1149
1150 return $GLOBALS['_paused_themes'][ $theme ];
1151}
1152
1153/**
1154 * Tries to resume a single theme.
1155 *
1156 * If a redirect was provided and a functions.php file was found, we first ensure that
1157 * functions.php file does not throw fatal errors anymore.
1158 *
1159 * The way it works is by setting the redirection to the error before trying to
1160 * include the file. If the theme fails, then the redirection will not be overwritten
1161 * with the success message and the theme will not be resumed.
1162 *
1163 * @since 5.2.0
1164 *
1165 * @global string $wp_stylesheet_path Path to current theme's stylesheet directory.
1166 * @global string $wp_template_path Path to current theme's template directory.
1167 *
1168 * @param string $theme Single theme to resume.
1169 * @param string $redirect Optional. URL to redirect to. Default empty string.
1170 * @return bool|WP_Error True on success, false if `$theme` was not paused,
1171 * `WP_Error` on failure.
1172 */
1173function resume_theme( $theme, $redirect = '' ) {
1174 global $wp_stylesheet_path, $wp_template_path;
1175
1176 list( $extension ) = explode( '/', $theme );
1177
1178 /*
1179 * We'll override this later if the theme could be resumed without
1180 * creating a fatal error.
1181 */
1182 if ( ! empty( $redirect ) ) {
1183 $functions_path = '';
1184 if ( str_contains( $wp_stylesheet_path, $extension ) ) {
1185 $functions_path = $wp_stylesheet_path . '/functions.php';
1186 } elseif ( str_contains( $wp_template_path, $extension ) ) {
1187 $functions_path = $wp_template_path . '/functions.php';
1188 }
1189
1190 if ( ! empty( $functions_path ) ) {
1191 wp_redirect(
1192 add_query_arg(
1193 '_error_nonce',
1194 wp_create_nonce( 'theme-resume-error_' . $theme ),
1195 $redirect
1196 )
1197 );
1198
1199 // Load the theme's functions.php to test whether it throws a fatal error.
1200 ob_start();
1201 if ( ! defined( 'WP_SANDBOX_SCRAPING' ) ) {
1202 define( 'WP_SANDBOX_SCRAPING', true );
1203 }
1204 include $functions_path;
1205 ob_clean();
1206 }
1207 }
1208
1209 $result = wp_paused_themes()->delete( $extension );
1210
1211 if ( ! $result ) {
1212 return new WP_Error(
1213 'could_not_resume_theme',
1214 __( 'Could not resume the theme.' )
1215 );
1216 }
1217
1218 return true;
1219}
1220
1221/**
1222 * Renders an admin notice in case some themes have been paused due to errors.
1223 *
1224 * @since 5.2.0
1225 *
1226 * @global string $pagenow The filename of the current screen.
1227 * @global WP_Paused_Extensions_Storage $_paused_themes
1228 */
1229function paused_themes_notice() {
1230 if ( 'themes.php' === $GLOBALS['pagenow'] ) {
1231 return;
1232 }
1233
1234 if ( ! current_user_can( 'resume_themes' ) ) {
1235 return;
1236 }
1237
1238 if ( ! isset( $GLOBALS['_paused_themes'] ) || empty( $GLOBALS['_paused_themes'] ) ) {
1239 return;
1240 }
1241
1242 $message = sprintf(
1243 '<p><strong>%s</strong><br>%s</p><p><a href="%s">%s</a></p>',
1244 __( 'One or more themes failed to load properly.' ),
1245 __( 'You can find more details and make changes on the Themes screen.' ),
1246 esc_url( admin_url( 'themes.php' ) ),
1247 __( 'Go to the Themes screen' )
1248 );
1249 wp_admin_notice(
1250 $message,
1251 array(
1252 'type' => 'error',
1253 'paragraph_wrap' => false,
1254 )
1255 );
1256}
1257