• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Yoast / wordpress-seo / 99ff5821fbe8444b259463501ff00132570d3061

25 Mar 2025 09:23AM UTC coverage: 52.446% (+3.7%) from 48.71%
99ff5821fbe8444b259463501ff00132570d3061

Pull #21958

github

web-flow
Merge d493347a3 into facbdded4
Pull Request #21958: Improve function words list for Farsi

7990 of 14101 branches covered (56.66%)

Branch coverage included in aggregate %.

20 of 20 new or added lines in 1 file covered. (100.0%)

1567 existing lines in 41 files now uncovered.

29816 of 57984 relevant lines covered (51.42%)

41124.2 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.23
/admin/class-yoast-network-admin.php
1
<?php
2
/**
3
 * WPSEO plugin file.
4
 *
5
 * @package WPSEO\Internals
6
 */
7

8
/**
9
 * Multisite utility class for network admin functionality.
10
 */
11
class Yoast_Network_Admin implements WPSEO_WordPress_AJAX_Integration, WPSEO_WordPress_Integration {
12

13
        /**
14
         * Action identifier for updating plugin network options.
15
         *
16
         * @var string
17
         */
18
        public const UPDATE_OPTIONS_ACTION = 'yoast_handle_network_options';
19

20
        /**
21
         * Action identifier for restoring a site.
22
         *
23
         * @var string
24
         */
25
        public const RESTORE_SITE_ACTION = 'yoast_restore_site';
26

27
        /**
28
         * Gets the available sites as choices, e.g. for a dropdown.
29
         *
30
         * @param bool $include_empty Optional. Whether to include an initial placeholder choice.
31
         *                            Default false.
32
         * @param bool $show_title    Optional. Whether to show the title for each site. This requires
33
         *                            switching through the sites, so has performance implications for
34
         *                            sites that do not use a persistent cache.
35
         *                            Default false.
36
         *
37
         * @return array Choices as $site_id => $site_label pairs.
38
         */
39
        public function get_site_choices( $include_empty = false, $show_title = false ) {
2✔
40
                $choices = [];
2✔
41

42
                if ( $include_empty ) {
2✔
43
                        $choices['-'] = __( 'None', 'wordpress-seo' );
1✔
44
                }
45

46
                $criteria = [
2✔
47
                        'deleted'    => 0,
2✔
48
                        'network_id' => get_current_network_id(),
2✔
49
                ];
2✔
50
                $sites    = get_sites( $criteria );
2✔
51

52
                foreach ( $sites as $site ) {
2✔
53
                        $site_name = $site->domain . $site->path;
2✔
54
                        if ( $show_title ) {
2✔
55
                                $site_name = $site->blogname . ' (' . $site->domain . $site->path . ')';
1✔
56
                        }
57
                        $choices[ $site->blog_id ] = $site->blog_id . ': ' . $site_name;
2✔
58

59
                        $site_states = $this->get_site_states( $site );
2✔
60
                        if ( ! empty( $site_states ) ) {
2✔
61
                                $choices[ $site->blog_id ] .= ' [' . implode( ', ', $site_states ) . ']';
2✔
62
                        }
63
                }
64

65
                return $choices;
2✔
66
        }
67

68
        /**
69
         * Gets the states of a site.
70
         *
71
         * @param WP_Site $site Site object.
72
         *
73
         * @return array Array of $state_slug => $state_label pairs.
74
         */
75
        public function get_site_states( $site ) {
1✔
76
                $available_states = [
1✔
77
                        'public'   => __( 'public', 'wordpress-seo' ),
1✔
78
                        'archived' => __( 'archived', 'wordpress-seo' ),
1✔
79
                        'mature'   => __( 'mature', 'wordpress-seo' ),
1✔
80
                        'spam'     => __( 'spam', 'wordpress-seo' ),
1✔
81
                        'deleted'  => __( 'deleted', 'wordpress-seo' ),
1✔
82
                ];
1✔
83

84
                $site_states = [];
1✔
85
                foreach ( $available_states as $state_slug => $state_label ) {
1✔
86
                        if ( $site->$state_slug === '1' ) {
1✔
87
                                $site_states[ $state_slug ] = $state_label;
1✔
88
                        }
89
                }
90

91
                return $site_states;
1✔
92
        }
93

94
        /**
95
         * Handles a request to update plugin network options.
96
         *
97
         * This method works similar to how option updates are handled in `wp-admin/options.php` and
98
         * `wp-admin/network/settings.php`.
99
         *
100
         * @return void
101
         */
102
        public function handle_update_options_request() {
2✔
103
                // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification will happen in verify_request below.
104
                if ( ! isset( $_POST['network_option_group'] ) || ! is_string( $_POST['network_option_group'] ) ) {
2✔
105
                        return;
×
106
                }
107

108
                // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification will happen in verify_request below.
109
                $option_group = sanitize_text_field( wp_unslash( $_POST['network_option_group'] ) );
2✔
110

111
                if ( empty( $option_group ) ) {
2✔
112
                        return;
×
113
                }
114

115
                $this->verify_request( "{$option_group}-network-options" );
2✔
116

117
                $whitelist_options = Yoast_Network_Settings_API::get()->get_whitelist_options( $option_group );
2✔
118

119
                if ( empty( $whitelist_options ) ) {
2✔
120
                        add_settings_error( $option_group, 'settings_updated', __( 'You are not allowed to modify unregistered network settings.', 'wordpress-seo' ), 'error' );
2✔
121

122
                        $this->terminate_request();
2✔
123
                        return;
2✔
124
                }
125

126
                // phpcs:disable WordPress.Security.NonceVerification -- Nonce verified via `verify_request()` above.
127
                foreach ( $whitelist_options as $option_name ) {
×
128
                        $value = null;
×
129
                        if ( isset( $_POST[ $option_name ] ) ) {
×
130
                                // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: Adding sanitize_text_field around this will break the saving of settings because it expects a string: https://github.com/Yoast/wordpress-seo/issues/12440.
131
                                $value = wp_unslash( $_POST[ $option_name ] );
×
132
                        }
133

134
                        WPSEO_Options::update_site_option( $option_name, $value );
×
135
                }
136
                // phpcs:enable WordPress.Security.NonceVerification
137

138
                $settings_errors = get_settings_errors();
×
139
                if ( empty( $settings_errors ) ) {
×
140
                        add_settings_error( $option_group, 'settings_updated', __( 'Settings Updated.', 'wordpress-seo' ), 'updated' );
×
141
                }
142

143
                $this->terminate_request();
×
144
        }
145

146
        /**
147
         * Handles a request to restore a site's default settings.
148
         *
149
         * @return void
150
         */
151
        public function handle_restore_site_request() {
2✔
152
                $this->verify_request( 'wpseo-network-restore', 'restore_site_nonce' );
2✔
153

154
                $option_group = 'wpseo_ms';
2✔
155

156
                // phpcs:ignore WordPress.Security.NonceVerification -- Nonce verified via `verify_request()` above.
157
                $site_id = ! empty( $_POST[ $option_group ]['site_id'] ) ? (int) $_POST[ $option_group ]['site_id'] : 0;
2✔
158
                if ( ! $site_id ) {
2✔
159
                        add_settings_error( $option_group, 'settings_updated', __( 'No site has been selected to restore.', 'wordpress-seo' ), 'error' );
2✔
160

161
                        $this->terminate_request();
2✔
162
                        return;
2✔
163
                }
164

165
                $site = get_site( $site_id );
×
166
                if ( ! $site ) {
×
167
                        /* translators: %s expands to the ID of a site within a multisite network. */
168
                        add_settings_error( $option_group, 'settings_updated', sprintf( __( 'Site with ID %d not found.', 'wordpress-seo' ), $site_id ), 'error' );
×
169
                }
170
                else {
171
                        WPSEO_Options::reset_ms_blog( $site_id );
×
172

173
                        /* translators: %s expands to the name of a site within a multisite network. */
174
                        add_settings_error( $option_group, 'settings_updated', sprintf( __( '%s restored to default SEO settings.', 'wordpress-seo' ), esc_html( $site->blogname ) ), 'updated' );
×
175
                }
176

177
                $this->terminate_request();
×
178
        }
179

180
        /**
181
         * Outputs nonce, action and option group fields for a network settings page in the plugin.
182
         *
183
         * @param string $option_group Option group name for the current page.
184
         *
185
         * @return void
186
         */
187
        public function settings_fields( $option_group ) {
2✔
188
                ?>
189
                <input type="hidden" name="network_option_group" value="<?php echo esc_attr( $option_group ); ?>" />
2✔
190
                <input type="hidden" name="action" value="<?php echo esc_attr( self::UPDATE_OPTIONS_ACTION ); ?>" />
2✔
191
                <?php
192
                wp_nonce_field( "$option_group-network-options" );
2✔
193
        }
194

195
        /**
196
         * Enqueues network admin assets.
197
         *
198
         * @return void
199
         */
200
        public function enqueue_assets() {
×
201
                $asset_manager = new WPSEO_Admin_Asset_Manager();
×
202
                $asset_manager->enqueue_script( 'network-admin' );
×
203

UNCOV
204
                $translations = [
×
205
                        /* translators: %s: success message */
206
                        'success_prefix' => __( 'Success: %s', 'wordpress-seo' ),
×
207
                        /* translators: %s: error message */
208
                        'error_prefix'   => __( 'Error: %s', 'wordpress-seo' ),
×
UNCOV
209
                ];
×
210
                $asset_manager->localize_script(
×
211
                        'network-admin',
×
212
                        'wpseoNetworkAdminGlobalL10n',
×
213
                        $translations
×
UNCOV
214
                );
×
215
        }
216

217
        /**
218
         * Hooks in the necessary actions and filters.
219
         *
220
         * @return void
221
         */
222
        public function register_hooks() {
2✔
223

224
                if ( ! $this->meets_requirements() ) {
2✔
225
                        return;
×
226
                }
227

228
                add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
2✔
229

230
                add_action( 'admin_action_' . self::UPDATE_OPTIONS_ACTION, [ $this, 'handle_update_options_request' ] );
2✔
231
                add_action( 'admin_action_' . self::RESTORE_SITE_ACTION, [ $this, 'handle_restore_site_request' ] );
2✔
232
        }
233

234
        /**
235
         * Hooks in the necessary AJAX actions.
236
         *
237
         * @return void
238
         */
239
        public function register_ajax_hooks() {
2✔
240
                add_action( 'wp_ajax_' . self::UPDATE_OPTIONS_ACTION, [ $this, 'handle_update_options_request' ] );
2✔
241
                add_action( 'wp_ajax_' . self::RESTORE_SITE_ACTION, [ $this, 'handle_restore_site_request' ] );
2✔
242
        }
243

244
        /**
245
         * Checks whether the requirements to use this class are met.
246
         *
247
         * @return bool True if requirements are met, false otherwise.
248
         */
249
        public function meets_requirements() {
2✔
250
                return is_multisite() && is_network_admin();
2✔
251
        }
252

253
        /**
254
         * Verifies that the current request is valid.
255
         *
256
         * @param string $action    Nonce action.
257
         * @param string $query_arg Optional. Nonce query argument. Default '_wpnonce'.
258
         *
259
         * @return void
260
         */
261
        public function verify_request( $action, $query_arg = '_wpnonce' ) {
12✔
262
                $has_access = current_user_can( 'wpseo_manage_network_options' );
12✔
263

264
                if ( wp_doing_ajax() ) {
12✔
265
                        check_ajax_referer( $action, $query_arg );
6✔
266

267
                        if ( ! $has_access ) {
4✔
268
                                wp_die( -1, 403 );
2✔
269
                        }
270
                        return;
2✔
271
                }
272

273
                check_admin_referer( $action, $query_arg );
6✔
274

275
                if ( ! $has_access ) {
4✔
276
                        wp_die( esc_html__( 'You are not allowed to perform this action.', 'wordpress-seo' ) );
2✔
277
                }
278
        }
279

280
        /**
281
         * Terminates the current request by either redirecting back or sending an AJAX response.
282
         *
283
         * @return void
284
         */
285
        public function terminate_request() {
4✔
286
                if ( wp_doing_ajax() ) {
4✔
287
                        $settings_errors = get_settings_errors();
2✔
288

289
                        if ( ! empty( $settings_errors ) && $settings_errors[0]['type'] === 'updated' ) {
2✔
290
                                wp_send_json_success( $settings_errors, 200 );
×
291
                        }
292

293
                        wp_send_json_error( $settings_errors, 400 );
2✔
294
                }
295

296
                $this->persist_settings_errors();
2✔
297
                $this->redirect_back( [ 'settings-updated' => 'true' ] );
2✔
298
        }
299

300
        /**
301
         * Persists settings errors.
302
         *
303
         * Settings errors are stored in a transient for 30 seconds so that this transient
304
         * can be retrieved on the next page load.
305
         *
306
         * @return void
307
         */
308
        protected function persist_settings_errors() {
×
309
                /*
310
                 * A regular transient is used here, since it is automatically cleared right after the redirect.
311
                 * A network transient would be cleaner, but would require a lot of copied code from core for
312
                 * just a minor adjustment when displaying settings errors.
313
                 */
314
                set_transient( 'settings_errors', get_settings_errors(), 30 );
×
315
        }
316

317
        /**
318
         * Redirects back to the referer URL, with optional query arguments.
319
         *
320
         * @param array $query_args Optional. Query arguments to add to the redirect URL. Default none.
321
         *
322
         * @return void
323
         */
324
        protected function redirect_back( $query_args = [] ) {
×
325
                $sendback = wp_get_referer();
×
326

327
                if ( ! empty( $query_args ) ) {
×
328
                        $sendback = add_query_arg( $query_args, $sendback );
×
329
                }
330

331
                wp_safe_redirect( $sendback );
×
332
                exit;
×
333
        }
334
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc