PluginProbe
WPSSO Core – Complete Schema Markup and Meta Tags / 22.7.0
WPSSO Core – Complete Schema Markup and Meta Tags v22.7.0
22.7.0 22.6.1 22.6.0 22.5.3 22.5.2 22.5.1 22.4.0 22.3.0 22.2.1 22.2.0 22.1.3 22.1.2 22.1.1 22.1.0 22.0.0 21.13.3 21.13.2 trunk 21.13.1
← All changes | lib/admin.php +2681 -0 22.1.022.7.0 View file →
@@ -1,0 +1,2681 @@
1 +<?php
2 +/*
3 + * License: GPLv3
4 + * License URI: https://www.gnu.org/licenses/gpl.txt
5 + * Copyright 2012-2026 Jean-Sebastien Morisset (https://wpsso.com/)
6 + */
7 +
8 +if ( ! defined( 'ABSPATH' ) ) {
9 +
10 + die( 'These aren\'t the droids you\'re looking for.' );
11 +}
12 +
13 +if ( ! defined( 'WPSSO_PLUGINDIR' ) ) {
14 +
15 + die( 'Do. Or do not. There is no try.' );
16 +}
17 +
18 +if ( ! class_exists( 'SucomPlugin' ) ) {
19 +
20 + require_once WPSSO_PLUGINDIR . 'lib/com/plugin.php';
21 +}
22 +
23 +if ( ! class_exists( 'WpssoAdmin' ) ) {
24 +
25 + class WpssoAdmin {
26 +
27 + protected $p; // Wpsso class object.
28 + protected $menu_id;
29 + protected $menu_name;
30 + protected $menu_lib;
31 + protected $menu_ext; // Lowercase acronyn for plugin or add-on.
32 + protected $menu_metaboxes;
33 + protected $pagehook;
34 + protected $pageref_url;
35 + protected $pageref_title;
36 + protected $toolbar_notice_types = array( 'err', 'warn', 'inf', 'upd' );
37 +
38 + public static $readme = array();
39 +
40 + public $form = null;
41 + public $lang = array();
42 + public $submenu = array();
43 +
44 + /*
45 + * Instantiated by Wpsso->set_objects() when is_admin() is true.
46 + */
47 + public function __construct( &$plugin ) {
48 +
49 + $this->p =& $plugin;
50 +
51 + if ( $this->p->debug->enabled ) {
52 +
53 + $this->p->debug->mark();
54 + }
55 +
56 + $doing_ajax = SucomUtilWP::doing_ajax();
57 +
58 + require_once WPSSO_PLUGINDIR . 'lib/admin-head.php';
59 +
60 + new WpssoAdminHead( $plugin );
61 +
62 + require_once WPSSO_PLUGINDIR . 'lib/admin-dashboard.php';
63 +
64 + new WpssoAdminDashboard( $plugin );
65 +
66 + /*
67 + * The WpssoScript add_iframe_inline_script() method includes jQuery in the thickbox iframe to add the
68 + * iframe_parent arguments when the Install or Update button is clicked.
69 + *
70 + * These class properties are used by both the WpssoAdmin plugin_complete_actions() and
71 + * plugin_complete_redirect() methods to direct the user back to the thickbox iframe parent after plugin
72 + * installation / activation / update.
73 + */
74 + if ( ! empty( $_GET[ 'wpsso_pageref_title' ] ) ) {
75 +
76 + $this->pageref_title = esc_html( urldecode( $_GET[ 'wpsso_pageref_title' ] ) );
77 + }
78 +
79 + if ( ! empty( $_GET[ 'wpsso_pageref_url' ] ) ) {
80 +
81 + $this->pageref_url = esc_url_raw( urldecode( $_GET[ 'wpsso_pageref_url' ] ) );
82 + }
83 +
84 + add_action( 'activated_plugin', array( $this, 'activated_plugin' ), 10, 2 );
85 + add_action( 'after_switch_theme', array( $this, 'after_switch_theme' ), 10, 2 );
86 + add_action( 'upgrader_process_complete', array( $this, 'upgrader_process_complete' ), 10, 2 );
87 +
88 + /*
89 + * Refresh the cache when the WordPress "home" or "blog_public" options are changed.
90 + *
91 + * See WpssAdmin->wp_site_option_changed().
92 + * See SucomUpdate->wp_site_option_changed().
93 + * See SucomUtilWP->raw_do_option().
94 + * See SucomUpdateUtilWP->raw_do_option().
95 + */
96 + add_action( 'update_option_home', array( $this, 'wp_site_option_changed' ), PHP_INT_MAX, 3 );
97 + add_action( 'update_option_blog_public', array( $this, 'wp_site_option_changed' ), PHP_INT_MAX, 3 );
98 + add_action( 'sucom_update_option_home', array( $this, 'wp_site_option_changed' ), PHP_INT_MAX, 3 );
99 +
100 + /*
101 + * This filter re-sorts (if necessary) the active plugins array to load WPSSO Core before its add-ons.
102 + */
103 + add_filter( 'pre_update_option_active_plugins', array( $this, 'pre_update_active_plugins' ), PHP_INT_MAX, 3 );
104 + add_filter( 'pre_update_option_active_sitewide_plugins', array( $this, 'pre_update_active_plugins' ), PHP_INT_MAX, 3 );
105 +
106 + /*
107 + * Define and disable the "Expect: 100-continue" header.
108 + */
109 + add_filter( 'http_request_args', array( $this, 'add_expect_header' ), 1000, 2 );
110 +
111 + add_filter( 'http_request_host_is_external', array( $this, 'allow_safe_hosts' ), 1000, 3 );
112 +
113 + /*
114 + * Provides plugin data / information from the readme.txt for additional add-ons. Don't hook the
115 + * 'plugins_api_result' filter if the update manager is active as it provides more complete plugin data
116 + * than what's available from the readme.txt.
117 + */
118 + if ( empty( $this->p->avail[ 'p_ext' ][ 'um' ] ) ) {
119 +
120 + add_filter( 'plugins_api_result', array( $this, 'external_plugin_data' ), 1000, 3 );
121 + }
122 +
123 + /*
124 + * Add plugin / add-on settings links to the WordPress Plugins page. Hook this filter even when doing ajax
125 + * since the WordPress plugin search results are created using an ajax call.
126 + *
127 + * The 5th argument is $menu_lib = 'submenu', so always limit the method arguments to 4.
128 + */
129 + add_filter( 'plugin_action_links', array( $this, 'add_plugin_action_links' ), 1000, 4 );
130 +
131 + if ( is_multisite() ) {
132 +
133 + /*
134 + * The 5th argument is $menu_lib = 'sitesubmenu', so always limit the method arguments to 4.
135 + */
136 + add_filter( 'network_admin_plugin_action_links', array( $this, 'add_site_plugin_action_links' ), 1000, 4 );
137 + }
138 +
139 + if ( ! $doing_ajax ) {
140 +
141 + add_action( 'wp_dashboard_setup', array( $this, 'dashboard_setup' ) );
142 +
143 + /*
144 + * The admin_menu action is run before admin_init.
145 + */
146 + add_action( 'admin_menu', array( $this, 'load_menu_objects' ), -1000 );
147 + add_action( 'admin_menu', array( $this, 'add_admin_menus' ), WPSSO_ADD_MENU_PRIORITY, 0 );
148 + add_action( 'admin_menu', array( $this, 'add_admin_submenus' ), WPSSO_ADD_SUBMENU_PRIORITY, 0 );
149 +
150 + add_action( 'admin_init', array( $this, 'init_check_options' ), -1000 );
151 + add_action( 'admin_init', array( $this, 'add_plugins_page_upgrade_notice' ) );
152 + add_action( 'admin_init', array( $this, 'register_setting' ) );
153 +
154 + if ( is_multisite() ) {
155 +
156 + add_action( 'network_admin_menu', array( $this, 'load_network_menu_objects' ), -1000 );
157 + add_action( 'network_admin_menu', array( $this, 'add_network_admin_menus' ), WPSSO_ADD_MENU_PRIORITY, 0 );
158 + add_action( 'network_admin_edit_' . WPSSO_SITE_OPTIONS_NAME, array( $this, 'save_site_settings' ) );
159 + }
160 +
161 + add_filter( 'install_plugin_complete_actions', array( $this, 'plugin_complete_actions' ), 1000, 1 );
162 + add_filter( 'update_plugin_complete_actions', array( $this, 'plugin_complete_actions' ), 1000, 1 );
163 +
164 + add_filter( 'wp_redirect', array( $this, 'plugin_complete_redirect' ), 1000, 1 );
165 + add_filter( 'wp_redirect', array( $this, 'profile_updated_redirect' ), -100, 2 );
166 +
167 + add_action( 'admin_bar_menu', array( $this, 'add_admin_bar_menu_notices' ), WPSSO_TB_NOTICE_MENU_ORDER );
168 + add_action( 'admin_bar_menu', array( $this, 'check_admin_bar_menu_notices' ), PHP_INT_MAX );
169 +
170 + add_filter( 'sucom_toolbar_notice_types', array( $this, 'filter_toolbar_notice_types' ) );
171 + }
172 + }
173 +
174 + /*
175 + * Since WPSSO Core v9.3.0.
176 + *
177 + * This action is run by WordPress after any plugin is activated.
178 + *
179 + * If a plugin is silently activated (such as during an update), this action does not run.
180 + */
181 + public function activated_plugin( $plugin_base, $network_activation ) {
182 +
183 + $this->p->reg->reset_admin_checks();
184 +
185 + /*
186 + * Since WPSSO Core v15.11.0.
187 + *
188 + * If the array of supported plugins has changed, then refresh the cache.
189 + */
190 + $old_avail = $this->p->avail;
191 + $new_avail = $this->p->check->get_avail();
192 +
193 + foreach ( array( 'p', 'p_ext' ) as $key ) { // Remove plugin and add-on information.
194 +
195 + unset( $old_avail[ $key ], $new_avail[ $key ] );
196 + }
197 +
198 + if ( $old_avail !== $new_avail ) {
199 +
200 + $user_id = get_current_user_id();
201 +
202 + WpssoRegister::do_multisite( $network_activation, array( $this->p->util->cache, 'schedule_refresh' ), $args = array( $user_id ) );
203 + }
204 + }
205 +
206 + /*
207 + * Since WPSSO Core v9.3.0.
208 + *
209 + * This action is run by WordPress multiple times and the parameters differ according to the context (ie. if the
210 + * old theme exists or not). If the old theme is missing, the parameter will be the slug of the old theme.
211 + */
212 + public function after_switch_theme( $old_name, $old_theme ) {
213 +
214 + $this->p->reg->reset_admin_checks();
215 + }
216 +
217 + /*
218 + * Since WPSSO Core v9.3.0.
219 + *
220 + * This action is run by WordPress when the upgrader process is complete.
221 + */
222 + public function upgrader_process_complete( $wp_upgrader_obj, $hook_extra ) {
223 +
224 + $this->p->reg->reset_admin_checks();
225 + }
226 +
227 + /*
228 + * Refresh the cache when the WordPress "home" or "blog_public" options are changed.
229 + *
230 + * See WpssAdmin->wp_site_option_changed().
231 + * See SucomUpdate->wp_site_option_changed().
232 + * See SucomUtilWP->raw_do_option().
233 + * See SucomUpdateUtilWP->raw_do_option().
234 + */
235 + public function wp_site_option_changed( $old_value, $new_value, $option_name ) {
236 +
237 + if ( $this->p->debug->enabled ) {
238 +
239 + $this->p->debug->mark();
240 + }
241 +
242 + static $do_once = null;
243 +
244 + if ( $do_once ) return; // Stop here.
245 +
246 + $do_once = true;
247 +
248 + /*
249 + * Standardize old and new values for string comparison.
250 + */
251 + $old_value = strtolower( untrailingslashit( maybe_serialize( $old_value ) ) );
252 + $new_value = strtolower( untrailingslashit( maybe_serialize( $new_value ) ) );
253 +
254 + if ( $old_value !== $new_value ) {
255 +
256 + if ( $user_id = get_current_user_id() ) {
257 +
258 + if ( 'home' === $option_name ) {
259 +
260 + $notice_msg = sprintf( __( 'The Site Address URL value has been changed from %1$s to %2$s.', 'wpsso' ), $old_value, $new_value );
261 + $notice_key = __FUNCTION__ . '_' . $option_name . '_' . $old_value . '_' . $new_value;
262 +
263 + $this->p->notice->upd( $notice_msg, $user_id, $notice_key );
264 + }
265 + }
266 +
267 + $this->p->util->cache->schedule_refresh();
268 + }
269 + }
270 +
271 + public function dashboard_setup() {
272 +
273 + $classnames = $this->p->get_lib_classnames( 'dashboard' ); // Always returns an array.
274 +
275 + foreach ( $classnames as $id => $classname ) {
276 +
277 + new $classname( $this->p );
278 + }
279 + }
280 +
281 + public function load_network_menu_objects() {
282 +
283 + if ( $this->p->debug->enabled ) {
284 +
285 + $this->p->debug->mark();
286 + }
287 +
288 + $this->load_menu_objects( array( 'submenu', 'sitesubmenu' ) );
289 + }
290 +
291 + public function load_menu_objects( $menu_libs = array() ) {
292 +
293 + if ( $this->p->debug->enabled ) {
294 +
295 + $this->p->debug->mark();
296 + }
297 +
298 + if ( empty( $menu_libs ) ) {
299 +
300 + $menu_libs = array( 'dashboard', 'plugins', 'profile', 'settings', 'submenu', 'tools', 'users' );
301 + }
302 +
303 + foreach ( $menu_libs as $menu_lib ) {
304 +
305 + foreach ( $this->p->cf[ 'plugin' ] as $ext => $info ) {
306 +
307 + if ( ! isset( $info[ 'lib' ][ $menu_lib ] ) ) { // Not all add-ons have submenus.
308 +
309 + continue;
310 + }
311 +
312 + foreach ( $info[ 'lib' ][ $menu_lib ] as $menu_id => $menu_name ) {
313 +
314 + $add_menu_item = true;
315 +
316 + if ( empty( $this->p->cf[ 'menu' ][ 'must_load' ][ $menu_id ] ) ) { // Settings page can be disabled.
317 +
318 + $opt_key = SucomUtil::sanitize_key( 'plugin_add_' . $menu_lib . '_' . $menu_id );
319 + $filter_name = SucomUtil::sanitize_hookname( 'wpsso_add_menu_' . $menu_lib . '_item_' . $menu_id );
320 + $add_menu_item = isset( $this->p->options[ $opt_key ] ) ? (bool) $this->p->options[ $opt_key ] : true;
321 + $add_menu_item = apply_filters( $filter_name, $add_menu_item );
322 + }
323 +
324 + if ( $add_menu_item ) {
325 +
326 + $classname = apply_filters( $ext . '_load_lib', false, $menu_lib . '/' . $menu_id );
327 +
328 + if ( is_string( $classname ) && class_exists( $classname ) ) {
329 +
330 + if ( $this->p->debug->enabled ) {
331 +
332 + $this->p->debug->log( 'loading classname ' . $classname . ' for menu id ' . $menu_id );
333 + }
334 +
335 + $menu_title = $this->get_submenu_title( $info, $menu_lib, $menu_id );
336 +
337 + $this->submenu[ $menu_id ] = new $classname( $this->p, $menu_id, $menu_title, $menu_lib, $ext );
338 +
339 + } elseif ( $this->p->debug->enabled ) {
340 +
341 + $this->p->debug->log( 'classname not found for menu lib ' . $menu_lib . '/' . $menu_id );
342 + }
343 + }
344 + }
345 + }
346 + }
347 + }
348 +
349 + public function add_network_admin_menus() {
350 +
351 + if ( $this->p->debug->enabled ) {
352 +
353 + $this->p->debug->mark();
354 + }
355 +
356 + $this->add_admin_menus( 'sitesubmenu' );
357 + }
358 +
359 + /*
360 + * Add a new main menu and its sub-menu items.
361 + */
362 + public function add_admin_menus( $menu_lib = 'submenu' ) {
363 +
364 + foreach ( $this->p->cf[ '*' ][ 'lib' ][ $menu_lib ] as $this->menu_id => $this->menu_name ) {
365 +
366 + if ( isset( $this->submenu[ $this->menu_id ] ) ) {
367 +
368 + $menu_slug = 'wpsso-' . $this->menu_id;
369 +
370 + $this->submenu[ $this->menu_id ]->add_menu_page( $menu_slug );
371 +
372 + break;
373 + }
374 + }
375 +
376 + $menu_args = $this->get_submenu_args( $menu_lib );
377 +
378 + foreach ( $menu_args as $menu_id => $args ) {
379 +
380 + if ( isset( $this->submenu[ $menu_id ] ) ) { // Just in case.
381 +
382 + call_user_func_array( array( $this->submenu[ $menu_id ], 'add_submenu_page' ), $args );
383 + }
384 + }
385 + }
386 +
387 + /*
388 + * Add sub-menu items to existing WordPress menus.
389 + */
390 + public function add_admin_submenus() {
391 +
392 + foreach ( array( 'dashboard', 'plugins', 'profile', 'settings', 'tools', 'users' ) as $menu_lib ) {
393 +
394 + $menu_args = $this->get_submenu_args( $menu_lib );
395 +
396 + foreach ( $menu_args as $menu_id => $args ) {
397 +
398 + if ( isset( $this->submenu[ $menu_id ] ) ) { // Just in case.
399 +
400 + call_user_func_array( array( $this->submenu[ $menu_id ], 'add_submenu_page' ), $args );
401 + }
402 + }
403 + }
404 + }
405 +
406 + /*
407 + * Return and maybe set/reset the WpssoAdmin->form value.
408 + */
409 + public function &get_form_object( $menu_ext ) {
410 +
411 + if ( $this->p->debug->enabled ) {
412 +
413 + $this->p->debug->mark();
414 + }
415 +
416 + if ( ! isset( $this->form ) ) {
417 +
418 + if ( $this->p->debug->enabled ) {
419 +
420 + $this->p->debug->log( 'form object not defined' );
421 + }
422 +
423 + $this->set_form_object( $menu_ext );
424 +
425 + } elseif ( $this->form->get_ext_id() !== $menu_ext ) {
426 +
427 + if ( $this->p->debug->enabled ) {
428 +
429 + $this->p->debug->log( 'form object does not match' );
430 + }
431 +
432 + $this->set_form_object( $menu_ext );
433 + }
434 +
435 + return $this->form;
436 + }
437 +
438 + /*
439 + * Set the WpssoAdmin->form value.
440 + */
441 + protected function set_form_object( $menu_ext ) { // $menu_ext required for text_domain.
442 +
443 + if ( $this->p->debug->enabled ) {
444 +
445 + $this->p->debug->log( 'setting form object for ' . $menu_ext );
446 + }
447 +
448 + $defs = $this->p->opt->get_defaults();
449 +
450 + $this->form = new SucomForm( $this->p, WPSSO_OPTIONS_NAME, $this->p->options, $defs, $menu_ext );
451 + }
452 +
453 + public function register_setting() {
454 +
455 + register_setting( 'wpsso_settings', WPSSO_OPTIONS_NAME, $args = array(
456 + 'sanitize_callback' => array( $this, 'settings_sanitation' ),
457 + ) );
458 + }
459 +
460 + /*
461 + * Runs at admin_init priority -1000.
462 + */
463 + public function init_check_options() {
464 +
465 + if ( $this->p->debug->enabled ) {
466 +
467 + $this->p->debug->mark();
468 + }
469 +
470 + /*
471 + * set_options() may have loaded the static defaults for new or missing options.
472 + *
473 + * After all objects have been loaded, and all filter / action hooks registered, check to see if the
474 + * options array needs to be reloaded from the filtered defaults.
475 + */
476 + if ( ! empty( $this->p->options[ '__reload_defaults' ] ) ) {
477 +
478 + $this->p->options = $this->p->opt->get_defaults();
479 + }
480 +
481 + if ( is_multisite() ) { // Load the site options array.
482 +
483 + if ( ! empty( $this->p->site_options[ '__reload_defaults' ] ) ) {
484 +
485 + $this->p->site_options = $this->p->opt->get_site_defaults();
486 + }
487 + }
488 +
489 + if ( $this->p->debug->enabled ) {
490 +
491 + $this->p->debug->log( 'checking ' . WPSSO_OPTIONS_NAME . ' options' );
492 + }
493 +
494 + $this->p->options = $this->p->opt->check_options( WPSSO_OPTIONS_NAME, $this->p->options, $network = false );
495 +
496 + if ( is_multisite() ) { // Load the site options array.
497 +
498 + if ( $this->p->debug->enabled ) {
499 +
500 + $this->p->debug->log( 'checking ' . WPSSO_SITE_OPTIONS_NAME . ' options' );
501 + }
502 +
503 + $this->p->site_options = $this->p->opt->check_options( WPSSO_SITE_OPTIONS_NAME, $this->p->site_options, $network = true );
504 + }
505 +
506 + /*
507 + * Init option checks.
508 + *
509 + * See WpssoWcmd->init_check_options().
510 + */
511 + do_action( 'wpsso_init_check_options' );
512 + }
513 +
514 + public function add_plugins_page_upgrade_notice() {
515 +
516 + if ( $this->p->debug->enabled ) {
517 +
518 + $this->p->debug->mark();
519 + }
520 +
521 + foreach ( $this->p->cf[ 'plugin' ] as $ext => $info ) {
522 +
523 + if ( ! empty( $info[ 'base' ] ) ) {
524 +
525 + /*
526 + * Fires at the end of the update message container in each row of the plugins list table.
527 + *
528 + * See wordpress/wp-admin/includes/update.php:587.
529 + */
530 + add_action( 'in_plugin_update_message-' . $info[ 'base' ], array( $this, 'show_upgrade_notice' ), 10, 2 );
531 + }
532 + }
533 + }
534 +
535 + public function show_upgrade_notice( $data, $response ) {
536 +
537 + if ( $this->p->debug->enabled ) {
538 +
539 + $this->p->debug->mark();
540 + }
541 +
542 + if ( isset( $data[ 'upgrade_notice' ] ) ) { // Just in case.
543 +
544 + echo '<span style="display:table;border-collapse:collapse;margin-left:26px;">';
545 + echo '<span style="display:table-cell;">' . strip_tags( $data[ 'upgrade_notice' ] ) . '</span>';
546 + echo '</span>';
547 + }
548 + }
549 +
550 + protected function add_menu_page( $menu_slug ) {
551 +
552 + $pkg_info = $this->p->util->get_pkg_info(); // Uses a local cache.
553 + $page_title = $pkg_info[ 'wpsso' ][ 'short_pkg' ] . ' - ' . $this->menu_name;
554 + $menu_title = $this->get_menu_title();
555 + $cf_wp_admin = $this->p->cf[ 'wp' ][ 'admin' ];
556 + $capability = isset( $cf_wp_admin[ $this->menu_lib ][ 'cap' ] ) ? $cf_wp_admin[ $this->menu_lib ][ 'cap' ] : 'manage_options';
557 + $icon_url = 'none'; // Icon provided by WpssoStyle::admin_register_page_styles().
558 + $function = array( $this, 'show_settings_page' );
559 + $position = WPSSO_MENU_ORDER;
560 +
561 + $this->pagehook = add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $function, $icon_url, $position );
562 +
563 + add_action( 'load-' . $this->pagehook, array( $this, 'load_settings_page' ) );
564 + }
565 +
566 + protected function add_submenu_page( $parent_slug, $menu_id, $menu_title, $menu_lib, $menu_ext, $css_class = '' ) {
567 +
568 + $css_class = trim( 'wpsso-menu-item wpsso-' . $menu_id . ' ' . $css_class );
569 + $menu_title = '<div class="' . $css_class . ' menu-item-label">' . $menu_title . '</div>'; // Wrap the title string.
570 + $pkg_info = $this->p->util->get_pkg_info(); // Uses a local cache.
571 + $page_title = $pkg_info[ $menu_ext ][ 'short_pkg' ] . ' - ' . $menu_title;
572 + $cf_wp_admin = $this->p->cf[ 'wp' ][ 'admin' ];
573 + $capability = isset( $cf_wp_admin[ $menu_lib ][ 'cap' ] ) ? $cf_wp_admin[ $menu_lib ][ 'cap' ] : 'manage_options';
574 + $menu_slug = 'wpsso-' . $menu_id;
575 + $function = array( $this, 'show_settings_page' );
576 + $position = null;
577 +
578 + if ( isset( $cf_wp_admin[ $menu_lib ][ 'sub' ][ $menu_id ] ) ) {
579 +
580 + $cf_menu_id = $cf_wp_admin[ $menu_lib ][ 'sub' ][ $menu_id ];
581 + $capability = isset( $cf_menu_id[ 'cap' ] ) ? $cf_menu_id[ 'cap' ] : $capability;
582 + $position = isset( $cf_menu_id[ 'pos' ] ) ? $cf_menu_id[ 'pos' ] : $position;
583 + }
584 +
585 + $this->pagehook = add_submenu_page( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $function, $position );
586 +
587 + add_action( 'load-' . $this->pagehook, array( $this, 'load_settings_page' ) );
588 + }
589 +
590 + /*
591 + * Add plugin links for the WordPress network plugins page.
592 + */
593 + public function add_site_plugin_action_links( $action_links, $plugin_base, $plugin_data, $context, $menu_lib = 'sitesubmenu' ) {
594 +
595 + return $this->add_plugin_action_links( $action_links, $plugin_base, $plugin_data, $context, $menu_lib );
596 + }
597 +
598 + /*
599 + * Add plugin links for the WordPress plugins page.
600 + *
601 + * $plugin_data is an array of plugin data. See get_plugin_data().
602 + *
603 + * $context can be 'all', 'active', 'inactive', 'recently_activated', 'upgrade', 'mustuse', 'dropins', or 'search'.
604 + */
605 + public function add_plugin_action_links( $action_links, $plugin_base, $plugin_data, $context, $menu_lib = 'submenu' ) {
606 +
607 + if ( ! isset( $this->p->cf[ '*' ][ 'base' ][ $plugin_base ] ) ) {
608 +
609 + return $action_links;
610 + }
611 +
612 + $ext = $this->p->cf[ '*' ][ 'base' ][ $plugin_base ];
613 + $info = $this->p->cf[ 'plugin' ][ $ext ];
614 + $settings_page = empty( $info[ 'lib' ][ $menu_lib ] ) ? '' : key( $info[ 'lib' ][ $menu_lib ] );
615 + $settings_page_url = $this->p->util->get_admin_url( $settings_page );
616 +
617 + switch ( $ext ) {
618 +
619 + case 'wpsso':
620 +
621 + $addons_page = 'sitesubmenu' === $menu_lib ? 'site-addons' : 'addons';
622 + $addons_page_url = $this->p->util->get_admin_url( $addons_page );
623 +
624 + if ( ! empty( $settings_page ) ) {
625 +
626 + $action_links[] = '<a href="' . $settings_page_url . '">' . _x( 'Plugin Settings', 'plugin action link', 'wpsso' ) . '</a>';
627 + }
628 +
629 + $action_links[] = '<a href="' . $addons_page_url . '">' . _x( 'Plugin Add-ons', 'plugin action link', 'wpsso' ) . '</a>';
630 +
631 + break;
632 +
633 + default:
634 +
635 + if ( ! empty( $settings_page ) ) {
636 +
637 + $action_links[] = '<a href="' . $settings_page_url . '">' . _x( 'Add-on Settings', 'plugin action link', 'wpsso' ) . '</a>';
638 + }
639 +
640 + break;
641 + }
642 +
643 + return $action_links;
644 + }
645 +
646 + /*
647 + * Define and disable the "Expect: 100-continue" header.
648 + *
649 + * $parsed_args should be an array, so make sure other filters aren't giving us a string or boolean.
650 + */
651 + public function add_expect_header( $parsed_args, $url ) {
652 +
653 + if ( ! is_array( $parsed_args ) ) { // Just in case.
654 +
655 + $parsed_args = array();
656 + }
657 +
658 + if ( empty( $parsed_args[ 'headers' ] ) ) { // Just in case.
659 +
660 + $parsed_args[ 'headers' ] = array();
661 +
662 + /*
663 + * WordPress allows passing headers as a string -- fix that issue here so we can update the 'headers' array
664 + * properly.
665 + *
666 + * See https://core.trac.wordpress.org/browser/tags/5.7.1/src/wp-includes/class-http.php#L310.
667 + */
668 + } elseif ( ! is_array( $parsed_args[ 'headers' ] ) ) {
669 +
670 + $processedHeaders = WP_Http::processHeaders( $parsed_args[ 'headers' ] );
671 +
672 + $parsed_args[ 'headers' ] = $processedHeaders[ 'headers' ];
673 + }
674 +
675 + $parsed_args[ 'headers' ][ 'Expect' ] = '';
676 +
677 + return $parsed_args;
678 + }
679 +
680 + public function allow_safe_hosts( $is_allowed, $ip, $url ) {
681 +
682 + if ( $is_allowed ) { // Already allowed.
683 +
684 + return $is_allowed;
685 + }
686 +
687 + if ( isset( $this->p->cf[ 'extend' ] ) ) {
688 +
689 + foreach ( $this->p->cf[ 'extend' ] as $host ) {
690 +
691 + if ( 0 === strpos( $url, $host ) ) {
692 +
693 + return true;
694 + }
695 + }
696 + }
697 +
698 + return $is_allowed;
699 + }
700 +
701 + /*
702 + * Provides plugin data / information from the readme.txt for additional add-ons.
703 + */
704 + public function external_plugin_data( $result, $action = null, $args = null ) {
705 +
706 + if ( $this->p->debug->enabled ) {
707 +
708 + $this->p->debug->mark();
709 + }
710 +
711 + if ( 'plugin_information' !== $action ) { // This filter only provides plugin data.
712 +
713 + return $result;
714 +
715 + } elseif ( empty( $args->slug ) ) { // Make sure we have a slug in the request.
716 +
717 + return $result;
718 +
719 + } elseif ( empty( $this->p->cf[ '*' ][ 'slug' ][ $args->slug ] ) ) { // Make sure the plugin slug is one of ours.
720 +
721 + return $result;
722 +
723 + } elseif ( isset( $result->slug ) && $result->slug === $args->slug ) { // If the object from WordPress looks complete, return it as-is.
724 +
725 + return $result;
726 + }
727 +
728 + $ext = $this->p->cf[ '*' ][ 'slug' ][ $args->slug ]; // Get the add-on acronym to read its config.
729 +
730 + if ( empty( $this->p->cf[ 'plugin' ][ $ext ] ) ) { // Make sure we have a config for that acronym.
731 +
732 + return $result;
733 + }
734 +
735 + $plugin_data = $this->get_plugin_data( $ext, $read_cache = true ); // Get plugin data from the plugin readme.
736 +
737 + if ( empty( $plugin_data ) ) { // Make sure we have some data to return.
738 +
739 + return $result;
740 + }
741 +
742 + $plugin_data->external = true; // Let WordPress known that this is not a wordpress.org plugin.
743 +
744 + return $plugin_data;
745 + }
746 +
747 + /*
748 + * This method receives only a partial options array, so re-create a full one.
749 + *
750 + * WordPress handles the actual saving of the options to the database table.
751 + */
752 + public function settings_sanitation( $opts ) {
753 +
754 + $current_user_id = get_current_user_id(); // Always returns an integer.
755 +
756 + /*
757 + * Clear any old notices for the current user before sanitation checks.
758 + */
759 + $this->p->notice->clear();
760 +
761 + /*
762 + * Make sure the input is an array.
763 + */
764 + if ( ! is_array( $opts ) ) {
765 +
766 + $opts = array();
767 + }
768 +
769 + $defs = $this->p->opt->get_defaults();
770 + $opts = SucomUtil::restore_checkboxes( $opts );
771 + $opts = SucomUtil::array_merge_recursive_distinct( $this->p->options, $opts );
772 + $opts = $this->p->opt->sanitize( $opts, $defs, $network = false );
773 + $opts = apply_filters( 'wpsso_save_settings_options', $opts, $network = false, $upgrading = false );
774 +
775 + /*
776 + * Maybe clear dismissed notices for enabled options that were previously disabled.
777 + *
778 + * See WpssoAdminHeadSuggestOptions->suggest_options_integration().
779 + */
780 + foreach ( array(
781 + 'add_link_rel_canonical' => 'suggest-options-seo-add_link_rel_canonical',
782 + 'add_link_rel_shortlink' => 'suggest-options-seo-add_link_rel_shortlink',
783 + 'add_meta_name_description' => 'suggest-options-seo-add_meta_name_description',
784 + 'add_meta_name_robots' => 'suggest-options-seo-add_meta_name_robots',
785 + 'plugin_filter_content' => 'notice-content-filters-disabled',
786 + 'plugin_check_img_dims' => 'notice-check-img-dims-disabled',
787 + 'plugin_inherit_featured' => 'notice-wc-inherit-featured-disabled',
788 + ) as $opt_key => $notice_key ) {
789 +
790 + if ( ! empty( $opts[ $opt_key ] ) ) { // Option is enabled.
791 +
792 + $this->p->notice->clear_dismissed( $notice_key, $current_user_id );
793 + }
794 + }
795 +
796 + /*
797 + * Update the current options with any changes.
798 + */
799 + $this->p->options = $opts;
800 +
801 + $this->settings_saved_notice();
802 +
803 + return $opts;
804 + }
805 +
806 + public function settings_saved_notice() {
807 +
808 + $cache_md5_pre = 'wpsso_h_'; // Use 'wpsso_cache_expire_head_markup' filter.
809 + $cache_exp_secs = $this->p->util->get_cache_exp_secs( $cache_md5_pre, $cache_type = 'transient' );
810 + $user_id = get_current_user_id();
811 + $notice_msg = '<strong>' . __( 'Plugin settings have been saved.', 'wpsso' ) . '</strong>' . ' ';
812 + $notice_key = 'settings-saved';
813 +
814 + if ( $cache_exp_secs > 0 ) {
815 +
816 + $refresh_cache_url = $this->p->util->get_admin_url( $menu_id = 'tools' );
817 + $refresh_cache_url = add_query_arg( 'wpsso-action', 'refresh_cache', $refresh_cache_url );
818 + $refresh_cache_url = wp_nonce_url( $refresh_cache_url, WpssoAdmin::get_nonce_action(), WPSSO_NONCE_NAME );
819 + $refresh_cache_link = '<a href="' . $refresh_cache_url . '">' . __( 'refresh the cache now', 'wpsso' ) . '</a>';
820 + $human_cache_exp = human_time_diff( 0, $cache_exp_secs );
821 +
822 + $notice_msg .= sprintf( __( 'You can %1$s or let the cache refresh over the next %2$s.', 'wpsso' ), $refresh_cache_link, $human_cache_exp );
823 + }
824 +
825 + $this->p->notice->upd( $notice_msg, $user_id, $notice_key );
826 + }
827 +
828 + public function save_site_settings() {
829 +
830 + $default_page = key( $this->p->cf[ '*' ][ 'lib' ][ 'sitesubmenu' ] );
831 + $default_page_url = $this->p->util->get_admin_url( $default_page );
832 + $setting_page = SucomUtil::get_request_value( 'page', 'POST', $default_page );
833 + $setting_page_url = $this->p->util->get_admin_url( $setting_page );
834 +
835 + if ( empty( $_POST[ WPSSO_NONCE_NAME ] ) ) { // WPSSO_NONCE_NAME is an md5() string.
836 +
837 + if ( $this->p->debug->enabled ) {
838 +
839 + $this->p->debug->log( 'nonce token validation post field missing' );
840 + }
841 +
842 + wp_redirect( $default_page_url ); // Do not trust the 'page' request value.
843 +
844 + exit;
845 +
846 + } elseif ( ! wp_verify_nonce( $_POST[ WPSSO_NONCE_NAME ], WpssoAdmin::get_nonce_action() ) ) {
847 +
848 + $this->p->notice->err( __( 'Nonce token validation failed for network options (update ignored).', 'wpsso' ) );
849 +
850 + wp_redirect( $default_page_url ); // Do not trust the 'page' request value.
851 +
852 + exit;
853 +
854 + } elseif ( ! current_user_can( 'manage_network_options' ) ) {
855 +
856 + $this->p->notice->err( __( 'Insufficient privileges to manage network options.', 'wpsso' ) );
857 +
858 + wp_redirect( $setting_page_url );
859 +
860 + exit;
861 + }
862 +
863 + /*
864 + * Clear any old notices for the current user before sanitation checks.
865 + */
866 + $this->p->notice->clear();
867 +
868 + /*
869 + * Make sure the input is an array.
870 + */
871 + $opts = array();
872 +
873 + if ( isset( $_POST[ WPSSO_SITE_OPTIONS_NAME ] ) && is_array( $_POST[ WPSSO_SITE_OPTIONS_NAME ] ) ) {
874 +
875 + $opts = $_POST[ WPSSO_SITE_OPTIONS_NAME ];
876 + }
877 +
878 + $defs = $this->p->opt->get_site_defaults();
879 + $opts = SucomUtil::restore_checkboxes( $opts );
880 + $opts = SucomUtil::array_merge_recursive_distinct( $this->p->site_options, $opts ); // Complete the array with previous options.
881 + $opts = $this->p->opt->sanitize( $opts, $defs, $network = true );
882 + $opts = apply_filters( 'wpsso_save_settings_options', $opts, $network = true, $upgrading = false );
883 +
884 + /*
885 + * Update the current options with any changes.
886 + */
887 + $this->p->site_options = $opts;
888 +
889 + update_site_option( WPSSO_SITE_OPTIONS_NAME, $opts );
890 +
891 + $this->p->notice->upd( '<strong>' . __( 'Network plugin settings have been saved.', 'wpsso' ) . '</strong>' );
892 +
893 + $setting_page_url = add_query_arg( 'settings-updated', 'true', $setting_page_url );
894 +
895 + wp_redirect( $setting_page_url );
896 +
897 + exit; // Stop after redirect.
898 + }
899 +
900 + public function load_settings_page() {
901 +
902 + if ( $this->p->debug->enabled ) {
903 +
904 + $this->p->debug->mark();
905 + }
906 +
907 + $user_id = get_current_user_id();
908 + $action_query = 'wpsso-action';
909 + $action_value = SucomUtil::get_request_value( $action_query ) ; // POST or GET with sanitize_text_field().
910 + $action_value = SucomUtil::sanitize_hookname( $action_value );
911 + $nonce_value = SucomUtil::get_request_value( WPSSO_NONCE_NAME ) ; // POST or GET with sanitize_text_field().
912 +
913 + $_SERVER[ 'REQUEST_URI' ] = remove_query_arg( array( $action_query, WPSSO_NONCE_NAME ) );
914 +
915 + wp_enqueue_script( 'postbox' );
916 +
917 + if ( ! empty( $action_value ) ) {
918 +
919 + if ( empty( $nonce_value ) ) { // WPSSO_NONCE_NAME is an md5() string.
920 +
921 + if ( $this->p->debug->enabled ) {
922 +
923 + $this->p->debug->log( 'nonce token validation field missing' );
924 + }
925 +
926 + } elseif ( ! wp_verify_nonce( $nonce_value, WpssoAdmin::get_nonce_action() ) ) {
927 +
928 + $notice_msg = sprintf( __( 'Nonce token validation failed for %1$s action "%2$s".', 'wpsso' ), 'admin', $action_value );
929 +
930 + $this->p->notice->err( $notice_msg, $user_id );
931 +
932 + } else {
933 +
934 + if ( $this->p->debug->enabled ) {
935 +
936 + $this->p->debug->log( 'action_value = ' . $action_value );
937 + }
938 +
939 + switch ( $action_value ) {
940 +
941 + case 'refresh_cache':
942 +
943 + $this->p->util->cache->schedule_refresh( $user_id );
944 +
945 + break;
946 +
947 + case 'clear_cache_files':
948 +
949 + $cleared_count = $this->p->util->cache->clear_cache_files();
950 +
951 + $notice_msg = sprintf( __( '%s cache files have been cleared.', 'wpsso' ), $cleared_count );
952 +
953 + $this->p->notice->upd( $notice_msg, $user_id );
954 +
955 + break;
956 +
957 + case 'clear_ignored_urls':
958 +
959 + $cleared_count = $this->p->util->cache->clear_ignored_urls();
960 +
961 + $notice_msg = sprintf( __( '%s failed URL connections have been cleared.', 'wpsso' ), $cleared_count );
962 +
963 + $this->p->notice->upd( $notice_msg, $user_id );
964 +
965 + break;
966 +
967 + case 'clear_db_transients':
968 +
969 + $cleared_count = $this->p->util->cache->clear_db_transients( $key_prefix = '', $incl_shortened = true );
970 +
971 + $notice_msg = sprintf( __( '%s database transients have been cleared.', 'wpsso' ), $cleared_count );
972 +
973 + $this->p->notice->upd( $notice_msg, $user_id );
974 +
975 + break;
976 +
977 + case 'clear_db_transients_expired':
978 +
979 + $cleared_count = $this->p->util->cache->clear_db_transients_expired();
980 +
981 + $notice_msg = sprintf( __( '%s expired transients have been cleared.', 'wpsso' ), $cleared_count );
982 +
983 + $this->p->notice->upd( $notice_msg, $user_id );
984 +
985 + break;
986 +
987 + case 'clear_db_transients_no_short':
988 +
989 + $cleared_count = $this->p->util->cache->clear_db_transients( $key_prefix = '', $incl_shortened = false );
990 +
991 + $notice_msg = sprintf( __( '%s database transients have been cleared (shortened URLs preserved).', 'wpsso' ),
992 + $cleared_count );
993 +
994 + $this->p->notice->upd( $notice_msg, $user_id );
995 +
996 + break;
997 +
998 + case 'clear_db_transients_shortened':
999 +
1000 + $cleared_count = $this->p->util->cache->clear_db_transients( $key_prefix = 'wpsso_s_', $incl_shortened = true );
1001 +
1002 + $notice_msg = sprintf( __( '%s shortened URL database transients have been cleared.', 'wpsso' ),
1003 + $cleared_count );
1004 +
1005 + $this->p->notice->upd( $notice_msg, $user_id );
1006 +
1007 + break;
1008 +
1009 + case 'clear_cron_jobs':
1010 +
1011 + $count_cron_jobs = number_format_i18n( $this->p->util->count_cron_jobs() );
1012 +
1013 + update_option( 'cron', '' );
1014 +
1015 + $notice_msg = sprintf( __( '%s WordPress cron jobs have been cleared.', 'wpsso' ), $count_cron_jobs );
1016 +
1017 + $this->p->notice->upd( $notice_msg, $user_id );
1018 +
1019 + break;
1020 +
1021 + case 'flush_rewrite_rules':
1022 +
1023 + /*
1024 + * Update .htaccess and the 'rewrite_rules' option.
1025 + *
1026 + * This is an expensive operation so it should only be used when necessary.
1027 + */
1028 + flush_rewrite_rules( $hard = true );
1029 +
1030 + $notice_msg = __( 'The WordPress rewrite rules have been flushed.', 'wpsso' );
1031 +
1032 + $this->p->notice->upd( $notice_msg, $user_id );
1033 +
1034 + break;
1035 +
1036 + case 'add_persons':
1037 +
1038 + $this->p->user->schedule_add_person_role( $user_id );
1039 +
1040 + break;
1041 +
1042 + case 'remove_persons':
1043 +
1044 + $this->p->user->schedule_remove_person_role( $user_id );
1045 +
1046 + break;
1047 +
1048 + case 'reset_user_metabox_layout':
1049 +
1050 + WpssoUser::delete_metabox_prefs( $user_id );
1051 +
1052 + $user_obj = get_userdata( $user_id );
1053 +
1054 + $user_name = $user_obj->display_name;
1055 +
1056 + $notice_msg = sprintf( __( 'Metabox layout preferences for user ID #%d "%s" have been reset.', 'wpsso' ),
1057 + $user_id, $user_name );
1058 +
1059 + $this->p->notice->upd( $notice_msg, $user_id );
1060 +
1061 + break;
1062 +
1063 + case 'reset_user_dismissed_notices':
1064 +
1065 + $this->p->notice->reset_dismissed( $user_id );
1066 +
1067 + $user_obj = get_userdata( $user_id );
1068 +
1069 + $user_name = $user_obj->display_name;
1070 +
1071 + $notice_msg = sprintf( __( 'Dismissed notices for user ID #%d "%s" have been reset.', 'wpsso' ),
1072 + $user_id, $user_name );
1073 +
1074 + $this->p->notice->upd( $notice_msg, $user_id );
1075 +
1076 + break;
1077 +
1078 + case 'change_show_options':
1079 +
1080 + $_SERVER[ 'REQUEST_URI' ] = remove_query_arg( array( 'show-opts' ) );
1081 +
1082 + $show_opts_key = isset( $_GET[ 'show-opts' ] ) ? SucomUtil::sanitize_key( $_GET[ 'show-opts' ] ) : null;
1083 +
1084 + if ( isset( $this->p->cf[ 'form' ][ 'show_options' ][ $show_opts_key ] ) ) {
1085 +
1086 + $show_name_transl = _x( $this->p->cf[ 'form' ][ 'show_options' ][ $show_opts_key ], 'option value', 'wpsso' );
1087 +
1088 + WpssoUser::save_pref( array( 'show_opts' => $show_opts_key ) );
1089 +
1090 + $notice_msg = '<strong>' . __( 'Option preference has been saved.', 'wpsso' ) . '</strong>';
1091 +
1092 + $notice_msg .= sprintf( __( 'Now viewing "%s" by default.', 'wpsso' ), $show_name_transl );
1093 +
1094 + $this->p->notice->upd( $notice_msg, $user_id );
1095 + }
1096 +
1097 + break;
1098 +
1099 + case 'reload_default_image_sizes':
1100 +
1101 + $defs = $this->p->opt->get_defaults();
1102 +
1103 + $img_defs = SucomUtil::preg_grep_keys( '/_img_(width|height|crop|crop_x|crop_y)$/', $defs );
1104 +
1105 + $this->p->options = SucomUtil::array_merge_recursive_distinct( $this->p->options, $img_defs );
1106 +
1107 + $this->p->opt->save_options( WPSSO_OPTIONS_NAME, $this->p->options, $network = false );
1108 +
1109 + $this->p->notice->upd( __( 'Image size settings have been reloaded with their default values and saved.', 'wpsso' ) );
1110 +
1111 + break;
1112 +
1113 + case 'export_plugin_settings_json':
1114 +
1115 + $this->export_plugin_settings_json();
1116 +
1117 + break;
1118 +
1119 + case 'import_plugin_settings_json':
1120 +
1121 + $this->import_plugin_settings_json();
1122 +
1123 + break;
1124 +
1125 + default:
1126 +
1127 + if ( $this->p->debug->enabled ) {
1128 +
1129 + $this->p->debug->log( 'doing action "wpsso_load_settings_page_' . $action_value . '"' );
1130 + }
1131 +
1132 + do_action( 'wpsso_load_settings_page_' . $action_value, $this->pagehook,
1133 + $this->menu_id, $this->menu_name, $this->menu_lib );
1134 +
1135 + break;
1136 + }
1137 + }
1138 + }
1139 +
1140 + $menu_ext = empty( $this->menu_ext ) ? $this->p->id : $this->menu_ext;
1141 +
1142 + $this->get_form_object( $menu_ext ); // Return and maybe set/reset the WpssoAdmin->form value.
1143 +
1144 + $this->add_settings_page_callbacks();
1145 +
1146 + $this->add_settings_page_metaboxes();
1147 +
1148 + $this->add_settings_page_footer();
1149 + }
1150 +
1151 + /*
1152 + * Add actions and filters for this settings page.
1153 + *
1154 + * See WpssoAdmin->load_settings_page().
1155 + */
1156 + protected function add_settings_page_callbacks() {
1157 + }
1158 +
1159 + /*
1160 + * Add metaboxes for this settings page.
1161 + *
1162 + * See WpssoAdmin->load_settings_page().
1163 + */
1164 + protected function add_settings_page_metaboxes( $callback_args = array() ) {
1165 +
1166 + if ( $this->p->debug->enabled ) {
1167 +
1168 + $this->p->debug->mark();
1169 + }
1170 +
1171 + /*
1172 + * Defined in the child construct method of this settings page.
1173 + */
1174 + if ( empty( $this->menu_metaboxes ) ) {
1175 +
1176 + return;
1177 + }
1178 +
1179 + foreach ( $this->menu_metaboxes as $metabox_id => $metabox_title ) {
1180 +
1181 + $metabox_screen = $this->pagehook;
1182 + $metabox_context = 'normal';
1183 + $metabox_prio = 'default';
1184 +
1185 + $callback_args[ 'page_id' ] = $this->menu_id;
1186 + $callback_args[ 'metabox_id' ] = $metabox_id;
1187 + $callback_args[ 'metabox_title' ] = $metabox_title;
1188 + $callback_args[ 'network' ] = 'sitesubmenu' === $this->menu_lib ? true : false;
1189 +
1190 + $method_name = method_exists( $this, 'show_metabox_' . $metabox_id ) ?
1191 + 'show_metabox_' . $metabox_id : 'show_metabox_table';
1192 +
1193 + add_meta_box( $this->pagehook . '_' . $metabox_id, $metabox_title, array( $this, $method_name ),
1194 + $metabox_screen, $metabox_context, $metabox_prio, $callback_args );
1195 +
1196 + /*
1197 + * Add a class to set a minimum width for the network postboxes.
1198 + */
1199 + if ( 'sitesubmenu' === $this->menu_lib ) {
1200 +
1201 + add_filter( 'postbox_classes_' . $this->pagehook . '_' . $this->pagehook . '_' . $metabox_id,
1202 + array( $this, 'add_class_postbox_network' ) );
1203 + }
1204 + }
1205 + }
1206 +
1207 + /*
1208 + * Include add-on name and version in settings page footer.
1209 + */
1210 + protected function add_settings_page_footer() {
1211 +
1212 + add_filter( 'admin_footer_text', array( $this, 'admin_footer_ext' ) );
1213 +
1214 + add_filter( 'update_footer', array( $this, 'admin_footer_host' ) );
1215 + }
1216 +
1217 + protected function maybe_show_language_notice() {
1218 +
1219 + $current_locale = SucomUtilWP::get_locale();
1220 + $default_locale = SucomUtilWP::get_locale( 'default' );
1221 +
1222 + if ( $current_locale && $default_locale && $current_locale !== $default_locale ) {
1223 +
1224 + $notice_msg = sprintf( __( 'Your current language selection [%1$s] is different from the default site language [%2$s].', 'wpsso' ),
1225 + $current_locale, $default_locale ) . ' ';
1226 +
1227 + $notice_msg .= sprintf( __( 'Localized option values [%1$s] will be used for webpages and content in that language.', 'wpsso' ),
1228 + $current_locale );
1229 +
1230 + $notice_key = $this->menu_id . '-language-notice-' . $current_locale . '-' . $default_locale;
1231 +
1232 + $this->p->notice->inf( $notice_msg, null, $notice_key, $dismiss_time = true );
1233 + }
1234 + }
1235 +
1236 + public function show_settings_page() {
1237 +
1238 + if ( ! $this->is_settings() ) { // Default check is for $this->menu_id.
1239 +
1240 + settings_errors( WPSSO_OPTIONS_NAME );
1241 + }
1242 +
1243 + if ( ! $this->is_plugin_found() ) { // Just in case.
1244 +
1245 + return;
1246 + }
1247 +
1248 + $side_col_boxes = $this->get_side_col_boxes();
1249 + $dashicon_html = $this->get_menu_dashicon_html( $this->menu_id );
1250 +
1251 + /*
1252 + * Settings page wrapper.
1253 + */
1254 + echo '<div id="' . $this->pagehook . '" class="wrap">' . "\n";
1255 +
1256 + /*
1257 + * Settings page header.
1258 + */
1259 + echo '<div id="wpsso-setting-page-header">' . "\n";
1260 + echo '<h1>' . $dashicon_html . ' '. $this->menu_name . '</h1>' . "\n";
1261 + echo '</div><!-- #wpsso-setting-page-header -->' . "\n";
1262 +
1263 + /*
1264 + * Settings page content.
1265 + */
1266 + echo '<div id="wpsso-setting-page-content" class="' . ( empty( $side_col_boxes ) ? 'no' : 'has' ) . '-side-column">' . "\n";
1267 +
1268 + /*
1269 + * Metaboxes.
1270 + */
1271 + echo '<div id="poststuff" class="metabox-holder no-right-sidebar">' . "\n";
1272 + echo '<div id="post-body" class="no-sidebar">' . "\n";
1273 + echo '<div id="post-body-content" class="no-sidebar-content">' . "\n";
1274 +
1275 + $this->show_post_body_settings_form();
1276 +
1277 + echo '</div><!-- #post-body-content -->' . "\n";
1278 + echo '</div><!-- #post-body -->' . "\n";
1279 + echo '</div><!-- #poststuff -->' . "\n";
1280 +
1281 + /*
1282 + * Information boxes.
1283 + */
1284 + if ( ! empty( $side_col_boxes ) ) {
1285 +
1286 + echo '<div id="side-column">' . "\n";
1287 +
1288 + foreach ( $side_col_boxes as $box ) {
1289 +
1290 + echo '<table class="sucom-settings wpsso side-box">' . "\n";
1291 + echo '<tr><td>' . "\n";
1292 + echo $box;
1293 + echo '</td></tr>' . "\n";
1294 + echo '</table><!-- .side-box -->' . "\n";
1295 + }
1296 +
1297 + echo '</div><!-- #side-column -->' . "\n";
1298 + }
1299 +
1300 + echo '</div><!-- #wpsso-setting-page-content -->' . "\n";
1301 + echo '</div><!-- #' . $this->pagehook .' -->' . "\n";
1302 +
1303 + /*
1304 + * The type="text/javascript" attribute is unnecessary for JavaScript resources and creates warnings in the W3C validator.
1305 + */
1306 + ?><script>
1307 +
1308 + jQuery( function(){
1309 +
1310 + jQuery( '.if-js-closed' ).removeClass( 'if-js-closed' ).addClass( 'closed' ); // Postboxes that should be closed.
1311 +
1312 + postboxes.add_postbox_toggles( '<?php echo $this->pagehook; ?>' ); // Postboxes setup.
1313 + });
1314 +
1315 + </script><?php
1316 + }
1317 +
1318 + public function profile_updated_redirect( $url, $status ) {
1319 +
1320 + if ( false !== strpos( $url, 'updated=' ) && strpos( $url, 'wp_http_referer=' ) ) {
1321 +
1322 + /*
1323 + * Match WordPress behavior (users page for admins, profile page for everyone else).
1324 + */
1325 + $menu_lib = current_user_can( 'list_users' ) ? 'users' : 'profile';
1326 + $parent_slug = $this->p->cf[ 'wp' ][ 'admin' ][ $menu_lib ][ 'page' ];
1327 + $referer_match = '/' . $parent_slug . '?page=wpsso-';
1328 +
1329 + /*
1330 + * Parses string as if it were the query string passed via a URL and sets variables in the current
1331 + * scope (or in the array if result is provided).
1332 + *
1333 + * See https://www.php.net/manual/en/function.parse-str.php.
1334 + */
1335 + parse_str( wp_parse_url( $url, PHP_URL_QUERY ), $parts );
1336 +
1337 + if ( strpos( $parts[ 'wp_http_referer' ], $referer_match ) ) {
1338 +
1339 + // translators: Please ignore - translation uses a different text domain.
1340 + $this->p->notice->upd( __( 'Profile updated.' ) );
1341 +
1342 + $url = add_query_arg( 'updated', true, $parts[ 'wp_http_referer' ] );
1343 + }
1344 + }
1345 +
1346 + return $url;
1347 + }
1348 +
1349 + protected function show_post_body_settings_form() {
1350 +
1351 + $menu_hookname = SucomUtil::sanitize_hookname( $this->menu_id );
1352 + $form_css_id = 'wpsso_settings_form_' . $menu_hookname;
1353 +
1354 + switch ( $this->menu_lib ) {
1355 +
1356 + case 'profile':
1357 +
1358 + $user_id = get_current_user_id();
1359 + $user_obj = get_user_to_edit( $user_id );
1360 + $admin_color = get_user_option( 'admin_color', $user_id ); // Note that $user_id is the second argument.
1361 +
1362 + if ( empty( $admin_color ) ) {
1363 +
1364 + $admin_color = 'fresh';
1365 + }
1366 +
1367 + /*
1368 + * Match WordPress behavior (users page for admins, profile page for everyone else).
1369 + */
1370 + $referer_admin_url = current_user_can( 'list_users' ) ?
1371 + $this->p->util->get_admin_url( $this->menu_id, null, 'users' ) :
1372 + $this->p->util->get_admin_url( $this->menu_id, null, $this->menu_lib );
1373 +
1374 + echo '<form name="wpsso" id="' . $form_css_id . '" action="user-edit.php" method="post">' . "\n";
1375 + echo '<input type="hidden" name="wp_http_referer" value="' . $referer_admin_url . '" />' . "\n";
1376 + echo '<input type="hidden" name="action" value="update" />' . "\n";
1377 + echo '<input type="hidden" name="user_id" value="' . $user_id . '" />' . "\n";
1378 + echo '<input type="hidden" name="nickname" value="' . $user_obj->nickname . '" />' . "\n";
1379 + echo '<input type="hidden" name="email" value="' . $user_obj->user_email . '" />' . "\n";
1380 + echo '<input type="hidden" name="admin_color" value="' . $admin_color . '" />' . "\n";
1381 + echo '<input type="hidden" name="rich_editing" value="' . $user_obj->rich_editing . '" />' . "\n";
1382 + echo '<input type="hidden" name="comment_shortcuts" value="' . $user_obj->comment_shortcuts . '" />' . "\n";
1383 + echo '<input type="hidden" name="admin_bar_front" value="' . _get_admin_bar_pref( 'front', $user_id ) . '" />' . "\n";
1384 +
1385 + wp_nonce_field( 'update-user_' . $user_id );
1386 +
1387 + break;
1388 +
1389 + case 'dashboard':
1390 + case 'plugins':
1391 + case 'settings':
1392 + case 'submenu':
1393 + case 'tools':
1394 +
1395 + echo '<form name="wpsso" id="' . $form_css_id . '" action="options.php" method="post">' . "\n";
1396 +
1397 + settings_fields( 'wpsso_settings' );
1398 +
1399 + break;
1400 +
1401 + case 'sitesubmenu':
1402 +
1403 + echo '<form name="wpsso" id="' . $form_css_id . '" action="edit.php?action=' . WPSSO_SITE_OPTIONS_NAME . '" method="post">' . "\n";
1404 + echo '<input type="hidden" name="page" value="' . $this->menu_id . '" />' . "\n";
1405 +
1406 + break;
1407 +
1408 + default:
1409 +
1410 + return;
1411 + }
1412 +
1413 + echo "\n" . '<!-- wpsso nonce fields -->' . "\n";
1414 +
1415 + wp_nonce_field( WpssoAdmin::get_nonce_action(), WPSSO_NONCE_NAME );
1416 +
1417 + echo "\n";
1418 +
1419 + wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false );
1420 +
1421 + echo "\n";
1422 +
1423 + wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false );
1424 +
1425 + echo "\n";
1426 +
1427 + do_meta_boxes( $this->pagehook, $context = 'normal', $object = null );
1428 +
1429 + do_action( 'wpsso_form_content_metaboxes_' . $menu_hookname, $this->pagehook );
1430 +
1431 + echo $this->get_form_buttons();
1432 +
1433 + echo '</form>', "\n";
1434 + }
1435 +
1436 + protected function get_side_col_boxes() {
1437 +
1438 + $col_boxes = array();
1439 + $pkg_info = $this->p->util->get_pkg_info(); // Uses a local cache.
1440 + $icon_px = 128;
1441 +
1442 + foreach ( $this->p->cf[ 'plugin' ] as $ext => $info ) {
1443 +
1444 + if ( empty( $info[ 'version' ] ) ) { // Exclude add-ons that are not active.
1445 +
1446 + continue;
1447 +
1448 + } elseif ( empty( $info[ 'url' ][ 'purchase' ] ) ) {
1449 +
1450 + continue;
1451 +
1452 + } elseif ( $pkg_info[ $ext ][ 'pp' ] ) {
1453 +
1454 + continue;
1455 + }
1456 +
1457 + $box = '<div class="side-box-header">' . "\n";
1458 + // translators: %s is the Premium add-on short name.
1459 + $box .= '<h2>' . sprintf( __( 'Upgrade to %s', 'wpsso' ), $pkg_info[ $ext ][ 'short_pro' ] ) . '</h2>' . "\n";
1460 + $box .= '</div><!-- .side-box-header -->' . "\n";
1461 +
1462 + $box .= '<div class="side-box-icon">' . "\n";
1463 + $box .= $this->get_ext_img_icon( $ext, $icon_px ) . "\n";
1464 + $box .= '</div><!-- .side-box-icon -->' . "\n";
1465 +
1466 + $box .= '<div class="side-box-content has-buttons">' . "\n";
1467 + $box .= $this->p->msgs->get( 'column-purchase-' . $ext, $info ) . "\n";
1468 + $box .= '</div><!-- .side-box-content -->' . "\n";
1469 +
1470 + $box .= '<div class="side-box-buttons">' . "\n";
1471 + $box .= $this->form->get_button( sprintf( _x( 'Get %s', 'submit button', 'wpsso' ), $pkg_info[ $ext ][ 'short_pro' ] ),
1472 + 'button-secondary', 'column-purchase', $info[ 'url' ][ 'purchase' ], true ) . "\n";
1473 + $box .= '</div><!-- .side-box-buttons -->' . "\n";
1474 +
1475 + $col_boxes[] = $box;
1476 + }
1477 +
1478 + return $col_boxes;
1479 + }
1480 +
1481 + /*
1482 + * Called by WpssoAdmin->show_post_body_settings_form().
1483 + * Called by WpssoSubmenuTools->show_post_body_settings_form().
1484 + */
1485 + protected function get_form_buttons() {
1486 +
1487 + $this->add_form_buttons_submit( $form_button_rows );
1488 + $this->add_form_buttons_change_show_options( $form_button_rows );
1489 + $this->add_form_buttons( $form_button_rows );
1490 +
1491 + $filter_name = 'wpsso_form_button_rows';
1492 +
1493 + if ( $this->p->debug->enabled ) {
1494 +
1495 + $this->p->debug->log( 'applying filters "' . $filter_name . '"' );
1496 + }
1497 +
1498 + $form_button_rows = apply_filters( $filter_name, $form_button_rows, $this->menu_id );
1499 +
1500 + $buttons_html = '';
1501 + $button_row_num = 1;
1502 +
1503 + foreach ( $form_button_rows as $key => $buttons_row ) {
1504 +
1505 + if ( $button_row_num >= 3 ) {
1506 +
1507 + $css_class = 'button-secondary'; // Third+ row.
1508 +
1509 + } elseif ( $button_row_num >= 2 ) {
1510 +
1511 + $css_class = 'button-secondary button-alt'; // Second row.
1512 +
1513 + } else $css_class = 'button-secondary button-highlight'; // First row.
1514 +
1515 + $buttons_html .= '<div class="submit-buttons">';
1516 +
1517 + foreach ( $buttons_row as $action_value => $mixed ) {
1518 +
1519 + if ( empty( $action_value ) || empty( $mixed ) ) { // Just in case.
1520 +
1521 + continue;
1522 + }
1523 +
1524 + if ( is_string( $mixed ) ) {
1525 +
1526 + if ( 'submit' === $action_value ) {
1527 +
1528 + $buttons_html .= $this->form->get_submit( $mixed, 'button-primary' );
1529 +
1530 + } else {
1531 +
1532 + $action_url = $this->p->util->get_admin_url( '?wpsso-action=' . $action_value );
1533 + $button_url = wp_nonce_url( $action_url, WpssoAdmin::get_nonce_action(), WPSSO_NONCE_NAME );
1534 + $buttons_html .= $this->form->get_button( $mixed, $css_class, '', $button_url );
1535 + }
1536 +
1537 + } elseif ( is_array( $mixed ) ) {
1538 +
1539 + if ( ! empty( $mixed[ 'html' ] ) ) {
1540 +
1541 + $buttons_html .= $mixed[ 'html' ];
1542 + }
1543 + }
1544 + }
1545 +
1546 + $buttons_html .= '</div>';
1547 +
1548 + $button_row_num++;
1549 + }
1550 +
1551 + return $buttons_html;
1552 + }
1553 +
1554 + protected function add_form_buttons_submit( &$form_button_rows ) {
1555 +
1556 + if ( $this->menu_lib === 'profile' ) {
1557 +
1558 + $submit_label_transl = _x( 'Save Profile Settings', 'submit button', 'wpsso' );
1559 +
1560 + } else $submit_label_transl = _x( 'Save Plugin Settings', 'submit button', 'wpsso' );
1561 +
1562 + $form_button_rows = array(
1563 + array(
1564 + 'submit' => $submit_label_transl,
1565 + ),
1566 + );
1567 + }
1568 +
1569 + protected function add_form_buttons_change_show_options( &$form_button_rows ) {
1570 +
1571 + $change_show_next_key = SucomUtil::next_key( WpssoUser::show_opts(), $this->p->cf[ 'form' ][ 'show_options' ] );
1572 + $change_show_name_transl = _x( $this->p->cf[ 'form' ][ 'show_options' ][ $change_show_next_key ], 'option value', 'wpsso' );
1573 + $change_show_label_transl = sprintf( _x( 'Change to "%s" View', 'submit button', 'wpsso' ), $change_show_name_transl );
1574 +
1575 + $form_button_rows[ 0 ][ 'change_show_options&show-opts=' . $change_show_next_key ] = $change_show_label_transl;
1576 + }
1577 +
1578 + protected function add_form_buttons( &$form_button_rows ) {
1579 + }
1580 +
1581 + /*
1582 + * Callback method must be public for add_meta_box() hook.
1583 + *
1584 + * See WpssoAdmin->add_settings_page_metaboxes().
1585 + */
1586 + public function show_metabox_table( $obj, $mb ) {
1587 +
1588 + $args = isset( $mb[ 'args' ] ) ? $mb[ 'args' ] : array();
1589 + $page_id = isset( $args[ 'page_id' ] ) ? $args[ 'page_id' ] : '';
1590 + $metabox_id = isset( $args[ 'metabox_id' ] ) ? $args[ 'metabox_id' ] : '';
1591 + $filter_prefix = 'wpsso_mb_' . $page_id . '_' . $metabox_id;
1592 + $filter_name = SucomUtil::sanitize_hookname( $filter_prefix . '_rows' );
1593 + $table_rows = $this->get_table_rows( $page_id, $metabox_id, $tab_key = '', $args );
1594 +
1595 + if ( $this->p->debug->enabled ) {
1596 +
1597 + $this->p->debug->log( 'applying filters "' . $filter_name . '"' );
1598 + }
1599 +
1600 + $table_rows = apply_filters( $filter_name, $table_rows, $this->form, $args );
1601 +
1602 + $this->p->util->metabox->do_table( $table_rows, 'metabox-' . $page_id . '-' . $metabox_id );
1603 + }
1604 +
1605 + /*
1606 + * Since WPSSO Core v17.0.0.
1607 + */
1608 + protected function show_metabox_tabbed( $obj, $mb, array $tabs ) {
1609 +
1610 + $args = isset( $mb[ 'args' ] ) ? $mb[ 'args' ] : array();
1611 + $page_id = isset( $args[ 'page_id' ] ) ? $args[ 'page_id' ] : '';
1612 + $metabox_id = isset( $args[ 'metabox_id' ] ) ? $args[ 'metabox_id' ] : '';
1613 + $filter_prefix = 'wpsso_mb_' . $page_id . '_' . $metabox_id;
1614 + $filter_name = SucomUtil::sanitize_hookname( $filter_prefix . '_tabs' );
1615 +
1616 + if ( $this->p->debug->enabled ) {
1617 +
1618 + $this->p->debug->log( 'applying filters "' . $filter_name . '"' );
1619 + }
1620 +
1621 + $tabs = apply_filters( $filter_name, $tabs );
1622 +
1623 + if ( empty( $tabs ) ) {
1624 +
1625 + return $this->show_metabox_table( $obj, $mb );
1626 + }
1627 +
1628 + $table_rows = array();
1629 +
1630 + foreach ( $tabs as $tab_key => $tab_title ) {
1631 +
1632 + $table_rows[ $tab_key ] = $this->get_table_rows( $page_id, $metabox_id, $tab_key, $args );
1633 +
1634 + $filter_name = SucomUtil::sanitize_hookname( $filter_prefix . '_' . $tab_key . '_rows' );
1635 +
1636 + if ( $this->p->debug->enabled ) {
1637 +
1638 + $this->p->debug->log( 'applying filters "' . $filter_name . '"' );
1639 + }
1640 +
1641 + $table_rows[ $tab_key ] = apply_filters( $filter_name, $table_rows[ $tab_key ], $this->form, $args );
1642 + }
1643 +
1644 + $this->p->util->metabox->do_tabbed( $metabox_id, $tabs, $table_rows );
1645 + }
1646 +
1647 + /*
1648 + * This method is extended by each submenu page with additional arguments:
1649 + *
1650 + * get_table_rows( $page_id, $metabox_id, $tab_key = '', $args = array() );
1651 + */
1652 + protected function get_table_rows( $page_id, $metabox_id ) {
1653 +
1654 + return array();
1655 + }
1656 +
1657 + public function get_table_rows_cols( array $table_cells, $row_cols = 2 ) {
1658 +
1659 + sort( $table_cells );
1660 +
1661 + $per_col = ceil( count( $table_cells ) / $row_cols );
1662 +
1663 + $table_rows = array();
1664 +
1665 + foreach ( $table_cells as $num => $cell ) {
1666 +
1667 + if ( empty( $table_rows[ $num % $per_col ] ) ) { // Initialize the array element.
1668 +
1669 + $table_rows[ $num % $per_col ] = '';
1670 + }
1671 +
1672 + $table_rows[ $num % $per_col ] .= $cell; // Create the html for each row.
1673 + }
1674 +
1675 + return $table_rows;
1676 + }
1677 +
1678 + /*
1679 + * Always call as WpssoAdmin::get_nonce_action() to have a reliable __METHOD__ value.
1680 + */
1681 + public static function get_nonce_action() {
1682 +
1683 + $salt = __FILE__ . __LINE__ . __METHOD__;
1684 +
1685 + return md5( $salt );
1686 + }
1687 +
1688 + private function is_plugin_found() {
1689 +
1690 + if ( ! SucomPlugin::is_plugin_active( 'wpsso/wpsso.php' ) ) {
1691 +
1692 + $pkg_info = $this->p->util->get_pkg_info(); // Uses a local cache.
1693 + $notice_msg = sprintf( __( 'The %s plugin is not active and/or not found.', 'wpsso' ), $pkg_info[ 'wpsso' ][ 'name' ] );
1694 + $notice_key = 'wpsso-not-found';
1695 +
1696 + $this->p->notice->err( $notice_msg, $user_id = null, $notice_key );
1697 +
1698 + return false;
1699 + }
1700 +
1701 + return true;
1702 + }
1703 +
1704 + private function is_settings( $menu_id = false ) {
1705 +
1706 + return $this->is_lib( 'settings', $menu_id );
1707 + }
1708 +
1709 + private function is_lib( $lib_name, $menu_id = false ) {
1710 +
1711 + if ( false === $menu_id ) {
1712 +
1713 + $menu_id = $this->menu_id;
1714 + }
1715 +
1716 + return isset( $this->p->cf[ '*' ][ 'lib' ][ $lib_name ][ $menu_id ] ) ? true : false;
1717 + }
1718 +
1719 + public function add_admin_bar_menu_notices( $wp_admin_bar ) {
1720 +
1721 + $menu_icon = '<span class="ab-icon" id="wpsso-toolbar-notices-icon"></span>';
1722 + $menu_count = '<span class="ab-label" id="wpsso-toolbar-notices-count">0</span>';
1723 + $menu_text = sprintf( __( 'Fetching %s notifications...', 'wpsso' ), $this->get_menu_title() );
1724 +
1725 + $wp_admin_bar->add_node( array(
1726 + 'id' => 'wpsso-toolbar-notices',
1727 + 'title' => $menu_icon . $menu_count,
1728 + 'parent' => false,
1729 + 'href' => false,
1730 + 'group' => false,
1731 + 'meta' => array(),
1732 + ) );
1733 +
1734 + $wp_admin_bar->add_node( array(
1735 + 'id' => 'wpsso-toolbar-notices-container',
1736 + 'title' => $menu_text,
1737 + 'parent' => 'wpsso-toolbar-notices',
1738 + 'href' => false,
1739 + 'group' => false,
1740 + 'meta' => array(),
1741 + ) );
1742 + }
1743 +
1744 + public function check_admin_bar_menu_notices( $wp_admin_bar ) {
1745 +
1746 + if ( ! $wp_admin_bar->get_node( 'wpsso-toolbar-notices' ) ) {
1747 +
1748 + $this->toolbar_notice_types = array();
1749 + }
1750 + }
1751 +
1752 + /*
1753 + * $toolbar_notice_types may be false if is_admin_bar_showing() is false.
1754 + */
1755 + public function filter_toolbar_notice_types( $toolbar_notice_types ) {
1756 +
1757 + return is_array( $toolbar_notice_types ) ? $this->toolbar_notice_types : $toolbar_notice_types;
1758 + }
1759 +
1760 + public function admin_footer_ext( $footer_html ) {
1761 +
1762 + if ( SucomUtil::get_const( 'WPSSO_ADMIN_FOOTER_DISABLE' ) ) {
1763 +
1764 + return '';
1765 + }
1766 +
1767 + $pkg_info = $this->p->util->get_pkg_info(); // Uses a local cache.
1768 + $footer_html = '<div class="admin-footer-ext">';
1769 +
1770 + if ( isset( $pkg_info[ $this->menu_ext ][ 'name_pkg' ] ) ) {
1771 +
1772 + $footer_html .= $pkg_info[ $this->menu_ext ][ 'name_pkg' ] . '<br/>';
1773 + }
1774 +
1775 + if ( isset( $pkg_info[ $this->menu_ext ][ 'gen' ] ) ) {
1776 +
1777 + $footer_html .= $pkg_info[ $this->menu_ext ][ 'gen' ] . '<br/>';
1778 + }
1779 +
1780 + $footer_html .= '</div>';
1781 +
1782 + return $footer_html;
1783 + }
1784 +
1785 + public function admin_footer_host( $footer_html ) {
1786 +
1787 + if ( SucomUtil::get_const( 'WPSSO_ADMIN_FOOTER_DISABLE' ) ) {
1788 +
1789 + return '';
1790 + }
1791 +
1792 + global $wp_version;
1793 +
1794 + $home_url = SucomUtilWP::raw_get_home_url();
1795 + $home_path = preg_replace( '/^[a-z]+:\/\//i', '', $home_url );
1796 +
1797 + $footer_html = '<div class="admin-footer-host">';
1798 + $footer_html .= $home_path . '<br/>';
1799 + $footer_html .= 'WordPress ' . $wp_version . '<br/>';
1800 + $footer_html .= 'PHP ' . phpversion() . '<br/>';
1801 + $footer_html .= '</div>';
1802 +
1803 + return $footer_html;
1804 + }
1805 +
1806 + /*
1807 + * WordPress sorts the active plugins array before updating the 'active_plugins' option. The default PHP sort order
1808 + * loads WPSSO add-ons before the WPSSO Core plugin. This filter re-sorts (if necessary) the active plugins array
1809 + * to load WPSSO Core before its add-ons. This allows WPSSO Core to load the latest WpssoAbstractAddOn and
1810 + * SucomAbstractAddon classes (for example) before any (possibly older) add-on does.
1811 + *
1812 + * When activating a plugin, the activate_plugin() function executes the following:
1813 + *
1814 + * $current = get_option( 'active_plugins', array() ); // Get current plugins array.
1815 + * $current[] = $plugin; // Add the new plugin.
1816 + * sort( $current ); // Sort the plugin array.
1817 + * update_option( 'active_plugins', $current ); // Save the plugin array.
1818 + *
1819 + * See the activate_plugin() function in wordpress/wp-admin/includes/plugin.php for additional context.
1820 + */
1821 + public function pre_update_active_plugins( $current, $old_value, $option = 'active_plugins' ) {
1822 +
1823 + usort( $current, array( __CLASS__, 'sort_active_plugins' ) );
1824 +
1825 + return $current;
1826 + }
1827 +
1828 + /*
1829 + * Since WPSSO Core v13.4.1.
1830 + */
1831 + public static function get_option_unit_comment( $opt_key ) {
1832 +
1833 + if ( $unit_text = WpssoUtilUnits::get_mixed_label( $opt_key ) ) {
1834 +
1835 + return ' ' . sprintf( _x( 'in %s', 'option comment', 'wpsso' ), $unit_text );
1836 + }
1837 +
1838 + return '';
1839 + }
1840 +
1841 + public static function get_option_site_use( $name, $form, $network = false, $is_enabled = false ) {
1842 +
1843 + $html = '';
1844 +
1845 + if ( $network ) {
1846 +
1847 + $wpsso =& Wpsso::get_instance();
1848 + $pkg_info = $wpsso->util->get_pkg_info(); // Uses a local cache.
1849 + $is_enabled = $is_enabled || $pkg_info[ 'wpsso' ][ 'pp' ] ? true : false;
1850 +
1851 + $html .= $form->get_th_html( _x( 'Site Use', 'option label (very short)', 'wpsso' ), 'site-use' );
1852 + $html .= $is_enabled ? '<td class="site-use">' : '<td class="blank site-use">';
1853 + $html .= $is_enabled ? $form->get_select( $name . ':use', WpssoConfig::$cf[ 'form' ][ 'site_option_use' ], $css_class = 'site-use' ) :
1854 + $form->get_no_select( $name . ':use', WpssoConfig::$cf[ 'form' ][ 'site_option_use' ], $css_class = 'site-use' );
1855 + $html .= '</td>';
1856 + }
1857 +
1858 + return $html;
1859 + }
1860 +
1861 + public function plugin_complete_actions( $actions ) {
1862 +
1863 + if ( ! empty( $this->pageref_url ) && ! empty( $this->pageref_title ) ) {
1864 +
1865 + foreach ( $actions as $action => &$html ) {
1866 +
1867 + switch ( $action ) {
1868 +
1869 + case 'plugins_page':
1870 +
1871 + $html = '<a href="' . $this->pageref_url . '" target="_parent">' .
1872 + sprintf( __( 'Return to %s', 'wpsso' ), $this->pageref_title ) . '</a>';
1873 +
1874 + break;
1875 +
1876 + default:
1877 +
1878 + if ( preg_match( '/^(.*href=["\'])([^"\']+)(["\'].*)$/', $html, $matches ) ) {
1879 +
1880 + $url = add_query_arg( array(
1881 + 'wpsso_pageref_url' => urlencode( $this->pageref_url ),
1882 + 'wpsso_pageref_title' => urlencode( $this->pageref_title ),
1883 + ), $matches[ 2 ] );
1884 +
1885 + $html = $matches[ 1 ] . $url . $matches[ 3 ];
1886 + }
1887 +
1888 + break;
1889 + }
1890 + }
1891 + }
1892 +
1893 + return $actions;
1894 + }
1895 +
1896 + public function plugin_complete_redirect( $url ) {
1897 +
1898 + if ( strpos( $url, '?activate=true' ) ) {
1899 +
1900 + if ( ! empty( $this->pageref_url ) ) {
1901 +
1902 + // translators: Please ignore - translation uses a different text domain.
1903 + $this->p->notice->upd( __( 'Plugin <strong>activated</strong>.' ) );
1904 +
1905 + $url = $this->pageref_url;
1906 + }
1907 + }
1908 +
1909 + return $url;
1910 + }
1911 +
1912 + public function get_check_for_updates_link( $get_notice = true ) {
1913 +
1914 + $check_url = '';
1915 + $notice_msg = '';
1916 +
1917 + if ( class_exists( 'WpssoUm' ) ) {
1918 +
1919 + $pkg_info = $this->p->util->get_pkg_info(); // Uses a local cache.
1920 + $check_url = $this->p->util->get_admin_url( 'um-general?wpsso-action=check_for_updates' );
1921 + $check_url = wp_nonce_url( $check_url, WpssoAdmin::get_nonce_action(), WPSSO_NONCE_NAME );
1922 +
1923 + // translators: %1$s is the URL, %2$s is the short plugin name.
1924 + $notice_transl = __( 'You may <a href="%1$s">refresh the update information for %2$s and its add-ons</a> to check if newer versions are available.', 'wpsso' );
1925 +
1926 + $notice_msg = sprintf( $notice_transl, $check_url, $pkg_info[ 'wpsso' ][ 'short_pkg' ] );
1927 +
1928 + } elseif ( empty( $_GET[ 'force-check' ] ) ) {
1929 +
1930 + $check_url = self_admin_url( 'update-core.php?force-check=1' );
1931 +
1932 + // translators: %1$s is the URL.
1933 + $notice_transl = __( 'You may <a href="%1$s">refresh the update information for WordPress (plugins, themes and translations)</a> to check if newer versions are available.', 'wpsso' );
1934 +
1935 + $notice_msg = sprintf( $notice_transl, $check_url );
1936 + }
1937 +
1938 + return $get_notice ? $notice_msg : $check_url;
1939 + }
1940 +
1941 + /*
1942 + * Called from the network settings pages.
1943 + *
1944 + * Add a class to set a minimum width for the network postboxes.
1945 + */
1946 + public function add_class_postbox_network( $classes ) {
1947 +
1948 + $classes[] = 'postbox-network';
1949 +
1950 + return $classes;
1951 + }
1952 +
1953 + public function add_class_postbox_menu_id( $classes ) {
1954 +
1955 + global $wp_current_filter;
1956 +
1957 + $filter_name = end( $wp_current_filter );
1958 + $postbox_name = preg_replace( '/^.*-(' . $this->menu_id . '_.*)$/u', '$1', $filter_name );
1959 +
1960 + $classes[] = 'postbox-' . $this->menu_id;
1961 + $classes[] = 'postbox-' . $postbox_name;
1962 +
1963 + return $classes;
1964 + }
1965 +
1966 + public function export_plugin_settings_json() {
1967 +
1968 + $date_slug = date( 'YmdHiT' );
1969 + $home_slug = SucomUtil::sanitize_hookname( preg_replace( '/^.*\/\//', '', get_home_url() ) );
1970 + $mime_type_gz = 'application/x-gzip';
1971 + $file_name_gz = WpssoConfig::get_version( $add_slug = true ) . '-' . $home_slug . '-' . $date_slug . '.json.gz';
1972 + $opts_encoded = wp_json_encode( $this->p->options );
1973 + $gzdata = gzencode( $opts_encoded, 9, FORCE_GZIP );
1974 + $filesize = strlen( $gzdata );
1975 + $disposition = 'attachment';
1976 + $chunksize = 1024 * 32; // 32kb per fread().
1977 +
1978 + session_write_close();
1979 +
1980 + ignore_user_abort();
1981 +
1982 + set_time_limit( 300 ); // 5 minutes.
1983 +
1984 + ini_set( 'zlib.output_compression', 0 );
1985 +
1986 + if ( function_exists( 'apache_setenv' ) ) {
1987 +
1988 + apache_setenv( 'no-gzip', 1 );
1989 + }
1990 +
1991 + ob_implicit_flush( true );
1992 + ob_end_flush();
1993 +
1994 + /*
1995 + * Remove all dots, except last one, for MSIE clients.
1996 + */
1997 + if ( strstr( $_SERVER[ 'HTTP_USER_AGENT' ], 'MSIE' ) ) {
1998 +
1999 + $file_name_gz = preg_replace( '/\./', '%2e', $file_name_gz, substr_count( $file_name_gz, '.' ) - 1 );
2000 + }
2001 +
2002 + if ( isset( $_SERVER[ 'HTTPS' ] ) ) {
2003 +
2004 + $gmt_date = gmdate( 'D, d M Y H:i:s' ) . ' GMT';
2005 +
2006 + header( 'Pragma: ' );
2007 + header( 'Cache-Control: ' );
2008 + header( 'Expires: ' . $gmt_date );
2009 + header( 'Last-Modified: ' . $gmt_date );
2010 + header( 'Cache-Control: no-store, no-cache, must-revalidate' );
2011 + header( 'Cache-Control: post-check=0, pre-check=0', false );
2012 +
2013 + } elseif ( $disposition == 'attachment' ) {
2014 +
2015 + header( 'Cache-control: private' );
2016 +
2017 + } else {
2018 +
2019 + header( 'Cache-Control: no-cache, must-revalidate' );
2020 + header( 'Pragma: no-cache' );
2021 + }
2022 +
2023 + header( 'Content-Type: application/' . $mime_type_gz );
2024 + header( 'Content-Disposition: ' . $disposition . '; filename="' . $file_name_gz . '"' );
2025 + header( 'Content-Transfer-Encoding: binary' );
2026 + header( 'Content-Length: ' . $filesize );
2027 +
2028 + echo $gzdata;
2029 +
2030 + flush();
2031 +
2032 + sleep( 1 );
2033 +
2034 + exit();
2035 + }
2036 +
2037 + public function import_plugin_settings_json() {
2038 +
2039 + $mime_type_gz = 'application/x-gzip';
2040 + $dot_file_ext = '.json.gz';
2041 + $max_file_size = 100000; // 100K.
2042 +
2043 + if ( ! isset( $_FILES[ 'file' ][ 'error' ] ) ) {
2044 +
2045 + if ( $this->p->debug->enabled ) {
2046 +
2047 + $this->p->debug->log( 'incomplete post method upload' );
2048 + }
2049 +
2050 + return false;
2051 +
2052 + } elseif ( $_FILES[ 'file' ][ 'error' ] === UPLOAD_ERR_NO_FILE ) {
2053 +
2054 + $this->p->notice->err( sprintf( __( 'Please select a %1$s settings file to import.', 'wpsso' ), $dot_file_ext ) );
2055 +
2056 + return false;
2057 +
2058 + } elseif ( $_FILES[ 'file' ][ 'type' ] !== 'application/x-gzip' ) {
2059 +
2060 + $this->p->notice->err( sprintf( __( 'The %1$s settings file to import must be an "%2$s" mime type.', 'wpsso' ),
2061 + $dot_file_ext, $mime_type_gz ) );
2062 +
2063 + return false;
2064 +
2065 + } elseif ( $_FILES[ 'file' ][ 'size' ] > $max_file_size ) { // Just in case.
2066 +
2067 + $this->p->notice->err( sprintf( __( 'The %1$s settings file is larger than the maximum of %2$d bytes allowed.', 'wpsso' ),
2068 + $dot_file_ext, $max_file_size ) );
2069 +
2070 + return false;
2071 + }
2072 +
2073 + $gzdata = file_get_contents( $_FILES[ 'file' ][ 'tmp_name' ] );
2074 +
2075 + @unlink( $_FILES[ 'file' ][ 'tmp_name' ] );
2076 +
2077 + $opts_encoded = gzdecode( $gzdata );
2078 +
2079 + if ( empty( $opts_encoded ) ) { // false or empty array.
2080 +
2081 + $this->p->notice->err( sprintf( __( 'The %1$s settings file is appears to be empty or corrupted.', 'wpsso' ), $dot_file_ext ) );
2082 +
2083 + return false;
2084 + }
2085 +
2086 + $opts = json_decode( $opts_encoded, $assoc = true );
2087 +
2088 + if ( empty( $opts ) || ! is_array( $opts ) ) { // false or empty array.
2089 +
2090 + $this->p->notice->err( sprintf( __( 'The %1$s settings file could not be decoded into a settings array.', 'wpsso' ), $dot_file_ext ) );
2091 +
2092 + return false;
2093 + }
2094 +
2095 + $this->p->options = $this->p->opt->check_options( WPSSO_OPTIONS_NAME, $opts );
2096 +
2097 + $this->p->opt->save_options( WPSSO_OPTIONS_NAME, $opts, $network = false );
2098 +
2099 + $this->p->notice->upd( __( 'Import of plugin and add-on settings is complete.', 'wpsso' ) );
2100 +
2101 + return true;
2102 + }
2103 +
2104 + public function get_menu_title() {
2105 +
2106 + $menu_title = _x( $this->p->cf[ 'menu' ][ 'title' ], 'menu title', 'wpsso' );
2107 +
2108 + $filter_name = 'wpsso_menu_title';
2109 +
2110 + if ( $this->p->debug->enabled ) {
2111 +
2112 + $this->p->debug->log( 'applying filters "' . $filter_name . '"' );
2113 + }
2114 +
2115 + return apply_filters( 'wpsso_menu_title', $menu_title );
2116 + }
2117 +
2118 + public function get_menu_dashicon_html( $menu_id, $css_class = '' ) {
2119 +
2120 + $dashicon = $this->get_menu_dashicon( $menu_id );
2121 +
2122 + return '<div class="' . trim( $css_class . ' dashicons-before dashicons-' . $dashicon ) . '"></div>';
2123 + }
2124 +
2125 + public function get_menu_dashicon( $menu_id ) {
2126 +
2127 + $dashicon = 'admin-generic';
2128 +
2129 + if ( ! empty( $this->p->cf[ 'menu' ][ 'dashicons' ][ $menu_id ] ) ) {
2130 +
2131 + $dashicon = $this->p->cf[ 'menu' ][ 'dashicons' ][ $menu_id ];
2132 +
2133 + } elseif ( ! empty( $this->p->cf[ 'menu' ][ 'dashicons' ][ '*' ] ) ) {
2134 +
2135 + $dashicon = $this->p->cf[ 'menu' ][ 'dashicons' ][ '*' ];
2136 + }
2137 +
2138 + return $dashicon;
2139 + }
2140 +
2141 + public function get_submenu_title( $info, $menu_lib, $menu_id ) {
2142 +
2143 + $menu_title = '';
2144 +
2145 + if ( ! empty( $info[ 'lib' ][ $menu_lib ][ $menu_id ] ) ) { // Just in case.
2146 +
2147 + $menu_title = $info[ 'lib' ][ $menu_lib ][ $menu_id ];
2148 + }
2149 +
2150 + if ( ! empty( $info[ 'text_domain' ] ) ) {
2151 +
2152 + $menu_title = _x( $menu_title, 'lib file description', $info[ 'text_domain' ] );
2153 + }
2154 +
2155 + /*
2156 + * Example $filter_name = 'wpsso_menu_your_sso_title'
2157 + */
2158 + $filter_name = SucomUtil::sanitize_hookname( 'wpsso_menu_' . $menu_id . '_title' );
2159 +
2160 + if ( $this->p->debug->enabled ) {
2161 +
2162 + $this->p->debug->log( 'applying filters "' . $filter_name . '"' );
2163 + }
2164 +
2165 + return apply_filters( $filter_name, $menu_title );
2166 + }
2167 +
2168 + /*
2169 + * $menu_lib = 'dashboard', 'plugins', 'profile', 'settings', 'submenu', 'sitesubmenu', 'tools', or 'users'
2170 + */
2171 + public function get_submenu_args( $menu_lib = 'submenu' ) {
2172 +
2173 + if ( $this->p->debug->enabled ) {
2174 +
2175 + $this->p->debug->mark();
2176 + }
2177 +
2178 + if ( is_admin() ) {
2179 +
2180 + static $local_cache = array();
2181 +
2182 + if ( isset( $local_cache[ $menu_lib ] ) ) {
2183 +
2184 + return $local_cache[ $menu_lib ];
2185 + }
2186 +
2187 + } else $local_cache = array();
2188 +
2189 + $local_cache[ $menu_lib ] = array();
2190 +
2191 + $top_first_id = false;
2192 + $top_last_id = false;
2193 + $ext_first_id = false;
2194 + $ext_last_id = false;
2195 +
2196 + $sorted_menu = array();
2197 + $unsorted_menu = array();
2198 +
2199 + if ( 'profile' === $menu_lib && current_user_can( 'list_users' ) ) { // Match WordPress behavior.
2200 +
2201 + $parent_slug = $this->p->cf[ 'wp' ][ 'admin' ][ 'users' ][ 'page' ];
2202 +
2203 + } elseif ( 'submenu' === $menu_lib || 'sitesubmenu' === $menu_lib ) { // Plugin submenu.
2204 +
2205 + $parent_slug = 'wpsso-' . $this->menu_id;
2206 +
2207 + } else $parent_slug = $this->p->cf[ 'wp' ][ 'admin' ][ $menu_lib ][ 'page' ];
2208 +
2209 + foreach ( $this->p->cf[ 'plugin' ] as $ext => $info ) {
2210 +
2211 + if ( ! isset( $info[ 'lib' ][ $menu_lib ] ) ) { // Not all add-ons have submenus.
2212 +
2213 + continue;
2214 + }
2215 +
2216 + foreach ( $info[ 'lib' ][ $menu_lib ] as $menu_id => $menu_name ) {
2217 +
2218 + $menu_title = $this->get_submenu_title( $info, $menu_lib, $menu_id );
2219 +
2220 + /*
2221 + * Leave WPSSO Core submenu items in their original order.
2222 + */
2223 + if ( 'wpsso' === $ext ) {
2224 +
2225 + $unsorted_menu[] = array( $parent_slug, $menu_id, $menu_title, $menu_lib, $ext );
2226 +
2227 + if ( false === $top_first_id ) $top_first_id = $menu_id;
2228 +
2229 + $top_last_id = $menu_id;
2230 +
2231 + /*
2232 + * Sort add-on submenu items.
2233 + */
2234 + } else {
2235 +
2236 + $sorted_menu[ $menu_title . '-' . $menu_id ] = array( $parent_slug, $menu_id, $menu_title, $menu_lib, $ext );
2237 +
2238 + if ( false === $ext_first_id ) $ext_first_id = $menu_id;
2239 +
2240 + $ext_last_id = $menu_id;
2241 + }
2242 + }
2243 + }
2244 +
2245 + SucomUtil::natksort( $sorted_menu );
2246 +
2247 + foreach ( array_merge( $unsorted_menu, $sorted_menu ) as $sort_key => $args ) {
2248 +
2249 + $menu_id = $args[ 1 ];
2250 +
2251 + if ( 'submenu' === $menu_lib || 'sitesubmenu' === $menu_lib ) {
2252 +
2253 + if ( $menu_id === $top_first_id ) {
2254 +
2255 + $args[ 5 ] = 'top-first-submenu-page';
2256 +
2257 + } elseif ( $menu_id === $top_last_id ) {
2258 +
2259 + $args[ 5 ] = 'top-last-submenu-page' . ( empty( $ext_first_id ) ? ' no-add-ons' : ' with-add-ons' );
2260 +
2261 + } elseif ( $menu_id === $ext_first_id ) {
2262 +
2263 + $args[ 5 ] = 'ext-first-submenu-page';
2264 +
2265 + } elseif ( $menu_id === $ext_last_id ) {
2266 +
2267 + $args[ 5 ] = 'ext-last-submenu-page';
2268 + }
2269 + }
2270 +
2271 + $local_cache[ $menu_lib ][ $menu_id ] = $args;
2272 + }
2273 +
2274 + return $local_cache[ $menu_lib ];
2275 + }
2276 +
2277 + /*
2278 + * Get the plugin readme and convert array elements to a plugin data object.
2279 + */
2280 + public function get_plugin_data( $ext, $read_cache = true ) {
2281 +
2282 + $data = new StdClass;
2283 +
2284 + $info = $this->p->cf[ 'plugin' ][ $ext ];
2285 +
2286 + $readme = $this->get_readme_info( $ext, $read_cache );
2287 +
2288 + if ( empty( $readme ) ) { // Make sure we got something back.
2289 +
2290 + return array();
2291 + }
2292 +
2293 + foreach ( array(
2294 +
2295 + /*
2296 + * Readme array => Plugin object.
2297 + */
2298 + 'plugin_name' => 'name',
2299 + 'plugin_slug' => 'slug',
2300 + 'base' => 'plugin',
2301 + 'stable_tag' => 'version',
2302 + 'tested_up_to' => 'tested',
2303 + 'requires_at_least' => 'requires',
2304 + 'home' => 'homepage',
2305 + 'download' => 'download_link',
2306 + 'author' => 'author',
2307 + 'upgrade_notice' => 'upgrade_notice',
2308 + 'last_updated' => 'last_updated',
2309 + 'sections' => 'sections',
2310 + 'remaining_content' => 'other_notes', // Added to sections.
2311 + 'banners' => 'banners',
2312 + 'icons' => 'icons',
2313 + ) as $readme_key => $prop_name ) {
2314 +
2315 + switch ( $readme_key ) {
2316 +
2317 + case 'base': // From plugin config.
2318 +
2319 + if ( ! empty( $info[ $readme_key ] ) ) {
2320 +
2321 + $data->$prop_name = $info[ $readme_key ];
2322 + }
2323 +
2324 + break;
2325 +
2326 + case 'home': // From plugin config.
2327 +
2328 + if ( ! empty( $info[ 'url' ][ 'purchase' ] ) ) { // Check for purchase url first.
2329 +
2330 + $data->$prop_name = $info[ 'url' ][ 'purchase' ];
2331 +
2332 + } elseif ( ! empty( $info[ 'url' ][ $readme_key ] ) ) {
2333 +
2334 + $data->$prop_name = $info[ 'url' ][ $readme_key ];
2335 + }
2336 +
2337 + break;
2338 +
2339 + case 'download': // From plugin config.
2340 +
2341 + if ( ! empty( $info[ 'url' ][ $readme_key ] ) ) {
2342 +
2343 + $data->$prop_name = $info[ 'url' ][ $readme_key ];
2344 + }
2345 +
2346 + break;
2347 +
2348 + case 'banners': // From plugin config.
2349 + case 'icons': // From plugin config.
2350 +
2351 + if ( ! empty( $info[ 'assets' ][ $readme_key ] ) ) {
2352 +
2353 + $data->$prop_name = $info[ 'assets' ][ $readme_key ]; // Array with 1x / 2x images.
2354 + }
2355 +
2356 + break;
2357 +
2358 + case 'remaining_content':
2359 +
2360 + if ( ! empty( $readme[ $readme_key ] ) ) {
2361 +
2362 + $data->sections[ $prop_name ] = $readme[ $readme_key ];
2363 + }
2364 +
2365 + break;
2366 +
2367 + default:
2368 +
2369 + if ( ! empty( $readme[ $readme_key ] ) ) {
2370 +
2371 + $data->$prop_name = $readme[ $readme_key ];
2372 + }
2373 +
2374 + break;
2375 + }
2376 + }
2377 +
2378 + return $data;
2379 + }
2380 +
2381 + /*
2382 + * Return an associative array with plugin readme info.
2383 + *
2384 + * See WpssoAdmin->get_plugin_data().
2385 + * See WpssoAdminDashboard->show_metabox_version_info().
2386 + * See WpssoUmActions->action_load_settings_page_check_for_updates().
2387 + */
2388 + public function get_readme_info( $ext, $read_cache = true ) {
2389 +
2390 + if ( $this->p->debug->enabled ) {
2391 +
2392 + $this->p->debug->log_args( array(
2393 + 'ext' => $ext,
2394 + 'read_cache' => $read_cache,
2395 + ) );
2396 + }
2397 +
2398 + $rel_file = 'readme.txt';
2399 + $file_content = '';
2400 + $cache_md5_pre = 'wpsso_r_'; // 'wpsso_cache_expire_readme_info' filter.
2401 + $cache_salt = __METHOD__ . '(ext:' . $ext . ')';
2402 + $cache_id = $cache_md5_pre . md5( $cache_salt );
2403 + $cache_exp_secs = $this->p->util->get_cache_exp_secs( $cache_md5_pre, $cache_type = 'transient' );
2404 + $readme_info = array();
2405 +
2406 + /*
2407 + * Maybe get parsed readme from transient cache.
2408 + */
2409 + if ( $cache_exp_secs > 0 && $read_cache ) {
2410 +
2411 + $readme_info = get_transient( $cache_id );
2412 +
2413 + if ( ! empty( $readme_info ) && is_array( $readme_info ) ) {
2414 +
2415 + if ( $this->p->debug->enabled ) {
2416 +
2417 + $this->p->debug->log( 'readme_info retrieved from transient' );
2418 + }
2419 +
2420 + return $readme_info; // Stop here.
2421 + }
2422 +
2423 + } else delete_transient( $cache_id ); // Just in case.
2424 +
2425 + $file_content = $this->get_ext_file_content( $ext, $rel_file, $read_cache );
2426 +
2427 + if ( ! empty( $file_content ) ) {
2428 +
2429 + if ( ! class_exists( 'SuextParseReadme' ) ) {
2430 +
2431 + require_once WPSSO_PLUGINDIR . 'lib/ext/parse-readme.php';
2432 + }
2433 +
2434 + $readme_parser =& SuextParseReadme::get_instance();
2435 +
2436 + $readme_info = $readme_parser->parse_content( $file_content );
2437 + }
2438 +
2439 + /*
2440 + * Save the parsed readme to transient cache.
2441 + */
2442 + if ( $cache_exp_secs > 0 ) {
2443 +
2444 + set_transient( $cache_id, $readme_info, $cache_exp_secs );
2445 +
2446 + if ( $this->p->debug->enabled ) {
2447 +
2448 + $this->p->debug->log( 'readme_info saved to transient cache for ' . $cache_exp_secs . ' seconds' );
2449 + }
2450 + }
2451 +
2452 + return is_array( $readme_info ) ? $readme_info : array(); // Just in case.
2453 + }
2454 +
2455 + /*
2456 + * See WpssoSubmenuSetup->show_metabox_setup_guide().
2457 + */
2458 + public function get_ext_file_content( $ext, $rel_file, $read_cache = true ) {
2459 +
2460 + if ( $this->p->debug->enabled ) {
2461 +
2462 + $this->p->debug->log_args( array(
2463 + 'ext' => $ext,
2464 + 'rel_file' => $rel_file,
2465 + ) );
2466 + }
2467 +
2468 + $file_path = WpssoConfig::get_ext_file_path( $ext, $rel_file );
2469 + $file_url = WpssoConfig::get_ext_file_url( $ext, $rel_file ); // Returns sanitized URL or false.
2470 + $file_content = '';
2471 + $text_domain = WpssoConfig::get_ext_text_domain( $ext );
2472 + $cache_md5_pre = 'wpsso_c_'; // 'wpsso_cache_expire_file_content' filter.
2473 + $cache_exp_secs = $this->p->util->get_cache_exp_secs( $cache_md5_pre, $cache_type = 'file' );
2474 +
2475 + if ( $file_url ) { // Sanitized URL or false.
2476 +
2477 + if ( $cache_exp_secs > 0 ) {
2478 +
2479 + if ( ! $read_cache ) $this->p->cache->clear( $file_url );
2480 +
2481 + $file_content = $this->p->cache->get( $file_url, $format = 'raw', $cache_type = 'file', $cache_exp_secs );
2482 +
2483 + } else $this->p->cache->clear( $file_url );
2484 + }
2485 +
2486 + if ( empty( $file_content ) ) { // No content from the file URL.
2487 +
2488 + if ( $file_path && file_exists( $file_path ) && $fh = @fopen( $file_path, 'rb' ) ) {
2489 +
2490 + $file_content = fread( $fh, filesize( $file_path ) );
2491 +
2492 + fclose( $fh );
2493 + }
2494 + }
2495 +
2496 + if ( $text_domain ) { // False or text domain string.
2497 +
2498 + if ( ! class_exists( 'SucomGetText' ) ) {
2499 +
2500 + require_once WPSSO_PLUGINDIR . 'lib/com/gettext.php';
2501 + }
2502 +
2503 + $file_content = SucomGetText::get_html_transl( $file_content, $text_domain );
2504 + }
2505 +
2506 + return $file_content;
2507 + }
2508 +
2509 + /*
2510 + * Plugin links for the addons and licenses settings page.
2511 + */
2512 + public function get_ext_action_links( $ext, $info, &$tabindex = false ) {
2513 +
2514 + if ( $this->p->debug->enabled ) {
2515 +
2516 + $this->p->debug->mark();
2517 + }
2518 +
2519 + $pkg_info = $this->p->util->get_pkg_info(); // Uses a local cache.
2520 + $action_links = array();
2521 +
2522 + if ( ! empty( $info[ 'base' ] ) ) {
2523 +
2524 + $install_url = is_multisite() ?
2525 + network_admin_url( 'plugin-install.php', null ) :
2526 + get_admin_url( $blog_id = null, 'plugin-install.php' );
2527 +
2528 + $details_url = add_query_arg( array(
2529 + 'plugin' => $info[ 'slug' ],
2530 + 'tab' => 'plugin-information',
2531 + 'TB_iframe' => 'true',
2532 + 'width' => $this->p->cf[ 'wp' ][ 'tb_iframe' ][ 'width' ],
2533 + 'height' => $this->p->cf[ 'wp' ][ 'tb_iframe' ][ 'height' ],
2534 + ), $install_url );
2535 +
2536 + if ( SucomPlugin::is_plugin_installed( $info[ 'base' ] ) ) { // Uses a local cache.
2537 +
2538 + if ( $this->p->debug->enabled ) {
2539 +
2540 + $this->p->debug->log( $info[ 'base' ] . ' is installed' );
2541 + }
2542 +
2543 + if ( SucomPlugin::have_plugin_update( $info[ 'base' ] ) ) { // Uses a local cache.
2544 +
2545 + if ( $this->p->debug->enabled ) {
2546 +
2547 + $this->p->debug->log( $info[ 'base' ] . ' has an update' );
2548 + }
2549 +
2550 + $action_links[] = '<a href="' . $details_url . '" class="thickbox" tabindex="' . ++$tabindex . '">' .
2551 + '<font color="red">' . ( 'wpsso' === $ext ? _x( 'Plugin Details and Update',
2552 + 'plugin action link', 'wpsso' ) : _x( 'Add-on Details and Update',
2553 + 'plugin action link', 'wpsso' ) ) . '</font></a>';
2554 + } else {
2555 +
2556 + $action_links[] = '<a href="' . $details_url . '" class="thickbox" tabindex="' . ++$tabindex . '">' .
2557 + ( 'wpsso' === $ext ? _x( 'Plugin Details', 'plugin action link', 'wpsso' ) :
2558 + _x( 'Add-on Details', 'plugin action link', 'wpsso' ) ) . '</a>';
2559 + }
2560 +
2561 + } else {
2562 +
2563 + if ( $this->p->debug->enabled ) {
2564 +
2565 + $this->p->debug->log( $info[ 'base' ] . ' is not installed' );
2566 + }
2567 +
2568 + $action_links[] = '<a href="' . $details_url . '" class="thickbox" tabindex="' . ++$tabindex . '">' .
2569 + ( 'wpsso' === $ext ? _x( 'Plugin Details and Install', 'plugin action link', 'wpsso' ) :
2570 + _x( 'Add-on Details and Install', 'plugin action link', 'wpsso' ) ) . '</a>';
2571 + }
2572 +
2573 + } elseif ( ! empty( $info[ 'url' ][ 'home' ] ) ) {
2574 +
2575 + $action_links[] = '<a href="' . $info[ 'url' ][ 'home' ] . '" tabindex="' . ++$tabindex . '">' .
2576 + _x( 'About Page', 'plugin action link', 'wpsso' ) . '</a>';
2577 + }
2578 +
2579 + if ( ! empty( $info[ 'url' ][ 'docs' ] ) ) {
2580 +
2581 + $action_links[] = '<a href="' . $info[ 'url' ][ 'docs' ] . '"' .
2582 + ( false !== $tabindex ? ' tabindex="' . ++$tabindex . '"' : '' ) . '>' .
2583 + _x( 'Documentation', 'plugin action link', 'wpsso' ) . '</a>';
2584 + }
2585 +
2586 + if ( ! empty( $info[ 'url' ][ 'support' ] ) && ! empty( $pkg_info[ $ext ][ 'pp' ] ) ) {
2587 +
2588 + $action_links[] = '<a href="' . $info[ 'url' ][ 'support' ] . '"' .
2589 + ( false !== $tabindex ? ' tabindex="' . ++$tabindex . '"' : '' ) . '>' .
2590 + sprintf( _x( '%s Support', 'plugin action link', 'wpsso' ),
2591 + _x( $this->p->cf[ 'packages' ][ 'pro' ], 'package name', 'wpsso' ) ) . '</a>';
2592 +
2593 + } elseif ( ! empty( $info[ 'url' ][ 'forum' ] ) ) {
2594 +
2595 + $action_links[] = '<a href="' . $info[ 'url' ][ 'forum' ] . '"' .
2596 + ( false !== $tabindex ? ' tabindex="' . ++$tabindex . '"' : '' ) . '>' .
2597 + _x( 'Community Forum', 'plugin action link', 'wpsso' ) . '</a>';
2598 + }
2599 +
2600 + if ( ! empty( $info[ 'url' ][ 'purchase' ] ) ) {
2601 +
2602 + $action_links[] = $this->p->msgs->get( 'pro-purchase-link', array(
2603 + 'ext' => $ext,
2604 + 'url' => $info[ 'url' ][ 'purchase' ],
2605 + 'tabindex' => false !== $tabindex ? ++$tabindex : false,
2606 + ) );
2607 + }
2608 +
2609 + return $action_links;
2610 + }
2611 +
2612 + /*
2613 + * Returns a 128x128px image by default.
2614 + */
2615 + public function get_ext_img_icon( $ext, $icon_px = 128 ) {
2616 +
2617 + /*
2618 + * The default image is a transparent 1px gif.
2619 + */
2620 + $img_src = 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==';
2621 + $img_srcset = '';
2622 +
2623 + if ( ! empty( $this->p->cf[ 'plugin' ][ $ext ][ 'assets' ][ 'icons' ] ) ) {
2624 +
2625 + $icons = $this->p->cf[ 'plugin' ][ $ext ][ 'assets' ][ 'icons' ];
2626 +
2627 + if ( ! empty( $icons[ '1x' ] ) ) {
2628 +
2629 + $img_src = $icons[ '1x' ];
2630 + $img_srcset .= $icons[ '1x' ] . ' 128w, ';
2631 + }
2632 +
2633 + if ( ! empty( $icons[ '2x' ] ) ) {
2634 +
2635 + $img_srcset .= $icons[ '2x' ] . ' 256w, ';
2636 + }
2637 + }
2638 +
2639 + return '<img src="' . $img_src . '" srcset="' . trim( $img_srcset, $chars = ', ' ) . '" '
2640 + . 'width="' . $icon_px . '" height="' . $icon_px . '" style="width:' . $icon_px . 'px; height:' . $icon_px . 'px;"/>';
2641 + }
2642 +
2643 + /*
2644 + * Sort the WPSSO Core plugin slug before the WPSSO add-on slugs.
2645 + */
2646 + private static function sort_active_plugins( $a, $b ) {
2647 +
2648 + $wpsso_any = 'wpsso';
2649 + $wpsso_core = 'wpsso/';
2650 + $wpsso_addon = 'wpsso-';
2651 + $jsm_plugin = 'jsm-';
2652 +
2653 + if ( 0 === strpos( $a, $wpsso_any ) || 0 === strpos( $b, $wpsso_any ) ) {
2654 +
2655 + /*
2656 + * Sort WPSSO Core plugin before WPSSO add-on.
2657 + */
2658 + if ( 0 === strpos( $a, $wpsso_core ) && 0 === strpos( $b, $wpsso_addon ) ) return -1;
2659 +
2660 + /*
2661 + * Sort WPSSO add-on after the WPSSO Core plugin.
2662 + */
2663 + if ( 0 === strpos( $a, $wpsso_addon ) && 0 === strpos( $b, $wpsso_core ) ) return 1;
2664 +
2665 + /*
2666 + * Sort WPSSO plugins before JSM plugins.
2667 + */
2668 + if ( 0 === strpos( $a, $wpsso_any ) && 0 === strpos( $b, $jsm_plugin ) ) return -1;
2669 +
2670 + /*
2671 + * Sort JSM plugins after WPSSO plugins.
2672 + */
2673 + if ( 0 === strpos( $a, $jsm_plugin ) && 0 === strpos( $b, $wpsso_any ) ) return 1;
2674 +
2675 + return strcmp( $a, $b );
2676 + }
2677 +
2678 + return 0; // No change.
2679 + }
2680 + }
2681 +}