run:R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
23.8 KB
2026-03-11 16:18:51
R W Run
7.8 KB
2026-03-11 16:18:52
R W Run
36.1 KB
2026-03-11 16:18:51
R W Run
11.9 KB
2026-03-11 16:18:52
R W Run
18.94 KB
2026-03-11 16:18:52
R W Run
7.35 KB
2026-03-11 16:18:52
R W Run
28.6 KB
2026-03-11 16:18:51
R W Run
316 By
2026-03-11 16:18:51
R W Run
12.9 KB
2026-03-11 16:18:51
R W Run
61.02 KB
2026-03-11 16:18:52
R W Run
15 KB
2026-03-11 16:18:51
R W Run
112.05 KB
2026-03-11 16:18:51
R W Run
12.47 KB
2026-03-11 16:18:51
R W Run
15.07 KB
2026-03-11 16:18:52
R W Run
9.84 KB
2026-03-11 16:18:52
R W Run
13.17 KB
2026-03-11 16:18:52
R W Run
33.83 KB
2026-03-11 16:18:51
R W Run
42.63 KB
2026-03-11 16:18:51
R W Run
55.71 KB
2026-03-11 16:18:52
R W Run
12.53 KB
2026-03-11 16:18:51
R W Run
2.55 KB
2026-03-11 16:18:52
R W Run
28.92 KB
2026-03-11 16:18:52
R W Run
539 By
2026-03-11 16:18:51
R W Run
367 By
2026-03-11 16:18:52
R W Run
42.65 KB
2026-03-11 16:18:51
R W Run
401 By
2026-03-11 16:18:51
R W Run
6.61 KB
2026-03-11 16:18:51
R W Run
664 By
2026-03-11 16:18:52
R W Run
20.63 KB
2026-03-11 16:18:51
R W Run
2.18 KB
2026-03-11 16:18:52
R W Run
453 By
2026-03-11 16:18:52
R W Run
457 By
2026-03-11 16:18:51
R W Run
36.83 KB
2026-03-11 16:18:52
R W Run
2.41 KB
2026-03-11 16:18:52
R W Run
8.28 KB
2026-03-11 16:18:51
R W Run
13.89 KB
2026-03-11 16:18:51
R W Run
11.76 KB
2026-03-11 16:18:51
R W Run
2.65 KB
2026-03-11 16:18:51
R W Run
7.43 KB
2026-03-11 16:18:51
R W Run
17.46 KB
2026-03-11 16:18:51
R W Run
5.14 KB
2026-03-11 16:18:52
R W Run
16.7 KB
2026-03-11 16:18:51
R W Run
8.28 KB
2026-03-11 16:18:52
R W Run
2.92 KB
2026-03-11 16:18:52
R W Run
1.32 KB
2026-03-11 16:18:51
R W Run
4.6 KB
2026-03-11 16:18:52
R W Run
11.62 KB
2026-03-11 16:18:52
R W Run
2.5 KB
2026-03-11 16:18:51
R W Run
1.97 KB
2026-03-11 16:18:51
R W Run
11.25 KB
2026-03-11 16:18:52
R W Run
5.32 KB
2026-03-11 16:18:51
R W Run
10.99 KB
2026-03-11 16:18:52
R W Run
68.32 KB
2026-03-11 16:18:51
R W Run
6.34 KB
2026-03-11 16:18:51
R W Run
5.49 KB
2026-03-11 16:18:51
R W Run
1.99 KB
2026-03-11 16:18:52
R W Run
7.02 KB
2026-03-11 16:18:51
R W Run
4.91 KB
2026-03-11 16:18:52
R W Run
16.86 KB
2026-03-11 16:18:51
R W Run
24.23 KB
2026-03-11 16:18:51
R W Run
3.97 KB
2026-03-11 16:18:51
R W Run
47.66 KB
2026-03-11 16:18:51
R W Run
9.22 KB
2026-03-11 16:18:51
R W Run
25.51 KB
2026-03-11 16:18:51
R W Run
198.38 KB
2026-03-11 16:18:52
R W Run
56.65 KB
2026-03-11 16:18:51
R W Run
10.46 KB
2026-03-11 16:18:51
R W Run
10.95 KB
2026-03-11 16:18:52
R W Run
29.26 KB
2026-03-11 16:18:51
R W Run
70.91 KB
2026-03-11 16:18:52
R W Run
35.3 KB
2026-03-11 16:18:52
R W Run
16.61 KB
2026-03-11 16:18:52
R W Run
2.57 KB
2026-03-11 16:18:52
R W Run
39.83 KB
2026-03-11 16:18:51
R W Run
70.64 KB
2026-03-11 16:18:51
R W Run
15.56 KB
2026-03-11 16:18:52
R W Run
7.33 KB
2026-03-11 16:18:52
R W Run
253 By
2026-03-11 16:18:51
R W Run
7.96 KB
2026-03-11 16:18:52
R W Run
3.23 KB
2026-03-11 16:18:52
R W Run
969 By
2026-03-11 16:18:52
R W Run
16.28 KB
2026-03-11 16:18:51
R W Run
7.22 KB
2026-03-11 16:18:51
R W Run
12.95 KB
2026-03-11 16:18:51
R W Run
6.53 KB
2026-03-11 16:18:51
R W Run
3.42 KB
2026-03-11 16:18:52
R W Run
5.84 KB
2026-03-11 16:18:51
R W Run
1.97 KB
2026-03-11 16:18:51
R W Run
4.3 KB
2026-03-11 16:18:52
R W Run
2.91 KB
2026-03-11 16:18:51
R W Run
16.46 KB
2026-03-11 16:18:52
R W Run
40.6 KB
2026-03-11 16:18:51
R W Run
20.22 KB
2026-03-11 16:18:51
R W Run
36.11 KB
2026-03-11 16:18:52
R W Run
17.01 KB
2026-03-11 16:18:51
R W Run
7.27 KB
2026-03-11 16:18:52
R W Run
6.62 KB
2026-03-11 16:18:52
R W Run
16.49 KB
2026-03-11 16:18:52
R W Run
1.79 KB
2026-03-11 16:18:52
R W Run
29.82 KB
2026-03-11 16:18:51
R W Run
6.67 KB
2026-03-11 16:18:52
R W Run
8.98 KB
2026-03-11 16:18:52
R W Run
19.42 KB
2026-03-11 16:18:51
R W Run
12.01 KB
2026-03-11 16:18:51
R W Run
17.11 KB
2026-03-11 16:18:51
R W Run
6.74 KB
2026-03-11 16:18:52
R W Run
30.93 KB
2026-03-11 16:18:51
R W Run
4.99 KB
2026-03-11 16:18:51
R W Run
4.25 KB
2026-03-11 16:18:51
R W Run
24.72 KB
2026-03-11 16:18:51
R W Run
29.96 KB
2026-03-11 16:18:52
R W Run
6.41 KB
2026-03-11 16:18:51
R W Run
160 KB
2026-03-11 16:18:51
R W Run
6.72 KB
2026-03-11 16:18:52
R W Run
10.92 KB
2026-03-11 16:18:51
R W Run
4.77 KB
2026-03-11 16:18:51
R W Run
3.38 KB
2026-03-11 16:18:51
R W Run
11.18 KB
2026-03-11 16:18:51
R W Run
62.19 KB
2026-03-11 16:18:51
R W Run
2.46 KB
2026-03-11 16:18:51
R W Run
9.17 KB
2026-03-11 16:18:51
R W Run
32.15 KB
2026-03-11 16:18:51
R W Run
34.05 KB
2026-03-11 16:18:52
R W Run
7.15 KB
2026-03-11 16:18:51
R W Run
3.47 KB
2026-03-11 16:18:52
R W Run
1.87 KB
2026-03-11 16:18:52
R W Run
30.91 KB
2026-03-11 16:18:51
R W Run
7.29 KB
2026-03-11 16:18:52
R W Run
7.35 KB
2026-03-11 16:18:51
R W Run
12.54 KB
2026-03-11 16:18:51
R W Run
19.12 KB
2026-03-11 16:18:51
R W Run
18.12 KB
2026-03-11 16:18:52
R W Run
39.99 KB
2026-03-11 16:18:52
R W Run
5.17 KB
2026-03-11 16:18:52
R W Run
979 By
2026-03-11 16:18:51
R W Run
18.44 KB
2026-03-11 16:18:52
R W Run
10.24 KB
2026-03-11 16:18:51
R W Run
1.77 KB
2026-03-11 16:18:52
R W Run
34.9 KB
2026-03-11 16:18:51
R W Run
7.19 KB
2026-03-11 16:18:52
R W Run
160.5 KB
2026-03-11 16:18:51
R W Run
64.27 KB
2026-03-11 16:18:51
R W Run
27.95 KB
2026-03-11 16:18:51
R W Run
4.69 KB
2026-03-11 16:18:51
R W Run
2.94 KB
2026-03-11 16:18:51
R W Run
43.13 KB
2026-03-11 16:18:52
R W Run
2.25 KB
2026-03-11 16:18:52
R W Run
22.5 KB
2026-03-11 16:18:51
R W Run
13.01 KB
2026-03-11 16:18:52
R W Run
3.27 KB
2026-03-11 16:18:51
R W Run
18 KB
2026-03-11 16:18:51
R W Run
210.4 KB
2026-03-11 16:18:52
R W Run
25.86 KB
2026-03-11 16:18:52
R W Run
115.85 KB
2026-03-11 16:18:51
R W Run
373 By
2026-03-11 16:18:52
R W Run
343 By
2026-03-11 16:18:52
R W Run
338 By
2026-03-11 16:18:51
R W Run
100.73 KB
2026-03-11 16:18:52
R W Run
130.93 KB
2026-03-11 16:18:51
R W Run
19.1 KB
2026-03-11 16:18:51
R W Run
17.41 KB
2026-03-11 16:18:52
R W Run
41.98 KB
2026-03-11 16:18:52
R W Run
400 By
2026-03-11 16:18:52
R W Run
11.1 KB
2026-03-11 16:18:52
R W Run
37.02 KB
2026-03-11 16:18:51
R W Run
2.24 KB
2026-03-11 16:18:51
R W Run
188.13 KB
2026-03-11 16:18:51
R W Run
338 By
2026-03-11 16:18:51
R W Run
38 KB
2026-03-11 16:18:51
R W Run
4.02 KB
2026-03-11 16:18:52
R W Run
5.38 KB
2026-03-11 16:18:51
R W Run
3.05 KB
2026-03-11 16:18:52
R W Run
2.61 KB
2026-03-11 16:18:51
R W Run
1.16 KB
2026-03-11 16:18:52
R W Run
4.04 KB
2026-03-11 16:18:51
R W Run
3.71 KB
2026-03-11 16:18:51
R W Run
24.6 KB
2026-03-11 16:18:51
R W Run
9.56 KB
2026-03-11 16:18:51
R W Run
346.43 KB
2026-03-11 16:18:52
R W Run
281.84 KB
2026-03-11 16:18:52
R W Run
14.95 KB
2026-03-11 16:18:51
R W Run
8.44 KB
2026-03-11 16:18:52
R W Run
168.95 KB
2026-03-11 16:18:52
R W Run
20.71 KB
2026-03-11 16:18:52
R W Run
25.27 KB
2026-03-11 16:18:51
R W Run
5.72 KB
2026-03-11 16:18:51
R W Run
4.63 KB
2026-03-11 16:18:52
R W Run
81.73 KB
2026-03-11 16:18:51
R W Run
67.18 KB
2026-03-11 16:18:51
R W Run
156.36 KB
2026-03-11 16:18:52
R W Run
55.19 KB
2026-03-11 16:18:51
R W Run
162 By
2026-03-11 16:18:51
R W Run
61.72 KB
2026-03-11 16:18:51
R W Run
216.06 KB
2026-03-11 16:18:52
R W Run
65.09 KB
2026-03-11 16:18:51
R W Run
25.24 KB
2026-03-11 16:18:52
R W Run
4.81 KB
2026-03-11 16:18:51
R W Run
6.48 KB
2026-03-11 16:18:52
R W Run
21.25 KB
2026-03-11 16:18:51
R W Run
2.79 KB
2026-03-11 16:18:52
R W Run
89.69 KB
2026-03-11 16:18:52
R W Run
19.42 KB
2026-03-11 16:18:52
R W Run
3.69 KB
2026-03-11 16:18:52
R W Run
4.11 KB
2026-03-11 16:18:51
R W Run
40.74 KB
2026-03-11 16:18:51
R W Run
25.38 KB
2026-03-11 16:18:51
R W Run
43.31 KB
2026-03-11 16:18:52
R W Run
102.57 KB
2026-03-11 16:18:52
R W Run
6.18 KB
2026-03-11 16:18:51
R W Run
124.47 KB
2026-03-11 16:18:52
R W Run
35.65 KB
2026-03-11 16:18:52
R W Run
6.94 KB
2026-03-11 16:18:52
R W Run
67.04 KB
2026-03-11 16:18:52
R W Run
10.62 KB
2026-03-11 16:18:51
R W Run
289.35 KB
2026-03-11 16:18:52
R W Run
36.23 KB
2026-03-11 16:18:51
R W Run
200 By
2026-03-11 16:18:52
R W Run
200 By
2026-03-11 16:18:52
R W Run
98.29 KB
2026-03-11 16:18:52
R W Run
30.02 KB
2026-03-11 16:18:52
R W Run
19.03 KB
2026-03-11 16:18:52
R W Run
5.06 KB
2026-03-11 16:18:52
R W Run
255 By
2026-03-11 16:18:51
R W Run
22.66 KB
2026-03-11 16:18:52
R W Run
154.63 KB
2026-03-11 16:18:51
R W Run
9.68 KB
2026-03-11 16:18:51
R W Run
258 By
2026-03-11 16:18:51
R W Run
23.49 KB
2026-03-11 16:18:51
R W Run
3.16 KB
2026-03-11 16:18:51
R W Run
8.4 KB
2026-03-11 16:18:52
R W Run
441 By
2026-03-11 16:18:51
R W Run
7.39 KB
2026-03-11 16:18:51
R W Run
173 KB
2026-03-11 16:18:52
R W Run
544 By
2026-03-11 16:18:52
R W Run
4.17 KB
2026-03-11 16:18:51
R W Run
35.97 KB
2026-03-11 16:18:52
R W Run
1.69 KB
2026-03-11 16:18:51
R W Run
2.84 KB
2026-03-11 16:18:52
R W Run
6.09 KB
2026-03-11 16:18:51
R W Run
8.71 KB
2026-03-11 16:18:51
R W Run
131.84 KB
2026-03-11 16:18:51
R W Run
37.45 KB
2026-03-11 16:18:51
R W Run
173.89 KB
2026-03-11 16:18:51
R W Run
7.09 KB
2026-03-11 16:18:51
R W Run
6.41 KB
2026-03-11 16:18:51
R W Run
1.08 KB
2026-03-11 16:18:51
R W Run
69.46 KB
2026-03-11 16:18:52
R W Run
445 By
2026-03-11 16:18:51
R W Run
799 By
2026-03-11 16:18:52
R W Run
error_log
📄class-wp-recovery-mode.php
1<?php
2/**
3 * Error Protection API: WP_Recovery_Mode class
4 *
5 * @package WordPress
6 * @since 5.2.0
7 */
8
9/**
10 * Core class used to implement Recovery Mode.
11 *
12 * @since 5.2.0
13 */
14#[AllowDynamicProperties]
15class WP_Recovery_Mode {
16
17 const EXIT_ACTION = 'exit_recovery_mode';
18
19 /**
20 * Service to handle cookies.
21 *
22 * @since 5.2.0
23 * @var WP_Recovery_Mode_Cookie_Service
24 */
25 private $cookie_service;
26
27 /**
28 * Service to generate a recovery mode key.
29 *
30 * @since 5.2.0
31 * @var WP_Recovery_Mode_Key_Service
32 */
33 private $key_service;
34
35 /**
36 * Service to generate and validate recovery mode links.
37 *
38 * @since 5.2.0
39 * @var WP_Recovery_Mode_Link_Service
40 */
41 private $link_service;
42
43 /**
44 * Service to handle sending an email with a recovery mode link.
45 *
46 * @since 5.2.0
47 * @var WP_Recovery_Mode_Email_Service
48 */
49 private $email_service;
50
51 /**
52 * Is recovery mode initialized.
53 *
54 * @since 5.2.0
55 * @var bool
56 */
57 private $is_initialized = false;
58
59 /**
60 * Is recovery mode active in this session.
61 *
62 * @since 5.2.0
63 * @var bool
64 */
65 private $is_active = false;
66
67 /**
68 * Get an ID representing the current recovery mode session.
69 *
70 * @since 5.2.0
71 * @var string
72 */
73 private $session_id = '';
74
75 /**
76 * WP_Recovery_Mode constructor.
77 *
78 * @since 5.2.0
79 */
80 public function __construct() {
81 $this->cookie_service = new WP_Recovery_Mode_Cookie_Service();
82 $this->key_service = new WP_Recovery_Mode_Key_Service();
83 $this->link_service = new WP_Recovery_Mode_Link_Service( $this->cookie_service, $this->key_service );
84 $this->email_service = new WP_Recovery_Mode_Email_Service( $this->link_service );
85 }
86
87 /**
88 * Initialize recovery mode for the current request.
89 *
90 * @since 5.2.0
91 */
92 public function initialize() {
93 $this->is_initialized = true;
94
95 add_action( 'wp_logout', array( $this, 'exit_recovery_mode' ) );
96 add_action( 'login_form_' . self::EXIT_ACTION, array( $this, 'handle_exit_recovery_mode' ) );
97 add_action( 'recovery_mode_clean_expired_keys', array( $this, 'clean_expired_keys' ) );
98
99 if ( ! wp_next_scheduled( 'recovery_mode_clean_expired_keys' ) && ! wp_installing() ) {
100 wp_schedule_event( time(), 'daily', 'recovery_mode_clean_expired_keys' );
101 }
102
103 if ( defined( 'WP_RECOVERY_MODE_SESSION_ID' ) ) {
104 $this->is_active = true;
105 $this->session_id = WP_RECOVERY_MODE_SESSION_ID;
106
107 return;
108 }
109
110 if ( $this->cookie_service->is_cookie_set() ) {
111 $this->handle_cookie();
112
113 return;
114 }
115
116 $this->link_service->handle_begin_link( $this->get_link_ttl() );
117 }
118
119 /**
120 * Checks whether recovery mode is active.
121 *
122 * This will not change after recovery mode has been initialized. {@see WP_Recovery_Mode::run()}.
123 *
124 * @since 5.2.0
125 *
126 * @return bool True if recovery mode is active, false otherwise.
127 */
128 public function is_active() {
129 return $this->is_active;
130 }
131
132 /**
133 * Gets the recovery mode session ID.
134 *
135 * @since 5.2.0
136 *
137 * @return string The session ID if recovery mode is active, empty string otherwise.
138 */
139 public function get_session_id() {
140 return $this->session_id;
141 }
142
143 /**
144 * Checks whether recovery mode has been initialized.
145 *
146 * Recovery mode should not be used until this point. Initialization happens immediately before loading plugins.
147 *
148 * @since 5.2.0
149 *
150 * @return bool
151 */
152 public function is_initialized() {
153 return $this->is_initialized;
154 }
155
156 /**
157 * Handles a fatal error occurring.
158 *
159 * The calling API should immediately die() after calling this function.
160 *
161 * @since 5.2.0
162 *
163 * @param array $error Error details from `error_get_last()`.
164 * @return true|WP_Error|void True if the error was handled and headers have already been sent.
165 * Or the request will exit to try and catch multiple errors at once.
166 * WP_Error if an error occurred preventing it from being handled.
167 */
168 public function handle_error( array $error ) {
169
170 $extension = $this->get_extension_for_error( $error );
171
172 if ( ! $extension || $this->is_network_plugin( $extension ) ) {
173 return new WP_Error( 'invalid_source', __( 'Error not caused by a plugin or theme.' ) );
174 }
175
176 if ( ! $this->is_active() ) {
177 if ( ! is_protected_endpoint() ) {
178 return new WP_Error( 'non_protected_endpoint', __( 'Error occurred on a non-protected endpoint.' ) );
179 }
180
181 if ( ! function_exists( 'wp_generate_password' ) ) {
182 require_once ABSPATH . WPINC . '/pluggable.php';
183 }
184
185 return $this->email_service->maybe_send_recovery_mode_email( $this->get_email_rate_limit(), $error, $extension );
186 }
187
188 if ( ! $this->store_error( $error ) ) {
189 return new WP_Error( 'storage_error', __( 'Failed to store the error.' ) );
190 }
191
192 if ( headers_sent() ) {
193 return true;
194 }
195
196 $this->redirect_protected();
197 }
198
199 /**
200 * Ends the current recovery mode session.
201 *
202 * @since 5.2.0
203 *
204 * @return bool True on success, false on failure.
205 */
206 public function exit_recovery_mode() {
207 if ( ! $this->is_active() ) {
208 return false;
209 }
210
211 $this->email_service->clear_rate_limit();
212 $this->cookie_service->clear_cookie();
213
214 wp_paused_plugins()->delete_all();
215 wp_paused_themes()->delete_all();
216
217 return true;
218 }
219
220 /**
221 * Handles a request to exit Recovery Mode.
222 *
223 * @since 5.2.0
224 */
225 public function handle_exit_recovery_mode() {
226 $redirect_to = wp_get_referer();
227
228 // Safety check in case referrer returns false.
229 if ( ! $redirect_to ) {
230 $redirect_to = is_user_logged_in() ? admin_url() : home_url();
231 }
232
233 if ( ! $this->is_active() ) {
234 wp_safe_redirect( $redirect_to );
235 die;
236 }
237
238 if ( ! isset( $_GET['action'] ) || self::EXIT_ACTION !== $_GET['action'] ) {
239 return;
240 }
241
242 if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], self::EXIT_ACTION ) ) {
243 wp_die( __( 'Exit recovery mode link expired.' ), 403 );
244 }
245
246 if ( ! $this->exit_recovery_mode() ) {
247 wp_die( __( 'Failed to exit recovery mode. Please try again later.' ) );
248 }
249
250 wp_safe_redirect( $redirect_to );
251 die;
252 }
253
254 /**
255 * Cleans any recovery mode keys that have expired according to the link TTL.
256 *
257 * Executes on a daily cron schedule.
258 *
259 * @since 5.2.0
260 */
261 public function clean_expired_keys() {
262 $this->key_service->clean_expired_keys( $this->get_link_ttl() );
263 }
264
265 /**
266 * Handles checking for the recovery mode cookie and validating it.
267 *
268 * @since 5.2.0
269 */
270 protected function handle_cookie() {
271 $validated = $this->cookie_service->validate_cookie();
272
273 if ( is_wp_error( $validated ) ) {
274 $this->cookie_service->clear_cookie();
275
276 $validated->add_data( array( 'status' => 403 ) );
277 wp_die( $validated );
278 }
279
280 $session_id = $this->cookie_service->get_session_id_from_cookie();
281 if ( is_wp_error( $session_id ) ) {
282 $this->cookie_service->clear_cookie();
283
284 $session_id->add_data( array( 'status' => 403 ) );
285 wp_die( $session_id );
286 }
287
288 $this->is_active = true;
289 $this->session_id = $session_id;
290 }
291
292 /**
293 * Gets the rate limit between sending new recovery mode email links.
294 *
295 * @since 5.2.0
296 *
297 * @return int Rate limit in seconds.
298 */
299 protected function get_email_rate_limit() {
300 /**
301 * Filters the rate limit between sending new recovery mode email links.
302 *
303 * @since 5.2.0
304 *
305 * @param int $rate_limit Time to wait in seconds. Defaults to 1 day.
306 */
307 return apply_filters( 'recovery_mode_email_rate_limit', DAY_IN_SECONDS );
308 }
309
310 /**
311 * Gets the number of seconds the recovery mode link is valid for.
312 *
313 * @since 5.2.0
314 *
315 * @return int Interval in seconds.
316 */
317 protected function get_link_ttl() {
318
319 $rate_limit = $this->get_email_rate_limit();
320 $valid_for = $rate_limit;
321
322 /**
323 * Filters the amount of time the recovery mode email link is valid for.
324 *
325 * The ttl must be at least as long as the email rate limit.
326 *
327 * @since 5.2.0
328 *
329 * @param int $valid_for The number of seconds the link is valid for.
330 */
331 $valid_for = apply_filters( 'recovery_mode_email_link_ttl', $valid_for );
332
333 return max( $valid_for, $rate_limit );
334 }
335
336 /**
337 * Gets the extension that the error occurred in.
338 *
339 * @since 5.2.0
340 *
341 * @global string[] $wp_theme_directories
342 *
343 * @param array $error Error details from `error_get_last()`.
344 * @return array|false {
345 * Extension details.
346 *
347 * @type string $slug The extension slug. This is the plugin or theme's directory.
348 * @type string $type The extension type. Either 'plugin' or 'theme'.
349 * }
350 */
351 protected function get_extension_for_error( $error ) {
352 global $wp_theme_directories;
353
354 if ( ! isset( $error['file'] ) ) {
355 return false;
356 }
357
358 if ( ! defined( 'WP_PLUGIN_DIR' ) ) {
359 return false;
360 }
361
362 $error_file = wp_normalize_path( $error['file'] );
363 $wp_plugin_dir = wp_normalize_path( WP_PLUGIN_DIR );
364
365 if ( str_starts_with( $error_file, $wp_plugin_dir ) ) {
366 $path = str_replace( $wp_plugin_dir . '/', '', $error_file );
367 $parts = explode( '/', $path );
368
369 return array(
370 'type' => 'plugin',
371 'slug' => $parts[0],
372 );
373 }
374
375 if ( empty( $wp_theme_directories ) ) {
376 return false;
377 }
378
379 foreach ( $wp_theme_directories as $theme_directory ) {
380 $theme_directory = wp_normalize_path( $theme_directory );
381
382 if ( str_starts_with( $error_file, $theme_directory ) ) {
383 $path = str_replace( $theme_directory . '/', '', $error_file );
384 $parts = explode( '/', $path );
385
386 return array(
387 'type' => 'theme',
388 'slug' => $parts[0],
389 );
390 }
391 }
392
393 return false;
394 }
395
396 /**
397 * Checks whether the given extension a network activated plugin.
398 *
399 * @since 5.2.0
400 *
401 * @param array $extension Extension data.
402 * @return bool True if network plugin, false otherwise.
403 */
404 protected function is_network_plugin( $extension ) {
405 if ( 'plugin' !== $extension['type'] ) {
406 return false;
407 }
408
409 if ( ! is_multisite() ) {
410 return false;
411 }
412
413 $network_plugins = wp_get_active_network_plugins();
414
415 foreach ( $network_plugins as $plugin ) {
416 if ( str_starts_with( $plugin, $extension['slug'] . '/' ) ) {
417 return true;
418 }
419 }
420
421 return false;
422 }
423
424 /**
425 * Stores the given error so that the extension causing it is paused.
426 *
427 * @since 5.2.0
428 *
429 * @param array $error Error details from `error_get_last()`.
430 * @return bool True if the error was stored successfully, false otherwise.
431 */
432 protected function store_error( $error ) {
433 $extension = $this->get_extension_for_error( $error );
434
435 if ( ! $extension ) {
436 return false;
437 }
438
439 switch ( $extension['type'] ) {
440 case 'plugin':
441 return wp_paused_plugins()->set( $extension['slug'], $error );
442 case 'theme':
443 return wp_paused_themes()->set( $extension['slug'], $error );
444 default:
445 return false;
446 }
447 }
448
449 /**
450 * Redirects the current request to allow recovering multiple errors in one go.
451 *
452 * The redirection will only happen when on a protected endpoint.
453 *
454 * It must be ensured that this method is only called when an error actually occurred and will not occur on the
455 * next request again. Otherwise it will create a redirect loop.
456 *
457 * @since 5.2.0
458 */
459 protected function redirect_protected() {
460 // Pluggable is usually loaded after plugins, so we manually include it here for redirection functionality.
461 if ( ! function_exists( 'wp_safe_redirect' ) ) {
462 require_once ABSPATH . WPINC . '/pluggable.php';
463 }
464
465 $scheme = is_ssl() ? 'https://' : 'http://';
466
467 $url = "{$scheme}{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
468 wp_safe_redirect( $url );
469 exit;
470 }
471}
472