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
📄class-wp-automatic-updater.php
1<?php
2/**
3 * Upgrade API: WP_Automatic_Updater class
4 *
5 * @package WordPress
6 * @subpackage Upgrader
7 * @since 4.6.0
8 */
9
10/**
11 * Core class used for handling automatic background updates.
12 *
13 * @since 3.7.0
14 * @since 4.6.0 Moved to its own file from wp-admin/includes/class-wp-upgrader.php.
15 */
16#[AllowDynamicProperties]
17class WP_Automatic_Updater {
18
19 /**
20 * Tracks update results during processing.
21 *
22 * @var array
23 */
24 protected $update_results = array();
25
26 /**
27 * Determines whether the entire automatic updater is disabled.
28 *
29 * @since 3.7.0
30 *
31 * @return bool True if the automatic updater is disabled, false otherwise.
32 */
33 public function is_disabled() {
34 // Background updates are disabled if you don't want file changes.
35 if ( ! wp_is_file_mod_allowed( 'automatic_updater' ) ) {
36 return true;
37 }
38
39 if ( wp_installing() ) {
40 return true;
41 }
42
43 // More fine grained control can be done through the WP_AUTO_UPDATE_CORE constant and filters.
44 $disabled = defined( 'AUTOMATIC_UPDATER_DISABLED' ) && AUTOMATIC_UPDATER_DISABLED;
45
46 /**
47 * Filters whether to entirely disable background updates.
48 *
49 * There are more fine-grained filters and controls for selective disabling.
50 * This filter parallels the AUTOMATIC_UPDATER_DISABLED constant in name.
51 *
52 * This also disables update notification emails. That may change in the future.
53 *
54 * @since 3.7.0
55 *
56 * @param bool $disabled Whether the updater should be disabled.
57 */
58 return apply_filters( 'automatic_updater_disabled', $disabled );
59 }
60
61 /**
62 * Checks whether access to a given directory is allowed.
63 *
64 * This is used when detecting version control checkouts. Takes into account
65 * the PHP `open_basedir` restrictions, so that WordPress does not try to access
66 * directories it is not allowed to.
67 *
68 * @since 6.2.0
69 *
70 * @param string $dir The directory to check.
71 * @return bool True if access to the directory is allowed, false otherwise.
72 */
73 public function is_allowed_dir( $dir ) {
74 if ( is_string( $dir ) ) {
75 $dir = trim( $dir );
76 }
77
78 if ( ! is_string( $dir ) || '' === $dir ) {
79 _doing_it_wrong(
80 __METHOD__,
81 sprintf(
82 /* translators: %s: The "$dir" argument. */
83 __( 'The "%s" argument must be a non-empty string.' ),
84 '$dir'
85 ),
86 '6.2.0'
87 );
88
89 return false;
90 }
91
92 $open_basedir = ini_get( 'open_basedir' );
93
94 if ( empty( $open_basedir ) ) {
95 return true;
96 }
97
98 $open_basedir_list = explode( PATH_SEPARATOR, $open_basedir );
99
100 foreach ( $open_basedir_list as $basedir ) {
101 if ( '' !== trim( $basedir ) && str_starts_with( $dir, $basedir ) ) {
102 return true;
103 }
104 }
105
106 return false;
107 }
108
109 /**
110 * Checks for version control checkouts.
111 *
112 * Checks for Subversion, Git, Mercurial, and Bazaar. It recursively looks up the
113 * filesystem to the top of the drive, erring on the side of detecting a VCS
114 * checkout somewhere.
115 *
116 * ABSPATH is always checked in addition to whatever `$context` is (which may be the
117 * wp-content directory, for example). The underlying assumption is that if you are
118 * using version control *anywhere*, then you should be making decisions for
119 * how things get updated.
120 *
121 * @since 3.7.0
122 *
123 * @param string $context The filesystem path to check, in addition to ABSPATH.
124 * @return bool True if a VCS checkout was discovered at `$context` or ABSPATH,
125 * or anywhere higher. False otherwise.
126 */
127 public function is_vcs_checkout( $context ) {
128 $context_dirs = array( untrailingslashit( $context ) );
129 if ( ABSPATH !== $context ) {
130 $context_dirs[] = untrailingslashit( ABSPATH );
131 }
132
133 $vcs_dirs = array( '.svn', '.git', '.hg', '.bzr' );
134 $check_dirs = array();
135
136 foreach ( $context_dirs as $context_dir ) {
137 // Walk up from $context_dir to the root.
138 do {
139 $check_dirs[] = $context_dir;
140
141 // Once we've hit '/' or 'C:\', we need to stop. dirname will keep returning the input here.
142 if ( dirname( $context_dir ) === $context_dir ) {
143 break;
144 }
145
146 // Continue one level at a time.
147 } while ( $context_dir = dirname( $context_dir ) );
148 }
149
150 $check_dirs = array_unique( $check_dirs );
151 $checkout = false;
152
153 // Search all directories we've found for evidence of version control.
154 foreach ( $vcs_dirs as $vcs_dir ) {
155 foreach ( $check_dirs as $check_dir ) {
156 if ( ! $this->is_allowed_dir( $check_dir ) ) {
157 continue;
158 }
159
160 $checkout = is_dir( rtrim( $check_dir, '\\/' ) . "/$vcs_dir" );
161 if ( $checkout ) {
162 break 2;
163 }
164 }
165 }
166
167 /**
168 * Filters whether the automatic updater should consider a filesystem
169 * location to be potentially managed by a version control system.
170 *
171 * @since 3.7.0
172 *
173 * @param bool $checkout Whether a VCS checkout was discovered at `$context`
174 * or ABSPATH, or anywhere higher.
175 * @param string $context The filesystem context (a path) against which
176 * filesystem status should be checked.
177 */
178 return apply_filters( 'automatic_updates_is_vcs_checkout', $checkout, $context );
179 }
180
181 /**
182 * Tests to see if we can and should update a specific item.
183 *
184 * @since 3.7.0
185 *
186 * @global wpdb $wpdb WordPress database abstraction object.
187 *
188 * @param string $type The type of update being checked: 'core', 'theme',
189 * 'plugin', 'translation'.
190 * @param object $item The update offer.
191 * @param string $context The filesystem context (a path) against which filesystem
192 * access and status should be checked.
193 * @return bool True if the item should be updated, false otherwise.
194 */
195 public function should_update( $type, $item, $context ) {
196 // Used to see if WP_Filesystem is set up to allow unattended updates.
197 $skin = new Automatic_Upgrader_Skin();
198
199 if ( $this->is_disabled() ) {
200 return false;
201 }
202
203 // Only relax the filesystem checks when the update doesn't include new files.
204 $allow_relaxed_file_ownership = false;
205 if ( 'core' === $type && isset( $item->new_files ) && ! $item->new_files ) {
206 $allow_relaxed_file_ownership = true;
207 }
208
209 // If we can't do an auto core update, we may still be able to email the user.
210 if ( ! $skin->request_filesystem_credentials( false, $context, $allow_relaxed_file_ownership )
211 || $this->is_vcs_checkout( $context )
212 ) {
213 if ( 'core' === $type ) {
214 $this->send_core_update_notification_email( $item );
215 }
216 return false;
217 }
218
219 // Next up, is this an item we can update?
220 if ( 'core' === $type ) {
221 $update = Core_Upgrader::should_update_to_version( $item->current );
222 } elseif ( 'plugin' === $type || 'theme' === $type ) {
223 $update = ! empty( $item->autoupdate );
224
225 if ( ! $update && wp_is_auto_update_enabled_for_type( $type ) ) {
226 // Check if the site admin has enabled auto-updates by default for the specific item.
227 $auto_updates = (array) get_site_option( "auto_update_{$type}s", array() );
228 $update = in_array( $item->{$type}, $auto_updates, true );
229 }
230 } else {
231 $update = ! empty( $item->autoupdate );
232 }
233
234 // If the `disable_autoupdate` flag is set, override any user-choice, but allow filters.
235 if ( ! empty( $item->disable_autoupdate ) ) {
236 $update = false;
237 }
238
239 /**
240 * Filters whether to automatically update core, a plugin, a theme, or a language.
241 *
242 * The dynamic portion of the hook name, `$type`, refers to the type of update
243 * being checked.
244 *
245 * Possible hook names include:
246 *
247 * - `auto_update_core`
248 * - `auto_update_plugin`
249 * - `auto_update_theme`
250 * - `auto_update_translation`
251 *
252 * Since WordPress 3.7, minor and development versions of core, and translations have
253 * been auto-updated by default. New installs on WordPress 5.6 or higher will also
254 * auto-update major versions by default. Starting in 5.6, older sites can opt-in to
255 * major version auto-updates, and auto-updates for plugins and themes.
256 *
257 * See the {@see 'allow_dev_auto_core_updates'}, {@see 'allow_minor_auto_core_updates'},
258 * and {@see 'allow_major_auto_core_updates'} filters for a more straightforward way to
259 * adjust core updates.
260 *
261 * @since 3.7.0
262 * @since 5.5.0 The `$update` parameter accepts the value of null.
263 *
264 * @param bool|null $update Whether to update. The value of null is internally used
265 * to detect whether nothing has hooked into this filter.
266 * @param object $item The update offer.
267 */
268 $update = apply_filters( "auto_update_{$type}", $update, $item );
269
270 if ( ! $update ) {
271 if ( 'core' === $type ) {
272 $this->send_core_update_notification_email( $item );
273 }
274 return false;
275 }
276
277 // If it's a core update, are we actually compatible with its requirements?
278 if ( 'core' === $type ) {
279 global $wpdb;
280
281 $php_compat = version_compare( PHP_VERSION, $item->php_version, '>=' );
282 if ( file_exists( WP_CONTENT_DIR . '/db.php' ) && empty( $wpdb->is_mysql ) ) {
283 $mysql_compat = true;
284 } else {
285 $mysql_compat = version_compare( $wpdb->db_version(), $item->mysql_version, '>=' );
286 }
287
288 if ( ! $php_compat || ! $mysql_compat ) {
289 return false;
290 }
291 }
292
293 // If updating a plugin or theme, ensure the minimum PHP version requirements are satisfied.
294 if ( in_array( $type, array( 'plugin', 'theme' ), true ) ) {
295 if ( ! empty( $item->requires_php ) && version_compare( PHP_VERSION, $item->requires_php, '<' ) ) {
296 return false;
297 }
298 }
299
300 return true;
301 }
302
303 /**
304 * Notifies an administrator of a core update.
305 *
306 * @since 3.7.0
307 *
308 * @param object $item The update offer.
309 * @return bool True if the site administrator is notified of a core update,
310 * false otherwise.
311 */
312 protected function send_core_update_notification_email( $item ) {
313 $notified = get_site_option( 'auto_core_update_notified' );
314
315 // Don't notify if we've already notified the same email address of the same version.
316 if ( $notified
317 && get_site_option( 'admin_email' ) === $notified['email']
318 && $notified['version'] === $item->current
319 ) {
320 return false;
321 }
322
323 // See if we need to notify users of a core update.
324 $notify = ! empty( $item->notify_email );
325
326 /**
327 * Filters whether to notify the site administrator of a new core update.
328 *
329 * By default, administrators are notified when the update offer received
330 * from WordPress.org sets a particular flag. This allows some discretion
331 * in if and when to notify.
332 *
333 * This filter is only evaluated once per release. If the same email address
334 * was already notified of the same new version, WordPress won't repeatedly
335 * email the administrator.
336 *
337 * This filter is also used on about.php to check if a plugin has disabled
338 * these notifications.
339 *
340 * @since 3.7.0
341 *
342 * @param bool $notify Whether the site administrator is notified.
343 * @param object $item The update offer.
344 */
345 if ( ! apply_filters( 'send_core_update_notification_email', $notify, $item ) ) {
346 return false;
347 }
348
349 $this->send_email( 'manual', $item );
350 return true;
351 }
352
353 /**
354 * Updates an item, if appropriate.
355 *
356 * @since 3.7.0
357 *
358 * @param string $type The type of update being checked: 'core', 'theme', 'plugin', 'translation'.
359 * @param object $item The update offer.
360 * @return null|WP_Error
361 */
362 public function update( $type, $item ) {
363 $skin = new Automatic_Upgrader_Skin();
364
365 switch ( $type ) {
366 case 'core':
367 // The Core upgrader doesn't use the Upgrader's skin during the actual main part of the upgrade, instead, firing a filter.
368 add_filter( 'update_feedback', array( $skin, 'feedback' ) );
369 $upgrader = new Core_Upgrader( $skin );
370 $context = ABSPATH;
371 break;
372 case 'plugin':
373 $upgrader = new Plugin_Upgrader( $skin );
374 $context = WP_PLUGIN_DIR; // We don't support custom Plugin directories, or updates for WPMU_PLUGIN_DIR.
375 break;
376 case 'theme':
377 $upgrader = new Theme_Upgrader( $skin );
378 $context = get_theme_root( $item->theme );
379 break;
380 case 'translation':
381 $upgrader = new Language_Pack_Upgrader( $skin );
382 $context = WP_CONTENT_DIR; // WP_LANG_DIR;
383 break;
384 }
385
386 // Determine whether we can and should perform this update.
387 if ( ! $this->should_update( $type, $item, $context ) ) {
388 return false;
389 }
390
391 /**
392 * Fires immediately prior to an auto-update.
393 *
394 * @since 4.4.0
395 *
396 * @param string $type The type of update being checked: 'core', 'theme', 'plugin', or 'translation'.
397 * @param object $item The update offer.
398 * @param string $context The filesystem context (a path) against which filesystem access and status
399 * should be checked.
400 */
401 do_action( 'pre_auto_update', $type, $item, $context );
402
403 $upgrader_item = $item;
404 switch ( $type ) {
405 case 'core':
406 /* translators: %s: WordPress version. */
407 $skin->feedback( __( 'Updating to WordPress %s' ), $item->version );
408 /* translators: %s: WordPress version. */
409 $item_name = sprintf( __( 'WordPress %s' ), $item->version );
410 break;
411 case 'theme':
412 $upgrader_item = $item->theme;
413 $theme = wp_get_theme( $upgrader_item );
414 $item_name = $theme->get( 'Name' );
415 // Add the current version so that it can be reported in the notification email.
416 $item->current_version = $theme->get( 'Version' );
417 if ( empty( $item->current_version ) ) {
418 $item->current_version = false;
419 }
420 /* translators: %s: Theme name. */
421 $skin->feedback( __( 'Updating theme: %s' ), $item_name );
422 break;
423 case 'plugin':
424 $upgrader_item = $item->plugin;
425 $plugin_data = get_plugin_data( $context . '/' . $upgrader_item );
426 $item_name = $plugin_data['Name'];
427 // Add the current version so that it can be reported in the notification email.
428 $item->current_version = $plugin_data['Version'];
429 if ( empty( $item->current_version ) ) {
430 $item->current_version = false;
431 }
432 /* translators: %s: Plugin name. */
433 $skin->feedback( __( 'Updating plugin: %s' ), $item_name );
434 break;
435 case 'translation':
436 $language_item_name = $upgrader->get_name_for_update( $item );
437 /* translators: %s: Project name (plugin, theme, or WordPress). */
438 $item_name = sprintf( __( 'Translations for %s' ), $language_item_name );
439 /* translators: 1: Project name (plugin, theme, or WordPress), 2: Language. */
440 $skin->feedback( sprintf( __( 'Updating translations for %1$s (%2$s)&#8230;' ), $language_item_name, $item->language ) );
441 break;
442 }
443
444 $allow_relaxed_file_ownership = false;
445 if ( 'core' === $type && isset( $item->new_files ) && ! $item->new_files ) {
446 $allow_relaxed_file_ownership = true;
447 }
448
449 $is_debug = WP_DEBUG && WP_DEBUG_LOG;
450 if ( 'plugin' === $type ) {
451 $was_active = is_plugin_active( $upgrader_item );
452 if ( $is_debug ) {
453 error_log( ' Upgrading plugin ' . var_export( $item->slug, true ) . '...' );
454 }
455 }
456
457 if ( 'theme' === $type && $is_debug ) {
458 error_log( ' Upgrading theme ' . var_export( $item->theme, true ) . '...' );
459 }
460
461 /*
462 * Enable maintenance mode before upgrading the plugin or theme.
463 *
464 * This avoids potential non-fatal errors being detected
465 * while scraping for a fatal error if some files are still
466 * being moved.
467 *
468 * While these checks are intended only for plugins,
469 * maintenance mode is enabled for all upgrade types as any
470 * update could contain an error or warning, which could cause
471 * the scrape to miss a fatal error in the plugin update.
472 */
473 if ( 'translation' !== $type ) {
474 $upgrader->maintenance_mode( true );
475 }
476
477 // Boom, this site's about to get a whole new splash of paint!
478 $upgrade_result = $upgrader->upgrade(
479 $upgrader_item,
480 array(
481 'clear_update_cache' => false,
482 // Always use partial builds if possible for core updates.
483 'pre_check_md5' => false,
484 // Only available for core updates.
485 'attempt_rollback' => true,
486 // Allow relaxed file ownership in some scenarios.
487 'allow_relaxed_file_ownership' => $allow_relaxed_file_ownership,
488 )
489 );
490
491 /*
492 * After WP_Upgrader::upgrade() completes, maintenance mode is disabled.
493 *
494 * Re-enable maintenance mode while attempting to detect fatal errors
495 * and potentially rolling back.
496 *
497 * This avoids errors if the site is visited while fatal errors exist
498 * or while files are still being moved.
499 */
500 if ( 'translation' !== $type ) {
501 $upgrader->maintenance_mode( true );
502 }
503
504 // If the filesystem is unavailable, false is returned.
505 if ( false === $upgrade_result ) {
506 $upgrade_result = new WP_Error( 'fs_unavailable', __( 'Could not access filesystem.' ) );
507 }
508
509 if ( 'core' === $type ) {
510 if ( is_wp_error( $upgrade_result )
511 && ( 'up_to_date' === $upgrade_result->get_error_code()
512 || 'locked' === $upgrade_result->get_error_code() )
513 ) {
514 // Allow visitors to browse the site again.
515 $upgrader->maintenance_mode( false );
516
517 /*
518 * These aren't actual errors, treat it as a skipped-update instead
519 * to avoid triggering the post-core update failure routines.
520 */
521 return false;
522 }
523
524 // Core doesn't output this, so let's append it, so we don't get confused.
525 if ( is_wp_error( $upgrade_result ) ) {
526 $upgrade_result->add( 'installation_failed', __( 'Installation failed.' ) );
527 $skin->error( $upgrade_result );
528 } else {
529 $skin->feedback( __( 'WordPress updated successfully.' ) );
530 }
531 }
532
533 $is_debug = WP_DEBUG && WP_DEBUG_LOG;
534
535 if ( 'theme' === $type && $is_debug ) {
536 error_log( ' Theme ' . var_export( $item->theme, true ) . ' has been upgraded.' );
537 }
538
539 if ( 'plugin' === $type ) {
540 if ( $is_debug ) {
541 error_log( ' Plugin ' . var_export( $item->slug, true ) . ' has been upgraded.' );
542 if ( is_plugin_inactive( $upgrader_item ) ) {
543 error_log( ' ' . var_export( $upgrader_item, true ) . ' is inactive and will not be checked for fatal errors.' );
544 }
545 }
546
547 if ( $was_active && ! is_wp_error( $upgrade_result ) ) {
548
549 /*
550 * The usual time limit is five minutes. However, as a loopback request
551 * is about to be performed, increase the time limit to account for this.
552 */
553 if ( function_exists( 'set_time_limit' ) ) {
554 set_time_limit( 10 * MINUTE_IN_SECONDS );
555 }
556
557 /*
558 * Avoids a race condition when there are 2 sequential plugins that have
559 * fatal errors. It seems a slight delay is required for the loopback to
560 * use the updated plugin code in the request. This can cause the second
561 * plugin's fatal error checking to be inaccurate, and may also affect
562 * subsequent plugin checks.
563 */
564 sleep( 2 );
565
566 if ( $this->has_fatal_error() ) {
567 $upgrade_result = new WP_Error();
568 $temp_backup = array(
569 array(
570 'dir' => 'plugins',
571 'slug' => $item->slug,
572 'src' => WP_PLUGIN_DIR,
573 ),
574 );
575
576 $backup_restored = $upgrader->restore_temp_backup( $temp_backup );
577 if ( is_wp_error( $backup_restored ) ) {
578 $upgrade_result->add(
579 'plugin_update_fatal_error_rollback_failed',
580 sprintf(
581 /* translators: %s: The plugin's slug. */
582 __( "The update for '%s' contained a fatal error. The previously installed version could not be restored." ),
583 $item->slug
584 )
585 );
586
587 $upgrade_result->merge_from( $backup_restored );
588 } else {
589 $upgrade_result->add(
590 'plugin_update_fatal_error_rollback_successful',
591 sprintf(
592 /* translators: %s: The plugin's slug. */
593 __( "The update for '%s' contained a fatal error. The previously installed version has been restored." ),
594 $item->slug
595 )
596 );
597
598 $backup_deleted = $upgrader->delete_temp_backup( $temp_backup );
599 if ( is_wp_error( $backup_deleted ) ) {
600 $upgrade_result->merge_from( $backup_deleted );
601 }
602 }
603
604 /*
605 * Should emails not be working, log the message(s) so that
606 * the log file contains context for the fatal error,
607 * and whether a rollback was performed.
608 *
609 * `trigger_error()` is not used as it outputs a stack trace
610 * to this location rather than to the fatal error, which will
611 * appear above this entry in the log file.
612 */
613 if ( $is_debug ) {
614 error_log( ' ' . implode( "\n", $upgrade_result->get_error_messages() ) );
615 }
616 } elseif ( $is_debug ) {
617 error_log( ' The update for ' . var_export( $item->slug, true ) . ' has no fatal errors.' );
618 }
619 }
620 }
621
622 // All processes are complete. Allow visitors to browse the site again.
623 if ( 'translation' !== $type ) {
624 $upgrader->maintenance_mode( false );
625 }
626
627 $this->update_results[ $type ][] = (object) array(
628 'item' => $item,
629 'result' => $upgrade_result,
630 'name' => $item_name,
631 'messages' => $skin->get_upgrade_messages(),
632 );
633
634 return $upgrade_result;
635 }
636
637 /**
638 * Kicks off the background update process, looping through all pending updates.
639 *
640 * @since 3.7.0
641 */
642 public function run() {
643 if ( $this->is_disabled() ) {
644 return;
645 }
646
647 if ( ! is_main_network() || ! is_main_site() ) {
648 return;
649 }
650
651 if ( ! WP_Upgrader::create_lock( 'auto_updater' ) ) {
652 return;
653 }
654
655 $is_debug = WP_DEBUG && WP_DEBUG_LOG;
656
657 if ( $is_debug ) {
658 error_log( 'Automatic updates starting...' );
659 }
660
661 // Don't automatically run these things, as we'll handle it ourselves.
662 remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
663 remove_action( 'upgrader_process_complete', 'wp_version_check' );
664 remove_action( 'upgrader_process_complete', 'wp_update_plugins' );
665 remove_action( 'upgrader_process_complete', 'wp_update_themes' );
666
667 // Next, plugins.
668 wp_update_plugins(); // Check for plugin updates.
669 $plugin_updates = get_site_transient( 'update_plugins' );
670 if ( $plugin_updates && ! empty( $plugin_updates->response ) ) {
671 if ( $is_debug ) {
672 error_log( ' Automatic plugin updates starting...' );
673 }
674
675 foreach ( $plugin_updates->response as $plugin ) {
676 $this->update( 'plugin', $plugin );
677 }
678
679 // Force refresh of plugin update information.
680 wp_clean_plugins_cache();
681
682 if ( $is_debug ) {
683 error_log( ' Automatic plugin updates complete.' );
684 }
685 }
686
687 // Next, those themes we all love.
688 wp_update_themes(); // Check for theme updates.
689 $theme_updates = get_site_transient( 'update_themes' );
690 if ( $theme_updates && ! empty( $theme_updates->response ) ) {
691 if ( $is_debug ) {
692 error_log( ' Automatic theme updates starting...' );
693 }
694
695 foreach ( $theme_updates->response as $theme ) {
696 $this->update( 'theme', (object) $theme );
697 }
698 // Force refresh of theme update information.
699 wp_clean_themes_cache();
700
701 if ( $is_debug ) {
702 error_log( ' Automatic theme updates complete.' );
703 }
704 }
705
706 if ( $is_debug ) {
707 error_log( 'Automatic updates complete.' );
708 }
709
710 // Next, process any core update.
711 wp_version_check(); // Check for core updates.
712 $core_update = find_core_auto_update();
713
714 if ( $core_update ) {
715 $this->update( 'core', $core_update );
716 }
717
718 /*
719 * Clean up, and check for any pending translations.
720 * (Core_Upgrader checks for core updates.)
721 */
722 $theme_stats = array();
723 if ( isset( $this->update_results['theme'] ) ) {
724 foreach ( $this->update_results['theme'] as $upgrade ) {
725 $theme_stats[ $upgrade->item->theme ] = ( true === $upgrade->result );
726 }
727 }
728 wp_update_themes( $theme_stats ); // Check for theme updates.
729
730 $plugin_stats = array();
731 if ( isset( $this->update_results['plugin'] ) ) {
732 foreach ( $this->update_results['plugin'] as $upgrade ) {
733 $plugin_stats[ $upgrade->item->plugin ] = ( true === $upgrade->result );
734 }
735 }
736 wp_update_plugins( $plugin_stats ); // Check for plugin updates.
737
738 // Finally, process any new translations.
739 $language_updates = wp_get_translation_updates();
740 if ( $language_updates ) {
741 foreach ( $language_updates as $update ) {
742 $this->update( 'translation', $update );
743 }
744
745 // Clear existing caches.
746 wp_clean_update_cache();
747
748 wp_version_check(); // Check for core updates.
749 wp_update_themes(); // Check for theme updates.
750 wp_update_plugins(); // Check for plugin updates.
751 }
752
753 // Send debugging email to admin for all development installations.
754 if ( ! empty( $this->update_results ) ) {
755 $development_version = str_contains( wp_get_wp_version(), '-' );
756
757 /**
758 * Filters whether to send a debugging email for each automatic background update.
759 *
760 * @since 3.7.0
761 *
762 * @param bool $development_version By default, emails are sent if the
763 * install is a development version.
764 * Return false to avoid the email.
765 */
766 if ( apply_filters( 'automatic_updates_send_debug_email', $development_version ) ) {
767 $this->send_debug_email();
768 }
769
770 if ( ! empty( $this->update_results['core'] ) ) {
771 $this->after_core_update( $this->update_results['core'][0] );
772 } elseif ( ! empty( $this->update_results['plugin'] ) || ! empty( $this->update_results['theme'] ) ) {
773 $this->after_plugin_theme_update( $this->update_results );
774 }
775
776 /**
777 * Fires after all automatic updates have run.
778 *
779 * @since 3.8.0
780 *
781 * @param array $update_results The results of all attempted updates.
782 */
783 do_action( 'automatic_updates_complete', $this->update_results );
784 }
785
786 WP_Upgrader::release_lock( 'auto_updater' );
787 }
788
789 /**
790 * Checks whether to send an email and avoid processing future updates after
791 * attempting a core update.
792 *
793 * @since 3.7.0
794 *
795 * @param object $update_result The result of the core update. Includes the update offer and result.
796 */
797 protected function after_core_update( $update_result ) {
798 $wp_version = wp_get_wp_version();
799
800 $core_update = $update_result->item;
801 $result = $update_result->result;
802
803 if ( ! is_wp_error( $result ) ) {
804 $this->send_email( 'success', $core_update );
805 return;
806 }
807
808 $error_code = $result->get_error_code();
809
810 /*
811 * Any of these WP_Error codes are critical failures, as in they occurred after we started to copy core files.
812 * We should not try to perform a background update again until there is a successful one-click update performed by the user.
813 */
814 $critical = false;
815 if ( 'disk_full' === $error_code || str_contains( $error_code, '__copy_dir' ) ) {
816 $critical = true;
817 } elseif ( 'rollback_was_required' === $error_code && is_wp_error( $result->get_error_data()->rollback ) ) {
818 // A rollback is only critical if it failed too.
819 $critical = true;
820 $rollback_result = $result->get_error_data()->rollback;
821 } elseif ( str_contains( $error_code, 'do_rollback' ) ) {
822 $critical = true;
823 }
824
825 if ( $critical ) {
826 $critical_data = array(
827 'attempted' => $core_update->current,
828 'current' => $wp_version,
829 'error_code' => $error_code,
830 'error_data' => $result->get_error_data(),
831 'timestamp' => time(),
832 'critical' => true,
833 );
834 if ( isset( $rollback_result ) ) {
835 $critical_data['rollback_code'] = $rollback_result->get_error_code();
836 $critical_data['rollback_data'] = $rollback_result->get_error_data();
837 }
838 update_site_option( 'auto_core_update_failed', $critical_data );
839 $this->send_email( 'critical', $core_update, $result );
840 return;
841 }
842
843 /*
844 * Any other WP_Error code (like download_failed or files_not_writable) occurs before
845 * we tried to copy over core files. Thus, the failures are early and graceful.
846 *
847 * We should avoid trying to perform a background update again for the same version.
848 * But we can try again if another version is released.
849 *
850 * For certain 'transient' failures, like download_failed, we should allow retries.
851 * In fact, let's schedule a special update for an hour from now. (It's possible
852 * the issue could actually be on WordPress.org's side.) If that one fails, then email.
853 */
854 $send = true;
855 $transient_failures = array( 'incompatible_archive', 'download_failed', 'insane_distro', 'locked' );
856 if ( in_array( $error_code, $transient_failures, true ) && ! get_site_option( 'auto_core_update_failed' ) ) {
857 wp_schedule_single_event( time() + HOUR_IN_SECONDS, 'wp_maybe_auto_update' );
858 $send = false;
859 }
860
861 $notified = get_site_option( 'auto_core_update_notified' );
862
863 // Don't notify if we've already notified the same email address of the same version of the same notification type.
864 if ( $notified
865 && 'fail' === $notified['type']
866 && get_site_option( 'admin_email' ) === $notified['email']
867 && $notified['version'] === $core_update->current
868 ) {
869 $send = false;
870 }
871
872 update_site_option(
873 'auto_core_update_failed',
874 array(
875 'attempted' => $core_update->current,
876 'current' => $wp_version,
877 'error_code' => $error_code,
878 'error_data' => $result->get_error_data(),
879 'timestamp' => time(),
880 'retry' => in_array( $error_code, $transient_failures, true ),
881 )
882 );
883
884 if ( $send ) {
885 $this->send_email( 'fail', $core_update, $result );
886 }
887 }
888
889 /**
890 * Sends an email upon the completion or failure of a background core update.
891 *
892 * @since 3.7.0
893 *
894 * @param string $type The type of email to send. Can be one of 'success', 'fail', 'manual', 'critical'.
895 * @param object $core_update The update offer that was attempted.
896 * @param mixed $result Optional. The result for the core update. Can be WP_Error.
897 */
898 protected function send_email( $type, $core_update, $result = null ) {
899 update_site_option(
900 'auto_core_update_notified',
901 array(
902 'type' => $type,
903 'email' => get_site_option( 'admin_email' ),
904 'version' => $core_update->current,
905 'timestamp' => time(),
906 )
907 );
908
909 $next_user_core_update = get_preferred_from_update_core();
910
911 // If the update transient is empty, use the update we just performed.
912 if ( ! $next_user_core_update ) {
913 $next_user_core_update = $core_update;
914 }
915
916 if ( 'upgrade' === $next_user_core_update->response
917 && version_compare( $next_user_core_update->version, $core_update->version, '>' )
918 ) {
919 $newer_version_available = true;
920 } else {
921 $newer_version_available = false;
922 }
923
924 /**
925 * Filters whether to send an email following an automatic background core update.
926 *
927 * @since 3.7.0
928 *
929 * @param bool $send Whether to send the email. Default true.
930 * @param string $type The type of email to send. Can be one of
931 * 'success', 'fail', 'critical'.
932 * @param object $core_update The update offer that was attempted.
933 * @param mixed $result The result for the core update. Can be WP_Error.
934 */
935 if ( 'manual' !== $type && ! apply_filters( 'auto_core_update_send_email', true, $type, $core_update, $result ) ) {
936 return;
937 }
938
939 $admin_user = get_user_by( 'email', get_site_option( 'admin_email' ) );
940
941 if ( $admin_user ) {
942 $switched_locale = switch_to_user_locale( $admin_user->ID );
943 } else {
944 $switched_locale = switch_to_locale( get_locale() );
945 }
946
947 switch ( $type ) {
948 case 'success': // We updated.
949 /* translators: Site updated notification email subject. 1: Site title, 2: WordPress version. */
950 $subject = __( '[%1$s] Your site has updated to WordPress %2$s' );
951 break;
952
953 case 'fail': // We tried to update but couldn't.
954 case 'manual': // We can't update (and made no attempt).
955 /* translators: Update available notification email subject. 1: Site title, 2: WordPress version. */
956 $subject = __( '[%1$s] WordPress %2$s is available. Please update!' );
957 break;
958
959 case 'critical': // We tried to update, started to copy files, then things went wrong.
960 /* translators: Site down notification email subject. 1: Site title. */
961 $subject = __( '[%1$s] URGENT: Your site may be down due to a failed update' );
962 break;
963
964 default:
965 return;
966 }
967
968 // If the auto-update is not to the latest version, say that the current version of WP is available instead.
969 $version = 'success' === $type ? $core_update->current : $next_user_core_update->current;
970 $subject = sprintf( $subject, wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ), $version );
971
972 $body = '';
973
974 switch ( $type ) {
975 case 'success':
976 $body .= sprintf(
977 /* translators: 1: Home URL, 2: WordPress version. */
978 __( 'Howdy! Your site at %1$s has been updated automatically to WordPress %2$s.' ),
979 home_url(),
980 $core_update->current
981 );
982 $body .= "\n\n";
983 if ( ! $newer_version_available ) {
984 $body .= __( 'No further action is needed on your part.' ) . ' ';
985 }
986
987 // Can only reference the About screen if their update was successful.
988 list( $about_version ) = explode( '-', $core_update->current, 2 );
989 /* translators: %s: WordPress version. */
990 $body .= sprintf( __( 'For more on version %s, see the About WordPress screen:' ), $about_version );
991 $body .= "\n" . admin_url( 'about.php' );
992
993 if ( $newer_version_available ) {
994 /* translators: %s: WordPress latest version. */
995 $body .= "\n\n" . sprintf( __( 'WordPress %s is also now available.' ), $next_user_core_update->current ) . ' ';
996 $body .= __( 'Updating is easy and only takes a few moments:' );
997 $body .= "\n" . network_admin_url( 'update-core.php' );
998 }
999
1000 break;
1001
1002 case 'fail':
1003 case 'manual':
1004 $body .= sprintf(
1005 /* translators: 1: Home URL, 2: WordPress version. */
1006 __( 'Please update your site at %1$s to WordPress %2$s.' ),
1007 home_url(),
1008 $next_user_core_update->current
1009 );
1010
1011 $body .= "\n\n";
1012
1013 /*
1014 * Don't show this message if there is a newer version available.
1015 * Potential for confusion, and also not useful for them to know at this point.
1016 */
1017 if ( 'fail' === $type && ! $newer_version_available ) {
1018 $body .= __( 'An attempt was made, but your site could not be updated automatically.' ) . ' ';
1019 }
1020
1021 $body .= __( 'Updating is easy and only takes a few moments:' );
1022 $body .= "\n" . network_admin_url( 'update-core.php' );
1023 break;
1024
1025 case 'critical':
1026 if ( $newer_version_available ) {
1027 $body .= sprintf(
1028 /* translators: 1: Home URL, 2: WordPress version. */
1029 __( 'Your site at %1$s experienced a critical failure while trying to update WordPress to version %2$s.' ),
1030 home_url(),
1031 $core_update->current
1032 );
1033 } else {
1034 $body .= sprintf(
1035 /* translators: 1: Home URL, 2: WordPress latest version. */
1036 __( 'Your site at %1$s experienced a critical failure while trying to update to the latest version of WordPress, %2$s.' ),
1037 home_url(),
1038 $core_update->current
1039 );
1040 }
1041
1042 $body .= "\n\n" . __( "This means your site may be offline or broken. Don't panic; this can be fixed." );
1043
1044 $body .= "\n\n" . __( "Please check out your site now. It's possible that everything is working. If it says you need to update, you should do so:" );
1045 $body .= "\n" . network_admin_url( 'update-core.php' );
1046 break;
1047 }
1048
1049 $critical_support = 'critical' === $type && ! empty( $core_update->support_email );
1050 if ( $critical_support ) {
1051 // Support offer if available.
1052 $body .= "\n\n" . sprintf(
1053 /* translators: %s: Support email address. */
1054 __( 'The WordPress team is willing to help you. Forward this email to %s and the team will work with you to make sure your site is working.' ),
1055 $core_update->support_email
1056 );
1057 } else {
1058 // Add a note about the support forums.
1059 $body .= "\n\n" . __( 'If you experience any issues or need support, the volunteers in the WordPress.org support forums may be able to help.' );
1060 $body .= "\n" . __( 'https://wordpress.org/support/forums/' );
1061 }
1062
1063 // Updates are important!
1064 if ( 'success' !== $type || $newer_version_available ) {
1065 $body .= "\n\n" . __( 'Keeping your site updated is important for security. It also makes the internet a safer place for you and your readers.' );
1066 }
1067
1068 if ( $critical_support ) {
1069 $body .= ' ' . __( "Reach out to WordPress Core developers to ensure you'll never have this problem again." );
1070 }
1071
1072 // If things are successful and we're now on the latest, mention plugins and themes if any are out of date.
1073 if ( 'success' === $type && ! $newer_version_available && ( get_plugin_updates() || get_theme_updates() ) ) {
1074 $body .= "\n\n" . __( 'You also have some plugins or themes with updates available. Update them now:' );
1075 $body .= "\n" . network_admin_url();
1076 }
1077
1078 $body .= "\n\n" . __( 'The WordPress Team' ) . "\n";
1079
1080 if ( 'critical' === $type && is_wp_error( $result ) ) {
1081 $body .= "\n***\n\n";
1082 /* translators: %s: WordPress version. */
1083 $body .= sprintf( __( 'Your site was running version %s.' ), get_bloginfo( 'version' ) );
1084 $body .= ' ' . __( 'Some data that describes the error your site encountered has been put together.' );
1085 $body .= ' ' . __( 'Your hosting company, support forum volunteers, or a friendly developer may be able to use this information to help you:' );
1086
1087 /*
1088 * If we had a rollback and we're still critical, then the rollback failed too.
1089 * Loop through all errors (the main WP_Error, the update result, the rollback result) for code, data, etc.
1090 */
1091 if ( 'rollback_was_required' === $result->get_error_code() ) {
1092 $errors = array( $result, $result->get_error_data()->update, $result->get_error_data()->rollback );
1093 } else {
1094 $errors = array( $result );
1095 }
1096
1097 foreach ( $errors as $error ) {
1098 if ( ! is_wp_error( $error ) ) {
1099 continue;
1100 }
1101
1102 $error_code = $error->get_error_code();
1103 /* translators: %s: Error code. */
1104 $body .= "\n\n" . sprintf( __( 'Error code: %s' ), $error_code );
1105
1106 if ( 'rollback_was_required' === $error_code ) {
1107 continue;
1108 }
1109
1110 if ( $error->get_error_message() ) {
1111 $body .= "\n" . $error->get_error_message();
1112 }
1113
1114 $error_data = $error->get_error_data();
1115 if ( $error_data ) {
1116 $body .= "\n" . implode( ', ', (array) $error_data );
1117 }
1118 }
1119
1120 $body .= "\n";
1121 }
1122
1123 $to = get_site_option( 'admin_email' );
1124 $headers = '';
1125
1126 $email = compact( 'to', 'subject', 'body', 'headers' );
1127
1128 /**
1129 * Filters the email sent following an automatic background core update.
1130 *
1131 * @since 3.7.0
1132 *
1133 * @param array $email {
1134 * Array of email arguments that will be passed to wp_mail().
1135 *
1136 * @type string $to The email recipient. An array of emails
1137 * can be returned, as handled by wp_mail().
1138 * @type string $subject The email's subject.
1139 * @type string $body The email message body.
1140 * @type string $headers Any email headers, defaults to no headers.
1141 * }
1142 * @param string $type The type of email being sent. Can be one of
1143 * 'success', 'fail', 'manual', 'critical'.
1144 * @param object $core_update The update offer that was attempted.
1145 * @param mixed $result The result for the core update. Can be WP_Error.
1146 */
1147 $email = apply_filters( 'auto_core_update_email', $email, $type, $core_update, $result );
1148
1149 wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
1150
1151 if ( $switched_locale ) {
1152 restore_previous_locale();
1153 }
1154 }
1155
1156 /**
1157 * Checks whether an email should be sent after attempting plugin or theme updates.
1158 *
1159 * @since 5.5.0
1160 *
1161 * @param array $update_results The results of update tasks.
1162 */
1163 protected function after_plugin_theme_update( $update_results ) {
1164 $successful_updates = array();
1165 $failed_updates = array();
1166
1167 if ( ! empty( $update_results['plugin'] ) ) {
1168 /**
1169 * Filters whether to send an email following an automatic background plugin update.
1170 *
1171 * @since 5.5.0
1172 * @since 5.5.1 Added the `$update_results` parameter.
1173 *
1174 * @param bool $enabled True if plugin update notifications are enabled, false otherwise.
1175 * @param array $update_results The results of plugins update tasks.
1176 */
1177 $notifications_enabled = apply_filters( 'auto_plugin_update_send_email', true, $update_results['plugin'] );
1178
1179 if ( $notifications_enabled ) {
1180 foreach ( $update_results['plugin'] as $update_result ) {
1181 if ( true === $update_result->result ) {
1182 $successful_updates['plugin'][] = $update_result;
1183 } else {
1184 $failed_updates['plugin'][] = $update_result;
1185 }
1186 }
1187 }
1188 }
1189
1190 if ( ! empty( $update_results['theme'] ) ) {
1191 /**
1192 * Filters whether to send an email following an automatic background theme update.
1193 *
1194 * @since 5.5.0
1195 * @since 5.5.1 Added the `$update_results` parameter.
1196 *
1197 * @param bool $enabled True if theme update notifications are enabled, false otherwise.
1198 * @param array $update_results The results of theme update tasks.
1199 */
1200 $notifications_enabled = apply_filters( 'auto_theme_update_send_email', true, $update_results['theme'] );
1201
1202 if ( $notifications_enabled ) {
1203 foreach ( $update_results['theme'] as $update_result ) {
1204 if ( true === $update_result->result ) {
1205 $successful_updates['theme'][] = $update_result;
1206 } else {
1207 $failed_updates['theme'][] = $update_result;
1208 }
1209 }
1210 }
1211 }
1212
1213 if ( empty( $successful_updates ) && empty( $failed_updates ) ) {
1214 return;
1215 }
1216
1217 if ( empty( $failed_updates ) ) {
1218 $this->send_plugin_theme_email( 'success', $successful_updates, $failed_updates );
1219 } elseif ( empty( $successful_updates ) ) {
1220 $this->send_plugin_theme_email( 'fail', $successful_updates, $failed_updates );
1221 } else {
1222 $this->send_plugin_theme_email( 'mixed', $successful_updates, $failed_updates );
1223 }
1224 }
1225
1226 /**
1227 * Sends an email upon the completion or failure of a plugin or theme background update.
1228 *
1229 * @since 5.5.0
1230 *
1231 * @param string $type The type of email to send. Can be one of 'success', 'fail', 'mixed'.
1232 * @param array $successful_updates A list of updates that succeeded.
1233 * @param array $failed_updates A list of updates that failed.
1234 */
1235 protected function send_plugin_theme_email( $type, $successful_updates, $failed_updates ) {
1236 // No updates were attempted.
1237 if ( empty( $successful_updates ) && empty( $failed_updates ) ) {
1238 return;
1239 }
1240
1241 $unique_failures = false;
1242 $past_failure_emails = get_option( 'auto_plugin_theme_update_emails', array() );
1243
1244 /*
1245 * When only failures have occurred, an email should only be sent if there are unique failures.
1246 * A failure is considered unique if an email has not been sent for an update attempt failure
1247 * to a plugin or theme with the same new_version.
1248 */
1249 if ( 'fail' === $type ) {
1250 foreach ( $failed_updates as $update_type => $failures ) {
1251 foreach ( $failures as $failed_update ) {
1252 if ( ! isset( $past_failure_emails[ $failed_update->item->{$update_type} ] ) ) {
1253 $unique_failures = true;
1254 continue;
1255 }
1256
1257 // Check that the failure represents a new failure based on the new_version.
1258 if ( version_compare( $past_failure_emails[ $failed_update->item->{$update_type} ], $failed_update->item->new_version, '<' ) ) {
1259 $unique_failures = true;
1260 }
1261 }
1262 }
1263
1264 if ( ! $unique_failures ) {
1265 return;
1266 }
1267 }
1268
1269 $admin_user = get_user_by( 'email', get_site_option( 'admin_email' ) );
1270
1271 if ( $admin_user ) {
1272 $switched_locale = switch_to_user_locale( $admin_user->ID );
1273 } else {
1274 $switched_locale = switch_to_locale( get_locale() );
1275 }
1276
1277 $body = array();
1278 $successful_plugins = ( ! empty( $successful_updates['plugin'] ) );
1279 $successful_themes = ( ! empty( $successful_updates['theme'] ) );
1280 $failed_plugins = ( ! empty( $failed_updates['plugin'] ) );
1281 $failed_themes = ( ! empty( $failed_updates['theme'] ) );
1282
1283 switch ( $type ) {
1284 case 'success':
1285 if ( $successful_plugins && $successful_themes ) {
1286 /* translators: %s: Site title. */
1287 $subject = __( '[%s] Some plugins and themes have automatically updated' );
1288 $body[] = sprintf(
1289 /* translators: %s: Home URL. */
1290 __( 'Howdy! Some plugins and themes have automatically updated to their latest versions on your site at %s. No further action is needed on your part.' ),
1291 home_url()
1292 );
1293 } elseif ( $successful_plugins ) {
1294 /* translators: %s: Site title. */
1295 $subject = __( '[%s] Some plugins were automatically updated' );
1296 $body[] = sprintf(
1297 /* translators: %s: Home URL. */
1298 __( 'Howdy! Some plugins have automatically updated to their latest versions on your site at %s. No further action is needed on your part.' ),
1299 home_url()
1300 );
1301 } else {
1302 /* translators: %s: Site title. */
1303 $subject = __( '[%s] Some themes were automatically updated' );
1304 $body[] = sprintf(
1305 /* translators: %s: Home URL. */
1306 __( 'Howdy! Some themes have automatically updated to their latest versions on your site at %s. No further action is needed on your part.' ),
1307 home_url()
1308 );
1309 }
1310
1311 break;
1312 case 'fail':
1313 case 'mixed':
1314 if ( $failed_plugins && $failed_themes ) {
1315 /* translators: %s: Site title. */
1316 $subject = __( '[%s] Some plugins and themes have failed to update' );
1317 $body[] = sprintf(
1318 /* translators: %s: Home URL. */
1319 __( 'Howdy! Plugins and themes failed to update on your site at %s.' ),
1320 home_url()
1321 );
1322 } elseif ( $failed_plugins ) {
1323 /* translators: %s: Site title. */
1324 $subject = __( '[%s] Some plugins have failed to update' );
1325 $body[] = sprintf(
1326 /* translators: %s: Home URL. */
1327 __( 'Howdy! Plugins failed to update on your site at %s.' ),
1328 home_url()
1329 );
1330 } else {
1331 /* translators: %s: Site title. */
1332 $subject = __( '[%s] Some themes have failed to update' );
1333 $body[] = sprintf(
1334 /* translators: %s: Home URL. */
1335 __( 'Howdy! Themes failed to update on your site at %s.' ),
1336 home_url()
1337 );
1338 }
1339
1340 break;
1341 }
1342
1343 if ( in_array( $type, array( 'fail', 'mixed' ), true ) ) {
1344 $body[] = "\n";
1345 $body[] = __( 'Please check your site now. It’s possible that everything is working. If there are updates available, you should update.' );
1346 $body[] = "\n";
1347
1348 // List failed plugin updates.
1349 if ( ! empty( $failed_updates['plugin'] ) ) {
1350 $body[] = __( 'The following plugins failed to update. If there was a fatal error in the update, the previously installed version has been restored.' );
1351
1352 foreach ( $failed_updates['plugin'] as $item ) {
1353 $body_message = '';
1354 $item_url = '';
1355
1356 if ( ! empty( $item->item->url ) ) {
1357 $item_url = ' : ' . esc_url( $item->item->url );
1358 }
1359
1360 if ( $item->item->current_version ) {
1361 $body_message .= sprintf(
1362 /* translators: 1: Plugin name, 2: Current version number, 3: New version number, 4: Plugin URL. */
1363 __( '- %1$s (from version %2$s to %3$s)%4$s' ),
1364 html_entity_decode( $item->name ),
1365 $item->item->current_version,
1366 $item->item->new_version,
1367 $item_url
1368 );
1369 } else {
1370 $body_message .= sprintf(
1371 /* translators: 1: Plugin name, 2: Version number, 3: Plugin URL. */
1372 __( '- %1$s version %2$s%3$s' ),
1373 html_entity_decode( $item->name ),
1374 $item->item->new_version,
1375 $item_url
1376 );
1377 }
1378
1379 $body[] = $body_message;
1380
1381 $past_failure_emails[ $item->item->plugin ] = $item->item->new_version;
1382 }
1383
1384 $body[] = "\n";
1385 }
1386
1387 // List failed theme updates.
1388 if ( ! empty( $failed_updates['theme'] ) ) {
1389 $body[] = __( 'These themes failed to update:' );
1390
1391 foreach ( $failed_updates['theme'] as $item ) {
1392 if ( $item->item->current_version ) {
1393 $body[] = sprintf(
1394 /* translators: 1: Theme name, 2: Current version number, 3: New version number. */
1395 __( '- %1$s (from version %2$s to %3$s)' ),
1396 html_entity_decode( $item->name ),
1397 $item->item->current_version,
1398 $item->item->new_version
1399 );
1400 } else {
1401 $body[] = sprintf(
1402 /* translators: 1: Theme name, 2: Version number. */
1403 __( '- %1$s version %2$s' ),
1404 html_entity_decode( $item->name ),
1405 $item->item->new_version
1406 );
1407 }
1408
1409 $past_failure_emails[ $item->item->theme ] = $item->item->new_version;
1410 }
1411
1412 $body[] = "\n";
1413 }
1414 }
1415
1416 // List successful updates.
1417 if ( in_array( $type, array( 'success', 'mixed' ), true ) ) {
1418 $body[] = "\n";
1419
1420 // List successful plugin updates.
1421 if ( ! empty( $successful_updates['plugin'] ) ) {
1422 $body[] = __( 'These plugins are now up to date:' );
1423
1424 foreach ( $successful_updates['plugin'] as $item ) {
1425 $body_message = '';
1426 $item_url = '';
1427
1428 if ( ! empty( $item->item->url ) ) {
1429 $item_url = ' : ' . esc_url( $item->item->url );
1430 }
1431
1432 if ( $item->item->current_version ) {
1433 $body_message .= sprintf(
1434 /* translators: 1: Plugin name, 2: Current version number, 3: New version number, 4: Plugin URL. */
1435 __( '- %1$s (from version %2$s to %3$s)%4$s' ),
1436 html_entity_decode( $item->name ),
1437 $item->item->current_version,
1438 $item->item->new_version,
1439 $item_url
1440 );
1441 } else {
1442 $body_message .= sprintf(
1443 /* translators: 1: Plugin name, 2: Version number, 3: Plugin URL. */
1444 __( '- %1$s version %2$s%3$s' ),
1445 html_entity_decode( $item->name ),
1446 $item->item->new_version,
1447 $item_url
1448 );
1449 }
1450 $body[] = $body_message;
1451
1452 unset( $past_failure_emails[ $item->item->plugin ] );
1453 }
1454
1455 $body[] = "\n";
1456 }
1457
1458 // List successful theme updates.
1459 if ( ! empty( $successful_updates['theme'] ) ) {
1460 $body[] = __( 'These themes are now up to date:' );
1461
1462 foreach ( $successful_updates['theme'] as $item ) {
1463 if ( $item->item->current_version ) {
1464 $body[] = sprintf(
1465 /* translators: 1: Theme name, 2: Current version number, 3: New version number. */
1466 __( '- %1$s (from version %2$s to %3$s)' ),
1467 html_entity_decode( $item->name ),
1468 $item->item->current_version,
1469 $item->item->new_version
1470 );
1471 } else {
1472 $body[] = sprintf(
1473 /* translators: 1: Theme name, 2: Version number. */
1474 __( '- %1$s version %2$s' ),
1475 html_entity_decode( $item->name ),
1476 $item->item->new_version
1477 );
1478 }
1479
1480 unset( $past_failure_emails[ $item->item->theme ] );
1481 }
1482
1483 $body[] = "\n";
1484 }
1485 }
1486
1487 if ( $failed_plugins ) {
1488 $body[] = sprintf(
1489 /* translators: %s: Plugins screen URL. */
1490 __( 'To manage plugins on your site, visit the Plugins page: %s' ),
1491 admin_url( 'plugins.php' )
1492 );
1493 $body[] = "\n";
1494 }
1495
1496 if ( $failed_themes ) {
1497 $body[] = sprintf(
1498 /* translators: %s: Themes screen URL. */
1499 __( 'To manage themes on your site, visit the Themes page: %s' ),
1500 admin_url( 'themes.php' )
1501 );
1502 $body[] = "\n";
1503 }
1504
1505 // Add a note about the support forums.
1506 $body[] = __( 'If you experience any issues or need support, the volunteers in the WordPress.org support forums may be able to help.' );
1507 $body[] = __( 'https://wordpress.org/support/forums/' );
1508 $body[] = "\n" . __( 'The WordPress Team' );
1509
1510 if ( '' !== get_option( 'blogname' ) ) {
1511 $site_title = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
1512 } else {
1513 $site_title = parse_url( home_url(), PHP_URL_HOST );
1514 }
1515
1516 $body = implode( "\n", $body );
1517 $to = get_site_option( 'admin_email' );
1518 $subject = sprintf( $subject, $site_title );
1519 $headers = '';
1520
1521 $email = compact( 'to', 'subject', 'body', 'headers' );
1522
1523 /**
1524 * Filters the email sent following an automatic background update for plugins and themes.
1525 *
1526 * @since 5.5.0
1527 *
1528 * @param array $email {
1529 * Array of email arguments that will be passed to wp_mail().
1530 *
1531 * @type string $to The email recipient. An array of emails
1532 * can be returned, as handled by wp_mail().
1533 * @type string $subject The email's subject.
1534 * @type string $body The email message body.
1535 * @type string $headers Any email headers, defaults to no headers.
1536 * }
1537 * @param string $type The type of email being sent. Can be one of 'success', 'fail', 'mixed'.
1538 * @param array $successful_updates A list of updates that succeeded.
1539 * @param array $failed_updates A list of updates that failed.
1540 */
1541 $email = apply_filters( 'auto_plugin_theme_update_email', $email, $type, $successful_updates, $failed_updates );
1542
1543 $result = wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
1544
1545 if ( $result ) {
1546 update_option( 'auto_plugin_theme_update_emails', $past_failure_emails );
1547 }
1548
1549 if ( $switched_locale ) {
1550 restore_previous_locale();
1551 }
1552 }
1553
1554 /**
1555 * Prepares and sends an email of a full log of background update results, useful for debugging and geekery.
1556 *
1557 * @since 3.7.0
1558 */
1559 protected function send_debug_email() {
1560 $admin_user = get_user_by( 'email', get_site_option( 'admin_email' ) );
1561
1562 if ( $admin_user ) {
1563 $switched_locale = switch_to_user_locale( $admin_user->ID );
1564 } else {
1565 $switched_locale = switch_to_locale( get_locale() );
1566 }
1567
1568 $body = array();
1569 $failures = 0;
1570
1571 /* translators: %s: Network home URL. */
1572 $body[] = sprintf( __( 'WordPress site: %s' ), network_home_url( '/' ) );
1573
1574 // Core.
1575 if ( isset( $this->update_results['core'] ) ) {
1576 $result = $this->update_results['core'][0];
1577
1578 if ( $result->result && ! is_wp_error( $result->result ) ) {
1579 /* translators: %s: WordPress version. */
1580 $body[] = sprintf( __( 'SUCCESS: WordPress was successfully updated to %s' ), $result->name );
1581 } else {
1582 /* translators: %s: WordPress version. */
1583 $body[] = sprintf( __( 'FAILED: WordPress failed to update to %s' ), $result->name );
1584 ++$failures;
1585 }
1586
1587 $body[] = '';
1588 }
1589
1590 // Plugins, Themes, Translations.
1591 foreach ( array( 'plugin', 'theme', 'translation' ) as $type ) {
1592 if ( ! isset( $this->update_results[ $type ] ) ) {
1593 continue;
1594 }
1595
1596 $success_items = wp_list_filter( $this->update_results[ $type ], array( 'result' => true ) );
1597
1598 if ( $success_items ) {
1599 $messages = array(
1600 'plugin' => __( 'The following plugins were successfully updated:' ),
1601 'theme' => __( 'The following themes were successfully updated:' ),
1602 'translation' => __( 'The following translations were successfully updated:' ),
1603 );
1604
1605 $body[] = $messages[ $type ];
1606 foreach ( wp_list_pluck( $success_items, 'name' ) as $name ) {
1607 /* translators: %s: Name of plugin / theme / translation. */
1608 $body[] = ' * ' . sprintf( __( 'SUCCESS: %s' ), $name );
1609 }
1610 }
1611
1612 if ( $success_items !== $this->update_results[ $type ] ) {
1613 // Failed updates.
1614 $messages = array(
1615 'plugin' => __( 'The following plugins failed to update:' ),
1616 'theme' => __( 'The following themes failed to update:' ),
1617 'translation' => __( 'The following translations failed to update:' ),
1618 );
1619
1620 $body[] = $messages[ $type ];
1621
1622 foreach ( $this->update_results[ $type ] as $item ) {
1623 if ( ! $item->result || is_wp_error( $item->result ) ) {
1624 /* translators: %s: Name of plugin / theme / translation. */
1625 $body[] = ' * ' . sprintf( __( 'FAILED: %s' ), $item->name );
1626 ++$failures;
1627 }
1628 }
1629 }
1630
1631 $body[] = '';
1632 }
1633
1634 if ( '' !== get_bloginfo( 'name' ) ) {
1635 $site_title = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
1636 } else {
1637 $site_title = parse_url( home_url(), PHP_URL_HOST );
1638 }
1639
1640 if ( $failures ) {
1641 $body[] = trim(
1642 __(
1643 "BETA TESTING?
1644=============
1645
1646This debugging email is sent when you are using a development version of WordPress.
1647
1648If you think these failures might be due to a bug in WordPress, could you report it?
1649 * Open a thread in the support forums: https://wordpress.org/support/forum/alphabeta
1650 * Or, if you're comfortable writing a bug report: https://core.trac.wordpress.org/
1651
1652Thanks! -- The WordPress Team"
1653 )
1654 );
1655 $body[] = '';
1656
1657 /* translators: Background update failed notification email subject. %s: Site title. */
1658 $subject = sprintf( __( '[%s] Background Update Failed' ), $site_title );
1659 } else {
1660 /* translators: Background update finished notification email subject. %s: Site title. */
1661 $subject = sprintf( __( '[%s] Background Update Finished' ), $site_title );
1662 }
1663
1664 $body[] = trim(
1665 __(
1666 'UPDATE LOG
1667=========='
1668 )
1669 );
1670 $body[] = '';
1671
1672 foreach ( array( 'core', 'plugin', 'theme', 'translation' ) as $type ) {
1673 if ( ! isset( $this->update_results[ $type ] ) ) {
1674 continue;
1675 }
1676
1677 foreach ( $this->update_results[ $type ] as $update ) {
1678 $body[] = $update->name;
1679 $body[] = str_repeat( '-', strlen( $update->name ) );
1680
1681 foreach ( $update->messages as $message ) {
1682 $body[] = ' ' . html_entity_decode( str_replace( '&#8230;', '...', $message ) );
1683 }
1684
1685 if ( is_wp_error( $update->result ) ) {
1686 $results = array( 'update' => $update->result );
1687
1688 // If we rolled back, we want to know an error that occurred then too.
1689 if ( 'rollback_was_required' === $update->result->get_error_code() ) {
1690 $results = (array) $update->result->get_error_data();
1691 }
1692
1693 foreach ( $results as $result_type => $result ) {
1694 if ( ! is_wp_error( $result ) ) {
1695 continue;
1696 }
1697
1698 if ( 'rollback' === $result_type ) {
1699 /* translators: 1: Error code, 2: Error message. */
1700 $body[] = ' ' . sprintf( __( 'Rollback Error: [%1$s] %2$s' ), $result->get_error_code(), $result->get_error_message() );
1701 } else {
1702 /* translators: 1: Error code, 2: Error message. */
1703 $body[] = ' ' . sprintf( __( 'Error: [%1$s] %2$s' ), $result->get_error_code(), $result->get_error_message() );
1704 }
1705
1706 if ( $result->get_error_data() ) {
1707 $body[] = ' ' . implode( ', ', (array) $result->get_error_data() );
1708 }
1709 }
1710 }
1711
1712 $body[] = '';
1713 }
1714 }
1715
1716 $email = array(
1717 'to' => get_site_option( 'admin_email' ),
1718 'subject' => $subject,
1719 'body' => implode( "\n", $body ),
1720 'headers' => '',
1721 );
1722
1723 /**
1724 * Filters the debug email that can be sent following an automatic
1725 * background core update.
1726 *
1727 * @since 3.8.0
1728 *
1729 * @param array $email {
1730 * Array of email arguments that will be passed to wp_mail().
1731 *
1732 * @type string $to The email recipient. An array of emails
1733 * can be returned, as handled by wp_mail().
1734 * @type string $subject Email subject.
1735 * @type string $body Email message body.
1736 * @type string $headers Any email headers. Default empty.
1737 * }
1738 * @param int $failures The number of failures encountered while upgrading.
1739 * @param mixed $results The results of all attempted updates.
1740 */
1741 $email = apply_filters( 'automatic_updates_debug_email', $email, $failures, $this->update_results );
1742
1743 wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
1744
1745 if ( $switched_locale ) {
1746 restore_previous_locale();
1747 }
1748 }
1749
1750 /**
1751 * Performs a loopback request to check for potential fatal errors.
1752 *
1753 * Fatal errors cannot be detected unless maintenance mode is enabled.
1754 *
1755 * @since 6.6.0
1756 *
1757 * @global int $upgrading The Unix timestamp marking when upgrading WordPress began.
1758 *
1759 * @return bool Whether a fatal error was detected.
1760 */
1761 protected function has_fatal_error() {
1762 global $upgrading;
1763
1764 $maintenance_file = ABSPATH . '.maintenance';
1765 if ( ! file_exists( $maintenance_file ) ) {
1766 return false;
1767 }
1768
1769 require $maintenance_file;
1770 if ( ! is_int( $upgrading ) ) {
1771 return false;
1772 }
1773
1774 $scrape_key = md5( $upgrading );
1775 $scrape_nonce = (string) $upgrading;
1776 $transient = 'scrape_key_' . $scrape_key;
1777 set_transient( $transient, $scrape_nonce, 30 );
1778
1779 $cookies = wp_unslash( $_COOKIE );
1780 $scrape_params = array(
1781 'wp_scrape_key' => $scrape_key,
1782 'wp_scrape_nonce' => $scrape_nonce,
1783 );
1784 $headers = array(
1785 'Cache-Control' => 'no-cache',
1786 );
1787
1788 /** This filter is documented in wp-includes/class-wp-http-streams.php */
1789 $sslverify = apply_filters( 'https_local_ssl_verify', false );
1790
1791 // Include Basic auth in the loopback request.
1792 if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
1793 $headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
1794 }
1795
1796 // Time to wait for loopback request to finish.
1797 $timeout = 50; // 50 seconds.
1798
1799 $is_debug = WP_DEBUG && WP_DEBUG_LOG;
1800 if ( $is_debug ) {
1801 error_log( ' Scraping home page...' );
1802 }
1803
1804 $needle_start = "###### wp_scraping_result_start:$scrape_key ######";
1805 $needle_end = "###### wp_scraping_result_end:$scrape_key ######";
1806 $url = add_query_arg( $scrape_params, home_url( '/' ) );
1807 $response = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout', 'sslverify' ) );
1808
1809 if ( is_wp_error( $response ) ) {
1810 if ( $is_debug ) {
1811 error_log( 'Loopback request failed: ' . $response->get_error_message() );
1812 }
1813 return true;
1814 }
1815
1816 // If this outputs `true` in the log, it means there were no fatal errors detected.
1817 if ( $is_debug ) {
1818 error_log( var_export( substr( $response['body'], strpos( $response['body'], '###### wp_scraping_result_start:' ) ), true ) );
1819 }
1820
1821 $body = wp_remote_retrieve_body( $response );
1822 $scrape_result_position = strpos( $body, $needle_start );
1823 $result = null;
1824
1825 if ( false !== $scrape_result_position ) {
1826 $error_output = substr( $body, $scrape_result_position + strlen( $needle_start ) );
1827 $error_output = substr( $error_output, 0, strpos( $error_output, $needle_end ) );
1828 $result = json_decode( trim( $error_output ), true );
1829 }
1830
1831 delete_transient( $transient );
1832
1833 // Only fatal errors will result in a 'type' key.
1834 return isset( $result['type'] );
1835 }
1836}
1837
Ui Ux Design – Teachers Night Out https://cardgames4educators.com Wed, 16 Oct 2024 22:24:18 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 https://cardgames4educators.com/wp-content/uploads/2024/06/cropped-Card-4-Educators-logo-32x32.png Ui Ux Design – Teachers Night Out https://cardgames4educators.com 32 32 Masters In English How English Speaker https://cardgames4educators.com/masters-in-english-how-english-speaker/ https://cardgames4educators.com/masters-in-english-how-english-speaker/#comments Mon, 27 May 2024 08:54:45 +0000 https://themexriver.com/wp/kadu/?p=1

Erat himenaeos neque id sagittis massa. Hac suscipit pulvinar dignissim platea magnis eu. Don tellus a pharetra inceptos efficitur dui pulvinar. Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent pulvinar odio volutpat parturient. Quisque risus finibus suspendisse mus purus magnis facilisi condimentum consectetur dui. Curae elit suspendisse cursus vehicula.

Turpis taciti class non vel pretium quis pulvinar tempor lobortis nunc. Libero phasellus parturient sapien volutpat malesuada ornare. Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae. Porta est tempor ex eget feugiat vulputate ipsum. Justo nec iaculis habitant diam arcu fermentum.

We offer comprehen sive emplo ment services such as assistance wit employer compliance.Our company is your strategic HR partner as instead of HR. john smithson

Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae.

Exploring Learning Landscapes in Academic

Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent.

]]>
https://cardgames4educators.com/masters-in-english-how-english-speaker/feed/ 1