1<?php
2/**
3 * WordPress Administration Update API
4 *
5 * @package WordPress
6 * @subpackage Administration
7 */
8
9/**
10 * Selects the first update version from the update_core option.
11 *
12 * @since 2.7.0
13 *
14 * @return object|array|false The response from the API on success, false on failure.
15 */
16function get_preferred_from_update_core() {
17	$updates = get_core_updates();
18	if ( ! is_array( $updates ) ) {
19		return false;
20	}
21	if ( empty( $updates ) ) {
22		return (object) array( 'response' => 'latest' );
23	}
24	return $updates[0];
25}
26
27/**
28 * Gets available core updates.
29 *
30 * @since 2.7.0
31 *
32 * @param array $options Set $options['dismissed'] to true to show dismissed upgrades too,
33 *                       set $options['available'] to false to skip not-dismissed updates.
34 * @return array|false Array of the update objects on success, false on failure.
35 */
36function get_core_updates( $options = array() ) {
37	$options   = array_merge(
38		array(
39			'available' => true,
40			'dismissed' => false,
41		),
42		$options
43	);
44	$dismissed = get_site_option( 'dismissed_update_core' );
45
46	if ( ! is_array( $dismissed ) ) {
47		$dismissed = array();
48	}
49
50	$from_api = get_site_transient( 'update_core' );
51
52	if ( ! isset( $from_api->updates ) || ! is_array( $from_api->updates ) ) {
53		return false;
54	}
55
56	$updates = $from_api->updates;
57	$result  = array();
58	foreach ( $updates as $update ) {
59		if ( 'autoupdate' === $update->response ) {
60			continue;
61		}
62
63		if ( array_key_exists( $update->current . '|' . $update->locale, $dismissed ) ) {
64			if ( $options['dismissed'] ) {
65				$update->dismissed = true;
66				$result[]          = $update;
67			}
68		} else {
69			if ( $options['available'] ) {
70				$update->dismissed = false;
71				$result[]          = $update;
72			}
73		}
74	}
75	return $result;
76}
77
78/**
79 * Gets the best available (and enabled) Auto-Update for WordPress core.
80 *
81 * If there's 1.2.3 and 1.3 on offer, it'll choose 1.3 if the installation allows it, else, 1.2.3.
82 *
83 * @since 3.7.0
84 *
85 * @return object|false The core update offering on success, false on failure.
86 */
87function find_core_auto_update() {
88	$updates = get_site_transient( 'update_core' );
89	if ( ! $updates || empty( $updates->updates ) ) {
90		return false;
91	}
92
93	require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
94
95	$auto_update = false;
96	$upgrader    = new WP_Automatic_Updater;
97	foreach ( $updates->updates as $update ) {
98		if ( 'autoupdate' !== $update->response ) {
99			continue;
100		}
101
102		if ( ! $upgrader->should_update( 'core', $update, ABSPATH ) ) {
103			continue;
104		}
105
106		if ( ! $auto_update || version_compare( $update->current, $auto_update->current, '>' ) ) {
107			$auto_update = $update;
108		}
109	}
110	return $auto_update;
111}
112
113/**
114 * Gets and caches the checksums for the given version of WordPress.
115 *
116 * @since 3.7.0
117 *
118 * @param string $version Version string to query.
119 * @param string $locale  Locale to query.
120 * @return array|false An array of checksums on success, false on failure.
121 */
122function get_core_checksums( $version, $locale ) {
123	$http_url = 'http://api.wordpress.org/core/checksums/1.0/?' . http_build_query( compact( 'version', 'locale' ), null, '&' );
124	$url      = $http_url;
125
126	$ssl = wp_http_supports( array( 'ssl' ) );
127	if ( $ssl ) {
128		$url = set_url_scheme( $url, 'https' );
129	}
130
131	$options = array(
132		'timeout' => wp_doing_cron() ? 30 : 3,
133	);
134
135	$response = wp_remote_get( $url, $options );
136	if ( $ssl && is_wp_error( $response ) ) {
137		trigger_error(
138			sprintf(
139				/* translators: %s: Support forums URL. */
140				__( '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>.' ),
141				__( 'https://wordpress.org/support/forums/' )
142			) . ' ' . __( '(WordPress could not establish a secure connection to WordPress.org. Please contact your server administrator.)' ),
143			headers_sent() || WP_DEBUG ? E_USER_WARNING : E_USER_NOTICE
144		);
145		$response = wp_remote_get( $http_url, $options );
146	}
147
148	if ( is_wp_error( $response ) || 200 != wp_remote_retrieve_response_code( $response ) ) {
149		return false;
150	}
151
152	$body = trim( wp_remote_retrieve_body( $response ) );
153	$body = json_decode( $body, true );
154
155	if ( ! is_array( $body ) || ! isset( $body['checksums'] ) || ! is_array( $body['checksums'] ) ) {
156		return false;
157	}
158
159	return $body['checksums'];
160}
161
162/**
163 * Dismisses core update.
164 *
165 * @since 2.7.0
166 *
167 * @param object $update
168 * @return bool
169 */
170function dismiss_core_update( $update ) {
171	$dismissed = get_site_option( 'dismissed_update_core' );
172	$dismissed[ $update->current . '|' . $update->locale ] = true;
173	return update_site_option( 'dismissed_update_core', $dismissed );
174}
175
176/**
177 * Undismisses core update.
178 *
179 * @since 2.7.0
180 *
181 * @param string $version
182 * @param string $locale
183 * @return bool
184 */
185function undismiss_core_update( $version, $locale ) {
186	$dismissed = get_site_option( 'dismissed_update_core' );
187	$key       = $version . '|' . $locale;
188
189	if ( ! isset( $dismissed[ $key ] ) ) {
190		return false;
191	}
192
193	unset( $dismissed[ $key ] );
194	return update_site_option( 'dismissed_update_core', $dismissed );
195}
196
197/**
198 * Finds the available update for WordPress core.
199 *
200 * @since 2.7.0
201 *
202 * @param string $version Version string to find the update for.
203 * @param string $locale  Locale to find the update for.
204 * @return object|false The core update offering on success, false on failure.
205 */
206function find_core_update( $version, $locale ) {
207	$from_api = get_site_transient( 'update_core' );
208
209	if ( ! isset( $from_api->updates ) || ! is_array( $from_api->updates ) ) {
210		return false;
211	}
212
213	$updates = $from_api->updates;
214	foreach ( $updates as $update ) {
215		if ( $update->current == $version && $update->locale == $locale ) {
216			return $update;
217		}
218	}
219	return false;
220}
221
222/**
223 * @since 2.3.0
224 *
225 * @param string $msg
226 * @return string
227 */
228function core_update_footer( $msg = '' ) {
229	if ( ! current_user_can( 'update_core' ) ) {
230		/* translators: %s: WordPress version. */
231		return sprintf( __( 'Version %s' ), get_bloginfo( 'version', 'display' ) );
232	}
233
234	$cur = get_preferred_from_update_core();
235	if ( ! is_object( $cur ) ) {
236		$cur = new stdClass;
237	}
238
239	if ( ! isset( $cur->current ) ) {
240		$cur->current = '';
241	}
242
243	if ( ! isset( $cur->response ) ) {
244		$cur->response = '';
245	}
246
247	// Include an unmodified $wp_version.
248	require ABSPATH . WPINC . '/version.php';
249
250	$is_development_version = preg_match( '/alpha|beta|RC/', $wp_version );
251
252	if ( $is_development_version ) {
253		return sprintf(
254			/* translators: 1: WordPress version number, 2: URL to WordPress Updates screen. */
255			__( 'You are using a development version (%1$s). Cool! Please <a href="%2$s">stay updated</a>.' ),
256			get_bloginfo( 'version', 'display' ),
257			network_admin_url( 'update-core.php' )
258		);
259	}
260
261	switch ( $cur->response ) {
262		case 'upgrade':
263			return sprintf(
264				'<strong><a href="%s">%s</a></strong>',
265				network_admin_url( 'update-core.php' ),
266				/* translators: %s: WordPress version. */
267				sprintf( __( 'Get Version %s' ), $cur->current )
268			);
269
270		case 'latest':
271		default:
272			/* translators: %s: WordPress version. */
273			return sprintf( __( 'Version %s' ), get_bloginfo( 'version', 'display' ) );
274	}
275}
276
277/**
278 * @since 2.3.0
279 *
280 * @global string $pagenow
281 * @return void|false
282 */
283function update_nag() {
284	if ( is_multisite() && ! current_user_can( 'update_core' ) ) {
285		return false;
286	}
287
288	global $pagenow;
289
290	if ( 'update-core.php' === $pagenow ) {
291		return;
292	}
293
294	$cur = get_preferred_from_update_core();
295
296	if ( ! isset( $cur->response ) || 'upgrade' !== $cur->response ) {
297		return false;
298	}
299
300	$version_url = sprintf(
301		/* translators: %s: WordPress version. */
302		esc_url( __( 'https://wordpress.org/support/wordpress-version/version-%s/' ) ),
303		sanitize_title( $cur->current )
304	);
305
306	if ( current_user_can( 'update_core' ) ) {
307		$msg = sprintf(
308			/* translators: 1: URL to WordPress release notes, 2: New WordPress version, 3: URL to network admin, 4: Accessibility text. */
309			__( '<a href="%1$s">WordPress %2$s</a> is available! <a href="%3$s" aria-label="%4$s">Please update now</a>.' ),
310			$version_url,
311			$cur->current,
312			network_admin_url( 'update-core.php' ),
313			esc_attr__( 'Please update WordPress now' )
314		);
315	} else {
316		$msg = sprintf(
317			/* translators: 1: URL to WordPress release notes, 2: New WordPress version. */
318			__( '<a href="%1$s">WordPress %2$s</a> is available! Please notify the site administrator.' ),
319			$version_url,
320			$cur->current
321		);
322	}
323
324	echo "<div class='update-nag notice notice-warning inline'>$msg</div>";
325}
326
327/**
328 * Displays WordPress version and active theme in the 'At a Glance' dashboard widget.
329 *
330 * @since 2.5.0
331 */
332function update_right_now_message() {
333	$theme_name = wp_get_theme();
334	if ( current_user_can( 'switch_themes' ) ) {
335		$theme_name = sprintf( '<a href="themes.php">%1$s</a>', $theme_name );
336	}
337
338	$msg = '';
339
340	if ( current_user_can( 'update_core' ) ) {
341		$cur = get_preferred_from_update_core();
342
343		if ( isset( $cur->response ) && 'upgrade' === $cur->response ) {
344			$msg .= sprintf(
345				'<a href="%s" class="button" aria-describedby="wp-version">%s</a> ',
346				network_admin_url( 'update-core.php' ),
347				/* translators: %s: WordPress version number, or 'Latest' string. */
348				sprintf( __( 'Update to %s' ), $cur->current ? $cur->current : __( 'Latest' ) )
349			);
350		}
351	}
352
353	/* translators: 1: Version number, 2: Theme name. */
354	$content = __( 'WordPress %1$s running %2$s theme.' );
355
356	/**
357	 * Filters the text displayed in the 'At a Glance' dashboard widget.
358	 *
359	 * Prior to 3.8.0, the widget was named 'Right Now'.
360	 *
361	 * @since 4.4.0
362	 *
363	 * @param string $content Default text.
364	 */
365	$content = apply_filters( 'update_right_now_text', $content );
366
367	$msg .= sprintf( '<span id="wp-version">' . $content . '</span>', get_bloginfo( 'version', 'display' ), $theme_name );
368
369	echo "<p id='wp-version-message'>$msg</p>";
370}
371
372/**
373 * @since 2.9.0
374 *
375 * @return array
376 */
377function get_plugin_updates() {
378	$all_plugins     = get_plugins();
379	$upgrade_plugins = array();
380	$current         = get_site_transient( 'update_plugins' );
381	foreach ( (array) $all_plugins as $plugin_file => $plugin_data ) {
382		if ( isset( $current->response[ $plugin_file ] ) ) {
383			$upgrade_plugins[ $plugin_file ]         = (object) $plugin_data;
384			$upgrade_plugins[ $plugin_file ]->update = $current->response[ $plugin_file ];
385		}
386	}
387
388	return $upgrade_plugins;
389}
390
391/**
392 * @since 2.9.0
393 */
394function wp_plugin_update_rows() {
395	if ( ! current_user_can( 'update_plugins' ) ) {
396		return;
397	}
398
399	$plugins = get_site_transient( 'update_plugins' );
400	if ( isset( $plugins->response ) && is_array( $plugins->response ) ) {
401		$plugins = array_keys( $plugins->response );
402		foreach ( $plugins as $plugin_file ) {
403			add_action( "after_plugin_row_{$plugin_file}", 'wp_plugin_update_row', 10, 2 );
404		}
405	}
406}
407
408/**
409 * Displays update information for a plugin.
410 *
411 * @since 2.3.0
412 *
413 * @param string $file        Plugin basename.
414 * @param array  $plugin_data Plugin information.
415 * @return void|false
416 */
417function wp_plugin_update_row( $file, $plugin_data ) {
418	$current = get_site_transient( 'update_plugins' );
419	if ( ! isset( $current->response[ $file ] ) ) {
420		return false;
421	}
422
423	$response = $current->response[ $file ];
424
425	$plugins_allowedtags = array(
426		'a'       => array(
427			'href'  => array(),
428			'title' => array(),
429		),
430		'abbr'    => array( 'title' => array() ),
431		'acronym' => array( 'title' => array() ),
432		'code'    => array(),
433		'em'      => array(),
434		'strong'  => array(),
435	);
436
437	$plugin_name = wp_kses( $plugin_data['Name'], $plugins_allowedtags );
438	$plugin_slug = isset( $response->slug ) ? $response->slug : $response->id;
439
440	if ( isset( $response->slug ) ) {
441		$details_url = self_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $plugin_slug . '&section=changelog' );
442	} elseif ( isset( $response->url ) ) {
443		$details_url = $response->url;
444	} else {
445		$details_url = $plugin_data['PluginURI'];
446	}
447
448	$details_url = add_query_arg(
449		array(
450			'TB_iframe' => 'true',
451			'width'     => 600,
452			'height'    => 800,
453		),
454		$details_url
455	);
456
457	/** @var WP_Plugins_List_Table $wp_list_table */
458	$wp_list_table = _get_list_table(
459		'WP_Plugins_List_Table',
460		array(
461			'screen' => get_current_screen(),
462		)
463	);
464
465	if ( is_network_admin() || ! is_multisite() ) {
466		if ( is_network_admin() ) {
467			$active_class = is_plugin_active_for_network( $file ) ? ' active' : '';
468		} else {
469			$active_class = is_plugin_active( $file ) ? ' active' : '';
470		}
471
472		$requires_php   = isset( $response->requires_php ) ? $response->requires_php : null;
473		$compatible_php = is_php_version_compatible( $requires_php );
474		$notice_type    = $compatible_php ? 'notice-warning' : 'notice-error';
475
476		printf(
477			'<tr class="plugin-update-tr%s" id="%s" data-slug="%s" data-plugin="%s">' .
478			'<td colspan="%s" class="plugin-update colspanchange">' .
479			'<div class="update-message notice inline %s notice-alt"><p>',
480			$active_class,
481			esc_attr( $plugin_slug . '-update' ),
482			esc_attr( $plugin_slug ),
483			esc_attr( $file ),
484			esc_attr( $wp_list_table->get_column_count() ),
485			$notice_type
486		);
487
488		if ( ! current_user_can( 'update_plugins' ) ) {
489			printf(
490				/* translators: 1: Plugin name, 2: Details URL, 3: Additional link attributes, 4: Version number. */
491				__( 'There is a new version of %1$s available. <a href="%2$s" %3$s>View version %4$s details</a>.' ),
492				$plugin_name,
493				esc_url( $details_url ),
494				sprintf(
495					'class="thickbox open-plugin-details-modal" aria-label="%s"',
496					/* translators: 1: Plugin name, 2: Version number. */
497					esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $plugin_name, $response->new_version ) )
498				),
499				esc_attr( $response->new_version )
500			);
501		} elseif ( empty( $response->package ) ) {
502			printf(
503				/* translators: 1: Plugin name, 2: Details URL, 3: Additional link attributes, 4: Version number. */
504				__( '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 plugin.</em>' ),
505				$plugin_name,
506				esc_url( $details_url ),
507				sprintf(
508					'class="thickbox open-plugin-details-modal" aria-label="%s"',
509					/* translators: 1: Plugin name, 2: Version number. */
510					esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $plugin_name, $response->new_version ) )
511				),
512				esc_attr( $response->new_version )
513			);
514		} else {
515			if ( $compatible_php ) {
516				printf(
517					/* translators: 1: Plugin name, 2: Details URL, 3: Additional link attributes, 4: Version number, 5: Update URL, 6: Additional link attributes. */
518					__( '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>.' ),
519					$plugin_name,
520					esc_url( $details_url ),
521					sprintf(
522						'class="thickbox open-plugin-details-modal" aria-label="%s"',
523						/* translators: 1: Plugin name, 2: Version number. */
524						esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $plugin_name, $response->new_version ) )
525					),
526					esc_attr( $response->new_version ),
527					wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' ) . $file, 'upgrade-plugin_' . $file ),
528					sprintf(
529						'class="update-link" aria-label="%s"',
530						/* translators: %s: Plugin name. */
531						esc_attr( sprintf( _x( 'Update %s now', 'plugin' ), $plugin_name ) )
532					)
533				);
534			} else {
535				printf(
536					/* translators: 1: Plugin name, 2: Details URL, 3: Additional link attributes, 4: Version number 5: URL to Update PHP page. */
537					__( 'There is a new version of %1$s available, but it doesn&#8217;t work with your version of PHP. <a href="%2$s" %3$s>View version %4$s details</a> or <a href="%5$s">learn more about updating PHP</a>.' ),
538					$plugin_name,
539					esc_url( $details_url ),
540					sprintf(
541						'class="thickbox open-plugin-details-modal" aria-label="%s"',
542						/* translators: 1: Plugin name, 2: Version number. */
543						esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $plugin_name, $response->new_version ) )
544					),
545					esc_attr( $response->new_version ),
546					esc_url( wp_get_update_php_url() )
547				);
548				wp_update_php_annotation( '<br><em>', '</em>' );
549			}
550		}
551
552		/**
553		 * Fires at the end of the update message container in each
554		 * row of the plugins list table.
555		 *
556		 * The dynamic portion of the hook name, `$file`, refers to the path
557		 * of the plugin's primary file relative to the plugins directory.
558		 *
559		 * @since 2.8.0
560		 *
561		 * @param array $plugin_data {
562		 *     An array of plugin metadata.
563		 *
564		 *     @type string $name        The human-readable name of the plugin.
565		 *     @type string $plugin_uri  Plugin URI.
566		 *     @type string $version     Plugin version.
567		 *     @type string $description Plugin description.
568		 *     @type string $author      Plugin author.
569		 *     @type string $author_uri  Plugin author URI.
570		 *     @type string $text_domain Plugin text domain.
571		 *     @type string $domain_path Relative path to the plugin's .mo file(s).
572		 *     @type bool   $network     Whether the plugin can only be activated network wide.
573		 *     @type string $title       The human-readable title of the plugin.
574		 *     @type string $author_name Plugin author's name.
575		 *     @type bool   $update      Whether there's an available update. Default null.
576		 * }
577		 * @param array $response {
578		 *     An array of metadata about the available plugin update.
579		 *
580		 *     @type int    $id          Plugin ID.
581		 *     @type string $slug        Plugin slug.
582		 *     @type string $new_version New plugin version.
583		 *     @type string $url         Plugin URL.
584		 *     @type string $package     Plugin update package URL.
585		 * }
586		 */
587		do_action( "in_plugin_update_message-{$file}", $plugin_data, $response ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
588
589		echo '</p></div></td></tr>';
590	}
591}
592
593/**
594 * @since 2.9.0
595 *
596 * @return array
597 */
598function get_theme_updates() {
599	$current = get_site_transient( 'update_themes' );
600
601	if ( ! isset( $current->response ) ) {
602		return array();
603	}
604
605	$update_themes = array();
606	foreach ( $current->response as $stylesheet => $data ) {
607		$update_themes[ $stylesheet ]         = wp_get_theme( $stylesheet );
608		$update_themes[ $stylesheet ]->update = $data;
609	}
610
611	return $update_themes;
612}
613
614/**
615 * @since 3.1.0
616 */
617function wp_theme_update_rows() {
618	if ( ! current_user_can( 'update_themes' ) ) {
619		return;
620	}
621
622	$themes = get_site_transient( 'update_themes' );
623	if ( isset( $themes->response ) && is_array( $themes->response ) ) {
624		$themes = array_keys( $themes->response );
625
626		foreach ( $themes as $theme ) {
627			add_action( "after_theme_row_{$theme}", 'wp_theme_update_row', 10, 2 );
628		}
629	}
630}
631
632/**
633 * Displays update information for a theme.
634 *
635 * @since 3.1.0
636 *
637 * @param string   $theme_key Theme stylesheet.
638 * @param WP_Theme $theme     Theme object.
639 * @return void|false
640 */
641function wp_theme_update_row( $theme_key, $theme ) {
642	$current = get_site_transient( 'update_themes' );
643
644	if ( ! isset( $current->response[ $theme_key ] ) ) {
645		return false;
646	}
647
648	$response = $current->response[ $theme_key ];
649
650	$details_url = add_query_arg(
651		array(
652			'TB_iframe' => 'true',
653			'width'     => 1024,
654			'height'    => 800,
655		),
656		$current->response[ $theme_key ]['url']
657	);
658
659	/** @var WP_MS_Themes_List_Table $wp_list_table */
660	$wp_list_table = _get_list_table( 'WP_MS_Themes_List_Table' );
661
662	$active = $theme->is_allowed( 'network' ) ? ' active' : '';
663
664	$requires_wp  = isset( $response['requires'] ) ? $response['requires'] : null;
665	$requires_php = isset( $response['requires_php'] ) ? $response['requires_php'] : null;
666
667	$compatible_wp  = is_wp_version_compatible( $requires_wp );
668	$compatible_php = is_php_version_compatible( $requires_php );
669
670	printf(
671		'<tr class="plugin-update-tr%s" id="%s" data-slug="%s">' .
672		'<td colspan="%s" class="plugin-update colspanchange">' .
673		'<div class="update-message notice inline notice-warning notice-alt"><p>',
674		$active,
675		esc_attr( $theme->get_stylesheet() . '-update' ),
676		esc_attr( $theme->get_stylesheet() ),
677		$wp_list_table->get_column_count()
678	);
679
680	if ( $compatible_wp && $compatible_php ) {
681		if ( ! current_user_can( 'update_themes' ) ) {
682			printf(
683				/* translators: 1: Theme name, 2: Details URL, 3: Additional link attributes, 4: Version number. */
684				__( 'There is a new version of %1$s available. <a href="%2$s" %3$s>View version %4$s details</a>.' ),
685				$theme['Name'],
686				esc_url( $details_url ),
687				sprintf(
688					'class="thickbox open-plugin-details-modal" aria-label="%s"',
689					/* translators: 1: Theme name, 2: Version number. */
690					esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $theme['Name'], $response['new_version'] ) )
691				),
692				$response['new_version']
693			);
694		} elseif ( empty( $response['package'] ) ) {
695			printf(
696				/* translators: 1: Theme name, 2: Details URL, 3: Additional link attributes, 4: Version number. */
697				__( '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>' ),
698				$theme['Name'],
699				esc_url( $details_url ),
700				sprintf(
701					'class="thickbox open-plugin-details-modal" aria-label="%s"',
702					/* translators: 1: Theme name, 2: Version number. */
703					esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $theme['Name'], $response['new_version'] ) )
704				),
705				$response['new_version']
706			);
707		} else {
708			printf(
709				/* translators: 1: Theme name, 2: Details URL, 3: Additional link attributes, 4: Version number, 5: Update URL, 6: Additional link attributes. */
710				__( '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>.' ),
711				$theme['Name'],
712				esc_url( $details_url ),
713				sprintf(
714					'class="thickbox open-plugin-details-modal" aria-label="%s"',
715					/* translators: 1: Theme name, 2: Version number. */
716					esc_attr( sprintf( __( 'View %1$s version %2$s details' ), $theme['Name'], $response['new_version'] ) )
717				),
718				$response['new_version'],
719				wp_nonce_url( self_admin_url( 'update.php?action=upgrade-theme&theme=' ) . $theme_key, 'upgrade-theme_' . $theme_key ),
720				sprintf(
721					'class="update-link" aria-label="%s"',
722					/* translators: %s: Theme name. */
723					esc_attr( sprintf( _x( 'Update %s now', 'theme' ), $theme['Name'] ) )
724				)
725			);
726		}
727	} else {
728		if ( ! $compatible_wp && ! $compatible_php ) {
729			printf(
730				/* translators: %s: Theme name. */
731				__( 'There is a new version of %s available, but it doesn&#8217;t work with your versions of WordPress and PHP.' ),
732				$theme['Name']
733			);
734			if ( current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) {
735				printf(
736					/* translators: 1: URL to WordPress Updates screen, 2: URL to Update PHP page. */
737					' ' . __( '<a href="%1$s">Please update WordPress</a>, and then <a href="%2$s">learn more about updating PHP</a>.' ),
738					self_admin_url( 'update-core.php' ),
739					esc_url( wp_get_update_php_url() )
740				);
741				wp_update_php_annotation( '</p><p><em>', '</em>' );
742			} elseif ( current_user_can( 'update_core' ) ) {
743				printf(
744					/* translators: %s: URL to WordPress Updates screen. */
745					' ' . __( '<a href="%s">Please update WordPress</a>.' ),
746					self_admin_url( 'update-core.php' )
747				);
748			} elseif ( current_user_can( 'update_php' ) ) {
749				printf(
750					/* translators: %s: URL to Update PHP page. */
751					' ' . __( '<a href="%s">Learn more about updating PHP</a>.' ),
752					esc_url( wp_get_update_php_url() )
753				);
754				wp_update_php_annotation( '</p><p><em>', '</em>' );
755			}
756		} elseif ( ! $compatible_wp ) {
757			printf(
758				/* translators: %s: Theme name. */
759				__( 'There is a new version of %s available, but it doesn&#8217;t work with your version of WordPress.' ),
760				$theme['Name']
761			);
762			if ( current_user_can( 'update_core' ) ) {
763				printf(
764					/* translators: %s: URL to WordPress Updates screen. */
765					' ' . __( '<a href="%s">Please update WordPress</a>.' ),
766					self_admin_url( 'update-core.php' )
767				);
768			}
769		} elseif ( ! $compatible_php ) {
770			printf(
771				/* translators: %s: Theme name. */
772				__( 'There is a new version of %s available, but it doesn&#8217;t work with your version of PHP.' ),
773				$theme['Name']
774			);
775			if ( current_user_can( 'update_php' ) ) {
776				printf(
777					/* translators: %s: URL to Update PHP page. */
778					' ' . __( '<a href="%s">Learn more about updating PHP</a>.' ),
779					esc_url( wp_get_update_php_url() )
780				);
781				wp_update_php_annotation( '</p><p><em>', '</em>' );
782			}
783		}
784	}
785
786	/**
787	 * Fires at the end of the update message container in each
788	 * row of the themes list table.
789	 *
790	 * The dynamic portion of the hook name, `$theme_key`, refers to
791	 * the theme slug as found in the WordPress.org themes repository.
792	 *
793	 * @since 3.1.0
794	 *
795	 * @param WP_Theme $theme    The WP_Theme object.
796	 * @param array    $response {
797	 *     An array of metadata about the available theme update.
798	 *
799	 *     @type string $new_version New theme version.
800	 *     @type string $url         Theme URL.
801	 *     @type string $package     Theme update package URL.
802	 * }
803	 */
804	do_action( "in_theme_update_message-{$theme_key}", $theme, $response ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
805
806	echo '</p></div></td></tr>';
807}
808
809/**
810 * @since 2.7.0
811 *
812 * @global int $upgrading
813 * @return void|false
814 */
815function maintenance_nag() {
816	// Include an unmodified $wp_version.
817	require ABSPATH . WPINC . '/version.php';
818	global $upgrading;
819	$nag = isset( $upgrading );
820	if ( ! $nag ) {
821		$failed = get_site_option( 'auto_core_update_failed' );
822		/*
823		 * If an update failed critically, we may have copied over version.php but not other files.
824		 * In that case, if the installation claims we're running the version we attempted, nag.
825		 * This is serious enough to err on the side of nagging.
826		 *
827		 * If we simply failed to update before we tried to copy any files, then assume things are
828		 * OK if they are now running the latest.
829		 *
830		 * This flag is cleared whenever a successful update occurs using Core_Upgrader.
831		 */
832		$comparison = ! empty( $failed['critical'] ) ? '>=' : '>';
833		if ( isset( $failed['attempted'] ) && version_compare( $failed['attempted'], $wp_version, $comparison ) ) {
834			$nag = true;
835		}
836	}
837
838	if ( ! $nag ) {
839		return false;
840	}
841
842	if ( current_user_can( 'update_core' ) ) {
843		$msg = sprintf(
844			/* translators: %s: URL to WordPress Updates screen. */
845			__( 'An automated WordPress update has failed to complete - <a href="%s">please attempt the update again now</a>.' ),
846			'update-core.php'
847		);
848	} else {
849		$msg = __( 'An automated WordPress update has failed to complete! Please notify the site administrator.' );
850	}
851
852	echo "<div class='update-nag notice notice-warning inline'>$msg</div>";
853}
854
855/**
856 * Prints the JavaScript templates for update admin notices.
857 *
858 * Template takes one argument with four values:
859 *
860 *     param {object} data {
861 *         Arguments for admin notice.
862 *
863 *         @type string id        ID of the notice.
864 *         @type string className Class names for the notice.
865 *         @type string message   The notice's message.
866 *         @type string type      The type of update the notice is for. Either 'plugin' or 'theme'.
867 *     }
868 *
869 * @since 4.6.0
870 */
871function wp_print_admin_notice_templates() {
872	?>
873	<script id="tmpl-wp-updates-admin-notice" type="text/html">
874		<div <# if ( data.id ) { #>id="{{ data.id }}"<# } #> class="notice {{ data.className }}"><p>{{{ data.message }}}</p></div>
875	</script>
876	<script id="tmpl-wp-bulk-updates-admin-notice" type="text/html">
877		<div id="{{ data.id }}" class="{{ data.className }} notice <# if ( data.errors ) { #>notice-error<# } else { #>notice-success<# } #>">
878			<p>
879				<# if ( data.successes ) { #>
880					<# if ( 1 === data.successes ) { #>
881						<# if ( 'plugin' === data.type ) { #>
882							<?php
883							/* translators: %s: Number of plugins. */
884							printf( __( '%s plugin successfully updated.' ), '{{ data.successes }}' );
885							?>
886						<# } else { #>
887							<?php
888							/* translators: %s: Number of themes. */
889							printf( __( '%s theme successfully updated.' ), '{{ data.successes }}' );
890							?>
891						<# } #>
892					<# } else { #>
893						<# if ( 'plugin' === data.type ) { #>
894							<?php
895							/* translators: %s: Number of plugins. */
896							printf( __( '%s plugins successfully updated.' ), '{{ data.successes }}' );
897							?>
898						<# } else { #>
899							<?php
900							/* translators: %s: Number of themes. */
901							printf( __( '%s themes successfully updated.' ), '{{ data.successes }}' );
902							?>
903						<# } #>
904					<# } #>
905				<# } #>
906				<# if ( data.errors ) { #>
907					<button class="button-link bulk-action-errors-collapsed" aria-expanded="false">
908						<# if ( 1 === data.errors ) { #>
909							<?php
910							/* translators: %s: Number of failed updates. */
911							printf( __( '%s update failed.' ), '{{ data.errors }}' );
912							?>
913						<# } else { #>
914							<?php
915							/* translators: %s: Number of failed updates. */
916							printf( __( '%s updates failed.' ), '{{ data.errors }}' );
917							?>
918						<# } #>
919						<span class="screen-reader-text"><?php _e( 'Show more details' ); ?></span>
920						<span class="toggle-indicator" aria-hidden="true"></span>
921					</button>
922				<# } #>
923			</p>
924			<# if ( data.errors ) { #>
925				<ul class="bulk-action-errors hidden">
926					<# _.each( data.errorMessages, function( errorMessage ) { #>
927						<li>{{ errorMessage }}</li>
928					<# } ); #>
929				</ul>
930			<# } #>
931		</div>
932	</script>
933	<?php
934}
935
936/**
937 * Prints the JavaScript templates for update and deletion rows in list tables.
938 *
939 * The update template takes one argument with four values:
940 *
941 *     param {object} data {
942 *         Arguments for the update row
943 *
944 *         @type string slug    Plugin slug.
945 *         @type string plugin  Plugin base name.
946 *         @type string colspan The number of table columns this row spans.
947 *         @type string content The row content.
948 *     }
949 *
950 * The delete template takes one argument with four values:
951 *
952 *     param {object} data {
953 *         Arguments for the update row
954 *
955 *         @type string slug    Plugin slug.
956 *         @type string plugin  Plugin base name.
957 *         @type string name    Plugin name.
958 *         @type string colspan The number of table columns this row spans.
959 *     }
960 *
961 * @since 4.6.0
962 */
963function wp_print_update_row_templates() {
964	?>
965	<script id="tmpl-item-update-row" type="text/template">
966		<tr class="plugin-update-tr update" id="{{ data.slug }}-update" data-slug="{{ data.slug }}" <# if ( data.plugin ) { #>data-plugin="{{ data.plugin }}"<# } #>>
967			<td colspan="{{ data.colspan }}" class="plugin-update colspanchange">
968				{{{ data.content }}}
969			</td>
970		</tr>
971	</script>
972	<script id="tmpl-item-deleted-row" type="text/template">
973		<tr class="plugin-deleted-tr inactive deleted" id="{{ data.slug }}-deleted" data-slug="{{ data.slug }}" <# if ( data.plugin ) { #>data-plugin="{{ data.plugin }}"<# } #>>
974			<td colspan="{{ data.colspan }}" class="plugin-update colspanchange">
975				<# if ( data.plugin ) { #>
976					<?php
977					printf(
978						/* translators: %s: Plugin name. */
979						_x( '%s was successfully deleted.', 'plugin' ),
980						'<strong>{{{ data.name }}}</strong>'
981					);
982					?>
983				<# } else { #>
984					<?php
985					printf(
986						/* translators: %s: Theme name. */
987						_x( '%s was successfully deleted.', 'theme' ),
988						'<strong>{{{ data.name }}}</strong>'
989					);
990					?>
991				<# } #>
992			</td>
993		</tr>
994	</script>
995	<?php
996}
997
998/**
999 * Displays a notice when the user is in recovery mode.
1000 *
1001 * @since 5.2.0
1002 */
1003function wp_recovery_mode_nag() {
1004	if ( ! wp_is_recovery_mode() ) {
1005		return;
1006	}
1007
1008	$url = wp_login_url();
1009	$url = add_query_arg( 'action', WP_Recovery_Mode::EXIT_ACTION, $url );
1010	$url = wp_nonce_url( $url, WP_Recovery_Mode::EXIT_ACTION );
1011
1012	?>
1013	<div class="notice notice-info">
1014		<p>
1015			<?php
1016			printf(
1017				/* translators: %s: Recovery Mode exit link. */
1018				__( 'You are in recovery mode. This means there may be an error with a theme or plugin. To exit recovery mode, log out or use the Exit button. <a href="%s">Exit Recovery Mode</a>' ),
1019				esc_url( $url )
1020			);
1021			?>
1022		</p>
1023	</div>
1024	<?php
1025}
1026
1027/**
1028 * Checks whether auto-updates are enabled.
1029 *
1030 * @since 5.5.0
1031 *
1032 * @param string $type The type of update being checked: 'theme' or 'plugin'.
1033 * @return bool True if auto-updates are enabled for `$type`, false otherwise.
1034 */
1035function wp_is_auto_update_enabled_for_type( $type ) {
1036	if ( ! class_exists( 'WP_Automatic_Updater' ) ) {
1037		require_once ABSPATH . 'wp-admin/includes/class-wp-automatic-updater.php';
1038	}
1039
1040	$updater = new WP_Automatic_Updater();
1041	$enabled = ! $updater->is_disabled();
1042
1043	switch ( $type ) {
1044		case 'plugin':
1045			/**
1046			 * Filters whether plugins auto-update is enabled.
1047			 *
1048			 * @since 5.5.0
1049			 *
1050			 * @param bool $enabled True if plugins auto-update is enabled, false otherwise.
1051			 */
1052			return apply_filters( 'plugins_auto_update_enabled', $enabled );
1053		case 'theme':
1054			/**
1055			 * Filters whether themes auto-update is enabled.
1056			 *
1057			 * @since 5.5.0
1058			 *
1059			 * @param bool $enabled True if themes auto-update is enabled, false otherwise.
1060			 */
1061			return apply_filters( 'themes_auto_update_enabled', $enabled );
1062	}
1063
1064	return false;
1065}
1066
1067/**
1068 * Checks whether auto-updates are forced for an item.
1069 *
1070 * @since 5.6.0
1071 *
1072 * @param string    $type   The type of update being checked: 'theme' or 'plugin'.
1073 * @param bool|null $update Whether to update. The value of null is internally used
1074 *                          to detect whether nothing has hooked into this filter.
1075 * @param object    $item   The update offer.
1076 * @return bool True if auto-updates are forced for `$item`, false otherwise.
1077 */
1078function wp_is_auto_update_forced_for_item( $type, $update, $item ) {
1079	/** This filter is documented in wp-admin/includes/class-wp-automatic-updater.php */
1080	return apply_filters( "auto_update_{$type}", $update, $item );
1081}
1082
1083/**
1084 * Determines the appropriate auto-update message to be displayed.
1085 *
1086 * @since 5.5.0
1087 *
1088 * @return string The update message to be shown.
1089 */
1090function wp_get_auto_update_message() {
1091	$next_update_time = wp_next_scheduled( 'wp_version_check' );
1092
1093	// Check if the event exists.
1094	if ( false === $next_update_time ) {
1095		$message = __( 'Automatic update not scheduled. There may be a problem with WP-Cron.' );
1096	} else {
1097		$time_to_next_update = human_time_diff( (int) $next_update_time );
1098
1099		// See if cron is overdue.
1100		$overdue = ( time() - $next_update_time ) > 0;
1101
1102		if ( $overdue ) {
1103			$message = sprintf(
1104				/* translators: %s: Duration that WP-Cron has been overdue. */
1105				__( 'Automatic update overdue by %s. There may be a problem with WP-Cron.' ),
1106				$time_to_next_update
1107			);
1108		} else {
1109			$message = sprintf(
1110				/* translators: %s: Time until the next update. */
1111				__( 'Automatic update scheduled in %s.' ),
1112				$time_to_next_update
1113			);
1114		}
1115	}
1116
1117	return $message;
1118}
1119