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
📄pluggable.php
1<?php
2/**
3 * These functions can be replaced via plugins. If plugins do not redefine these
4 * functions, then these will be used instead.
5 *
6 * @package WordPress
7 */
8
9if ( ! function_exists( 'wp_set_current_user' ) ) :
10 /**
11 * Changes the current user by ID or name.
12 *
13 * Set $id to null and specify a name if you do not know a user's ID.
14 *
15 * Some WordPress functionality is based on the current user and not based on
16 * the signed in user. Therefore, it opens the ability to edit and perform
17 * actions on users who aren't signed in.
18 *
19 * @since 2.0.3
20 *
21 * @global WP_User $current_user The current user object which holds the user data.
22 *
23 * @param int|null $id User ID.
24 * @param string $name User's username.
25 * @return WP_User Current user User object.
26 */
27 function wp_set_current_user( $id, $name = '' ) {
28 global $current_user;
29
30 // If `$id` matches the current user, there is nothing to do.
31 if ( isset( $current_user )
32 && ( $current_user instanceof WP_User )
33 && ( $id === $current_user->ID )
34 && ( null !== $id )
35 ) {
36 return $current_user;
37 }
38
39 $current_user = new WP_User( $id, $name );
40
41 setup_userdata( $current_user->ID );
42
43 /**
44 * Fires after the current user is set.
45 *
46 * @since 2.0.1
47 */
48 do_action( 'set_current_user' );
49
50 return $current_user;
51 }
52endif;
53
54if ( ! function_exists( 'wp_get_current_user' ) ) :
55 /**
56 * Retrieves the current user object.
57 *
58 * Will set the current user, if the current user is not set. The current user
59 * will be set to the logged-in person. If no user is logged-in, then it will
60 * set the current user to 0, which is invalid and won't have any permissions.
61 *
62 * @since 2.0.3
63 *
64 * @see _wp_get_current_user()
65 * @global WP_User $current_user Checks if the current user is set.
66 *
67 * @return WP_User Current WP_User instance.
68 */
69 function wp_get_current_user() {
70 return _wp_get_current_user();
71 }
72endif;
73
74if ( ! function_exists( 'get_userdata' ) ) :
75 /**
76 * Retrieves user info by user ID.
77 *
78 * @since 0.71
79 *
80 * @param int $user_id User ID
81 * @return WP_User|false WP_User object on success, false on failure.
82 */
83 function get_userdata( $user_id ) {
84 return get_user_by( 'id', $user_id );
85 }
86endif;
87
88if ( ! function_exists( 'get_user_by' ) ) :
89 /**
90 * Retrieves user info by a given field.
91 *
92 * @since 2.8.0
93 * @since 4.4.0 Added 'ID' as an alias of 'id' for the `$field` parameter.
94 *
95 * @global WP_User $current_user The current user object which holds the user data.
96 *
97 * @param string $field The field to retrieve the user with. id | ID | slug | email | login.
98 * @param int|string $value A value for $field. A user ID, slug, email address, or login name.
99 * @return WP_User|false WP_User object on success, false on failure.
100 */
101 function get_user_by( $field, $value ) {
102 $userdata = WP_User::get_data_by( $field, $value );
103
104 if ( ! $userdata ) {
105 return false;
106 }
107
108 $user = new WP_User();
109 $user->init( $userdata );
110
111 return $user;
112 }
113endif;
114
115if ( ! function_exists( 'cache_users' ) ) :
116 /**
117 * Retrieves info for user lists to prevent multiple queries by get_userdata().
118 *
119 * @since 3.0.0
120 *
121 * @global wpdb $wpdb WordPress database abstraction object.
122 *
123 * @param int[] $user_ids User ID numbers list
124 */
125 function cache_users( $user_ids ) {
126 global $wpdb;
127
128 update_meta_cache( 'user', $user_ids );
129
130 $clean = _get_non_cached_ids( $user_ids, 'users' );
131
132 if ( empty( $clean ) ) {
133 return;
134 }
135
136 $list = implode( ',', $clean );
137
138 $users = $wpdb->get_results( "SELECT * FROM $wpdb->users WHERE ID IN ($list)" );
139
140 foreach ( $users as $user ) {
141 update_user_caches( $user );
142 }
143 }
144endif;
145
146if ( ! function_exists( 'wp_mail' ) ) :
147 /**
148 * Sends an email, similar to PHP's mail function.
149 *
150 * A true return value does not automatically mean that the user received the
151 * email successfully. It just only means that the method used was able to
152 * process the request without any errors.
153 *
154 * The default content type is `text/plain` which does not allow using HTML.
155 * However, you can set the content type of the email by using the
156 * {@see 'wp_mail_content_type'} filter.
157 *
158 * The default charset is based on the charset used on the blog. The charset can
159 * be set using the {@see 'wp_mail_charset'} filter.
160 *
161 * When using the `$embeds` parameter to embed images for use in HTML emails,
162 * reference the embedded file in your HTML with a `cid:` URL whose value
163 * matches the file's Content-ID. By default, the Content-ID (`cid`) used for
164 * each embedded file is the key in the embeds array, unless modified via the
165 * {@see 'wp_mail_embed_args'} filter. For example:
166 *
167 * `<img src="cid:0" alt="Logo">`
168 * `<img src="cid:my-image" alt="Image">`
169 *
170 * You may also customize the Content-ID for each file by using the
171 * {@see 'wp_mail_embed_args'} filter and setting the `cid` value.
172 *
173 * @since 1.2.1
174 * @since 5.5.0 is_email() is used for email validation,
175 * instead of PHPMailer's default validator.
176 * @since 6.9.0 Added $embeds parameter.
177 * @since 6.9.0 Improved Content-Type header handling for multipart messages.
178 *
179 * @global PHPMailer\PHPMailer\PHPMailer $phpmailer
180 *
181 * @param string|string[] $to Array or comma-separated list of email addresses to send message.
182 * @param string $subject Email subject.
183 * @param string $message Message contents.
184 * @param string|string[] $headers Optional. Additional headers.
185 * @param string|string[] $attachments Optional. Paths to files to attach.
186 * @param string|string[] $embeds Optional. Paths to files to embed.
187 * @return bool Whether the email was sent successfully.
188 */
189 function wp_mail( $to, $subject, $message, $headers = '', $attachments = array(), $embeds = array() ) {
190 // Compact the input, apply the filters, and extract them back out.
191
192 /**
193 * Filters the wp_mail() arguments.
194 *
195 * @since 2.2.0
196 *
197 * @param array $args {
198 * Array of the `wp_mail()` arguments.
199 *
200 * @type string|string[] $to Array or comma-separated list of email addresses to send message.
201 * @type string $subject Email subject.
202 * @type string $message Message contents.
203 * @type string|string[] $headers Additional headers.
204 * @type string|string[] $attachments Paths to files to attach.
205 * @type string|string[] $embeds Paths to files to embed.
206 * }
207 */
208 $atts = apply_filters( 'wp_mail', compact( 'to', 'subject', 'message', 'headers', 'attachments', 'embeds' ) );
209
210 /**
211 * Filters whether to preempt sending an email.
212 *
213 * Returning a non-null value will short-circuit {@see wp_mail()}, returning
214 * that value instead. A boolean return value should be used to indicate whether
215 * the email was successfully sent.
216 *
217 * @since 5.7.0
218 *
219 * @param null|bool $return Short-circuit return value.
220 * @param array $atts {
221 * Array of the `wp_mail()` arguments.
222 *
223 * @type string|string[] $to Array or comma-separated list of email addresses to send message.
224 * @type string $subject Email subject.
225 * @type string $message Message contents.
226 * @type string|string[] $headers Additional headers.
227 * @type string|string[] $attachments Paths to files to attach.
228 * @type string|string[] $embeds Paths to files to embed.
229 * }
230 */
231 $pre_wp_mail = apply_filters( 'pre_wp_mail', null, $atts );
232
233 if ( null !== $pre_wp_mail ) {
234 return $pre_wp_mail;
235 }
236
237 if ( isset( $atts['to'] ) ) {
238 $to = $atts['to'];
239 }
240
241 if ( ! is_array( $to ) ) {
242 $to = explode( ',', $to );
243 }
244
245 if ( isset( $atts['subject'] ) ) {
246 $subject = $atts['subject'];
247 }
248
249 if ( isset( $atts['message'] ) ) {
250 $message = $atts['message'];
251 }
252
253 if ( isset( $atts['headers'] ) ) {
254 $headers = $atts['headers'];
255 }
256
257 if ( isset( $atts['attachments'] ) ) {
258 $attachments = $atts['attachments'];
259 }
260
261 if ( ! is_array( $attachments ) ) {
262 $attachments = explode( "\n", str_replace( "\r\n", "\n", $attachments ) );
263 }
264
265 if ( isset( $atts['embeds'] ) ) {
266 $embeds = $atts['embeds'];
267 }
268
269 if ( ! is_array( $embeds ) ) {
270 $embeds = explode( "\n", str_replace( "\r\n", "\n", $embeds ) );
271 }
272
273 global $phpmailer;
274
275 // (Re)create it, if it's gone missing.
276 if ( ! ( $phpmailer instanceof PHPMailer\PHPMailer\PHPMailer ) ) {
277 require_once ABSPATH . WPINC . '/PHPMailer/PHPMailer.php';
278 require_once ABSPATH . WPINC . '/PHPMailer/SMTP.php';
279 require_once ABSPATH . WPINC . '/PHPMailer/Exception.php';
280 require_once ABSPATH . WPINC . '/class-wp-phpmailer.php';
281 $phpmailer = new WP_PHPMailer( true );
282
283 $phpmailer::$validator = static function ( $email ) {
284 return (bool) is_email( $email );
285 };
286 }
287
288 // Headers.
289 $cc = array();
290 $bcc = array();
291 $reply_to = array();
292
293 if ( empty( $headers ) ) {
294 $headers = array();
295 } else {
296 if ( ! is_array( $headers ) ) {
297 /*
298 * Explode the headers out, so this function can take
299 * both string headers and an array of headers.
300 */
301 $tempheaders = explode( "\n", str_replace( "\r\n", "\n", $headers ) );
302 } else {
303 $tempheaders = $headers;
304 }
305 $headers = array();
306
307 // If it's actually got contents.
308 if ( ! empty( $tempheaders ) ) {
309 // Iterate through the raw headers.
310 foreach ( (array) $tempheaders as $header ) {
311 if ( ! str_contains( $header, ':' ) ) {
312 if ( false !== stripos( $header, 'boundary=' ) ) {
313 $parts = preg_split( '/boundary=/i', trim( $header ) );
314 $boundary = trim( str_replace( array( "'", '"' ), '', $parts[1] ) );
315 }
316 continue;
317 }
318 // Explode them out.
319 list( $name, $content ) = explode( ':', trim( $header ), 2 );
320
321 // Cleanup crew.
322 $name = trim( $name );
323 $content = trim( $content );
324
325 switch ( strtolower( $name ) ) {
326 // Mainly for legacy -- process a "From:" header if it's there.
327 case 'from':
328 $bracket_pos = strpos( $content, '<' );
329 if ( false !== $bracket_pos ) {
330 // Text before the bracketed email is the "From" name.
331 if ( $bracket_pos > 0 ) {
332 $from_name = substr( $content, 0, $bracket_pos );
333 $from_name = str_replace( '"', '', $from_name );
334 $from_name = trim( $from_name );
335 }
336
337 $from_email = substr( $content, $bracket_pos + 1 );
338 $from_email = str_replace( '>', '', $from_email );
339 $from_email = trim( $from_email );
340
341 // Avoid setting an empty $from_email.
342 } elseif ( '' !== trim( $content ) ) {
343 $from_email = trim( $content );
344 }
345 break;
346 case 'content-type':
347 if ( str_contains( $content, ';' ) ) {
348 list( $type, $charset_content ) = explode( ';', $content );
349 $content_type = trim( $type );
350 if ( false !== stripos( $charset_content, 'charset=' ) ) {
351 $charset = trim( str_replace( array( 'charset=', '"' ), '', $charset_content ) );
352 } elseif ( false !== stripos( $charset_content, 'boundary=' ) ) {
353 $boundary = trim( str_replace( array( 'BOUNDARY=', 'boundary=', '"' ), '', $charset_content ) );
354 $charset = '';
355 if ( preg_match( '~^multipart/(\S+)~', $content_type, $matches ) ) {
356 $content_type = 'multipart/' . strtolower( $matches[1] ) . '; boundary="' . $boundary . '"';
357 }
358 }
359
360 // Avoid setting an empty $content_type.
361 } elseif ( '' !== trim( $content ) ) {
362 $content_type = trim( $content );
363 }
364 break;
365 case 'cc':
366 $cc = array_merge( (array) $cc, explode( ',', $content ) );
367 break;
368 case 'bcc':
369 $bcc = array_merge( (array) $bcc, explode( ',', $content ) );
370 break;
371 case 'reply-to':
372 $reply_to = array_merge( (array) $reply_to, explode( ',', $content ) );
373 break;
374 default:
375 // Add it to our grand headers array.
376 $headers[ trim( $name ) ] = trim( $content );
377 break;
378 }
379 }
380 }
381 }
382
383 // Empty out the values that may be set.
384 $phpmailer->clearAllRecipients();
385 $phpmailer->clearAttachments();
386 $phpmailer->clearCustomHeaders();
387 $phpmailer->clearReplyTos();
388 $phpmailer->Body = '';
389 $phpmailer->AltBody = '';
390
391 /*
392 * Reset encoding to 8-bit, as it may have been automatically downgraded
393 * to 7-bit by PHPMailer (based on the body contents) in a previous call
394 * to wp_mail().
395 *
396 * See https://core.trac.wordpress.org/ticket/33972
397 */
398 $phpmailer->Encoding = PHPMailer\PHPMailer\PHPMailer::ENCODING_8BIT;
399
400 // Set "From" name and email.
401
402 // If we don't have a name from the input headers.
403 if ( ! isset( $from_name ) ) {
404 $from_name = 'WordPress';
405 }
406
407 /*
408 * If we don't have an email from the input headers, default to wordpress@$sitename
409 * Some hosts will block outgoing mail from this address if it doesn't exist,
410 * but there's no easy alternative. Defaulting to admin_email might appear to be
411 * another option, but some hosts may refuse to relay mail from an unknown domain.
412 * See https://core.trac.wordpress.org/ticket/5007.
413 */
414 if ( ! isset( $from_email ) ) {
415 // Get the site domain and get rid of www.
416 $sitename = wp_parse_url( network_home_url(), PHP_URL_HOST );
417 $from_email = 'wordpress@';
418
419 if ( null !== $sitename ) {
420 if ( str_starts_with( $sitename, 'www.' ) ) {
421 $sitename = substr( $sitename, 4 );
422 }
423
424 $from_email .= $sitename;
425 }
426 }
427
428 /**
429 * Filters the email address to send from.
430 *
431 * @since 2.2.0
432 *
433 * @param string $from_email Email address to send from.
434 */
435 $from_email = apply_filters( 'wp_mail_from', $from_email );
436
437 /**
438 * Filters the name to associate with the "from" email address.
439 *
440 * @since 2.3.0
441 *
442 * @param string $from_name Name associated with the "from" email address.
443 */
444 $from_name = apply_filters( 'wp_mail_from_name', $from_name );
445
446 try {
447 $phpmailer->setFrom( $from_email, $from_name, false );
448 } catch ( PHPMailer\PHPMailer\Exception $e ) {
449 $mail_error_data = compact( 'to', 'subject', 'message', 'headers', 'attachments' );
450 $mail_error_data['phpmailer_exception_code'] = $e->getCode();
451
452 /** This filter is documented in wp-includes/pluggable.php */
453 do_action( 'wp_mail_failed', new WP_Error( 'wp_mail_failed', $e->getMessage(), $mail_error_data ) );
454
455 return false;
456 }
457
458 // Set mail's subject and body.
459 $phpmailer->Subject = $subject;
460 $phpmailer->Body = $message;
461
462 // Set destination addresses, using appropriate methods for handling addresses.
463 $address_headers = compact( 'to', 'cc', 'bcc', 'reply_to' );
464
465 foreach ( $address_headers as $address_header => $addresses ) {
466 if ( empty( $addresses ) ) {
467 continue;
468 }
469
470 foreach ( (array) $addresses as $address ) {
471 try {
472 // Break $recipient into name and address parts if in the format "Foo <bar@baz.com>".
473 $recipient_name = '';
474
475 if ( preg_match( '/(.*)<(.+)>/', $address, $matches ) ) {
476 if ( count( $matches ) === 3 ) {
477 $recipient_name = $matches[1];
478 $address = $matches[2];
479 }
480 }
481
482 switch ( $address_header ) {
483 case 'to':
484 $phpmailer->addAddress( $address, $recipient_name );
485 break;
486 case 'cc':
487 $phpmailer->addCC( $address, $recipient_name );
488 break;
489 case 'bcc':
490 $phpmailer->addBCC( $address, $recipient_name );
491 break;
492 case 'reply_to':
493 $phpmailer->addReplyTo( $address, $recipient_name );
494 break;
495 }
496 } catch ( PHPMailer\PHPMailer\Exception $e ) {
497 continue;
498 }
499 }
500 }
501
502 // Set to use PHP's mail().
503 $phpmailer->isMail();
504
505 // Set Content-Type and charset.
506
507 // If we don't have a Content-Type from the input headers.
508 if ( ! isset( $content_type ) ) {
509 $content_type = 'text/plain';
510 }
511
512 /**
513 * Filters the wp_mail() content type.
514 *
515 * @since 2.3.0
516 *
517 * @param string $content_type Default wp_mail() content type.
518 */
519 $content_type = apply_filters( 'wp_mail_content_type', $content_type );
520
521 $phpmailer->ContentType = $content_type;
522
523 // Set whether it's plaintext, depending on $content_type.
524 if ( 'text/html' === $content_type ) {
525 $phpmailer->isHTML( true );
526 }
527
528 // If we don't have a charset from the input headers.
529 if ( ! isset( $charset ) ) {
530 $charset = get_bloginfo( 'charset' );
531 }
532
533 /**
534 * Filters the default wp_mail() charset.
535 *
536 * @since 2.3.0
537 *
538 * @param string $charset Default email charset.
539 */
540 $phpmailer->CharSet = apply_filters( 'wp_mail_charset', $charset );
541
542 // Set custom headers.
543 if ( ! empty( $headers ) ) {
544 foreach ( (array) $headers as $name => $content ) {
545 // Only add custom headers not added automatically by PHPMailer.
546 if ( ! in_array( $name, array( 'MIME-Version', 'X-Mailer' ), true ) ) {
547 try {
548 $phpmailer->addCustomHeader( sprintf( '%1$s: %2$s', $name, $content ) );
549 } catch ( PHPMailer\PHPMailer\Exception $e ) {
550 continue;
551 }
552 }
553 }
554 }
555
556 if ( ! empty( $attachments ) ) {
557 foreach ( $attachments as $filename => $attachment ) {
558 $filename = is_string( $filename ) ? $filename : '';
559
560 try {
561 $phpmailer->addAttachment( $attachment, $filename );
562 } catch ( PHPMailer\PHPMailer\Exception $e ) {
563 continue;
564 }
565 }
566 }
567
568 if ( ! empty( $embeds ) ) {
569 foreach ( $embeds as $key => $embed_path ) {
570 /**
571 * Filters the arguments for PHPMailer's addEmbeddedImage() method.
572 *
573 * @since 6.9.0
574 *
575 * @param array $args {
576 * An array of arguments for `addEmbeddedImage()`.
577 * @type string $path The path to the file.
578 * @type string $cid The Content-ID of the image. Default: The key in the embeds array.
579 * @type string $name The filename of the image.
580 * @type string $encoding The encoding of the image. Default: 'base64'.
581 * @type string $type The MIME type of the image. Default: empty string, which lets PHPMailer auto-detect.
582 * @type string $disposition The disposition of the image. Default: 'inline'.
583 * }
584 */
585 $embed_args = apply_filters(
586 'wp_mail_embed_args',
587 array(
588 'path' => $embed_path,
589 'cid' => (string) $key,
590 'name' => basename( $embed_path ),
591 'encoding' => 'base64',
592 'type' => '',
593 'disposition' => 'inline',
594 )
595 );
596
597 try {
598 $phpmailer->addEmbeddedImage(
599 $embed_args['path'],
600 $embed_args['cid'],
601 $embed_args['name'],
602 $embed_args['encoding'],
603 $embed_args['type'],
604 $embed_args['disposition']
605 );
606 } catch ( PHPMailer\PHPMailer\Exception $e ) {
607 continue;
608 }
609 }
610 }
611
612 /**
613 * Fires after PHPMailer is initialized.
614 *
615 * @since 2.2.0
616 *
617 * @param PHPMailer $phpmailer The PHPMailer instance (passed by reference).
618 */
619 do_action_ref_array( 'phpmailer_init', array( &$phpmailer ) );
620
621 $mail_data = compact( 'to', 'subject', 'message', 'headers', 'attachments', 'embeds' );
622
623 // Send!
624 try {
625 $send = $phpmailer->send();
626
627 /**
628 * Fires after PHPMailer has successfully sent an email.
629 *
630 * The firing of this action does not necessarily mean that the recipient(s) received the
631 * email successfully. It only means that the `send` method above was able to
632 * process the request without any errors.
633 *
634 * @since 5.9.0
635 *
636 * @param array $mail_data {
637 * An array containing the email recipient(s), subject, message, headers, and attachments.
638 *
639 * @type string[] $to Email addresses to send message.
640 * @type string $subject Email subject.
641 * @type string $message Message contents.
642 * @type string[] $headers Additional headers.
643 * @type string[] $attachments Paths to files to attach.
644 * @type string[] $embeds Paths to files to embed.
645 * }
646 */
647 do_action( 'wp_mail_succeeded', $mail_data );
648
649 return $send;
650 } catch ( PHPMailer\PHPMailer\Exception $e ) {
651 $mail_data['phpmailer_exception_code'] = $e->getCode();
652
653 /**
654 * Fires after a PHPMailer\PHPMailer\Exception is caught.
655 *
656 * @since 4.4.0
657 *
658 * @param WP_Error $error A WP_Error object with the PHPMailer\PHPMailer\Exception message, and an array
659 * containing the mail recipient, subject, message, headers, and attachments.
660 */
661 do_action( 'wp_mail_failed', new WP_Error( 'wp_mail_failed', $e->getMessage(), $mail_data ) );
662
663 return false;
664 }
665 }
666endif;
667
668if ( ! function_exists( 'wp_authenticate' ) ) :
669 /**
670 * Authenticates a user, confirming the login credentials are valid.
671 *
672 * @since 2.5.0
673 * @since 4.5.0 `$username` now accepts an email address.
674 *
675 * @param string $username User's username or email address.
676 * @param string $password User's password.
677 * @return WP_User|WP_Error WP_User object if the credentials are valid,
678 * otherwise WP_Error.
679 */
680 function wp_authenticate(
681 $username,
682 #[\SensitiveParameter]
683 $password
684 ) {
685 $username = sanitize_user( $username );
686 $password = trim( $password );
687
688 /**
689 * Filters whether a set of user login credentials are valid.
690 *
691 * A WP_User object is returned if the credentials authenticate a user.
692 * WP_Error or null otherwise.
693 *
694 * @since 2.8.0
695 * @since 4.5.0 `$username` now accepts an email address.
696 *
697 * @param null|WP_User|WP_Error $user WP_User if the user is authenticated.
698 * WP_Error or null otherwise.
699 * @param string $username Username or email address.
700 * @param string $password User password.
701 */
702 $user = apply_filters( 'authenticate', null, $username, $password );
703
704 if ( null === $user || false === $user ) {
705 /*
706 * TODO: What should the error message be? (Or would these even happen?)
707 * Only needed if all authentication handlers fail to return anything.
708 */
709 $user = new WP_Error( 'authentication_failed', __( '<strong>Error:</strong> Invalid username, email address or incorrect password.' ) );
710 }
711
712 $ignore_codes = array( 'empty_username', 'empty_password' );
713
714 if ( is_wp_error( $user ) && ! in_array( $user->get_error_code(), $ignore_codes, true ) ) {
715 $error = $user;
716
717 /**
718 * Fires after a user login has failed.
719 *
720 * @since 2.5.0
721 * @since 4.5.0 The value of `$username` can now be an email address.
722 * @since 5.4.0 The `$error` parameter was added.
723 *
724 * @param string $username Username or email address.
725 * @param WP_Error $error A WP_Error object with the authentication failure details.
726 */
727 do_action( 'wp_login_failed', $username, $error );
728 }
729
730 return $user;
731 }
732endif;
733
734if ( ! function_exists( 'wp_logout' ) ) :
735 /**
736 * Logs the current user out.
737 *
738 * @since 2.5.0
739 */
740 function wp_logout() {
741 $user_id = get_current_user_id();
742
743 wp_destroy_current_session();
744 wp_clear_auth_cookie();
745 wp_set_current_user( 0 );
746
747 /**
748 * Fires after a user is logged out.
749 *
750 * @since 1.5.0
751 * @since 5.5.0 Added the `$user_id` parameter.
752 *
753 * @param int $user_id ID of the user that was logged out.
754 */
755 do_action( 'wp_logout', $user_id );
756 }
757endif;
758
759if ( ! function_exists( 'wp_validate_auth_cookie' ) ) :
760 /**
761 * Validates authentication cookie.
762 *
763 * The checks include making sure that the authentication cookie is set and
764 * pulling in the contents (if $cookie is not used).
765 *
766 * Makes sure the cookie is not expired. Verifies the hash in cookie is what is
767 * should be and compares the two.
768 *
769 * @since 2.5.0
770 *
771 * @global int $login_grace_period
772 *
773 * @param string $cookie Optional. If used, will validate contents instead of cookie's.
774 * @param string $scheme Optional. The cookie scheme to use: 'auth', 'secure_auth', or 'logged_in'.
775 * Note: This does *not* default to 'auth' like other cookie functions.
776 * @return int|false User ID if valid cookie, false if invalid.
777 */
778 function wp_validate_auth_cookie( $cookie = '', $scheme = '' ) {
779 $cookie_elements = wp_parse_auth_cookie( $cookie, $scheme );
780 if ( ! $cookie_elements ) {
781 /**
782 * Fires if an authentication cookie is malformed.
783 *
784 * @since 2.7.0
785 *
786 * @param string $cookie Malformed auth cookie.
787 * @param string $scheme Authentication scheme. Values include 'auth', 'secure_auth',
788 * or 'logged_in'.
789 */
790 do_action( 'auth_cookie_malformed', $cookie, $scheme );
791 return false;
792 }
793
794 $scheme = $cookie_elements['scheme'];
795 $username = $cookie_elements['username'];
796 $hmac = $cookie_elements['hmac'];
797 $token = $cookie_elements['token'];
798 $expiration = $cookie_elements['expiration'];
799
800 $expired = (int) $expiration;
801
802 // Allow a grace period for POST and Ajax requests.
803 if ( wp_doing_ajax() || 'POST' === $_SERVER['REQUEST_METHOD'] ) {
804 $expired += HOUR_IN_SECONDS;
805 }
806
807 // Quick check to see if an honest cookie has expired.
808 if ( $expired < time() ) {
809 /**
810 * Fires once an authentication cookie has expired.
811 *
812 * @since 2.7.0
813 *
814 * @param string[] $cookie_elements {
815 * Authentication cookie components. None of the components should be assumed
816 * to be valid as they come directly from a client-provided cookie value.
817 *
818 * @type string $username User's username.
819 * @type string $expiration The time the cookie expires as a UNIX timestamp.
820 * @type string $token User's session token used.
821 * @type string $hmac The security hash for the cookie.
822 * @type string $scheme The cookie scheme to use.
823 * }
824 */
825 do_action( 'auth_cookie_expired', $cookie_elements );
826 return false;
827 }
828
829 $user = get_user_by( 'login', $username );
830 if ( ! $user ) {
831 /**
832 * Fires if a bad username is entered in the user authentication process.
833 *
834 * @since 2.7.0
835 *
836 * @param string[] $cookie_elements {
837 * Authentication cookie components. None of the components should be assumed
838 * to be valid as they come directly from a client-provided cookie value.
839 *
840 * @type string $username User's username.
841 * @type string $expiration The time the cookie expires as a UNIX timestamp.
842 * @type string $token User's session token used.
843 * @type string $hmac The security hash for the cookie.
844 * @type string $scheme The cookie scheme to use.
845 * }
846 */
847 do_action( 'auth_cookie_bad_username', $cookie_elements );
848 return false;
849 }
850
851 if ( str_starts_with( $user->user_pass, '$P$' ) || str_starts_with( $user->user_pass, '$2y$' ) ) {
852 // Retain previous behaviour of phpass or vanilla bcrypt hashed passwords.
853 $pass_frag = substr( $user->user_pass, 8, 4 );
854 } else {
855 // Otherwise, use a substring from the end of the hash to avoid dealing with potentially long hash prefixes.
856 $pass_frag = substr( $user->user_pass, -4 );
857 }
858
859 $key = wp_hash( $username . '|' . $pass_frag . '|' . $expiration . '|' . $token, $scheme );
860
861 $hash = hash_hmac( 'sha256', $username . '|' . $expiration . '|' . $token, $key );
862
863 if ( ! hash_equals( $hash, $hmac ) ) {
864 /**
865 * Fires if a bad authentication cookie hash is encountered.
866 *
867 * @since 2.7.0
868 *
869 * @param string[] $cookie_elements {
870 * Authentication cookie components. None of the components should be assumed
871 * to be valid as they come directly from a client-provided cookie value.
872 *
873 * @type string $username User's username.
874 * @type string $expiration The time the cookie expires as a UNIX timestamp.
875 * @type string $token User's session token used.
876 * @type string $hmac The security hash for the cookie.
877 * @type string $scheme The cookie scheme to use.
878 * }
879 */
880 do_action( 'auth_cookie_bad_hash', $cookie_elements );
881 return false;
882 }
883
884 $manager = WP_Session_Tokens::get_instance( $user->ID );
885 if ( ! $manager->verify( $token ) ) {
886 /**
887 * Fires if a bad session token is encountered.
888 *
889 * @since 4.0.0
890 *
891 * @param string[] $cookie_elements {
892 * Authentication cookie components. None of the components should be assumed
893 * to be valid as they come directly from a client-provided cookie value.
894 *
895 * @type string $username User's username.
896 * @type string $expiration The time the cookie expires as a UNIX timestamp.
897 * @type string $token User's session token used.
898 * @type string $hmac The security hash for the cookie.
899 * @type string $scheme The cookie scheme to use.
900 * }
901 */
902 do_action( 'auth_cookie_bad_session_token', $cookie_elements );
903 return false;
904 }
905
906 // Ajax/POST grace period set above.
907 if ( $expiration < time() ) {
908 $GLOBALS['login_grace_period'] = 1;
909 }
910
911 /**
912 * Fires once an authentication cookie has been validated.
913 *
914 * @since 2.7.0
915 *
916 * @param string[] $cookie_elements {
917 * Authentication cookie components.
918 *
919 * @type string $username User's username.
920 * @type string $expiration The time the cookie expires as a UNIX timestamp.
921 * @type string $token User's session token used.
922 * @type string $hmac The security hash for the cookie.
923 * @type string $scheme The cookie scheme to use.
924 * }
925 * @param WP_User $user User object.
926 */
927 do_action( 'auth_cookie_valid', $cookie_elements, $user );
928
929 return $user->ID;
930 }
931endif;
932
933if ( ! function_exists( 'wp_generate_auth_cookie' ) ) :
934 /**
935 * Generates authentication cookie contents.
936 *
937 * @since 2.5.0
938 * @since 4.0.0 The `$token` parameter was added.
939 *
940 * @param int $user_id User ID.
941 * @param int $expiration The time the cookie expires as a UNIX timestamp.
942 * @param string $scheme Optional. The cookie scheme to use: 'auth', 'secure_auth', or 'logged_in'.
943 * Default 'auth'.
944 * @param string $token User's session token to use for this cookie.
945 * @return string Authentication cookie contents. Empty string if user does not exist.
946 */
947 function wp_generate_auth_cookie( $user_id, $expiration, $scheme = 'auth', $token = '' ) {
948 $user = get_userdata( $user_id );
949 if ( ! $user ) {
950 return '';
951 }
952
953 if ( ! $token ) {
954 $manager = WP_Session_Tokens::get_instance( $user_id );
955 $token = $manager->create( $expiration );
956 }
957
958 if ( str_starts_with( $user->user_pass, '$P$' ) || str_starts_with( $user->user_pass, '$2y$' ) ) {
959 // Retain previous behaviour of phpass or vanilla bcrypt hashed passwords.
960 $pass_frag = substr( $user->user_pass, 8, 4 );
961 } else {
962 // Otherwise, use a substring from the end of the hash to avoid dealing with potentially long hash prefixes.
963 $pass_frag = substr( $user->user_pass, -4 );
964 }
965
966 $key = wp_hash( $user->user_login . '|' . $pass_frag . '|' . $expiration . '|' . $token, $scheme );
967
968 $hash = hash_hmac( 'sha256', $user->user_login . '|' . $expiration . '|' . $token, $key );
969
970 $cookie = $user->user_login . '|' . $expiration . '|' . $token . '|' . $hash;
971
972 /**
973 * Filters the authentication cookie.
974 *
975 * @since 2.5.0
976 * @since 4.0.0 The `$token` parameter was added.
977 *
978 * @param string $cookie Authentication cookie.
979 * @param int $user_id User ID.
980 * @param int $expiration The time the cookie expires as a UNIX timestamp.
981 * @param string $scheme Cookie scheme used. Accepts 'auth', 'secure_auth', or 'logged_in'.
982 * @param string $token User's session token used.
983 */
984 return apply_filters( 'auth_cookie', $cookie, $user_id, $expiration, $scheme, $token );
985 }
986endif;
987
988if ( ! function_exists( 'wp_parse_auth_cookie' ) ) :
989 /**
990 * Parses a cookie into its components.
991 *
992 * @since 2.7.0
993 * @since 4.0.0 The `$token` element was added to the return value.
994 *
995 * @param string $cookie Authentication cookie.
996 * @param string $scheme Optional. The cookie scheme to use: 'auth', 'secure_auth', or 'logged_in'.
997 * @return string[]|false {
998 * Authentication cookie components. None of the components should be assumed
999 * to be valid as they come directly from a client-provided cookie value. If
1000 * the cookie value is malformed, false is returned.
1001 *
1002 * @type string $username User's username.
1003 * @type string $expiration The time the cookie expires as a UNIX timestamp.
1004 * @type string $token User's session token used.
1005 * @type string $hmac The security hash for the cookie.
1006 * @type string $scheme The cookie scheme to use.
1007 * }
1008 */
1009 function wp_parse_auth_cookie( $cookie = '', $scheme = '' ) {
1010 if ( empty( $cookie ) ) {
1011 switch ( $scheme ) {
1012 case 'auth':
1013 $cookie_name = AUTH_COOKIE;
1014 break;
1015 case 'secure_auth':
1016 $cookie_name = SECURE_AUTH_COOKIE;
1017 break;
1018 case 'logged_in':
1019 $cookie_name = LOGGED_IN_COOKIE;
1020 break;
1021 default:
1022 if ( is_ssl() ) {
1023 $cookie_name = SECURE_AUTH_COOKIE;
1024 $scheme = 'secure_auth';
1025 } else {
1026 $cookie_name = AUTH_COOKIE;
1027 $scheme = 'auth';
1028 }
1029 }
1030
1031 if ( empty( $_COOKIE[ $cookie_name ] ) ) {
1032 return false;
1033 }
1034 $cookie = $_COOKIE[ $cookie_name ];
1035 }
1036
1037 $cookie_elements = explode( '|', $cookie );
1038 if ( count( $cookie_elements ) !== 4 ) {
1039 return false;
1040 }
1041
1042 list( $username, $expiration, $token, $hmac ) = $cookie_elements;
1043
1044 return compact( 'username', 'expiration', 'token', 'hmac', 'scheme' );
1045 }
1046endif;
1047
1048if ( ! function_exists( 'wp_set_auth_cookie' ) ) :
1049 /**
1050 * Sets the authentication cookies for a given user ID.
1051 *
1052 * The `$remember` parameter controls cookie persistence:
1053 * - If true, the cookie is persistent (default 14 days, filterable via {@see 'auth_cookie_expiration'}).
1054 * - If false, the cookie is a browser session cookie (expires when the browser closes).
1055 * Internally, {@see 'auth_cookie_expiration'} is still applied, to expire the login after
1056 * two days or when the browser is closed, whichever occurs first.
1057 *
1058 * @since 2.5.0
1059 * @since 4.3.0 Added the `$token` parameter.
1060 *
1061 * @param int $user_id User ID.
1062 * @param bool $remember Whether to remember the user.
1063 * @param bool|string $secure Whether the auth cookie should only be sent over HTTPS. Default is an empty
1064 * string which means the value of `is_ssl()` will be used.
1065 * @param string $token Optional. User's session token to use for this cookie.
1066 */
1067 function wp_set_auth_cookie( $user_id, $remember = false, $secure = '', $token = '' ) {
1068 if ( $remember ) {
1069 /**
1070 * Filters the duration of the authentication cookie expiration period.
1071 *
1072 * @since 2.8.0
1073 *
1074 * @param int $length Duration of the expiration period in seconds.
1075 * @param int $user_id User ID.
1076 * @param bool $remember Whether to remember the user login. Default false.
1077 */
1078 $expiration = time() + apply_filters( 'auth_cookie_expiration', 14 * DAY_IN_SECONDS, $user_id, $remember );
1079
1080 /*
1081 * Ensure the browser will continue to send the cookie after the expiration time is reached.
1082 * Needed for the login grace period in wp_validate_auth_cookie().
1083 */
1084 $expire = $expiration + ( 12 * HOUR_IN_SECONDS );
1085 } else {
1086 /** This filter is documented in wp-includes/pluggable.php */
1087 $expiration = time() + apply_filters( 'auth_cookie_expiration', 2 * DAY_IN_SECONDS, $user_id, $remember );
1088 $expire = 0;
1089 }
1090
1091 if ( '' === $secure ) {
1092 $secure = is_ssl();
1093 }
1094
1095 // Front-end cookie is secure when the auth cookie is secure and the site's home URL uses HTTPS.
1096 $secure_logged_in_cookie = $secure && 'https' === parse_url( get_option( 'home' ), PHP_URL_SCHEME );
1097
1098 /**
1099 * Filters whether the auth cookie should only be sent over HTTPS.
1100 *
1101 * @since 3.1.0
1102 *
1103 * @param bool $secure Whether the cookie should only be sent over HTTPS.
1104 * @param int $user_id User ID.
1105 */
1106 $secure = apply_filters( 'secure_auth_cookie', $secure, $user_id );
1107
1108 /**
1109 * Filters whether the logged in cookie should only be sent over HTTPS.
1110 *
1111 * @since 3.1.0
1112 *
1113 * @param bool $secure_logged_in_cookie Whether the logged in cookie should only be sent over HTTPS.
1114 * @param int $user_id User ID.
1115 * @param bool $secure Whether the auth cookie should only be sent over HTTPS.
1116 */
1117 $secure_logged_in_cookie = apply_filters( 'secure_logged_in_cookie', $secure_logged_in_cookie, $user_id, $secure );
1118
1119 if ( $secure ) {
1120 $auth_cookie_name = SECURE_AUTH_COOKIE;
1121 $scheme = 'secure_auth';
1122 } else {
1123 $auth_cookie_name = AUTH_COOKIE;
1124 $scheme = 'auth';
1125 }
1126
1127 if ( '' === $token ) {
1128 $manager = WP_Session_Tokens::get_instance( $user_id );
1129 $token = $manager->create( $expiration );
1130 }
1131
1132 $auth_cookie = wp_generate_auth_cookie( $user_id, $expiration, $scheme, $token );
1133 $logged_in_cookie = wp_generate_auth_cookie( $user_id, $expiration, 'logged_in', $token );
1134
1135 /**
1136 * Fires immediately before the authentication cookie is set.
1137 *
1138 * @since 2.5.0
1139 * @since 4.9.0 The `$token` parameter was added.
1140 *
1141 * @param string $auth_cookie Authentication cookie value.
1142 * @param int $expire The time the login grace period expires as a UNIX timestamp.
1143 * Default is 12 hours past the cookie's expiration time.
1144 * @param int $expiration The time when the authentication cookie expires as a UNIX timestamp.
1145 * Default is 14 days from now.
1146 * @param int $user_id User ID.
1147 * @param string $scheme Authentication scheme. Values include 'auth' or 'secure_auth'.
1148 * @param string $token User's session token to use for this cookie.
1149 */
1150 do_action( 'set_auth_cookie', $auth_cookie, $expire, $expiration, $user_id, $scheme, $token );
1151
1152 /**
1153 * Fires immediately before the logged-in authentication cookie is set.
1154 *
1155 * @since 2.6.0
1156 * @since 4.9.0 The `$token` parameter was added.
1157 *
1158 * @param string $logged_in_cookie The logged-in cookie value.
1159 * @param int $expire The time the login grace period expires as a UNIX timestamp.
1160 * Default is 12 hours past the cookie's expiration time.
1161 * @param int $expiration The time when the logged-in authentication cookie expires as a UNIX timestamp.
1162 * Default is 14 days from now.
1163 * @param int $user_id User ID.
1164 * @param string $scheme Authentication scheme. Default 'logged_in'.
1165 * @param string $token User's session token to use for this cookie.
1166 */
1167 do_action( 'set_logged_in_cookie', $logged_in_cookie, $expire, $expiration, $user_id, 'logged_in', $token );
1168
1169 /**
1170 * Allows preventing auth cookies from actually being sent to the client.
1171 *
1172 * @since 4.7.4
1173 * @since 6.2.0 The `$expire`, `$expiration`, `$user_id`, `$scheme`, and `$token` parameters were added.
1174 *
1175 * @param bool $send Whether to send auth cookies to the client. Default true.
1176 * @param int $expire The time the login grace period expires as a UNIX timestamp.
1177 * Default is 12 hours past the cookie's expiration time. Zero when clearing cookies.
1178 * @param int $expiration The time when the logged-in authentication cookie expires as a UNIX timestamp.
1179 * Default is 14 days from now. Zero when clearing cookies.
1180 * @param int $user_id User ID. Zero when clearing cookies.
1181 * @param string $scheme Authentication scheme. Values include 'auth' or 'secure_auth'.
1182 * Empty string when clearing cookies.
1183 * @param string $token User's session token to use for this cookie. Empty string when clearing cookies.
1184 */
1185 if ( ! apply_filters( 'send_auth_cookies', true, $expire, $expiration, $user_id, $scheme, $token ) ) {
1186 return;
1187 }
1188
1189 setcookie( $auth_cookie_name, $auth_cookie, $expire, PLUGINS_COOKIE_PATH, COOKIE_DOMAIN, $secure, true );
1190 setcookie( $auth_cookie_name, $auth_cookie, $expire, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, $secure, true );
1191 setcookie( LOGGED_IN_COOKIE, $logged_in_cookie, $expire, COOKIEPATH, COOKIE_DOMAIN, $secure_logged_in_cookie, true );
1192 if ( COOKIEPATH !== SITECOOKIEPATH ) {
1193 setcookie( LOGGED_IN_COOKIE, $logged_in_cookie, $expire, SITECOOKIEPATH, COOKIE_DOMAIN, $secure_logged_in_cookie, true );
1194 }
1195 }
1196endif;
1197
1198if ( ! function_exists( 'wp_clear_auth_cookie' ) ) :
1199 /**
1200 * Removes all of the cookies associated with authentication.
1201 *
1202 * @since 2.5.0
1203 */
1204 function wp_clear_auth_cookie() {
1205 /**
1206 * Fires just before the authentication cookies are cleared.
1207 *
1208 * @since 2.7.0
1209 */
1210 do_action( 'clear_auth_cookie' );
1211
1212 /** This filter is documented in wp-includes/pluggable.php */
1213 if ( ! apply_filters( 'send_auth_cookies', true, 0, 0, 0, '', '' ) ) {
1214 return;
1215 }
1216
1217 // Auth cookies.
1218 setcookie( AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, ADMIN_COOKIE_PATH, COOKIE_DOMAIN );
1219 setcookie( SECURE_AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, ADMIN_COOKIE_PATH, COOKIE_DOMAIN );
1220 setcookie( AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, PLUGINS_COOKIE_PATH, COOKIE_DOMAIN );
1221 setcookie( SECURE_AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, PLUGINS_COOKIE_PATH, COOKIE_DOMAIN );
1222 setcookie( LOGGED_IN_COOKIE, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
1223 setcookie( LOGGED_IN_COOKIE, ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH, COOKIE_DOMAIN );
1224
1225 // Settings cookies.
1226 setcookie( 'wp-settings-' . get_current_user_id(), ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH );
1227 setcookie( 'wp-settings-time-' . get_current_user_id(), ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH );
1228
1229 // Old cookies.
1230 setcookie( AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
1231 setcookie( AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH, COOKIE_DOMAIN );
1232 setcookie( SECURE_AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
1233 setcookie( SECURE_AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH, COOKIE_DOMAIN );
1234
1235 // Even older cookies.
1236 setcookie( USER_COOKIE, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
1237 setcookie( PASS_COOKIE, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
1238 setcookie( USER_COOKIE, ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH, COOKIE_DOMAIN );
1239 setcookie( PASS_COOKIE, ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH, COOKIE_DOMAIN );
1240
1241 // Post password cookie.
1242 setcookie( 'wp-postpass_' . COOKIEHASH, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
1243 }
1244endif;
1245
1246if ( ! function_exists( 'is_user_logged_in' ) ) :
1247 /**
1248 * Determines whether the current visitor is a logged in user.
1249 *
1250 * For more information on this and similar theme functions, check out
1251 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
1252 * Conditional Tags} article in the Theme Developer Handbook.
1253 *
1254 * @since 2.0.0
1255 *
1256 * @return bool True if user is logged in, false if not logged in.
1257 */
1258 function is_user_logged_in() {
1259 $user = wp_get_current_user();
1260
1261 return $user->exists();
1262 }
1263endif;
1264
1265if ( ! function_exists( 'auth_redirect' ) ) :
1266 /**
1267 * Checks if a user is logged in, if not it redirects them to the login page.
1268 *
1269 * When this code is called from a page, it checks to see if the user viewing the page is logged in.
1270 * If the user is not logged in, they are redirected to the login page. The user is redirected
1271 * in such a way that, upon logging in, they will be sent directly to the page they were originally
1272 * trying to access.
1273 *
1274 * @since 1.5.0
1275 */
1276 function auth_redirect() {
1277 $secure = ( is_ssl() || force_ssl_admin() );
1278
1279 /**
1280 * Filters whether to use a secure authentication redirect.
1281 *
1282 * @since 3.1.0
1283 *
1284 * @param bool $secure Whether to use a secure authentication redirect. Default false.
1285 */
1286 $secure = apply_filters( 'secure_auth_redirect', $secure );
1287
1288 // If https is required and request is http, redirect.
1289 if ( $secure && ! is_ssl() && str_contains( $_SERVER['REQUEST_URI'], 'wp-admin' ) ) {
1290 if ( str_starts_with( $_SERVER['REQUEST_URI'], 'http' ) ) {
1291 wp_redirect( set_url_scheme( $_SERVER['REQUEST_URI'], 'https' ) );
1292 exit;
1293 } else {
1294 wp_redirect( 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
1295 exit;
1296 }
1297 }
1298
1299 /**
1300 * Filters the authentication redirect scheme.
1301 *
1302 * @since 2.9.0
1303 *
1304 * @param string $scheme Authentication redirect scheme. Default empty.
1305 */
1306 $scheme = apply_filters( 'auth_redirect_scheme', '' );
1307
1308 $user_id = wp_validate_auth_cookie( '', $scheme );
1309 if ( $user_id ) {
1310 /**
1311 * Fires before the authentication redirect.
1312 *
1313 * @since 2.8.0
1314 *
1315 * @param int $user_id User ID.
1316 */
1317 do_action( 'auth_redirect', $user_id );
1318
1319 // If the user wants ssl but the session is not ssl, redirect.
1320 if ( ! $secure && get_user_option( 'use_ssl', $user_id ) && str_contains( $_SERVER['REQUEST_URI'], 'wp-admin' ) ) {
1321 if ( str_starts_with( $_SERVER['REQUEST_URI'], 'http' ) ) {
1322 wp_redirect( set_url_scheme( $_SERVER['REQUEST_URI'], 'https' ) );
1323 exit;
1324 } else {
1325 wp_redirect( 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
1326 exit;
1327 }
1328 }
1329
1330 return; // The cookie is good, so we're done.
1331 }
1332
1333 // The cookie is no good, so force login.
1334 nocache_headers();
1335
1336 if ( str_contains( $_SERVER['REQUEST_URI'], '/options.php' ) && wp_get_referer() ) {
1337 $redirect = wp_get_referer();
1338 } else {
1339 $redirect = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
1340 }
1341
1342 $login_url = wp_login_url( $redirect, true );
1343
1344 wp_redirect( $login_url );
1345 exit;
1346 }
1347endif;
1348
1349if ( ! function_exists( 'check_admin_referer' ) ) :
1350 /**
1351 * Ensures intent by verifying that a user was referred from another admin page with the correct security nonce.
1352 *
1353 * This function ensures the user intends to perform a given action, which helps protect against clickjacking style
1354 * attacks. It verifies intent, not authorization, therefore it does not verify the user's capabilities. This should
1355 * be performed with `current_user_can()` or similar.
1356 *
1357 * If the nonce value is invalid, the function will exit with an "Are You Sure?" style message.
1358 *
1359 * @since 1.2.0
1360 * @since 2.5.0 The `$query_arg` parameter was added.
1361 *
1362 * @param int|string $action The nonce action.
1363 * @param string $query_arg Optional. Key to check for nonce in `$_REQUEST`. Default '_wpnonce'.
1364 * @return int|false 1 if the nonce is valid and generated between 0-12 hours ago,
1365 * 2 if the nonce is valid and generated between 12-24 hours ago.
1366 * False if the nonce is invalid.
1367 */
1368 function check_admin_referer( $action = -1, $query_arg = '_wpnonce' ) {
1369 if ( -1 === $action ) {
1370 _doing_it_wrong( __FUNCTION__, __( 'You should specify an action to be verified by using the first parameter.' ), '3.2.0' );
1371 }
1372
1373 $adminurl = strtolower( admin_url() );
1374 $referer = strtolower( wp_get_referer() );
1375 $result = isset( $_REQUEST[ $query_arg ] ) ? wp_verify_nonce( $_REQUEST[ $query_arg ], $action ) : false;
1376
1377 /**
1378 * Fires once the admin request has been validated or not.
1379 *
1380 * @since 1.5.1
1381 *
1382 * @param string $action The nonce action.
1383 * @param false|int $result False if the nonce is invalid, 1 if the nonce is valid and generated between
1384 * 0-12 hours ago, 2 if the nonce is valid and generated between 12-24 hours ago.
1385 */
1386 do_action( 'check_admin_referer', $action, $result );
1387
1388 if ( ! $result && ! ( -1 === $action && str_starts_with( $referer, $adminurl ) ) ) {
1389 wp_nonce_ays( $action );
1390 die();
1391 }
1392
1393 return $result;
1394 }
1395endif;
1396
1397if ( ! function_exists( 'check_ajax_referer' ) ) :
1398 /**
1399 * Verifies the Ajax request to prevent processing requests external of the blog.
1400 *
1401 * @since 2.0.3
1402 *
1403 * @param int|string $action Action nonce.
1404 * @param false|string $query_arg Optional. Key to check for the nonce in `$_REQUEST` (since 2.5). If false,
1405 * `$_REQUEST` values will be evaluated for '_ajax_nonce', and '_wpnonce'
1406 * (in that order). Default false.
1407 * @param bool $stop Optional. Whether to stop early when the nonce cannot be verified.
1408 * Default true.
1409 * @return int|false 1 if the nonce is valid and generated between 0-12 hours ago,
1410 * 2 if the nonce is valid and generated between 12-24 hours ago.
1411 * False if the nonce is invalid.
1412 */
1413 function check_ajax_referer( $action = -1, $query_arg = false, $stop = true ) {
1414 if ( -1 === $action ) {
1415 _doing_it_wrong( __FUNCTION__, __( 'You should specify an action to be verified by using the first parameter.' ), '4.7.0' );
1416 }
1417
1418 $nonce = '';
1419
1420 if ( $query_arg && isset( $_REQUEST[ $query_arg ] ) ) {
1421 $nonce = $_REQUEST[ $query_arg ];
1422 } elseif ( isset( $_REQUEST['_ajax_nonce'] ) ) {
1423 $nonce = $_REQUEST['_ajax_nonce'];
1424 } elseif ( isset( $_REQUEST['_wpnonce'] ) ) {
1425 $nonce = $_REQUEST['_wpnonce'];
1426 }
1427
1428 $result = wp_verify_nonce( $nonce, $action );
1429
1430 /**
1431 * Fires once the Ajax request has been validated or not.
1432 *
1433 * @since 2.1.0
1434 *
1435 * @param string $action The Ajax nonce action.
1436 * @param false|int $result False if the nonce is invalid, 1 if the nonce is valid and generated between
1437 * 0-12 hours ago, 2 if the nonce is valid and generated between 12-24 hours ago.
1438 */
1439 do_action( 'check_ajax_referer', $action, $result );
1440
1441 if ( $stop && false === $result ) {
1442 if ( wp_doing_ajax() ) {
1443 wp_die( -1, 403 );
1444 } else {
1445 die( '-1' );
1446 }
1447 }
1448
1449 return $result;
1450 }
1451endif;
1452
1453if ( ! function_exists( 'wp_redirect' ) ) :
1454 /**
1455 * Redirects to another page.
1456 *
1457 * Note: wp_redirect() does not exit automatically, and should almost always be
1458 * followed by a call to `exit;`:
1459 *
1460 * wp_redirect( $url );
1461 * exit;
1462 *
1463 * Exiting can also be selectively manipulated by using wp_redirect() as a conditional
1464 * in conjunction with the {@see 'wp_redirect'} and {@see 'wp_redirect_status'} filters:
1465 *
1466 * if ( wp_redirect( $url ) ) {
1467 * exit;
1468 * }
1469 *
1470 * @since 1.5.1
1471 * @since 5.1.0 The `$x_redirect_by` parameter was added.
1472 * @since 5.4.0 On invalid status codes, wp_die() is called.
1473 *
1474 * @global bool $is_IIS
1475 *
1476 * @param string $location The path or URL to redirect to.
1477 * @param int $status Optional. HTTP response status code to use. Default '302' (Moved Temporarily).
1478 * @param string|false $x_redirect_by Optional. The application doing the redirect or false to omit. Default 'WordPress'.
1479 * @return bool False if the redirect was canceled, true otherwise.
1480 */
1481 function wp_redirect( $location, $status = 302, $x_redirect_by = 'WordPress' ) {
1482 global $is_IIS;
1483
1484 /**
1485 * Filters the redirect location.
1486 *
1487 * @since 2.1.0
1488 *
1489 * @param string $location The path or URL to redirect to.
1490 * @param int $status The HTTP response status code to use.
1491 */
1492 $location = apply_filters( 'wp_redirect', $location, $status );
1493
1494 /**
1495 * Filters the redirect HTTP response status code to use.
1496 *
1497 * @since 2.3.0
1498 *
1499 * @param int $status The HTTP response status code to use.
1500 * @param string $location The path or URL to redirect to.
1501 */
1502 $status = apply_filters( 'wp_redirect_status', $status, $location );
1503
1504 if ( ! $location ) {
1505 return false;
1506 }
1507
1508 if ( $status < 300 || 399 < $status ) {
1509 wp_die( __( 'HTTP redirect status code must be a redirection code, 3xx.' ) );
1510 }
1511
1512 $location = wp_sanitize_redirect( $location );
1513
1514 if ( ! $is_IIS && 'cgi-fcgi' !== PHP_SAPI ) {
1515 status_header( $status ); // This causes problems on IIS and some FastCGI setups.
1516 }
1517
1518 /**
1519 * Filters the X-Redirect-By header.
1520 *
1521 * Allows applications to identify themselves when they're doing a redirect.
1522 *
1523 * @since 5.1.0
1524 *
1525 * @param string|false $x_redirect_by The application doing the redirect or false to omit the header.
1526 * @param int $status Status code to use.
1527 * @param string $location The path to redirect to.
1528 */
1529 $x_redirect_by = apply_filters( 'x_redirect_by', $x_redirect_by, $status, $location );
1530 if ( is_string( $x_redirect_by ) ) {
1531 header( "X-Redirect-By: $x_redirect_by" );
1532 }
1533
1534 header( "Location: $location", true, $status );
1535
1536 return true;
1537 }
1538endif;
1539
1540if ( ! function_exists( 'wp_sanitize_redirect' ) ) :
1541 /**
1542 * Sanitizes a URL for use in a redirect.
1543 *
1544 * @since 2.3.0
1545 *
1546 * @param string $location The path to redirect to.
1547 * @return string Redirect-sanitized URL.
1548 */
1549 function wp_sanitize_redirect( $location ) {
1550 // Encode spaces.
1551 $location = str_replace( ' ', '%20', $location );
1552
1553 $regex = '/
1554 (
1555 (?: [\xC2-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx
1556 | \xE0[\xA0-\xBF][\x80-\xBF] # triple-byte sequences 1110xxxx 10xxxxxx * 2
1557 | [\xE1-\xEC][\x80-\xBF]{2}
1558 | \xED[\x80-\x9F][\x80-\xBF]
1559 | [\xEE-\xEF][\x80-\xBF]{2}
1560 | \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences 11110xxx 10xxxxxx * 3
1561 | [\xF1-\xF3][\x80-\xBF]{3}
1562 | \xF4[\x80-\x8F][\x80-\xBF]{2}
1563 ){1,40} # ...one or more times
1564 )/x';
1565 $location = preg_replace_callback( $regex, '_wp_sanitize_utf8_in_redirect', $location );
1566 $location = preg_replace( '|[^a-z0-9-~+_.?#=&;,/:%!*\[\]()@]|i', '', $location );
1567 $location = wp_kses_no_null( $location );
1568
1569 // Remove %0D and %0A from location.
1570 $strip = array( '%0d', '%0a', '%0D', '%0A' );
1571 return _deep_replace( $strip, $location );
1572 }
1573
1574 /**
1575 * URL encodes UTF-8 characters in a URL.
1576 *
1577 * @ignore
1578 * @since 4.2.0
1579 * @access private
1580 *
1581 * @see wp_sanitize_redirect()
1582 *
1583 * @param array $matches RegEx matches against the redirect location.
1584 * @return string URL-encoded version of the first RegEx match.
1585 */
1586 function _wp_sanitize_utf8_in_redirect( $matches ) {
1587 return urlencode( $matches[0] );
1588 }
1589endif;
1590
1591if ( ! function_exists( 'wp_safe_redirect' ) ) :
1592 /**
1593 * Performs a safe (local) redirect, using wp_redirect().
1594 *
1595 * Checks whether the $location is using an allowed host, if it has an absolute
1596 * path. A plugin can therefore set or remove allowed host(s) to or from the
1597 * list.
1598 *
1599 * If the host is not allowed, then the redirect defaults to wp-admin on the siteurl
1600 * instead. This prevents malicious redirects which redirect to another host,
1601 * but only used in a few places.
1602 *
1603 * Note: wp_safe_redirect() does not exit automatically, and should almost always be
1604 * followed by a call to `exit;`:
1605 *
1606 * wp_safe_redirect( $url );
1607 * exit;
1608 *
1609 * Exiting can also be selectively manipulated by using wp_safe_redirect() as a conditional
1610 * in conjunction with the {@see 'wp_redirect'} and {@see 'wp_redirect_status'} filters:
1611 *
1612 * if ( wp_safe_redirect( $url ) ) {
1613 * exit;
1614 * }
1615 *
1616 * @since 2.3.0
1617 * @since 5.1.0 The return value from wp_redirect() is now passed on, and the `$x_redirect_by` parameter was added.
1618 *
1619 * @param string $location The path or URL to redirect to.
1620 * @param int $status Optional. HTTP response status code to use. Default '302' (Moved Temporarily).
1621 * @param string|false $x_redirect_by Optional. The application doing the redirect or false to omit. Default 'WordPress'.
1622 * @return bool False if the redirect was canceled, true otherwise.
1623 */
1624 function wp_safe_redirect( $location, $status = 302, $x_redirect_by = 'WordPress' ) {
1625
1626 // Need to look at the URL the way it will end up in wp_redirect().
1627 $location = wp_sanitize_redirect( $location );
1628
1629 /**
1630 * Filters the redirect fallback URL for when the provided redirect is not safe (local).
1631 *
1632 * @since 4.3.0
1633 *
1634 * @param string $fallback_url The fallback URL to use by default.
1635 * @param int $status The HTTP response status code to use.
1636 */
1637 $fallback_url = apply_filters( 'wp_safe_redirect_fallback', admin_url(), $status );
1638
1639 $location = wp_validate_redirect( $location, $fallback_url );
1640
1641 return wp_redirect( $location, $status, $x_redirect_by );
1642 }
1643endif;
1644
1645if ( ! function_exists( 'wp_validate_redirect' ) ) :
1646 /**
1647 * Validates a URL for use in a redirect.
1648 *
1649 * Checks whether the $location is using an allowed host, if it has an absolute
1650 * path. A plugin can therefore set or remove allowed host(s) to or from the
1651 * list.
1652 *
1653 * If the host is not allowed, then the redirect is to $fallback_url supplied.
1654 *
1655 * @since 2.8.1
1656 *
1657 * @param string $location The redirect to validate.
1658 * @param string $fallback_url The value to return if $location is not allowed.
1659 * @return string Redirect-sanitized URL.
1660 */
1661 function wp_validate_redirect( $location, $fallback_url = '' ) {
1662 $location = wp_sanitize_redirect( trim( $location, " \t\n\r\0\x08\x0B" ) );
1663 // Browsers will assume 'http' is your protocol, and will obey a redirect to a URL starting with '//'.
1664 if ( str_starts_with( $location, '//' ) ) {
1665 $location = 'http:' . $location;
1666 }
1667
1668 /*
1669 * In PHP 5 parse_url() may fail if the URL query part contains 'http://'.
1670 * See https://bugs.php.net/bug.php?id=38143
1671 */
1672 $cut = strpos( $location, '?' );
1673 $test = $cut ? substr( $location, 0, $cut ) : $location;
1674
1675 $lp = parse_url( $test );
1676
1677 // Give up if malformed URL.
1678 if ( false === $lp ) {
1679 return $fallback_url;
1680 }
1681
1682 // Allow only 'http' and 'https' schemes. No 'data:', etc.
1683 if ( isset( $lp['scheme'] ) && ! ( 'http' === $lp['scheme'] || 'https' === $lp['scheme'] ) ) {
1684 return $fallback_url;
1685 }
1686
1687 if ( ! isset( $lp['host'] ) && ! empty( $lp['path'] ) && '/' !== $lp['path'][0] ) {
1688 $path = '';
1689 if ( ! empty( $_SERVER['REQUEST_URI'] ) ) {
1690 $path = dirname( parse_url( 'http://placeholder' . $_SERVER['REQUEST_URI'], PHP_URL_PATH ) . '?' );
1691 $path = wp_normalize_path( $path );
1692 }
1693 $location = '/' . ltrim( $path . '/', '/' ) . $location;
1694 }
1695
1696 /*
1697 * Reject if certain components are set but host is not.
1698 * This catches URLs like https:host.com for which parse_url() does not set the host field.
1699 */
1700 if ( ! isset( $lp['host'] ) && ( isset( $lp['scheme'] ) || isset( $lp['user'] ) || isset( $lp['pass'] ) || isset( $lp['port'] ) ) ) {
1701 return $fallback_url;
1702 }
1703
1704 // Reject malformed components parse_url() can return on odd inputs.
1705 foreach ( array( 'user', 'pass', 'host' ) as $component ) {
1706 if ( isset( $lp[ $component ] ) && strpbrk( $lp[ $component ], ':/?#@' ) ) {
1707 return $fallback_url;
1708 }
1709 }
1710
1711 $wpp = parse_url( home_url() );
1712
1713 /**
1714 * Filters the list of allowed hosts to redirect to.
1715 *
1716 * @since 2.3.0
1717 *
1718 * @param string[] $hosts An array of allowed host names.
1719 * @param string $host The host name of the redirect destination; empty string if not set.
1720 */
1721 $allowed_hosts = (array) apply_filters( 'allowed_redirect_hosts', array( $wpp['host'] ), isset( $lp['host'] ) ? $lp['host'] : '' );
1722
1723 if ( isset( $lp['host'] ) && ( ! in_array( $lp['host'], $allowed_hosts, true ) && strtolower( $wpp['host'] ) !== $lp['host'] ) ) {
1724 $location = $fallback_url;
1725 }
1726
1727 return $location;
1728 }
1729endif;
1730
1731if ( ! function_exists( 'wp_notify_postauthor' ) ) :
1732 /**
1733 * Notifies an author (and/or others) of a comment/trackback/pingback on a post.
1734 *
1735 * @since 1.0.0
1736 *
1737 * @param int|WP_Comment $comment_id Comment ID or WP_Comment object.
1738 * @param string $deprecated Not used.
1739 * @return bool True on completion. False if no email addresses were specified.
1740 */
1741 function wp_notify_postauthor( $comment_id, $deprecated = null ) {
1742 if ( null !== $deprecated ) {
1743 _deprecated_argument( __FUNCTION__, '3.8.0' );
1744 }
1745
1746 $comment = get_comment( $comment_id );
1747 if ( empty( $comment ) || empty( $comment->comment_post_ID ) ) {
1748 return false;
1749 }
1750
1751 $post = get_post( $comment->comment_post_ID );
1752 $author = get_userdata( $post->post_author );
1753
1754 // Who to notify? By default, just the post author, but others can be added.
1755 $emails = array();
1756 if ( $author ) {
1757 $emails[] = $author->user_email;
1758 }
1759
1760 /**
1761 * Filters the list of email addresses to receive a comment notification.
1762 *
1763 * By default, only post authors are notified of comments. This filter allows
1764 * others to be added.
1765 *
1766 * @since 3.7.0
1767 *
1768 * @param string[] $emails An array of email addresses to receive a comment notification.
1769 * @param string $comment_id The comment ID as a numeric string.
1770 */
1771 $emails = apply_filters( 'comment_notification_recipients', $emails, $comment->comment_ID );
1772 $emails = array_filter( $emails );
1773
1774 // If there are no addresses to send the comment to, bail.
1775 if ( ! count( $emails ) ) {
1776 return false;
1777 }
1778
1779 // Facilitate unsetting below without knowing the keys.
1780 $emails = array_flip( $emails );
1781
1782 /**
1783 * Filters whether to notify comment authors of their comments on their own posts.
1784 *
1785 * By default, comment authors aren't notified of their comments on their own
1786 * posts. This filter allows you to override that.
1787 *
1788 * @since 3.8.0
1789 *
1790 * @param bool $notify Whether to notify the post author of their own comment.
1791 * Default false.
1792 * @param string $comment_id The comment ID as a numeric string.
1793 */
1794 $notify_author = apply_filters( 'comment_notification_notify_author', false, $comment->comment_ID );
1795
1796 // The comment was left by the author.
1797 if ( $author && ! $notify_author && (int) $comment->user_id === (int) $post->post_author ) {
1798 unset( $emails[ $author->user_email ] );
1799 }
1800
1801 // The author moderated a comment on their own post.
1802 if ( $author && ! $notify_author && get_current_user_id() === (int) $post->post_author ) {
1803 unset( $emails[ $author->user_email ] );
1804 }
1805
1806 // The post author is no longer a member of the blog.
1807 if ( $author && ! $notify_author && ! user_can( $post->post_author, 'read_post', $post->ID ) ) {
1808 unset( $emails[ $author->user_email ] );
1809 }
1810
1811 // If there's no email to send the comment to, bail, otherwise flip array back around for use below.
1812 if ( ! count( $emails ) ) {
1813 return false;
1814 } else {
1815 $emails = array_flip( $emails );
1816 }
1817
1818 $comment_author_domain = '';
1819 if ( WP_Http::is_ip_address( $comment->comment_author_IP ) ) {
1820 $comment_author_domain = gethostbyaddr( $comment->comment_author_IP );
1821 }
1822
1823 /*
1824 * The blogname option is escaped with esc_html() on the way into the database in sanitize_option().
1825 * We want to reverse this for the plain text arena of emails.
1826 */
1827 $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
1828 $comment_content = wp_specialchars_decode( $comment->comment_content );
1829
1830 $wp_email = 'wordpress@' . preg_replace( '#^www\.#', '', wp_parse_url( network_home_url(), PHP_URL_HOST ) );
1831
1832 if ( '' === $comment->comment_author ) {
1833 $from = "From: \"$blogname\" <$wp_email>";
1834 if ( '' !== $comment->comment_author_email ) {
1835 $reply_to = "Reply-To: $comment->comment_author_email";
1836 }
1837 } else {
1838 $from = "From: \"$comment->comment_author\" <$wp_email>";
1839 if ( '' !== $comment->comment_author_email ) {
1840 $reply_to = "Reply-To: \"$comment->comment_author_email\" <$comment->comment_author_email>";
1841 }
1842 }
1843
1844 $message_headers = "$from\n"
1845 . 'Content-Type: text/plain; charset="' . get_option( 'blog_charset' ) . "\"\n";
1846
1847 if ( isset( $reply_to ) ) {
1848 $message_headers .= $reply_to . "\n";
1849 }
1850
1851 /**
1852 * Filters the comment notification email headers.
1853 *
1854 * @since 1.5.2
1855 *
1856 * @param string $message_headers Headers for the comment notification email.
1857 * @param string $comment_id Comment ID as a numeric string.
1858 */
1859 $message_headers = apply_filters( 'comment_notification_headers', $message_headers, $comment->comment_ID );
1860
1861 foreach ( $emails as $email ) {
1862 $user = get_user_by( 'email', $email );
1863
1864 if ( $user ) {
1865 $switched_locale = switch_to_user_locale( $user->ID );
1866 } else {
1867 $switched_locale = switch_to_locale( get_locale() );
1868 }
1869
1870 switch ( $comment->comment_type ) {
1871 case 'trackback':
1872 /* translators: %s: Post title. */
1873 $notify_message = sprintf( __( 'New trackback on your post "%s"' ), $post->post_title ) . "\r\n";
1874 /* translators: 1: Trackback/pingback website name, 2: Website IP address, 3: Website hostname. */
1875 $notify_message .= sprintf( __( 'Website: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n";
1876 /* translators: %s: Trackback/pingback/comment author URL. */
1877 $notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n";
1878 /* translators: %s: Comment text. */
1879 $notify_message .= sprintf( __( 'Comment: %s' ), "\r\n" . $comment_content ) . "\r\n\r\n";
1880 $notify_message .= __( 'You can see all trackbacks on this post here:' ) . "\r\n";
1881 /* translators: Trackback notification email subject. 1: Site title, 2: Post title. */
1882 $subject = sprintf( __( '[%1$s] Trackback: "%2$s"' ), $blogname, $post->post_title );
1883 break;
1884
1885 case 'pingback':
1886 /* translators: %s: Post title. */
1887 $notify_message = sprintf( __( 'New pingback on your post "%s"' ), $post->post_title ) . "\r\n";
1888 /* translators: 1: Trackback/pingback website name, 2: Website IP address, 3: Website hostname. */
1889 $notify_message .= sprintf( __( 'Website: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n";
1890 /* translators: %s: Trackback/pingback/comment author URL. */
1891 $notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n";
1892 /* translators: %s: Comment text. */
1893 $notify_message .= sprintf( __( 'Comment: %s' ), "\r\n" . $comment_content ) . "\r\n\r\n";
1894 $notify_message .= __( 'You can see all pingbacks on this post here:' ) . "\r\n";
1895 /* translators: Pingback notification email subject. 1: Site title, 2: Post title. */
1896 $subject = sprintf( __( '[%1$s] Pingback: "%2$s"' ), $blogname, $post->post_title );
1897 break;
1898
1899 case 'note':
1900 /* translators: %s: Post title. */
1901 $notify_message = sprintf( __( 'New note on your post "%s"' ), $post->post_title ) . "\r\n";
1902 /* translators: 1: Note author's name, 2: Note author's IP address, 3: Note author's hostname. */
1903 $notify_message .= sprintf( __( 'Author: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n";
1904 /* translators: %s: Note author email. */
1905 $notify_message .= sprintf( __( 'Email: %s' ), $comment->comment_author_email ) . "\r\n";
1906 /* translators: %s: Note text. */
1907 $notify_message .= sprintf( __( 'Note: %s' ), "\r\n" . ( empty( $comment_content ) ? __( 'resolved/reopened' ) : $comment_content ) ) . "\r\n\r\n";
1908 $notify_message .= __( 'You can see all notes on this post here:' ) . "\r\n";
1909 /* translators: Note notification email subject. 1: Site title, 2: Post title. */
1910 $subject = sprintf( __( '[%1$s] Note: "%2$s"' ), $blogname, $post->post_title );
1911 break;
1912
1913 default: // Comments.
1914 /* translators: %s: Post title. */
1915 $notify_message = sprintf( __( 'New comment on your post "%s"' ), $post->post_title ) . "\r\n";
1916 /* translators: 1: Comment author's name, 2: Comment author's IP address, 3: Comment author's hostname. */
1917 $notify_message .= sprintf( __( 'Author: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n";
1918 /* translators: %s: Comment author email. */
1919 $notify_message .= sprintf( __( 'Email: %s' ), $comment->comment_author_email ) . "\r\n";
1920 /* translators: %s: Trackback/pingback/comment author URL. */
1921 $notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n";
1922
1923 if ( $comment->comment_parent && user_can( $post->post_author, 'edit_comment', $comment->comment_parent ) ) {
1924 /* translators: Comment moderation. %s: Parent comment edit URL. */
1925 $notify_message .= sprintf( __( 'In reply to: %s' ), admin_url( "comment.php?action=editcomment&c={$comment->comment_parent}#wpbody-content" ) ) . "\r\n";
1926 }
1927
1928 /* translators: %s: Comment text. */
1929 $notify_message .= sprintf( __( 'Comment: %s' ), "\r\n" . $comment_content ) . "\r\n\r\n";
1930 $notify_message .= __( 'You can see all comments on this post here:' ) . "\r\n";
1931 /* translators: Comment notification email subject. 1: Site title, 2: Post title. */
1932 $subject = sprintf( __( '[%1$s] Comment: "%2$s"' ), $blogname, $post->post_title );
1933 break;
1934 }
1935
1936 /* translators: %s: Comment URL. */
1937 if ( 'note' === $comment->comment_type ) {
1938 $notify_message .= get_edit_post_link( $comment->comment_post_ID, 'url' ) . "\r\n";
1939 } else {
1940 $notify_message .= get_permalink( $comment->comment_post_ID ) . "#comments\r\n\r\n";
1941 $notify_message .= sprintf( __( 'Permalink: %s' ), get_comment_link( $comment ) ) . "\r\n";
1942 }
1943
1944 if ( 'note' !== $comment->comment_type && user_can( $post->post_author, 'edit_comment', $comment->comment_ID ) ) {
1945 if ( EMPTY_TRASH_DAYS ) {
1946 /* translators: Comment moderation. %s: Comment action URL. */
1947 $notify_message .= sprintf( __( 'Trash it: %s' ), admin_url( "comment.php?action=trash&c={$comment->comment_ID}#wpbody-content" ) ) . "\r\n";
1948 } else {
1949 /* translators: Comment moderation. %s: Comment action URL. */
1950 $notify_message .= sprintf( __( 'Delete it: %s' ), admin_url( "comment.php?action=delete&c={$comment->comment_ID}#wpbody-content" ) ) . "\r\n";
1951 }
1952 /* translators: Comment moderation. %s: Comment action URL. */
1953 $notify_message .= sprintf( __( 'Spam it: %s' ), admin_url( "comment.php?action=spam&c={$comment->comment_ID}#wpbody-content" ) ) . "\r\n";
1954 }
1955
1956 /**
1957 * Filters the comment notification email text.
1958 *
1959 * @since 1.5.2
1960 *
1961 * @param string $notify_message The comment notification email text.
1962 * @param string $comment_id Comment ID as a numeric string.
1963 */
1964 $notify_message = apply_filters( 'comment_notification_text', $notify_message, $comment->comment_ID );
1965
1966 /**
1967 * Filters the comment notification email subject.
1968 *
1969 * @since 1.5.2
1970 *
1971 * @param string $subject The comment notification email subject.
1972 * @param string $comment_id Comment ID as a numeric string.
1973 */
1974 $subject = apply_filters( 'comment_notification_subject', $subject, $comment->comment_ID );
1975
1976 wp_mail( $email, wp_specialchars_decode( $subject ), $notify_message, $message_headers );
1977
1978 if ( $switched_locale ) {
1979 restore_previous_locale();
1980 }
1981 }
1982
1983 return true;
1984 }
1985endif;
1986
1987if ( ! function_exists( 'wp_notify_moderator' ) ) :
1988 /**
1989 * Notifies the moderator of the site about a new comment that is awaiting approval.
1990 *
1991 * @since 1.0.0
1992 *
1993 * @global wpdb $wpdb WordPress database abstraction object.
1994 *
1995 * Uses the {@see 'notify_moderator'} filter to determine whether the site moderator
1996 * should be notified, overriding the site setting.
1997 *
1998 * @param int $comment_id Comment ID.
1999 * @return true Always returns true.
2000 */
2001 function wp_notify_moderator( $comment_id ) {
2002 global $wpdb;
2003
2004 $maybe_notify = get_option( 'moderation_notify' );
2005
2006 /**
2007 * Filters whether to send the site moderator email notifications, overriding the site setting.
2008 *
2009 * @since 4.4.0
2010 *
2011 * @param bool $maybe_notify Whether to notify blog moderator.
2012 * @param int $comment_id The ID of the comment for the notification.
2013 */
2014 $maybe_notify = apply_filters( 'notify_moderator', $maybe_notify, $comment_id );
2015
2016 if ( ! $maybe_notify ) {
2017 return true;
2018 }
2019
2020 $comment = get_comment( $comment_id );
2021 $post = get_post( $comment->comment_post_ID );
2022 $user = get_userdata( $post->post_author );
2023 // Send to the administration and to the post author if the author can modify the comment.
2024 $emails = array( get_option( 'admin_email' ) );
2025 if ( $user && user_can( $user->ID, 'edit_comment', $comment_id ) && ! empty( $user->user_email ) ) {
2026 if ( 0 !== strcasecmp( $user->user_email, get_option( 'admin_email' ) ) ) {
2027 $emails[] = $user->user_email;
2028 }
2029 }
2030
2031 $comment_author_domain = '';
2032 if ( WP_Http::is_ip_address( $comment->comment_author_IP ) ) {
2033 $comment_author_domain = gethostbyaddr( $comment->comment_author_IP );
2034 }
2035
2036 $comments_waiting = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_approved = '0'" );
2037
2038 /*
2039 * The blogname option is escaped with esc_html() on the way into the database in sanitize_option().
2040 * We want to reverse this for the plain text arena of emails.
2041 */
2042 $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
2043 $comment_content = wp_specialchars_decode( $comment->comment_content );
2044
2045 $message_headers = '';
2046
2047 /**
2048 * Filters the list of recipients for comment moderation emails.
2049 *
2050 * @since 3.7.0
2051 *
2052 * @param string[] $emails List of email addresses to notify for comment moderation.
2053 * @param int $comment_id Comment ID.
2054 */
2055 $emails = apply_filters( 'comment_moderation_recipients', $emails, $comment_id );
2056
2057 /**
2058 * Filters the comment moderation email headers.
2059 *
2060 * @since 2.8.0
2061 *
2062 * @param string $message_headers Headers for the comment moderation email.
2063 * @param int $comment_id Comment ID.
2064 */
2065 $message_headers = apply_filters( 'comment_moderation_headers', $message_headers, $comment_id );
2066
2067 foreach ( $emails as $email ) {
2068 $user = get_user_by( 'email', $email );
2069
2070 if ( $user ) {
2071 $switched_locale = switch_to_user_locale( $user->ID );
2072 } else {
2073 $switched_locale = switch_to_locale( get_locale() );
2074 }
2075
2076 switch ( $comment->comment_type ) {
2077 case 'trackback':
2078 /* translators: %s: Post title. */
2079 $notify_message = sprintf( __( 'A new trackback on the post "%s" is waiting for your approval' ), $post->post_title ) . "\r\n";
2080 $notify_message .= get_permalink( $comment->comment_post_ID ) . "\r\n\r\n";
2081 /* translators: 1: Trackback/pingback website name, 2: Website IP address, 3: Website hostname. */
2082 $notify_message .= sprintf( __( 'Website: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n";
2083 /* translators: %s: Trackback/pingback/comment author URL. */
2084 $notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n";
2085 $notify_message .= __( 'Trackback excerpt: ' ) . "\r\n" . $comment_content . "\r\n\r\n";
2086 break;
2087
2088 case 'pingback':
2089 /* translators: %s: Post title. */
2090 $notify_message = sprintf( __( 'A new pingback on the post "%s" is waiting for your approval' ), $post->post_title ) . "\r\n";
2091 $notify_message .= get_permalink( $comment->comment_post_ID ) . "\r\n\r\n";
2092 /* translators: 1: Trackback/pingback website name, 2: Website IP address, 3: Website hostname. */
2093 $notify_message .= sprintf( __( 'Website: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n";
2094 /* translators: %s: Trackback/pingback/comment author URL. */
2095 $notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n";
2096 $notify_message .= __( 'Pingback excerpt: ' ) . "\r\n" . $comment_content . "\r\n\r\n";
2097 break;
2098
2099 default: // Comments.
2100 /* translators: %s: Post title. */
2101 $notify_message = sprintf( __( 'A new comment on the post "%s" is waiting for your approval' ), $post->post_title ) . "\r\n";
2102 $notify_message .= get_permalink( $comment->comment_post_ID ) . "\r\n\r\n";
2103 /* translators: 1: Comment author's name, 2: Comment author's IP address, 3: Comment author's hostname. */
2104 $notify_message .= sprintf( __( 'Author: %1$s (IP address: %2$s, %3$s)' ), $comment->comment_author, $comment->comment_author_IP, $comment_author_domain ) . "\r\n";
2105 /* translators: %s: Comment author email. */
2106 $notify_message .= sprintf( __( 'Email: %s' ), $comment->comment_author_email ) . "\r\n";
2107 /* translators: %s: Trackback/pingback/comment author URL. */
2108 $notify_message .= sprintf( __( 'URL: %s' ), $comment->comment_author_url ) . "\r\n";
2109
2110 if ( $comment->comment_parent ) {
2111 /* translators: Comment moderation. %s: Parent comment edit URL. */
2112 $notify_message .= sprintf( __( 'In reply to: %s' ), admin_url( "comment.php?action=editcomment&c={$comment->comment_parent}#wpbody-content" ) ) . "\r\n";
2113 }
2114
2115 /* translators: %s: Comment text. */
2116 $notify_message .= sprintf( __( 'Comment: %s' ), "\r\n" . $comment_content ) . "\r\n\r\n";
2117 break;
2118 }
2119
2120 /* translators: Comment moderation. %s: Comment action URL. */
2121 $notify_message .= sprintf( __( 'Approve it: %s' ), admin_url( "comment.php?action=approve&c={$comment_id}#wpbody-content" ) ) . "\r\n";
2122
2123 if ( EMPTY_TRASH_DAYS ) {
2124 /* translators: Comment moderation. %s: Comment action URL. */
2125 $notify_message .= sprintf( __( 'Trash it: %s' ), admin_url( "comment.php?action=trash&c={$comment_id}#wpbody-content" ) ) . "\r\n";
2126 } else {
2127 /* translators: Comment moderation. %s: Comment action URL. */
2128 $notify_message .= sprintf( __( 'Delete it: %s' ), admin_url( "comment.php?action=delete&c={$comment_id}#wpbody-content" ) ) . "\r\n";
2129 }
2130
2131 /* translators: Comment moderation. %s: Comment action URL. */
2132 $notify_message .= sprintf( __( 'Spam it: %s' ), admin_url( "comment.php?action=spam&c={$comment_id}#wpbody-content" ) ) . "\r\n";
2133
2134 $notify_message .= sprintf(
2135 /* translators: Comment moderation. %s: Number of comments awaiting approval. */
2136 _n(
2137 'Currently %s comment is waiting for approval. Please visit the moderation panel:',
2138 'Currently %s comments are waiting for approval. Please visit the moderation panel:',
2139 $comments_waiting
2140 ),
2141 number_format_i18n( $comments_waiting )
2142 ) . "\r\n";
2143 $notify_message .= admin_url( 'edit-comments.php?comment_status=moderated#wpbody-content' ) . "\r\n";
2144
2145 /* translators: Comment moderation notification email subject. 1: Site title, 2: Post title. */
2146 $subject = sprintf( __( '[%1$s] Please moderate: "%2$s"' ), $blogname, $post->post_title );
2147
2148 /**
2149 * Filters the comment moderation email text.
2150 *
2151 * @since 1.5.2
2152 *
2153 * @param string $notify_message Text of the comment moderation email.
2154 * @param int $comment_id Comment ID.
2155 */
2156 $notify_message = apply_filters( 'comment_moderation_text', $notify_message, $comment_id );
2157
2158 /**
2159 * Filters the comment moderation email subject.
2160 *
2161 * @since 1.5.2
2162 *
2163 * @param string $subject Subject of the comment moderation email.
2164 * @param int $comment_id Comment ID.
2165 */
2166 $subject = apply_filters( 'comment_moderation_subject', $subject, $comment_id );
2167
2168 wp_mail( $email, wp_specialchars_decode( $subject ), $notify_message, $message_headers );
2169
2170 if ( $switched_locale ) {
2171 restore_previous_locale();
2172 }
2173 }
2174
2175 return true;
2176 }
2177endif;
2178
2179if ( ! function_exists( 'wp_password_change_notification' ) ) :
2180 /**
2181 * Notifies the blog admin of a user changing password, normally via email.
2182 *
2183 * @since 2.7.0
2184 *
2185 * @param WP_User $user User object.
2186 */
2187 function wp_password_change_notification( $user ) {
2188 /*
2189 * Send a copy of password change notification to the admin,
2190 * but check to see if it's the admin whose password we're changing, and skip this.
2191 */
2192 if ( 0 !== strcasecmp( $user->user_email, get_option( 'admin_email' ) ) ) {
2193
2194 $admin_user = get_user_by( 'email', get_option( 'admin_email' ) );
2195
2196 if ( $admin_user ) {
2197 $switched_locale = switch_to_user_locale( $admin_user->ID );
2198 } else {
2199 $switched_locale = switch_to_locale( get_locale() );
2200 }
2201
2202 /* translators: %s: User name. */
2203 $message = sprintf( __( 'Password changed for user: %s' ), $user->user_login ) . "\r\n";
2204 /*
2205 * The blogname option is escaped with esc_html() on the way into the database in sanitize_option().
2206 * We want to reverse this for the plain text arena of emails.
2207 */
2208 $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
2209
2210 $wp_password_change_notification_email = array(
2211 'to' => get_option( 'admin_email' ),
2212 /* translators: Password change notification email subject. %s: Site title. */
2213 'subject' => __( '[%s] Password Changed' ),
2214 'message' => $message,
2215 'headers' => '',
2216 );
2217
2218 /**
2219 * Filters the contents of the password change notification email sent to the site admin.
2220 *
2221 * @since 4.9.0
2222 *
2223 * @param array $wp_password_change_notification_email {
2224 * Used to build wp_mail().
2225 *
2226 * @type string $to The intended recipient - site admin email address.
2227 * @type string $subject The subject of the email.
2228 * @type string $message The body of the email.
2229 * @type string $headers The headers of the email.
2230 * }
2231 * @param WP_User $user User object for user whose password was changed.
2232 * @param string $blogname The site title.
2233 */
2234 $wp_password_change_notification_email = apply_filters( 'wp_password_change_notification_email', $wp_password_change_notification_email, $user, $blogname );
2235
2236 wp_mail(
2237 $wp_password_change_notification_email['to'],
2238 wp_specialchars_decode( sprintf( $wp_password_change_notification_email['subject'], $blogname ) ),
2239 $wp_password_change_notification_email['message'],
2240 $wp_password_change_notification_email['headers']
2241 );
2242
2243 if ( $switched_locale ) {
2244 restore_previous_locale();
2245 }
2246 }
2247 }
2248endif;
2249
2250if ( ! function_exists( 'wp_new_user_notification' ) ) :
2251 /**
2252 * Emails login credentials to a newly-registered user.
2253 *
2254 * A new user registration notification is also sent to admin email.
2255 *
2256 * @since 2.0.0
2257 * @since 4.3.0 The `$plaintext_pass` parameter was changed to `$notify`.
2258 * @since 4.3.1 The `$plaintext_pass` parameter was deprecated. `$notify` added as a third parameter.
2259 * @since 4.6.0 The `$notify` parameter accepts 'user' for sending notification only to the user created.
2260 *
2261 * @param int $user_id User ID.
2262 * @param null $deprecated Not used (argument deprecated).
2263 * @param string $notify Optional. Type of notification that should happen. Accepts 'admin' or an empty
2264 * string (admin only), 'user', or 'both' (admin and user). Default empty.
2265 */
2266 function wp_new_user_notification( $user_id, $deprecated = null, $notify = '' ) {
2267 if ( null !== $deprecated ) {
2268 _deprecated_argument( __FUNCTION__, '4.3.1' );
2269 }
2270
2271 // Accepts only 'user', 'admin' , 'both' or default '' as $notify.
2272 if ( ! in_array( $notify, array( 'user', 'admin', 'both', '' ), true ) ) {
2273 return;
2274 }
2275
2276 $user = get_userdata( $user_id );
2277
2278 /*
2279 * The blogname option is escaped with esc_html() on the way into the database in sanitize_option().
2280 * We want to reverse this for the plain text arena of emails.
2281 */
2282 $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
2283
2284 /**
2285 * Filters whether the admin is notified of a new user registration.
2286 *
2287 * @since 6.1.0
2288 *
2289 * @param bool $send Whether to send the email. Default true.
2290 * @param WP_User $user User object for new user.
2291 */
2292 $send_notification_to_admin = apply_filters( 'wp_send_new_user_notification_to_admin', true, $user );
2293
2294 if ( 'user' !== $notify && true === $send_notification_to_admin ) {
2295
2296 $admin_user = get_user_by( 'email', get_option( 'admin_email' ) );
2297
2298 if ( $admin_user ) {
2299 $switched_locale = switch_to_user_locale( $admin_user->ID );
2300 } else {
2301 $switched_locale = switch_to_locale( get_locale() );
2302 }
2303
2304 /* translators: %s: Site title. */
2305 $message = sprintf( __( 'New user registration on your site %s:' ), $blogname ) . "\r\n\r\n";
2306 /* translators: %s: User login. */
2307 $message .= sprintf( __( 'Username: %s' ), $user->user_login ) . "\r\n\r\n";
2308 /* translators: %s: User email address. */
2309 $message .= sprintf( __( 'Email: %s' ), $user->user_email ) . "\r\n";
2310
2311 $wp_new_user_notification_email_admin = array(
2312 'to' => get_option( 'admin_email' ),
2313 /* translators: New user registration notification email subject. %s: Site title. */
2314 'subject' => __( '[%s] New User Registration' ),
2315 'message' => $message,
2316 'headers' => '',
2317 );
2318
2319 /**
2320 * Filters the contents of the new user notification email sent to the site admin.
2321 *
2322 * @since 4.9.0
2323 *
2324 * @param array $wp_new_user_notification_email_admin {
2325 * Used to build wp_mail().
2326 *
2327 * @type string $to The intended recipient - site admin email address.
2328 * @type string $subject The subject of the email.
2329 * @type string $message The body of the email.
2330 * @type string $headers The headers of the email.
2331 * }
2332 * @param WP_User $user User object for new user.
2333 * @param string $blogname The site title.
2334 */
2335 $wp_new_user_notification_email_admin = apply_filters( 'wp_new_user_notification_email_admin', $wp_new_user_notification_email_admin, $user, $blogname );
2336
2337 wp_mail(
2338 $wp_new_user_notification_email_admin['to'],
2339 wp_specialchars_decode( sprintf( $wp_new_user_notification_email_admin['subject'], $blogname ) ),
2340 $wp_new_user_notification_email_admin['message'],
2341 $wp_new_user_notification_email_admin['headers']
2342 );
2343
2344 if ( $switched_locale ) {
2345 restore_previous_locale();
2346 }
2347 }
2348
2349 /**
2350 * Filters whether the user is notified of their new user registration.
2351 *
2352 * @since 6.1.0
2353 *
2354 * @param bool $send Whether to send the email. Default true.
2355 * @param WP_User $user User object for new user.
2356 */
2357 $send_notification_to_user = apply_filters( 'wp_send_new_user_notification_to_user', true, $user );
2358
2359 // `$deprecated` was pre-4.3 `$plaintext_pass`. An empty `$plaintext_pass` didn't sent a user notification.
2360 if ( 'admin' === $notify || true !== $send_notification_to_user || ( empty( $deprecated ) && empty( $notify ) ) ) {
2361 return;
2362 }
2363
2364 $key = get_password_reset_key( $user );
2365 if ( is_wp_error( $key ) ) {
2366 return;
2367 }
2368
2369 $switched_locale = switch_to_user_locale( $user_id );
2370
2371 /* translators: %s: User login. */
2372 $message = sprintf( __( 'Username: %s' ), $user->user_login ) . "\r\n\r\n";
2373 $message .= __( 'To set your password, visit the following address:' ) . "\r\n\r\n";
2374
2375 /*
2376 * Since some user login names end in a period, this could produce ambiguous URLs that
2377 * end in a period. To avoid the ambiguity, ensure that the login is not the last query
2378 * arg in the URL. If moving it to the end, a trailing period will need to be escaped.
2379 *
2380 * @see https://core.trac.wordpress.org/tickets/42957
2381 */
2382 $message .= network_site_url( 'wp-login.php?login=' . rawurlencode( $user->user_login ) . "&key=$key&action=rp", 'login' ) . "\r\n\r\n";
2383
2384 $message .= wp_login_url() . "\r\n";
2385
2386 $wp_new_user_notification_email = array(
2387 'to' => $user->user_email,
2388 /* translators: Login details notification email subject. %s: Site title. */
2389 'subject' => __( '[%s] Login Details' ),
2390 'message' => $message,
2391 'headers' => '',
2392 );
2393
2394 /**
2395 * Filters the contents of the new user notification email sent to the new user.
2396 *
2397 * @since 4.9.0
2398 *
2399 * @param array $wp_new_user_notification_email {
2400 * Used to build wp_mail().
2401 *
2402 * @type string $to The intended recipient - New user email address.
2403 * @type string $subject The subject of the email.
2404 * @type string $message The body of the email.
2405 * @type string $headers The headers of the email.
2406 * }
2407 * @param WP_User $user User object for new user.
2408 * @param string $blogname The site title.
2409 */
2410 $wp_new_user_notification_email = apply_filters( 'wp_new_user_notification_email', $wp_new_user_notification_email, $user, $blogname );
2411
2412 wp_mail(
2413 $wp_new_user_notification_email['to'],
2414 wp_specialchars_decode( sprintf( $wp_new_user_notification_email['subject'], $blogname ) ),
2415 $wp_new_user_notification_email['message'],
2416 $wp_new_user_notification_email['headers']
2417 );
2418
2419 if ( $switched_locale ) {
2420 restore_previous_locale();
2421 }
2422 }
2423endif;
2424
2425if ( ! function_exists( 'wp_nonce_tick' ) ) :
2426 /**
2427 * Returns the time-dependent variable for nonce creation.
2428 *
2429 * A nonce has a lifespan of two ticks. Nonces in their second tick may be
2430 * updated, e.g. by autosave.
2431 *
2432 * @since 2.5.0
2433 * @since 6.1.0 Added `$action` argument.
2434 *
2435 * @param string|int $action Optional. The nonce action. Default -1.
2436 * @return float Float value rounded up to the next highest integer.
2437 */
2438 function wp_nonce_tick( $action = -1 ) {
2439 /**
2440 * Filters the lifespan of nonces in seconds.
2441 *
2442 * @since 2.5.0
2443 * @since 6.1.0 Added `$action` argument to allow for more targeted filters.
2444 *
2445 * @param int $lifespan Lifespan of nonces in seconds. Default 86,400 seconds, or one day.
2446 * @param string|int $action The nonce action, or -1 if none was provided.
2447 */
2448 $nonce_life = apply_filters( 'nonce_life', DAY_IN_SECONDS, $action );
2449
2450 return ceil( time() / ( $nonce_life / 2 ) );
2451 }
2452endif;
2453
2454if ( ! function_exists( 'wp_verify_nonce' ) ) :
2455 /**
2456 * Verifies that a correct security nonce was used with time limit.
2457 *
2458 * A nonce is valid for between 12 and 24 hours (by default).
2459 *
2460 * @since 2.0.3
2461 *
2462 * @param string $nonce Nonce value that was used for verification, usually via a form field.
2463 * @param string|int $action Should give context to what is taking place and be the same when nonce was created.
2464 * @return int|false 1 if the nonce is valid and generated between 0-12 hours ago,
2465 * 2 if the nonce is valid and generated between 12-24 hours ago.
2466 * False if the nonce is invalid.
2467 */
2468 function wp_verify_nonce( $nonce, $action = -1 ) {
2469 $nonce = (string) $nonce;
2470 $user = wp_get_current_user();
2471 $uid = (int) $user->ID;
2472 if ( ! $uid ) {
2473 /**
2474 * Filters whether the user who generated the nonce is logged out.
2475 *
2476 * @since 3.5.0
2477 *
2478 * @param int $uid ID of the nonce-owning user.
2479 * @param string|int $action The nonce action, or -1 if none was provided.
2480 */
2481 $uid = apply_filters( 'nonce_user_logged_out', $uid, $action );
2482 }
2483
2484 if ( empty( $nonce ) ) {
2485 return false;
2486 }
2487
2488 $token = wp_get_session_token();
2489 $i = wp_nonce_tick( $action );
2490
2491 // Nonce generated 0-12 hours ago.
2492 $expected = substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
2493 if ( hash_equals( $expected, $nonce ) ) {
2494 return 1;
2495 }
2496
2497 // Nonce generated 12-24 hours ago.
2498 $expected = substr( wp_hash( ( $i - 1 ) . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
2499 if ( hash_equals( $expected, $nonce ) ) {
2500 return 2;
2501 }
2502
2503 /**
2504 * Fires when nonce verification fails.
2505 *
2506 * @since 4.4.0
2507 *
2508 * @param string $nonce The invalid nonce.
2509 * @param string|int $action The nonce action.
2510 * @param WP_User $user The current user object.
2511 * @param string $token The user's session token.
2512 */
2513 do_action( 'wp_verify_nonce_failed', $nonce, $action, $user, $token );
2514
2515 // Invalid nonce.
2516 return false;
2517 }
2518endif;
2519
2520if ( ! function_exists( 'wp_create_nonce' ) ) :
2521 /**
2522 * Creates a cryptographic token tied to a specific action, user, user session,
2523 * and window of time.
2524 *
2525 * @since 2.0.3
2526 * @since 4.0.0 Session tokens were integrated with nonce creation.
2527 *
2528 * @param string|int $action Scalar value to add context to the nonce.
2529 * @return string The token.
2530 */
2531 function wp_create_nonce( $action = -1 ) {
2532 $user = wp_get_current_user();
2533 $uid = (int) $user->ID;
2534 if ( ! $uid ) {
2535 /** This filter is documented in wp-includes/pluggable.php */
2536 $uid = apply_filters( 'nonce_user_logged_out', $uid, $action );
2537 }
2538
2539 $token = wp_get_session_token();
2540 $i = wp_nonce_tick( $action );
2541
2542 return substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
2543 }
2544endif;
2545
2546if ( ! function_exists( 'wp_salt' ) ) :
2547 /**
2548 * Returns a salt to add to hashes.
2549 *
2550 * Salts are created using secret keys. Secret keys are located in two places:
2551 * in the database and in the wp-config.php file. The secret key in the database
2552 * is randomly generated and will be appended to the secret keys in wp-config.php.
2553 *
2554 * The secret keys in wp-config.php should be updated to strong, random keys to maximize
2555 * security. Below is an example of how the secret key constants are defined.
2556 * Do not paste this example directly into wp-config.php. Instead, have a
2557 * {@link https://api.wordpress.org/secret-key/1.1/salt/ secret key created} just
2558 * for you.
2559 *
2560 * define('AUTH_KEY', ' Xakm<o xQy rw4EMsLKM-?!T+,PFF})H4lzcW57AF0U@N@< >M%G4Yt>f`z]MON');
2561 * define('SECURE_AUTH_KEY', 'LzJ}op]mr|6+![P}Ak:uNdJCJZd>(Hx.-Mh#Tz)pCIU#uGEnfFz|f ;;eU%/U^O~');
2562 * define('LOGGED_IN_KEY', '|i|Ux`9<p-h$aFf(qnT:sDO:D1P^wZ$$/Ra@miTJi9G;ddp_<q}6H1)o|a +&JCM');
2563 * define('NONCE_KEY', '%:R{[P|,s.KuMltH5}cI;/k<Gx~j!f0I)m_sIyu+&NJZ)-iO>z7X>QYR0Z_XnZ@|');
2564 * define('AUTH_SALT', 'eZyT)-Naw]F8CwA*VaW#q*|.)g@o}||wf~@C-YSt}(dh_r6EbI#A,y|nU2{B#JBW');
2565 * define('SECURE_AUTH_SALT', '!=oLUTXh,QW=H `}`L|9/^4-3 STz},T(w}W<I`.JjPi)<Bmf1v,HpGe}T1:Xt7n');
2566 * define('LOGGED_IN_SALT', '+XSqHc;@Q*K_b|Z?NC[3H!!EONbh.n<+=uKR:>*c(u`g~EJBf#8u#R{mUEZrozmm');
2567 * define('NONCE_SALT', 'h`GXHhD>SLWVfg1(1(N{;.V!MoE(SfbA_ksP@&`+AycHcAV$+?@3q+rxV{%^VyKT');
2568 *
2569 * Salting passwords helps against tools which has stored hashed values of
2570 * common dictionary strings. The added values makes it harder to crack.
2571 *
2572 * @since 2.5.0
2573 *
2574 * @link https://api.wordpress.org/secret-key/1.1/salt/ Create secrets for wp-config.php
2575 *
2576 * @param string $scheme Authentication scheme (auth, secure_auth, logged_in, nonce).
2577 * @return string Salt value
2578 */
2579 function wp_salt( $scheme = 'auth' ) {
2580 static $cached_salts = array();
2581 if ( isset( $cached_salts[ $scheme ] ) ) {
2582 /**
2583 * Filters the WordPress salt.
2584 *
2585 * @since 2.5.0
2586 *
2587 * @param string $cached_salt Cached salt for the given scheme.
2588 * @param string $scheme Authentication scheme. Values include 'auth',
2589 * 'secure_auth', 'logged_in', and 'nonce'.
2590 */
2591 return apply_filters( 'salt', $cached_salts[ $scheme ], $scheme );
2592 }
2593
2594 static $duplicated_keys;
2595 if ( null === $duplicated_keys ) {
2596 $duplicated_keys = array();
2597
2598 foreach ( array( 'AUTH', 'SECURE_AUTH', 'LOGGED_IN', 'NONCE', 'SECRET' ) as $first ) {
2599 foreach ( array( 'KEY', 'SALT' ) as $second ) {
2600 if ( ! defined( "{$first}_{$second}" ) ) {
2601 continue;
2602 }
2603 $value = constant( "{$first}_{$second}" );
2604 $duplicated_keys[ $value ] = isset( $duplicated_keys[ $value ] );
2605 }
2606 }
2607
2608 $duplicated_keys['put your unique phrase here'] = true;
2609
2610 /*
2611 * translators: This string should only be translated if wp-config-sample.php is localized.
2612 * You can check the localized release package or
2613 * https://i18n.svn.wordpress.org/<locale code>/branches/<wp version>/dist/wp-config-sample.php
2614 */
2615 $duplicated_keys[ __( 'put your unique phrase here' ) ] = true;
2616 }
2617
2618 /*
2619 * Determine which options to prime.
2620 *
2621 * If the salt keys are undefined, use a duplicate value or the
2622 * default `put your unique phrase here` value the salt will be
2623 * generated via `wp_generate_password()` and stored as a site
2624 * option. These options will be primed to avoid repeated
2625 * database requests for undefined salts.
2626 */
2627 $options_to_prime = array();
2628 foreach ( array( 'auth', 'secure_auth', 'logged_in', 'nonce' ) as $key ) {
2629 foreach ( array( 'key', 'salt' ) as $second ) {
2630 $const = strtoupper( "{$key}_{$second}" );
2631 if ( ! defined( $const ) || true === $duplicated_keys[ constant( $const ) ] ) {
2632 $options_to_prime[] = "{$key}_{$second}";
2633 }
2634 }
2635 }
2636
2637 if ( ! empty( $options_to_prime ) ) {
2638 /*
2639 * Also prime `secret_key` used for undefined salting schemes.
2640 *
2641 * If the scheme is unknown, the default value for `secret_key` will be
2642 * used too for the salt. This should rarely happen, so the option is only
2643 * primed if other salts are undefined.
2644 *
2645 * At this point of execution it is known that a database call will be made
2646 * to prime salts, so the `secret_key` option can be primed regardless of the
2647 * constants status.
2648 */
2649 $options_to_prime[] = 'secret_key';
2650 wp_prime_site_option_caches( $options_to_prime );
2651 }
2652
2653 $values = array(
2654 'key' => '',
2655 'salt' => '',
2656 );
2657 if ( defined( 'SECRET_KEY' ) && SECRET_KEY && empty( $duplicated_keys[ SECRET_KEY ] ) ) {
2658 $values['key'] = SECRET_KEY;
2659 }
2660 if ( 'auth' === $scheme && defined( 'SECRET_SALT' ) && SECRET_SALT && empty( $duplicated_keys[ SECRET_SALT ] ) ) {
2661 $values['salt'] = SECRET_SALT;
2662 }
2663
2664 if ( in_array( $scheme, array( 'auth', 'secure_auth', 'logged_in', 'nonce' ), true ) ) {
2665 foreach ( array( 'key', 'salt' ) as $type ) {
2666 $const = strtoupper( "{$scheme}_{$type}" );
2667 if ( defined( $const ) && constant( $const ) && empty( $duplicated_keys[ constant( $const ) ] ) ) {
2668 $values[ $type ] = constant( $const );
2669 } elseif ( ! $values[ $type ] ) {
2670 $values[ $type ] = get_site_option( "{$scheme}_{$type}" );
2671 if ( ! $values[ $type ] ) {
2672 $values[ $type ] = wp_generate_password( 64, true, true );
2673 update_site_option( "{$scheme}_{$type}", $values[ $type ] );
2674 }
2675 }
2676 }
2677 } else {
2678 if ( ! $values['key'] ) {
2679 $values['key'] = get_site_option( 'secret_key' );
2680 if ( ! $values['key'] ) {
2681 $values['key'] = wp_generate_password( 64, true, true );
2682 update_site_option( 'secret_key', $values['key'] );
2683 }
2684 }
2685 $values['salt'] = hash_hmac( 'md5', $scheme, $values['key'] );
2686 }
2687
2688 $cached_salts[ $scheme ] = $values['key'] . $values['salt'];
2689
2690 /** This filter is documented in wp-includes/pluggable.php */
2691 return apply_filters( 'salt', $cached_salts[ $scheme ], $scheme );
2692 }
2693endif;
2694
2695if ( ! function_exists( 'wp_hash' ) ) :
2696 /**
2697 * Gets the hash of the given string.
2698 *
2699 * The default algorithm is md5 but can be changed to any algorithm supported by
2700 * `hash_hmac()`. Use the `hash_hmac_algos()` function to check the supported
2701 * algorithms.
2702 *
2703 * @since 2.0.3
2704 * @since 6.8.0 The `$algo` parameter was added.
2705 *
2706 * @throws InvalidArgumentException if the hashing algorithm is not supported.
2707 *
2708 * @param string $data Plain text to hash.
2709 * @param string $scheme Authentication scheme (auth, secure_auth, logged_in, nonce).
2710 * @param string $algo Hashing algorithm to use. Default: 'md5'.
2711 * @return string Hash of $data.
2712 */
2713 function wp_hash( $data, $scheme = 'auth', $algo = 'md5' ) {
2714 $salt = wp_salt( $scheme );
2715
2716 // Ensure the algorithm is supported by the hash_hmac function.
2717 if ( ! in_array( $algo, hash_hmac_algos(), true ) ) {
2718 throw new InvalidArgumentException(
2719 sprintf(
2720 /* translators: 1: Name of a cryptographic hash algorithm. 2: List of supported algorithms. */
2721 __( 'Unsupported hashing algorithm: %1$s. Supported algorithms are: %2$s' ),
2722 $algo,
2723 implode( ', ', hash_hmac_algos() )
2724 )
2725 );
2726 }
2727
2728 return hash_hmac( $algo, $data, $salt );
2729 }
2730endif;
2731
2732if ( ! function_exists( 'wp_hash_password' ) ) :
2733 /**
2734 * Creates a hash of a plain text password.
2735 *
2736 * For integration with other applications, this function can be overwritten to
2737 * instead use the other package password hashing algorithm.
2738 *
2739 * @since 2.5.0
2740 * @since 6.8.0 The password is now hashed using bcrypt by default instead of phpass.
2741 *
2742 * @global PasswordHash $wp_hasher phpass object.
2743 *
2744 * @param string $password Plain text user password to hash.
2745 * @return string The hash string of the password.
2746 */
2747 function wp_hash_password(
2748 #[\SensitiveParameter]
2749 $password
2750 ) {
2751 global $wp_hasher;
2752
2753 if ( ! empty( $wp_hasher ) ) {
2754 return $wp_hasher->HashPassword( trim( $password ) );
2755 }
2756
2757 if ( strlen( $password ) > 4096 ) {
2758 return '*';
2759 }
2760
2761 /**
2762 * Filters the hashing algorithm to use in the password_hash() and password_needs_rehash() functions.
2763 *
2764 * The default is the value of the `PASSWORD_BCRYPT` constant which means bcrypt is used.
2765 *
2766 * **Important:** The only password hashing algorithm that is guaranteed to be available across PHP
2767 * installations is bcrypt. If you use any other algorithm you must make sure that it is available on
2768 * the server. The `password_algos()` function can be used to check which hashing algorithms are available.
2769 *
2770 * The hashing options can be controlled via the {@see 'wp_hash_password_options'} filter.
2771 *
2772 * Other available constants include:
2773 *
2774 * - `PASSWORD_ARGON2I`
2775 * - `PASSWORD_ARGON2ID`
2776 * - `PASSWORD_DEFAULT`
2777 *
2778 * The values of the algorithm constants are strings in PHP 7.4+ and integers in PHP 7.3 and earlier.
2779 *
2780 * @since 6.8.0
2781 *
2782 * @param string|int $algorithm The hashing algorithm. Default is the value of the `PASSWORD_BCRYPT` constant.
2783 */
2784 $algorithm = apply_filters( 'wp_hash_password_algorithm', PASSWORD_BCRYPT );
2785
2786 /**
2787 * Filters the options passed to the password_hash() and password_needs_rehash() functions.
2788 *
2789 * The default hashing algorithm is bcrypt, but this can be changed via the {@see 'wp_hash_password_algorithm'}
2790 * filter. You must ensure that the options are appropriate for the algorithm in use.
2791 *
2792 * The values of the algorithm constants are strings in PHP 7.4+ and integers in PHP 7.3 and earlier.
2793 *
2794 * @since 6.8.0
2795 *
2796 * @param array $options Array of options to pass to the password hashing functions.
2797 * By default this is an empty array which means the default
2798 * options will be used.
2799 * @param string|int $algorithm The hashing algorithm in use.
2800 */
2801 $options = apply_filters( 'wp_hash_password_options', array(), $algorithm );
2802
2803 // Algorithms other than bcrypt don't need to use pre-hashing.
2804 if ( PASSWORD_BCRYPT !== $algorithm ) {
2805 return password_hash( $password, $algorithm, $options );
2806 }
2807
2808 // Use SHA-384 to retain entropy from a password that's longer than 72 bytes, and a `wp-sha384` key for domain separation.
2809 $password_to_hash = base64_encode( hash_hmac( 'sha384', trim( $password ), 'wp-sha384', true ) );
2810
2811 // Add a prefix to facilitate distinguishing vanilla bcrypt hashes.
2812 return '$wp' . password_hash( $password_to_hash, $algorithm, $options );
2813 }
2814endif;
2815
2816if ( ! function_exists( 'wp_check_password' ) ) :
2817 /**
2818 * Checks a plaintext password against a hashed password.
2819 *
2820 * Note that this function may be used to check a value that is not a user password.
2821 * A plugin may use this function to check a password of a different type, and there
2822 * may not always be a user ID associated with the password.
2823 *
2824 * For integration with other applications, this function can be overwritten to
2825 * instead use the other package password hashing algorithm.
2826 *
2827 * @since 2.5.0
2828 * @since 6.8.0 Passwords in WordPress are now hashed with bcrypt by default. A
2829 * password that wasn't hashed with bcrypt will be checked with phpass.
2830 *
2831 * @global PasswordHash $wp_hasher phpass object. Used as a fallback for verifying
2832 * passwords that were hashed with phpass.
2833 *
2834 * @param string $password Plaintext password.
2835 * @param string $hash Hash of the password to check against.
2836 * @param string|int $user_id Optional. ID of a user associated with the password.
2837 * @return bool False, if the $password does not match the hashed password.
2838 */
2839 function wp_check_password(
2840 #[\SensitiveParameter]
2841 $password,
2842 $hash,
2843 $user_id = ''
2844 ) {
2845 global $wp_hasher;
2846
2847 if ( strlen( $hash ) <= 32 ) {
2848 // Check the hash using md5 regardless of the current hashing mechanism.
2849 $check = hash_equals( $hash, md5( $password ) );
2850 } elseif ( ! empty( $wp_hasher ) ) {
2851 // Check the password using the overridden hasher.
2852 $check = $wp_hasher->CheckPassword( $password, $hash );
2853 } elseif ( strlen( $password ) > 4096 ) {
2854 // Passwords longer than 4096 characters are not supported.
2855 $check = false;
2856 } elseif ( str_starts_with( $hash, '$wp' ) ) {
2857 // Check the password using the current prefixed hash.
2858 $password_to_verify = base64_encode( hash_hmac( 'sha384', $password, 'wp-sha384', true ) );
2859 $check = password_verify( $password_to_verify, substr( $hash, 3 ) );
2860 } elseif ( str_starts_with( $hash, '$P$' ) ) {
2861 // Check the password using phpass.
2862 require_once ABSPATH . WPINC . '/class-phpass.php';
2863 $check = ( new PasswordHash( 8, true ) )->CheckPassword( $password, $hash );
2864 } else {
2865 // Check the password using compat support for any non-prefixed hash.
2866 $check = password_verify( $password, $hash );
2867 }
2868
2869 /**
2870 * Filters whether the plaintext password matches the hashed password.
2871 *
2872 * @since 2.5.0
2873 * @since 6.8.0 Passwords are now hashed with bcrypt by default.
2874 * Old passwords may still be hashed with phpass or md5.
2875 *
2876 * @param bool $check Whether the passwords match.
2877 * @param string $password The plaintext password.
2878 * @param string $hash The hashed password.
2879 * @param string|int $user_id Optional ID of a user associated with the password.
2880 * Can be empty.
2881 */
2882 return apply_filters( 'check_password', $check, $password, $hash, $user_id );
2883 }
2884endif;
2885
2886if ( ! function_exists( 'wp_password_needs_rehash' ) ) :
2887 /**
2888 * Checks whether a password hash needs to be rehashed.
2889 *
2890 * Passwords are hashed with bcrypt using the default cost. A password hashed in a prior version
2891 * of WordPress may still be hashed with phpass and will need to be rehashed. If the default cost
2892 * or algorithm is changed in PHP or WordPress then a password hashed in a previous version will
2893 * need to be rehashed.
2894 *
2895 * Note that, just like wp_check_password(), this function may be used to check a value that is
2896 * not a user password. A plugin may use this function to check a password of a different type,
2897 * and there may not always be a user ID associated with the password.
2898 *
2899 * @since 6.8.0
2900 *
2901 * @global PasswordHash $wp_hasher phpass object.
2902 *
2903 * @param string $hash Hash of a password to check.
2904 * @param string|int $user_id Optional. ID of a user associated with the password.
2905 * @return bool Whether the hash needs to be rehashed.
2906 */
2907 function wp_password_needs_rehash( $hash, $user_id = '' ) {
2908 global $wp_hasher;
2909
2910 if ( ! empty( $wp_hasher ) ) {
2911 return false;
2912 }
2913
2914 /** This filter is documented in wp-includes/pluggable.php */
2915 $algorithm = apply_filters( 'wp_hash_password_algorithm', PASSWORD_BCRYPT );
2916
2917 /** This filter is documented in wp-includes/pluggable.php */
2918 $options = apply_filters( 'wp_hash_password_options', array(), $algorithm );
2919
2920 $prefixed = str_starts_with( $hash, '$wp' );
2921
2922 if ( ( PASSWORD_BCRYPT === $algorithm ) && ! $prefixed ) {
2923 // If bcrypt is in use and the hash is not prefixed then it needs to be rehashed.
2924 $needs_rehash = true;
2925 } else {
2926 // Otherwise check the hash minus its prefix if necessary.
2927 $hash_to_check = $prefixed ? substr( $hash, 3 ) : $hash;
2928 $needs_rehash = password_needs_rehash( $hash_to_check, $algorithm, $options );
2929 }
2930
2931 /**
2932 * Filters whether the password hash needs to be rehashed.
2933 *
2934 * @since 6.8.0
2935 *
2936 * @param bool $needs_rehash Whether the password hash needs to be rehashed.
2937 * @param string $hash The password hash.
2938 * @param string|int $user_id Optional. ID of a user associated with the password.
2939 */
2940 return apply_filters( 'password_needs_rehash', $needs_rehash, $hash, $user_id );
2941 }
2942endif;
2943
2944if ( ! function_exists( 'wp_generate_password' ) ) :
2945 /**
2946 * Generates a random password drawn from the defined set of characters.
2947 *
2948 * Uses wp_rand() to create passwords with far less predictability
2949 * than similar native PHP functions like `rand()` or `mt_rand()`.
2950 *
2951 * @since 2.5.0
2952 *
2953 * @param int $length Optional. The length of password to generate. Default 12.
2954 * @param bool $special_chars Optional. Whether to include standard special characters.
2955 * Default true.
2956 * @param bool $extra_special_chars Optional. Whether to include other special characters.
2957 * Used when generating secret keys and salts. Default false.
2958 * @return string The random password.
2959 */
2960 function wp_generate_password( $length = 12, $special_chars = true, $extra_special_chars = false ) {
2961 $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
2962 if ( $special_chars ) {
2963 $chars .= '!@#$%^&*()';
2964 }
2965 if ( $extra_special_chars ) {
2966 $chars .= '-_ []{}<>~`+=,.;:/?|';
2967 }
2968
2969 $password = '';
2970 for ( $i = 0; $i < $length; $i++ ) {
2971 $password .= substr( $chars, wp_rand( 0, strlen( $chars ) - 1 ), 1 );
2972 }
2973
2974 /**
2975 * Filters the randomly-generated password.
2976 *
2977 * @since 3.0.0
2978 * @since 5.3.0 Added the `$length`, `$special_chars`, and `$extra_special_chars` parameters.
2979 *
2980 * @param string $password The generated password.
2981 * @param int $length The length of password to generate.
2982 * @param bool $special_chars Whether to include standard special characters.
2983 * @param bool $extra_special_chars Whether to include other special characters.
2984 */
2985 return apply_filters( 'random_password', $password, $length, $special_chars, $extra_special_chars );
2986 }
2987endif;
2988
2989if ( ! function_exists( 'wp_rand' ) ) :
2990 /**
2991 * Generates a random non-negative number.
2992 *
2993 * @since 2.6.2
2994 * @since 4.4.0 Uses PHP7 random_int() or the random_compat library if available.
2995 * @since 6.1.0 Returns zero instead of a random number if both `$min` and `$max` are zero.
2996 *
2997 * @global string $rnd_value
2998 *
2999 * @param int $min Optional. Lower limit for the generated number.
3000 * Accepts positive integers or zero. Defaults to 0.
3001 * @param int $max Optional. Upper limit for the generated number.
3002 * Accepts positive integers. Defaults to 4294967295.
3003 * @return int A random non-negative number between min and max.
3004 */
3005 function wp_rand( $min = null, $max = null ) {
3006 global $rnd_value;
3007
3008 /*
3009 * Some misconfigured 32-bit environments (Entropy PHP, for example)
3010 * truncate integers larger than PHP_INT_MAX to PHP_INT_MAX rather than overflowing them to floats.
3011 */
3012 $max_random_number = 3000000000 === 2147483647 ? (float) '4294967295' : 4294967295; // 4294967295 = 0xffffffff
3013
3014 if ( null === $min ) {
3015 $min = 0;
3016 }
3017
3018 if ( null === $max ) {
3019 $max = $max_random_number;
3020 }
3021
3022 // We only handle ints, floats are truncated to their integer value.
3023 $min = (int) $min;
3024 $max = (int) $max;
3025
3026 // Use PHP's CSPRNG, or a compatible method.
3027 static $use_random_int_functionality = true;
3028 if ( $use_random_int_functionality ) {
3029 try {
3030 // wp_rand() can accept arguments in either order, PHP cannot.
3031 $_max = max( $min, $max );
3032 $_min = min( $min, $max );
3033 $val = random_int( $_min, $_max );
3034 if ( false !== $val ) {
3035 return absint( $val );
3036 } else {
3037 $use_random_int_functionality = false;
3038 }
3039 } catch ( Error $e ) {
3040 $use_random_int_functionality = false;
3041 } catch ( Exception $e ) {
3042 $use_random_int_functionality = false;
3043 }
3044 }
3045
3046 /*
3047 * Reset $rnd_value after 14 uses.
3048 * 32 (md5) + 40 (sha1) + 40 (sha1) / 8 = 14 random numbers from $rnd_value.
3049 */
3050 if ( strlen( $rnd_value ) < 8 ) {
3051 if ( defined( 'WP_SETUP_CONFIG' ) ) {
3052 static $seed = '';
3053 } else {
3054 $seed = get_transient( 'random_seed' );
3055 }
3056 $rnd_value = md5( uniqid( microtime() . mt_rand(), true ) . $seed );
3057 $rnd_value .= sha1( $rnd_value );
3058 $rnd_value .= sha1( $rnd_value . $seed );
3059 $seed = md5( $seed . $rnd_value );
3060 if ( ! defined( 'WP_SETUP_CONFIG' ) && ! defined( 'WP_INSTALLING' ) ) {
3061 set_transient( 'random_seed', $seed );
3062 }
3063 }
3064
3065 // Take the first 8 digits for our value.
3066 $value = substr( $rnd_value, 0, 8 );
3067
3068 // Strip the first eight, leaving the remainder for the next call to wp_rand().
3069 $rnd_value = substr( $rnd_value, 8 );
3070
3071 $value = abs( hexdec( $value ) );
3072
3073 // Reduce the value to be within the min - max range.
3074 $value = $min + ( $max - $min + 1 ) * $value / ( $max_random_number + 1 );
3075
3076 return abs( (int) $value );
3077 }
3078endif;
3079
3080if ( ! function_exists( 'wp_set_password' ) ) :
3081 /**
3082 * Updates the user's password with a new hashed one.
3083 *
3084 * For integration with other applications, this function can be overwritten to
3085 * instead use the other package password checking algorithm.
3086 *
3087 * Please note: This function should be used sparingly and is really only meant for single-time
3088 * application. Leveraging this improperly in a plugin or theme could result in an endless loop
3089 * of password resets if precautions are not taken to ensure it does not execute on every page load.
3090 *
3091 * @since 2.5.0
3092 * @since 6.8.0 The password is now hashed using bcrypt by default instead of phpass.
3093 *
3094 * @global wpdb $wpdb WordPress database abstraction object.
3095 *
3096 * @param string $password The plaintext new user password.
3097 * @param int $user_id User ID.
3098 */
3099 function wp_set_password(
3100 #[\SensitiveParameter]
3101 $password,
3102 $user_id
3103 ) {
3104 global $wpdb;
3105
3106 $old_user_data = get_userdata( $user_id );
3107
3108 $hash = wp_hash_password( $password );
3109 $wpdb->update(
3110 $wpdb->users,
3111 array(
3112 'user_pass' => $hash,
3113 'user_activation_key' => '',
3114 ),
3115 array( 'ID' => $user_id )
3116 );
3117
3118 clean_user_cache( $user_id );
3119
3120 /**
3121 * Fires after the user password is set.
3122 *
3123 * @since 6.2.0
3124 * @since 6.7.0 The `$old_user_data` parameter was added.
3125 *
3126 * @param string $password The plaintext password just set.
3127 * @param int $user_id The ID of the user whose password was just set.
3128 * @param WP_User $old_user_data Object containing user's data prior to update.
3129 */
3130 do_action( 'wp_set_password', $password, $user_id, $old_user_data );
3131 }
3132endif;
3133
3134if ( ! function_exists( 'get_avatar' ) ) :
3135 /**
3136 * Retrieves the avatar `<img>` tag for a user, email address, MD5 hash, comment, or post.
3137 *
3138 * @since 2.5.0
3139 * @since 4.2.0 Added the optional `$args` parameter.
3140 * @since 5.5.0 Added the `loading` argument.
3141 * @since 6.1.0 Added the `decoding` argument.
3142 * @since 6.3.0 Added the `fetchpriority` argument.
3143 *
3144 * @param mixed $id_or_email The avatar to retrieve. Accepts a user ID, Gravatar MD5 hash,
3145 * user email, WP_User object, WP_Post object, or WP_Comment object.
3146 * @param int $size Optional. Height and width of the avatar in pixels. Default 96.
3147 * @param string $default_value URL for the default image or a default type. Accepts:
3148 * - '404' (return a 404 instead of a default image)
3149 * - 'retro' (a 8-bit arcade-style pixelated face)
3150 * - 'robohash' (a robot)
3151 * - 'monsterid' (a monster)
3152 * - 'wavatar' (a cartoon face)
3153 * - 'identicon' (the "quilt", a geometric pattern)
3154 * - 'initials' (initials based avatar with background color)
3155 * - 'color' (generated background color)
3156 * - 'mystery', 'mm', or 'mysteryman' (The Oyster Man)
3157 * - 'blank' (transparent GIF)
3158 * - 'gravatar_default' (the Gravatar logo)
3159 * Default is the value of the 'avatar_default' option,
3160 * with a fallback of 'mystery'.
3161 * @param string $alt Optional. Alternative text to use in the avatar image tag.
3162 * Default empty.
3163 * @param array $args {
3164 * Optional. Extra arguments to retrieve the avatar.
3165 *
3166 * @type int $height Display height of the avatar in pixels. Defaults to $size.
3167 * @type int $width Display width of the avatar in pixels. Defaults to $size.
3168 * @type bool $force_default Whether to always show the default image, never the Gravatar.
3169 * Default false.
3170 * @type string $rating What rating to display avatars up to. Accepts:
3171 * - 'G' (suitable for all audiences)
3172 * - 'PG' (possibly offensive, usually for audiences 13 and above)
3173 * - 'R' (intended for adult audiences above 17)
3174 * - 'X' (even more mature than above)
3175 * Default is the value of the 'avatar_rating' option.
3176 * @type string $scheme URL scheme to use. See set_url_scheme() for accepted values.
3177 * Default null.
3178 * @type array|string $class Array or string of additional classes to add to the img element.
3179 * Default null.
3180 * @type bool $force_display Whether to always show the avatar - ignores the show_avatars option.
3181 * Default false.
3182 * @type string $loading Value for the `loading` attribute.
3183 * Default null.
3184 * @type string $fetchpriority Value for the `fetchpriority` attribute.
3185 * Default null.
3186 * @type string $decoding Value for the `decoding` attribute.
3187 * Default null.
3188 * @type string $extra_attr HTML attributes to insert in the IMG element. Is not sanitized.
3189 * Default empty.
3190 * }
3191 * @return string|false `<img>` tag for the user's avatar. False on failure.
3192 */
3193 function get_avatar( $id_or_email, $size = 96, $default_value = '', $alt = '', $args = null ) {
3194 $defaults = array(
3195 // get_avatar_data() args.
3196 'size' => 96,
3197 'height' => null,
3198 'width' => null,
3199 'default' => get_option( 'avatar_default', 'mystery' ),
3200 'force_default' => false,
3201 'rating' => get_option( 'avatar_rating' ),
3202 'scheme' => null,
3203 'alt' => '',
3204 'class' => null,
3205 'force_display' => false,
3206 'loading' => null,
3207 'fetchpriority' => null,
3208 'decoding' => null,
3209 'extra_attr' => '',
3210 );
3211
3212 if ( empty( $args ) ) {
3213 $args = array();
3214 }
3215
3216 $args['size'] = (int) $size;
3217 $args['default'] = $default_value;
3218 $args['alt'] = $alt;
3219
3220 $args = wp_parse_args( $args, $defaults );
3221
3222 if ( empty( $args['height'] ) ) {
3223 $args['height'] = $args['size'];
3224 }
3225 if ( empty( $args['width'] ) ) {
3226 $args['width'] = $args['size'];
3227 }
3228
3229 // Update args with loading optimized attributes.
3230 $loading_optimization_attr = wp_get_loading_optimization_attributes( 'img', $args, 'get_avatar' );
3231
3232 $args = array_merge( $args, $loading_optimization_attr );
3233
3234 if ( is_object( $id_or_email ) && isset( $id_or_email->comment_ID ) ) {
3235 $id_or_email = get_comment( $id_or_email );
3236 }
3237
3238 /**
3239 * Allows the HTML for a user's avatar to be returned early.
3240 *
3241 * Returning a non-null value will effectively short-circuit get_avatar(), passing
3242 * the value through the {@see 'get_avatar'} filter and returning early.
3243 *
3244 * @since 4.2.0
3245 *
3246 * @param string|null $avatar HTML for the user's avatar. Default null.
3247 * @param mixed $id_or_email The avatar to retrieve. Accepts a user ID, Gravatar MD5 hash,
3248 * user email, WP_User object, WP_Post object, or WP_Comment object.
3249 * @param array $args Arguments passed to get_avatar_url(), after processing.
3250 */
3251 $avatar = apply_filters( 'pre_get_avatar', null, $id_or_email, $args );
3252
3253 if ( ! is_null( $avatar ) ) {
3254 /** This filter is documented in wp-includes/pluggable.php */
3255 return apply_filters( 'get_avatar', $avatar, $id_or_email, $args['size'], $args['default'], $args['alt'], $args );
3256 }
3257
3258 if ( ! $args['force_display'] && ! get_option( 'show_avatars' ) ) {
3259 return false;
3260 }
3261
3262 $url2x = get_avatar_url( $id_or_email, array_merge( $args, array( 'size' => $args['size'] * 2 ) ) );
3263
3264 $args = get_avatar_data( $id_or_email, $args );
3265
3266 $url = $args['url'];
3267
3268 if ( ! $url || is_wp_error( $url ) ) {
3269 return false;
3270 }
3271
3272 $class = array( 'avatar', 'avatar-' . (int) $args['size'], 'photo' );
3273
3274 if ( ! $args['found_avatar'] || $args['force_default'] ) {
3275 $class[] = 'avatar-default';
3276 }
3277
3278 if ( $args['class'] ) {
3279 if ( is_array( $args['class'] ) ) {
3280 $class = array_merge( $class, $args['class'] );
3281 } else {
3282 $class[] = $args['class'];
3283 }
3284 }
3285
3286 // Add `loading`, `fetchpriority`, and `decoding` attributes.
3287 $extra_attr = $args['extra_attr'];
3288
3289 if ( in_array( $args['loading'], array( 'lazy', 'eager' ), true )
3290 && ! preg_match( '/\bloading\s*=/', $extra_attr )
3291 ) {
3292 if ( ! empty( $extra_attr ) ) {
3293 $extra_attr .= ' ';
3294 }
3295
3296 $extra_attr .= "loading='{$args['loading']}'";
3297 }
3298
3299 if ( in_array( $args['fetchpriority'], array( 'high', 'low', 'auto' ), true )
3300 && ! preg_match( '/\bfetchpriority\s*=/', $extra_attr )
3301 ) {
3302 if ( ! empty( $extra_attr ) ) {
3303 $extra_attr .= ' ';
3304 }
3305
3306 $extra_attr .= "fetchpriority='{$args['fetchpriority']}'";
3307 }
3308
3309 if ( in_array( $args['decoding'], array( 'async', 'sync', 'auto' ), true )
3310 && ! preg_match( '/\bdecoding\s*=/', $extra_attr )
3311 ) {
3312 if ( ! empty( $extra_attr ) ) {
3313 $extra_attr .= ' ';
3314 }
3315
3316 $extra_attr .= "decoding='{$args['decoding']}'";
3317 }
3318
3319 $avatar = sprintf(
3320 "<img alt='%s' src='%s' srcset='%s' class='%s' height='%d' width='%d' %s/>",
3321 esc_attr( $args['alt'] ),
3322 esc_url( $url ),
3323 esc_url( $url2x ) . ' 2x',
3324 esc_attr( implode( ' ', $class ) ),
3325 (int) $args['height'],
3326 (int) $args['width'],
3327 $extra_attr
3328 );
3329
3330 /**
3331 * Filters the HTML for a user's avatar.
3332 *
3333 * @since 2.5.0
3334 * @since 4.2.0 Added the `$args` parameter.
3335 *
3336 * @param string $avatar HTML for the user's avatar.
3337 * @param mixed $id_or_email The avatar to retrieve. Accepts a user ID, Gravatar MD5 hash,
3338 * user email, WP_User object, WP_Post object, or WP_Comment object.
3339 * @param int $size Height and width of the avatar in pixels.
3340 * @param string $default_value URL for the default image or a default type. Accepts:
3341 * - '404' (return a 404 instead of a default image)
3342 * - 'retro' (a 8-bit arcade-style pixelated face)
3343 * - 'robohash' (a robot)
3344 * - 'monsterid' (a monster)
3345 * - 'wavatar' (a cartoon face)
3346 * - 'identicon' (the "quilt", a geometric pattern)
3347 * - 'mystery', 'mm', or 'mysteryman' (The Oyster Man)
3348 * - 'blank' (transparent GIF)
3349 * - 'gravatar_default' (the Gravatar logo)
3350 * @param string $alt Alternative text to use in the avatar image tag.
3351 * @param array $args Arguments passed to get_avatar_data(), after processing.
3352 */
3353 return apply_filters( 'get_avatar', $avatar, $id_or_email, $args['size'], $args['default'], $args['alt'], $args );
3354 }
3355endif;
3356
3357if ( ! function_exists( 'wp_text_diff' ) ) :
3358 /**
3359 * Displays a human readable HTML representation of the difference between two strings.
3360 *
3361 * The Diff is available for getting the changes between versions. The output is
3362 * HTML, so the primary use is for displaying the changes. If the two strings
3363 * are equivalent, then an empty string will be returned.
3364 *
3365 * @since 2.6.0
3366 *
3367 * @see wp_parse_args() Used to change defaults to user defined settings.
3368 * @uses Text_Diff
3369 * @uses WP_Text_Diff_Renderer_Table
3370 *
3371 * @param string $left_string "old" (left) version of string.
3372 * @param string $right_string "new" (right) version of string.
3373 * @param string|array $args {
3374 * Associative array of options to pass to WP_Text_Diff_Renderer_Table().
3375 *
3376 * @type string $title Titles the diff in a manner compatible
3377 * with the output. Default empty.
3378 * @type string $title_left Change the HTML to the left of the title.
3379 * Default empty.
3380 * @type string $title_right Change the HTML to the right of the title.
3381 * Default empty.
3382 * @type bool $show_split_view True for split view (two columns), false for
3383 * un-split view (single column). Default true.
3384 * }
3385 * @return string Empty string if strings are equivalent or HTML with differences.
3386 */
3387 function wp_text_diff( $left_string, $right_string, $args = null ) {
3388 $defaults = array(
3389 'title' => '',
3390 'title_left' => '',
3391 'title_right' => '',
3392 'show_split_view' => true,
3393 );
3394 $args = wp_parse_args( $args, $defaults );
3395
3396 if ( ! class_exists( 'WP_Text_Diff_Renderer_Table', false ) ) {
3397 require ABSPATH . WPINC . '/wp-diff.php';
3398 }
3399
3400 $left_string = normalize_whitespace( $left_string );
3401 $right_string = normalize_whitespace( $right_string );
3402
3403 $left_lines = explode( "\n", $left_string );
3404 $right_lines = explode( "\n", $right_string );
3405 $text_diff = new Text_Diff( $left_lines, $right_lines );
3406 $renderer = new WP_Text_Diff_Renderer_Table( $args );
3407 $diff = $renderer->render( $text_diff );
3408
3409 if ( ! $diff ) {
3410 return '';
3411 }
3412
3413 $is_split_view = ! empty( $args['show_split_view'] );
3414 $is_split_view_class = $is_split_view ? ' is-split-view' : '';
3415
3416 $r = "<table class='diff$is_split_view_class'>\n";
3417
3418 if ( $args['title'] ) {
3419 $r .= "<caption class='diff-title'>$args[title]</caption>\n";
3420 }
3421
3422 if ( $args['title_left'] || $args['title_right'] ) {
3423 $r .= '<thead>';
3424 }
3425
3426 if ( $args['title_left'] || $args['title_right'] ) {
3427 $th_or_td_left = empty( $args['title_left'] ) ? 'td' : 'th';
3428 $th_or_td_right = empty( $args['title_right'] ) ? 'td' : 'th';
3429
3430 $r .= "<tr class='diff-sub-title'>\n";
3431 $r .= "\t<$th_or_td_left>$args[title_left]</$th_or_td_left>\n";
3432 if ( $is_split_view ) {
3433 $r .= "\t<$th_or_td_right>$args[title_right]</$th_or_td_right>\n";
3434 }
3435 $r .= "</tr>\n";
3436 }
3437
3438 if ( $args['title_left'] || $args['title_right'] ) {
3439 $r .= "</thead>\n";
3440 }
3441
3442 $r .= "<tbody>\n$diff\n</tbody>\n";
3443 $r .= '</table>';
3444
3445 return $r;
3446 }
3447endif;
3448
Ui Ux Design – Teachers Night Out

Get in Touch

© 2024 Teachers Night Out. All Rights Reserved.