1033ac36 by Jeff Balicki

seo plugin

Signed-off-by: Jeff <jeff@gotenzing.com>
1 parent 36963a81
Showing 1000 changed files with 4984 additions and 0 deletions

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 /**
9 * A WordPress integration that listens for whether the SEO changes have been saved successfully.
10 */
11 class WPSEO_Admin_Settings_Changed_Listener implements WPSEO_WordPress_Integration {
12
13 /**
14 * Have the Yoast SEO settings been saved.
15 *
16 * @var bool
17 */
18 private static $settings_saved = false;
19
20 /**
21 * Registers all hooks to WordPress.
22 *
23 * @return void
24 */
25 public function register_hooks() {
26 add_action( 'admin_init', [ $this, 'intercept_save_update_notification' ] );
27 }
28
29 /**
30 * Checks and overwrites the wp_settings_errors global to determine whether the Yoast SEO settings have been saved.
31 */
32 public function intercept_save_update_notification() {
33 global $pagenow;
34
35 if ( $pagenow !== 'admin.php' || ! YoastSEO()->helpers->current_page->is_yoast_seo_page() ) {
36 return;
37 }
38
39 // Variable name is the same as the global that is set by get_settings_errors.
40 $wp_settings_errors = get_settings_errors();
41
42 foreach ( $wp_settings_errors as $key => $wp_settings_error ) {
43 if ( ! $this->is_settings_updated_notification( $wp_settings_error ) ) {
44 continue;
45 }
46
47 self::$settings_saved = true;
48 unset( $wp_settings_errors[ $key ] );
49 // phpcs:ignore WordPress.WP.GlobalVariablesOverride -- Overwrite the global with the list excluding the Changed saved message.
50 $GLOBALS['wp_settings_errors'] = $wp_settings_errors;
51 break;
52 }
53 }
54
55 /**
56 * Checks whether the settings notification is a settings_updated notification.
57 *
58 * @param array $wp_settings_error The settings object.
59 *
60 * @return bool Whether this is a settings updated settings notification.
61 */
62 public function is_settings_updated_notification( $wp_settings_error ) {
63 return ! empty( $wp_settings_error['code'] ) && $wp_settings_error['code'] === 'settings_updated';
64 }
65
66 /**
67 * Get whether the settings have successfully been saved
68 *
69 * @return bool Whether the settings have successfully been saved.
70 */
71 public function have_settings_been_saved() {
72 return self::$settings_saved;
73 }
74
75 /**
76 * Renders a success message if the Yoast SEO settings have been saved.
77 */
78 public function show_success_message() {
79 if ( $this->have_settings_been_saved() ) {
80 echo '<p class="wpseo-message"><span class="dashicons dashicons-yes"></span>',
81 esc_html__( 'Settings saved.', 'wordpress-seo' ),
82 '</p>';
83 }
84 }
85 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin\Ajax
6 */
7
8 /**
9 * Class WPSEO_Shortcode_Filter.
10 *
11 * Used for parsing WP shortcodes with AJAX.
12 */
13 class WPSEO_Shortcode_Filter {
14
15 /**
16 * Initialize the AJAX hooks.
17 */
18 public function __construct() {
19 add_action( 'wp_ajax_wpseo_filter_shortcodes', [ $this, 'do_filter' ] );
20 }
21
22 /**
23 * Parse the shortcodes.
24 */
25 public function do_filter() {
26 check_ajax_referer( 'wpseo-filter-shortcodes', 'nonce' );
27
28 if ( ! isset( $_POST['data'] ) || ! is_array( $_POST['data'] ) ) {
29 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: WPSEO_Utils::format_json_encode is considered safe.
30 wp_die( WPSEO_Utils::format_json_encode( [] ) );
31 }
32
33 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: $shortcodes is getting sanitized later, before it's used.
34 $shortcodes = wp_unslash( $_POST['data'] );
35 $parsed_shortcodes = [];
36
37 foreach ( $shortcodes as $shortcode ) {
38 if ( $shortcode !== sanitize_text_field( $shortcode ) ) {
39 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: WPSEO_Utils::format_json_encode is considered safe.
40 wp_die( WPSEO_Utils::format_json_encode( [] ) );
41 }
42
43 $parsed_shortcodes[] = [
44 'shortcode' => $shortcode,
45 'output' => do_shortcode( $shortcode ),
46 ];
47 }
48
49 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: WPSEO_Utils::format_json_encode is considered safe.
50 wp_die( WPSEO_Utils::format_json_encode( $parsed_shortcodes ) );
51 }
52 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin\Ajax
6 */
7
8 /**
9 * This class will catch the request to dismiss the target notice (set by notice_name)
10 * and will store the dismiss status as an user meta in the database.
11 */
12 class Yoast_Dismissable_Notice_Ajax {
13
14 /**
15 * Notice type toggle value for user notices.
16 *
17 * @var string
18 */
19 const FOR_USER = 'user_meta';
20
21 /**
22 * Notice type toggle value for network notices.
23 *
24 * @var string
25 */
26 const FOR_NETWORK = 'site_option';
27
28 /**
29 * Notice type toggle value for site notices.
30 *
31 * @var string
32 */
33 const FOR_SITE = 'option';
34
35 /**
36 * Name of the notice that will be dismissed.
37 *
38 * @var string
39 */
40 private $notice_name;
41
42 /**
43 * The type of the current notice.
44 *
45 * @var string
46 */
47 private $notice_type;
48
49 /**
50 * Initialize the hooks for the AJAX request.
51 *
52 * @param string $notice_name The name for the hook to catch the notice.
53 * @param string $notice_type The notice type.
54 */
55 public function __construct( $notice_name, $notice_type = self::FOR_USER ) {
56 $this->notice_name = $notice_name;
57 $this->notice_type = $notice_type;
58
59 add_action( 'wp_ajax_wpseo_dismiss_' . $notice_name, [ $this, 'dismiss_notice' ] );
60 }
61
62 /**
63 * Handles the dismiss notice request.
64 */
65 public function dismiss_notice() {
66 check_ajax_referer( 'wpseo-dismiss-' . $this->notice_name );
67
68 $this->save_dismissed();
69
70 wp_die( 'true' );
71 }
72
73 /**
74 * Storing the dismissed value in the database. The target location is based on the set notification type.
75 */
76 private function save_dismissed() {
77 if ( $this->notice_type === self::FOR_SITE ) {
78 update_option( 'wpseo_dismiss_' . $this->notice_name, 1 );
79
80 return;
81 }
82
83 if ( $this->notice_type === self::FOR_NETWORK ) {
84 update_site_option( 'wpseo_dismiss_' . $this->notice_name, 1 );
85
86 return;
87 }
88
89 update_user_meta( get_current_user_id(), 'wpseo_dismiss_' . $this->notice_name, 1 );
90 }
91 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin\Ajax
6 */
7
8 /**
9 * Class Yoast_Plugin_Conflict_Ajax.
10 */
11 class Yoast_Plugin_Conflict_Ajax {
12
13 /**
14 * Option identifier where dismissed conflicts are stored.
15 *
16 * @var string
17 */
18 private $option_name = 'wpseo_dismissed_conflicts';
19
20 /**
21 * List of notification identifiers that have been dismissed.
22 *
23 * @var array
24 */
25 private $dismissed_conflicts = [];
26
27 /**
28 * Initialize the hooks for the AJAX request.
29 */
30 public function __construct() {
31 add_action( 'wp_ajax_wpseo_dismiss_plugin_conflict', [ $this, 'dismiss_notice' ] );
32 }
33
34 /**
35 * Handles the dismiss notice request.
36 */
37 public function dismiss_notice() {
38 check_ajax_referer( 'dismiss-plugin-conflict' );
39
40 if ( ! isset( $_POST['data'] ) || ! is_array( $_POST['data'] ) ) {
41 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: WPSEO_Utils::format_json_encode is considered safe.
42 wp_die( WPSEO_Utils::format_json_encode( [] ) );
43 }
44
45 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: $conflict_data is getting sanitized later.
46 $conflict_data = wp_unslash( $_POST['data'] );
47
48 $conflict_data = [
49 'section' => sanitize_text_field( $conflict_data['section'] ),
50 'plugins' => sanitize_text_field( $conflict_data['plugins'] ),
51 ];
52
53 $this->dismissed_conflicts = $this->get_dismissed_conflicts( $conflict_data['section'] );
54
55 $this->compare_plugins( $conflict_data['plugins'] );
56
57 $this->save_dismissed_conflicts( $conflict_data['section'] );
58
59 wp_die( 'true' );
60 }
61
62 /**
63 * Getting the user option from the database.
64 *
65 * @return bool|array
66 */
67 private function get_dismissed_option() {
68 return get_user_meta( get_current_user_id(), $this->option_name, true );
69 }
70
71 /**
72 * Getting the dismissed conflicts from the database
73 *
74 * @param string $plugin_section Type of conflict group (such as Open Graph or sitemap).
75 *
76 * @return array
77 */
78 private function get_dismissed_conflicts( $plugin_section ) {
79 $dismissed_conflicts = $this->get_dismissed_option();
80
81 if ( is_array( $dismissed_conflicts ) && array_key_exists( $plugin_section, $dismissed_conflicts ) ) {
82 return $dismissed_conflicts[ $plugin_section ];
83 }
84
85 return [];
86 }
87
88 /**
89 * Storing the conflicting plugins as an user option in the database.
90 *
91 * @param string $plugin_section Plugin conflict type (such as Open Graph or sitemap).
92 */
93 private function save_dismissed_conflicts( $plugin_section ) {
94 $dismissed_conflicts = $this->get_dismissed_option();
95
96 $dismissed_conflicts[ $plugin_section ] = $this->dismissed_conflicts;
97
98 update_user_meta( get_current_user_id(), $this->option_name, $dismissed_conflicts );
99 }
100
101 /**
102 * Loop through the plugins to compare them with the already stored dismissed plugin conflicts.
103 *
104 * @param array $posted_plugins Plugin set to check.
105 */
106 public function compare_plugins( array $posted_plugins ) {
107 foreach ( $posted_plugins as $posted_plugin ) {
108 $this->compare_plugin( $posted_plugin );
109 }
110 }
111
112 /**
113 * Check if plugin is already dismissed, if not store it in the array that will be saved later.
114 *
115 * @param string $posted_plugin Plugin to check against dismissed conflicts.
116 */
117 private function compare_plugin( $posted_plugin ) {
118 if ( ! in_array( $posted_plugin, $this->dismissed_conflicts, true ) ) {
119 $this->dismissed_conflicts[] = $posted_plugin;
120 }
121 }
122 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin\Capabilities
6 */
7
8 /**
9 * Abstract Capability Manager shared code.
10 */
11 abstract class WPSEO_Abstract_Capability_Manager implements WPSEO_Capability_Manager {
12
13 /**
14 * Registered capabilities.
15 *
16 * @var array
17 */
18 protected $capabilities = [];
19
20 /**
21 * Registers a capability.
22 *
23 * @param string $capability Capability to register.
24 * @param array $roles Roles to add the capability to.
25 * @param bool $overwrite Optional. Use add or overwrite as registration method.
26 */
27 public function register( $capability, array $roles, $overwrite = false ) {
28 if ( $overwrite || ! isset( $this->capabilities[ $capability ] ) ) {
29 $this->capabilities[ $capability ] = $roles;
30
31 return;
32 }
33
34 // Combine configurations.
35 $this->capabilities[ $capability ] = array_merge( $roles, $this->capabilities[ $capability ] );
36
37 // Remove doubles.
38 $this->capabilities[ $capability ] = array_unique( $this->capabilities[ $capability ] );
39 }
40
41 /**
42 * Returns the list of registered capabilitities.
43 *
44 * @return string[] Registered capabilities.
45 */
46 public function get_capabilities() {
47 return array_keys( $this->capabilities );
48 }
49
50 /**
51 * Returns a list of WP_Role roles.
52 *
53 * The string array of role names are converted to actual WP_Role objects.
54 * These are needed to be able to use the API on them.
55 *
56 * @param array $roles Roles to retrieve the objects for.
57 *
58 * @return WP_Role[] List of WP_Role objects.
59 */
60 protected function get_wp_roles( array $roles ) {
61 $wp_roles = array_map( 'get_role', $roles );
62
63 return array_filter( $wp_roles );
64 }
65
66 /**
67 * Filter capability roles.
68 *
69 * @param string $capability Capability to filter roles for.
70 * @param array $roles List of roles which can be filtered.
71 *
72 * @return array Filtered list of roles for the capability.
73 */
74 protected function filter_roles( $capability, array $roles ) {
75 /**
76 * Filter: Allow changing roles that a capability is added to.
77 *
78 * @api array $roles The default roles to be filtered.
79 */
80 $filtered = apply_filters( $capability . '_roles', $roles );
81
82 // Make sure we have the expected type.
83 if ( ! is_array( $filtered ) ) {
84 return [];
85 }
86
87 return $filtered;
88 }
89 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin\Capabilities
6 */
7
8 /**
9 * Capability Manager Factory.
10 */
11 class WPSEO_Capability_Manager_Factory {
12
13 /**
14 * Returns the Manager to use.
15 *
16 * @param string $plugin_type Whether it's Free or Premium.
17 *
18 * @return WPSEO_Capability_Manager Manager to use.
19 */
20 public static function get( $plugin_type = 'free' ) {
21 static $manager = [];
22
23 if ( ! array_key_exists( $plugin_type, $manager ) ) {
24 if ( function_exists( 'wpcom_vip_add_role_caps' ) ) {
25 $manager[ $plugin_type ] = new WPSEO_Capability_Manager_VIP();
26 }
27
28 if ( ! function_exists( 'wpcom_vip_add_role_caps' ) ) {
29 $manager[ $plugin_type ] = new WPSEO_Capability_Manager_WP();
30 }
31 }
32
33 return $manager[ $plugin_type ];
34 }
35 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin\Capabilities
6 */
7
8 /**
9 * Integrates Yoast SEO capabilities with third party role manager plugins.
10 *
11 * Integrates with: Members
12 * Integrates with: User Role Editor
13 */
14 class WPSEO_Capability_Manager_Integration implements WPSEO_WordPress_Integration {
15
16 /**
17 * Capability manager to use.
18 *
19 * @var WPSEO_Capability_Manager
20 */
21 public $manager;
22
23 /**
24 * WPSEO_Capability_Manager_Integration constructor.
25 *
26 * @param WPSEO_Capability_Manager $manager The capability manager to use.
27 */
28 public function __construct( WPSEO_Capability_Manager $manager ) {
29 $this->manager = $manager;
30 }
31
32 /**
33 * Registers the hooks.
34 *
35 * @return void
36 */
37 public function register_hooks() {
38 add_filter( 'members_get_capabilities', [ $this, 'get_capabilities' ] );
39 add_action( 'members_register_cap_groups', [ $this, 'action_members_register_cap_group' ] );
40
41 add_filter( 'ure_capabilities_groups_tree', [ $this, 'filter_ure_capabilities_groups_tree' ] );
42 add_filter( 'ure_custom_capability_groups', [ $this, 'filter_ure_custom_capability_groups' ], 10, 2 );
43 }
44
45 /**
46 * Get the Yoast SEO capabilities.
47 * Optionally append them to an existing array.
48 *
49 * @param array $caps Optional existing capability list.
50 * @return array
51 */
52 public function get_capabilities( array $caps = [] ) {
53 if ( ! did_action( 'wpseo_register_capabilities' ) ) {
54 do_action( 'wpseo_register_capabilities' );
55 }
56
57 return array_merge( $caps, $this->manager->get_capabilities() );
58 }
59
60 /**
61 * Add capabilities to its own group in the Members plugin.
62 *
63 * @see members_register_cap_group()
64 */
65 public function action_members_register_cap_group() {
66 if ( ! function_exists( 'members_register_cap_group' ) ) {
67 return;
68 }
69
70 // Register the yoast group.
71 $args = [
72 'label' => esc_html__( 'Yoast SEO', 'wordpress-seo' ),
73 'caps' => $this->get_capabilities(),
74 'icon' => 'dashicons-admin-plugins',
75 'diff_added' => true,
76 ];
77 members_register_cap_group( 'wordpress-seo', $args );
78 }
79
80 /**
81 * Adds Yoast SEO capability group in the User Role Editor plugin.
82 *
83 * @see URE_Capabilities_Groups_Manager::get_groups_tree()
84 *
85 * @param array $groups Current groups.
86 *
87 * @return array Filtered list of capabilty groups.
88 */
89 public function filter_ure_capabilities_groups_tree( $groups = [] ) {
90 $groups = (array) $groups;
91
92 $groups['wordpress-seo'] = [
93 'caption' => 'Yoast SEO',
94 'parent' => 'custom',
95 'level' => 3,
96 ];
97
98 return $groups;
99 }
100
101 /**
102 * Adds capabilities to the Yoast SEO group in the User Role Editor plugin.
103 *
104 * @see URE_Capabilities_Groups_Manager::get_cap_groups()
105 *
106 * @param array $groups Current capability groups.
107 * @param string $cap_id Capability identifier.
108 *
109 * @return array List of filtered groups.
110 */
111 public function filter_ure_custom_capability_groups( $groups = [], $cap_id = '' ) {
112 if ( in_array( $cap_id, $this->get_capabilities(), true ) ) {
113 $groups = (array) $groups;
114 $groups[] = 'wordpress-seo';
115 }
116
117 return $groups;
118 }
119 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin\Capabilities
6 */
7
8 /**
9 * VIP implementation of the Capability Manager.
10 */
11 final class WPSEO_Capability_Manager_VIP extends WPSEO_Abstract_Capability_Manager {
12
13 /**
14 * Adds the registered capabilities to the system.
15 *
16 * @return void
17 */
18 public function add() {
19 $role_capabilities = [];
20 foreach ( $this->capabilities as $capability => $roles ) {
21 $role_capabilities = $this->get_role_capabilities( $role_capabilities, $capability, $roles );
22 }
23
24 foreach ( $role_capabilities as $role => $capabilities ) {
25 wpcom_vip_add_role_caps( $role, $capabilities );
26 }
27 }
28
29 /**
30 * Removes the registered capabilities from the system
31 *
32 * @return void
33 */
34 public function remove() {
35 // Remove from any role it has been added to.
36 $roles = wp_roles()->get_names();
37 $roles = array_keys( $roles );
38
39 $role_capabilities = [];
40 foreach ( array_keys( $this->capabilities ) as $capability ) {
41 // Allow filtering of roles.
42 $role_capabilities = $this->get_role_capabilities( $role_capabilities, $capability, $roles );
43 }
44
45 foreach ( $role_capabilities as $role => $capabilities ) {
46 wpcom_vip_remove_role_caps( $role, $capabilities );
47 }
48 }
49
50 /**
51 * Returns the roles which the capability is registered on.
52 *
53 * @param array $role_capabilities List of all roles with their capabilities.
54 * @param string $capability Capability to filter roles for.
55 * @param array $roles List of default roles.
56 *
57 * @return array List of capabilities.
58 */
59 protected function get_role_capabilities( $role_capabilities, $capability, $roles ) {
60 // Allow filtering of roles.
61 $filtered_roles = $this->filter_roles( $capability, $roles );
62
63 foreach ( $filtered_roles as $role ) {
64 if ( ! isset( $add_role_caps[ $role ] ) ) {
65 $role_capabilities[ $role ] = [];
66 }
67
68 $role_capabilities[ $role ][] = $capability;
69 }
70
71 return $role_capabilities;
72 }
73 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin\Capabilities
6 */
7
8 /**
9 * Default WordPress capability manager implementation.
10 */
11 final class WPSEO_Capability_Manager_WP extends WPSEO_Abstract_Capability_Manager {
12
13 /**
14 * Adds the capabilities to the roles.
15 *
16 * @return void
17 */
18 public function add() {
19 foreach ( $this->capabilities as $capability => $roles ) {
20 $filtered_roles = $this->filter_roles( $capability, $roles );
21
22 $wp_roles = $this->get_wp_roles( $filtered_roles );
23 foreach ( $wp_roles as $wp_role ) {
24 $wp_role->add_cap( $capability );
25 }
26 }
27 }
28
29 /**
30 * Unregisters the capabilities from the system.
31 *
32 * @return void
33 */
34 public function remove() {
35 // Remove from any roles it has been added to.
36 $roles = wp_roles()->get_names();
37 $roles = array_keys( $roles );
38
39 foreach ( $this->capabilities as $capability => $_roles ) {
40 $registered_roles = array_unique( array_merge( $roles, $this->capabilities[ $capability ] ) );
41
42 // Allow filtering of roles.
43 $filtered_roles = $this->filter_roles( $capability, $registered_roles );
44
45 $wp_roles = $this->get_wp_roles( $filtered_roles );
46 foreach ( $wp_roles as $wp_role ) {
47 $wp_role->remove_cap( $capability );
48 }
49 }
50 }
51 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin\Capabilities
6 */
7
8 /**
9 * Capability Manager interface.
10 */
11 interface WPSEO_Capability_Manager {
12
13 /**
14 * Registers a capability.
15 *
16 * @param string $capability Capability to register.
17 * @param array $roles Roles to add the capability to.
18 * @param bool $overwrite Optional. Use add or overwrite as registration method.
19 */
20 public function register( $capability, array $roles, $overwrite = false );
21
22 /**
23 * Adds the registerd capabilities to the system.
24 */
25 public function add();
26
27 /**
28 * Removes the registered capabilities from the system.
29 */
30 public function remove();
31
32 /**
33 * Returns the list of registered capabilities.
34 *
35 * @return string[] List of registered capabilities.
36 */
37 public function get_capabilities();
38 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin\Capabilities
6 */
7
8 /**
9 * Capability Utils collection.
10 */
11 class WPSEO_Capability_Utils {
12
13 /**
14 * Checks if the user has the proper capabilities.
15 *
16 * @param string $capability Capability to check.
17 *
18 * @return bool True if the user has the proper rights.
19 */
20 public static function current_user_can( $capability ) {
21 if ( $capability === 'wpseo_manage_options' ) {
22 return self::has( $capability );
23 }
24
25 return self::has_any( [ 'wpseo_manage_options', $capability ] );
26 }
27
28 /**
29 * Retrieves the users that have the specified capability.
30 *
31 * @param string $capability The name of the capability.
32 *
33 * @return array The users that have the capability.
34 */
35 public static function get_applicable_users( $capability ) {
36 $applicable_roles = self::get_applicable_roles( $capability );
37
38 if ( $applicable_roles === [] ) {
39 return [];
40 }
41
42 return get_users( [ 'role__in' => $applicable_roles ] );
43 }
44
45 /**
46 * Retrieves the roles that have the specified capability.
47 *
48 * @param string $capability The name of the capability.
49 *
50 * @return array The names of the roles that have the capability.
51 */
52 public static function get_applicable_roles( $capability ) {
53 $roles = wp_roles();
54 $role_names = $roles->get_names();
55
56 $applicable_roles = [];
57 foreach ( array_keys( $role_names ) as $role_name ) {
58 $role = $roles->get_role( $role_name );
59
60 if ( ! $role ) {
61 continue;
62 }
63
64 // Add role if it has the capability.
65 if ( array_key_exists( $capability, $role->capabilities ) && $role->capabilities[ $capability ] === true ) {
66 $applicable_roles[] = $role_name;
67 }
68 }
69
70 return $applicable_roles;
71 }
72
73 /**
74 * Checks if the current user has at least one of the supplied capabilities.
75 *
76 * @param array $capabilities Capabilities to check against.
77 *
78 * @return bool True if the user has at least one capability.
79 */
80 protected static function has_any( array $capabilities ) {
81 foreach ( $capabilities as $capability ) {
82 if ( self::has( $capability ) ) {
83 return true;
84 }
85 }
86
87 return false;
88 }
89
90 /**
91 * Checks if the user has a certain capability.
92 *
93 * @param string $capability Capability to check against.
94 *
95 * @return bool True if the user has the capability.
96 */
97 protected static function has( $capability ) {
98 return current_user_can( $capability );
99 }
100 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin\Capabilities
6 */
7
8 /**
9 * Capabilities registration class.
10 */
11 class WPSEO_Register_Capabilities implements WPSEO_WordPress_Integration {
12
13 /**
14 * Registers the hooks.
15 *
16 * @return void
17 */
18 public function register_hooks() {
19 add_action( 'wpseo_register_capabilities', [ $this, 'register' ] );
20
21 if ( is_multisite() ) {
22 add_action( 'user_has_cap', [ $this, 'filter_user_has_wpseo_manage_options_cap' ], 10, 4 );
23 }
24
25 /**
26 * Maybe add manage_privacy_options capability for wpseo_manager user role.
27 */
28 add_filter( 'map_meta_cap', [ $this, 'map_meta_cap_for_seo_manager' ], 10, 2 );
29 }
30
31 /**
32 * Registers the capabilities.
33 *
34 * @return void
35 */
36 public function register() {
37 $manager = WPSEO_Capability_Manager_Factory::get();
38
39 $manager->register( 'wpseo_bulk_edit', [ 'editor', 'wpseo_editor', 'wpseo_manager' ] );
40 $manager->register( 'wpseo_edit_advanced_metadata', [ 'editor', 'wpseo_editor', 'wpseo_manager' ] );
41
42 $manager->register( 'wpseo_manage_options', [ 'administrator', 'wpseo_manager' ] );
43 $manager->register( 'view_site_health_checks', [ 'wpseo_manager' ] );
44 }
45
46 /**
47 * Revokes the 'wpseo_manage_options' capability from administrator users if it should
48 * only be granted to network administrators.
49 *
50 * @param array $allcaps An array of all the user's capabilities.
51 * @param array $caps Actual capabilities being checked.
52 * @param array $args Optional parameters passed to has_cap(), typically object ID.
53 * @param WP_User $user The user object.
54 *
55 * @return array Possibly modified array of the user's capabilities.
56 */
57 public function filter_user_has_wpseo_manage_options_cap( $allcaps, $caps, $args, $user ) {
58
59 // We only need to do something if 'wpseo_manage_options' is being checked.
60 if ( ! in_array( 'wpseo_manage_options', $caps, true ) ) {
61 return $allcaps;
62 }
63
64 // If the user does not have 'wpseo_manage_options' anyway, we don't need to revoke access.
65 if ( empty( $allcaps['wpseo_manage_options'] ) ) {
66 return $allcaps;
67 }
68
69 // If the user does not have 'delete_users', they are not an administrator.
70 if ( empty( $allcaps['delete_users'] ) ) {
71 return $allcaps;
72 }
73
74 $options = WPSEO_Options::get_instance();
75
76 if ( $options->get( 'access' ) === 'superadmin' && ! is_super_admin( $user->ID ) ) {
77 unset( $allcaps['wpseo_manage_options'] );
78 }
79
80 return $allcaps;
81 }
82
83 /**
84 * Maybe add manage_privacy_options capability for wpseo_manager user role.
85 *
86 * @param string[] $caps Primitive capabilities required of the user.
87 * @param string[] $cap Capability being checked.
88 *
89 * @return string[] Filtered primitive capabilities required of the user.
90 */
91 public function map_meta_cap_for_seo_manager( $caps, $cap ) {
92 $user = wp_get_current_user();
93
94 // No multisite support.
95 if ( is_multisite() ) {
96 return $caps;
97 }
98
99 // User must be of role wpseo_manager.
100 if ( ! in_array( 'wpseo_manager', $user->roles, true ) ) {
101 return $caps;
102 }
103
104 // Remove manage_options cap requirement if requested cap is manage_privacy_options.
105 if ( $cap === 'manage_privacy_options' ) {
106 return array_diff( $caps, [ 'manage_options' ] );
107 }
108
109 return $caps;
110 }
111 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 /**
9 * Represents a way to determine the analysis worker asset location.
10 */
11 final class WPSEO_Admin_Asset_Analysis_Worker_Location implements WPSEO_Admin_Asset_Location {
12
13 /**
14 * Holds the asset's location.
15 *
16 * @var WPSEO_Admin_Asset_Location
17 */
18 private $asset_location;
19
20 /**
21 * Holds the asset itself.
22 *
23 * @var WPSEO_Admin_Asset
24 */
25 private $asset;
26
27 /**
28 * Constructs the location of the analysis worker asset.
29 *
30 * @param string $flat_version The flat version of the asset.
31 * @param string $name The name of the analysis worker asset.
32 */
33 public function __construct( $flat_version = '', $name = 'analysis-worker' ) {
34 if ( $flat_version === '' ) {
35 $asset_manager = new WPSEO_Admin_Asset_Manager();
36 $flat_version = $asset_manager->flatten_version( WPSEO_VERSION );
37 }
38
39 $analysis_worker = $name . '-' . $flat_version . '.js';
40
41 $this->asset_location = WPSEO_Admin_Asset_Manager::create_default_location();
42 $this->asset = new WPSEO_Admin_Asset(
43 [
44 'name' => $name,
45 'src' => $analysis_worker,
46 ]
47 );
48 }
49
50 /**
51 * Retrieves the analysis worker asset.
52 *
53 * @return WPSEO_Admin_Asset The analysis worker asset.
54 */
55 public function get_asset() {
56 return $this->asset;
57 }
58
59 /**
60 * Determines the URL of the asset on the dev server.
61 *
62 * @param WPSEO_Admin_Asset $asset The asset to determine the URL for.
63 * @param string $type The type of asset. Usually JS or CSS.
64 *
65 * @return string The URL of the asset.
66 */
67 public function get_url( WPSEO_Admin_Asset $asset, $type ) {
68 $scheme = wp_parse_url( $asset->get_src(), PHP_URL_SCHEME );
69 if ( in_array( $scheme, [ 'http', 'https' ], true ) ) {
70 return $asset->get_src();
71 }
72
73 return $this->asset_location->get_url( $asset, $type );
74 }
75 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 /**
9 * Changes the asset paths to dev server paths.
10 */
11 final class WPSEO_Admin_Asset_Dev_Server_Location implements WPSEO_Admin_Asset_Location {
12
13 /**
14 * Holds the dev server's default URL.
15 *
16 * @var string
17 */
18 const DEFAULT_URL = 'http://localhost:8080';
19
20 /**
21 * Holds the url where the server is located.
22 *
23 * @var string
24 */
25 private $url;
26
27 /**
28 * Class constructor.
29 *
30 * @param string|null $url Where the dev server is located.
31 */
32 public function __construct( $url = null ) {
33 if ( $url === null ) {
34 $url = self::DEFAULT_URL;
35 }
36
37 $this->url = $url;
38 }
39
40 /**
41 * Determines the URL of the asset on the dev server.
42 *
43 * @param WPSEO_Admin_Asset $asset The asset to determine the URL for.
44 * @param string $type The type of asset. Usually JS or CSS.
45 *
46 * @return string The URL of the asset.
47 */
48 public function get_url( WPSEO_Admin_Asset $asset, $type ) {
49 if ( $type === WPSEO_Admin_Asset::TYPE_CSS ) {
50 return $this->get_default_url( $asset, $type );
51 }
52
53 $path = sprintf( 'js/dist/%s%s.js', $asset->get_src(), $asset->get_suffix() );
54
55 return trailingslashit( $this->url ) . $path;
56 }
57
58 /**
59 * Determines the URL of the asset not using the dev server.
60 *
61 * @param WPSEO_Admin_Asset $asset The asset to determine the URL for.
62 * @param string $type The type of asset.
63 *
64 * @return string The URL of the asset file.
65 */
66 public function get_default_url( WPSEO_Admin_Asset $asset, $type ) {
67 $default_location = new WPSEO_Admin_Asset_SEO_Location( WPSEO_FILE );
68
69 return $default_location->get_url( $asset, $type );
70 }
71 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 /**
9 * Represents a way to determine an assets location.
10 */
11 interface WPSEO_Admin_Asset_Location {
12
13 /**
14 * Determines the URL of the asset on the dev server.
15 *
16 * @param WPSEO_Admin_Asset $asset The asset to determine the URL for.
17 * @param string $type The type of asset. Usually JS or CSS.
18 *
19 * @return string The URL of the asset.
20 */
21 public function get_url( WPSEO_Admin_Asset $asset, $type );
22 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 /**
9 * Determines the location of an asset within the SEO plugin.
10 */
11 final class WPSEO_Admin_Asset_SEO_Location implements WPSEO_Admin_Asset_Location {
12
13 /**
14 * Path to the plugin file.
15 *
16 * @var string
17 */
18 protected $plugin_file;
19
20 /**
21 * Whether or not to add the file suffix to the asset.
22 *
23 * @var bool
24 */
25 protected $add_suffix = true;
26
27 /**
28 * The plugin file to base the asset location upon.
29 *
30 * @param string $plugin_file The plugin file string.
31 * @param bool $add_suffix Optional. Whether or not a file suffix should be added.
32 */
33 public function __construct( $plugin_file, $add_suffix = true ) {
34 $this->plugin_file = $plugin_file;
35 $this->add_suffix = $add_suffix;
36 }
37
38 /**
39 * Determines the URL of the asset on the dev server.
40 *
41 * @param WPSEO_Admin_Asset $asset The asset to determine the URL for.
42 * @param string $type The type of asset. Usually JS or CSS.
43 *
44 * @return string The URL of the asset.
45 */
46 public function get_url( WPSEO_Admin_Asset $asset, $type ) {
47 $path = $this->get_path( $asset, $type );
48 if ( empty( $path ) ) {
49 return '';
50 }
51
52 return plugins_url( $path, $this->plugin_file );
53 }
54
55 /**
56 * Determines the path relative to the plugin folder of an asset.
57 *
58 * @param WPSEO_Admin_Asset $asset The asset to determine the path for.
59 * @param string $type The type of asset.
60 *
61 * @return string The path to the asset file.
62 */
63 protected function get_path( WPSEO_Admin_Asset $asset, $type ) {
64 $relative_path = '';
65 $rtl_suffix = '';
66
67 switch ( $type ) {
68 case WPSEO_Admin_Asset::TYPE_JS:
69 $relative_path = 'js/dist/' . $asset->get_src();
70 if ( $this->add_suffix ) {
71 $relative_path .= $asset->get_suffix() . '.js';
72 }
73 break;
74
75 case WPSEO_Admin_Asset::TYPE_CSS:
76 // Path and suffix for RTL stylesheets.
77 if ( is_rtl() && $asset->has_rtl() ) {
78 $rtl_suffix = '-rtl';
79 }
80 $relative_path = 'css/dist/' . $asset->get_src() . $rtl_suffix . $asset->get_suffix() . '.css';
81 break;
82 }
83
84 return $relative_path;
85 }
86 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 /**
9 * Localizes JavaScript files.
10 *
11 * @codeCoverageIgnore
12 * @deprecated 18.0
13 */
14 final class WPSEO_Admin_Asset_Yoast_Components_L10n {
15
16 /**
17 * Represents the asset manager.
18 *
19 * @var WPSEO_Admin_Asset_Manager
20 */
21 protected $asset_manager;
22
23 /**
24 * WPSEO_Admin_Asset_Yoast_Components_L10n constructor.
25 *
26 * @codeCoverageIgnore
27 * @deprecated 18.0
28 */
29 public function __construct() {
30 _deprecated_constructor( __CLASS__, '18.0' );
31 $this->asset_manager = new WPSEO_Admin_Asset_Manager();
32 }
33
34 /**
35 * Localizes the given script with the JavaScript translations.
36 *
37 * @codeCoverageIgnore
38 * @deprecated 18.0
39 *
40 * @param string $script_handle The script handle to localize for.
41 *
42 * @return void
43 */
44 public function localize_script( $script_handle ) {
45 _deprecated_function( __FUNCTION__, '18.0' );
46 $translations = [
47 'yoast-components' => $this->get_translations( 'yoast-components' ),
48 'wordpress-seo' => $this->get_translations( 'wordpress-seojs' ),
49 'yoast-schema-blocks' => $this->get_translations( 'yoast-schema-blocks' ),
50 ];
51 $this->asset_manager->localize_script( $script_handle, 'wpseoYoastJSL10n', $translations );
52 }
53
54 /**
55 * Returns translations necessary for JS files.
56 *
57 * @codeCoverageIgnore
58 * @deprecated 18.0
59 *
60 * @param string $component The component to retrieve the translations for.
61 * @return object|null The translations in a Jed format for JS files.
62 */
63 protected function get_translations( $component ) {
64 _deprecated_function( __FUNCTION__, '18.0' );
65 $locale = \get_user_locale();
66
67 $file = WPSEO_PATH . 'languages/' . $component . '-' . $locale . '.json';
68 if ( file_exists( $file ) ) {
69 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Retrieving a local file.
70 $file = file_get_contents( $file );
71 if ( is_string( $file ) && $file !== '' ) {
72 return json_decode( $file, true );
73 }
74 }
75
76 return null;
77 }
78 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 /**
9 * Determines the editor specific replacement variables.
10 */
11 class WPSEO_Admin_Editor_Specific_Replace_Vars {
12
13 /**
14 * Holds the editor specific replacements variables.
15 *
16 * @var array The editor specific replacement variables.
17 */
18 protected $replacement_variables = [
19 // Posts types.
20 'page' => [ 'id', 'pt_single', 'pt_plural', 'parent_title' ],
21 'post' => [ 'id', 'term404', 'pt_single', 'pt_plural' ],
22 // Custom post type.
23 'custom_post_type' => [ 'id', 'term404', 'pt_single', 'pt_plural', 'parent_title' ],
24 // Settings - archive pages.
25 'custom-post-type_archive' => [ 'pt_single', 'pt_plural' ],
26
27 // Taxonomies.
28 'category' => [ 'term_title', 'term_description', 'category_description', 'parent_title', 'term_hierarchy' ],
29 'post_tag' => [ 'term_title', 'term_description', 'tag_description' ],
30 'post_format' => [ 'term_title' ],
31 // Custom taxonomy.
32 'term-in-custom-taxonomy' => [ 'term_title', 'term_description', 'category_description', 'parent_title', 'term_hierarchy' ],
33
34 // Settings - special pages.
35 'search' => [ 'searchphrase' ],
36 ];
37
38 /**
39 * WPSEO_Admin_Editor_Specific_Replace_Vars constructor.
40 */
41 public function __construct() {
42 $this->add_for_page_types(
43 [ 'page', 'post', 'custom_post_type' ],
44 WPSEO_Custom_Fields::get_custom_fields()
45 );
46
47 $this->add_for_page_types(
48 [ 'post', 'term-in-custom-taxonomy' ],
49 WPSEO_Custom_Taxonomies::get_custom_taxonomies()
50 );
51 }
52
53 /**
54 * Retrieves the editor specific replacement variables.
55 *
56 * @return array The editor specific replacement variables.
57 */
58 public function get() {
59 /**
60 * Filter: Adds the possibility to add extra editor specific replacement variables.
61 *
62 * @api array $replacement_variables Array of editor specific replace vars.
63 */
64 $replacement_variables = apply_filters(
65 'wpseo_editor_specific_replace_vars',
66 $this->replacement_variables
67 );
68
69 if ( ! is_array( $replacement_variables ) ) {
70 $replacement_variables = $this->replacement_variables;
71 }
72
73 return array_filter( $replacement_variables, 'is_array' );
74 }
75
76 /**
77 * Retrieves the generic replacement variable names.
78 *
79 * Which are the replacement variables without the editor specific ones.
80 *
81 * @param array $replacement_variables Possibly generic replacement variables.
82 *
83 * @return array The generic replacement variable names.
84 */
85 public function get_generic( $replacement_variables ) {
86 $shared_variables = array_diff(
87 $this->extract_names( $replacement_variables ),
88 $this->get_unique_replacement_variables()
89 );
90
91 return array_values( $shared_variables );
92 }
93
94 /**
95 * Determines the page type of the current term.
96 *
97 * @param string $taxonomy The taxonomy name.
98 *
99 * @return string The page type.
100 */
101 public function determine_for_term( $taxonomy ) {
102 $replacement_variables = $this->get();
103 if ( array_key_exists( $taxonomy, $replacement_variables ) ) {
104 return $taxonomy;
105 }
106
107 return 'term-in-custom-taxonomy';
108 }
109
110 /**
111 * Determines the page type of the current post.
112 *
113 * @param WP_Post $post A WordPress post instance.
114 *
115 * @return string The page type.
116 */
117 public function determine_for_post( $post ) {
118 if ( $post instanceof WP_Post === false ) {
119 return 'post';
120 }
121
122 $replacement_variables = $this->get();
123 if ( array_key_exists( $post->post_type, $replacement_variables ) ) {
124 return $post->post_type;
125 }
126
127 return 'custom_post_type';
128 }
129
130 /**
131 * Determines the page type for a post type.
132 *
133 * @param string $post_type The name of the post_type.
134 * @param string $fallback The page type to fall back to.
135 *
136 * @return string The page type.
137 */
138 public function determine_for_post_type( $post_type, $fallback = 'custom_post_type' ) {
139 if ( ! $this->has_for_page_type( $post_type ) ) {
140 return $fallback;
141 }
142
143 return $post_type;
144 }
145
146 /**
147 * Determines the page type for an archive page.
148 *
149 * @param string $name The name of the archive.
150 * @param string $fallback The page type to fall back to.
151 *
152 * @return string The page type.
153 */
154 public function determine_for_archive( $name, $fallback = 'custom-post-type_archive' ) {
155 $page_type = $name . '_archive';
156
157 if ( ! $this->has_for_page_type( $page_type ) ) {
158 return $fallback;
159 }
160
161 return $page_type;
162 }
163
164 /**
165 * Adds the replavement variables for the given page types.
166 *
167 * @param array $page_types Page types to add variables for.
168 * @param array $replacement_variables_to_add The variables to add.
169 *
170 * @return void
171 */
172 protected function add_for_page_types( array $page_types, array $replacement_variables_to_add ) {
173 if ( empty( $replacement_variables_to_add ) ) {
174 return;
175 }
176
177 $replacement_variables_to_add = array_fill_keys( $page_types, $replacement_variables_to_add );
178 $replacement_variables = $this->replacement_variables;
179
180 $this->replacement_variables = array_merge_recursive( $replacement_variables, $replacement_variables_to_add );
181 }
182
183 /**
184 * Extracts the names from the given replacements variables.
185 *
186 * @param array $replacement_variables Replacement variables to extract the name from.
187 *
188 * @return array Extracted names.
189 */
190 protected function extract_names( $replacement_variables ) {
191 $extracted_names = [];
192
193 foreach ( $replacement_variables as $replacement_variable ) {
194 if ( empty( $replacement_variable['name'] ) ) {
195 continue;
196 }
197
198 $extracted_names[] = $replacement_variable['name'];
199 }
200
201 return $extracted_names;
202 }
203
204 /**
205 * Returns whether the given page type has editor specific replace vars.
206 *
207 * @param string $page_type The page type to check.
208 *
209 * @return bool True if there are associated editor specific replace vars.
210 */
211 protected function has_for_page_type( $page_type ) {
212 $replacement_variables = $this->get();
213
214 return ( ! empty( $replacement_variables[ $page_type ] ) && is_array( $replacement_variables[ $page_type ] ) );
215 }
216
217 /**
218 * Merges all editor specific replacement variables into one array and removes duplicates.
219 *
220 * @return array The list of unique editor specific replacement variables.
221 */
222 protected function get_unique_replacement_variables() {
223 $merged_replacement_variables = call_user_func_array( 'array_merge', array_values( $this->get() ) );
224
225 return array_unique( $merged_replacement_variables );
226 }
227 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 /**
9 * Handles the Gutenberg Compatibility notification showing and hiding.
10 */
11 class WPSEO_Admin_Gutenberg_Compatibility_Notification implements WPSEO_WordPress_Integration {
12
13 /**
14 * Notification ID to use.
15 *
16 * @var string
17 */
18 private $notification_id = 'wpseo-outdated-gutenberg-plugin';
19
20 /**
21 * Instance of gutenberg compatibility checker.
22 *
23 * @var WPSEO_Gutenberg_Compatibility
24 */
25 protected $compatibility_checker;
26
27 /**
28 * Instance of Yoast Notification Center.
29 *
30 * @var Yoast_Notification_Center
31 */
32 protected $notification_center;
33
34 /**
35 * WPSEO_Admin_Gutenberg_Compatibility_Notification constructor.
36 */
37 public function __construct() {
38 $this->compatibility_checker = new WPSEO_Gutenberg_Compatibility();
39 $this->notification_center = Yoast_Notification_Center::get();
40 }
41
42 /**
43 * Registers all hooks to WordPress.
44 *
45 * @return void
46 */
47 public function register_hooks() {
48 add_action( 'admin_init', [ $this, 'manage_notification' ] );
49 }
50
51 /**
52 * Manages if the notification should be shown or removed.
53 *
54 * @return void
55 */
56 public function manage_notification() {
57 /**
58 * Filter: 'yoast_display_gutenberg_compat_notification' - Allows developer to disable the Gutenberg compatibility
59 * notification.
60 *
61 * @api bool
62 */
63 $display_notification = apply_filters( 'yoast_display_gutenberg_compat_notification', true );
64
65 if (
66 ! $this->compatibility_checker->is_installed()
67 || $this->compatibility_checker->is_fully_compatible()
68 || ! $display_notification
69 ) {
70 $this->notification_center->remove_notification_by_id( $this->notification_id );
71
72 return;
73 }
74
75 $this->add_notification();
76 }
77
78 /**
79 * Adds the notification to the notificaton center.
80 *
81 * @return void
82 */
83 protected function add_notification() {
84 $level = $this->compatibility_checker->is_below_minimum() ? Yoast_Notification::ERROR : Yoast_Notification::WARNING;
85
86 $message = sprintf(
87 /* translators: %1$s expands to Yoast SEO, %2$s expands to the installed version, %3$s expands to Gutenberg */
88 __( '%1$s detected you are using version %2$s of %3$s, please update to the latest version to prevent compatibility issues.', 'wordpress-seo' ),
89 'Yoast SEO',
90 $this->compatibility_checker->get_installed_version(),
91 'Gutenberg'
92 );
93
94 $notification = new Yoast_Notification(
95 $message,
96 [
97 'id' => $this->notification_id,
98 'type' => $level,
99 'priority' => 1,
100 ]
101 );
102
103 $this->notification_center->add_notification( $notification );
104 }
105 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 /**
9 * Generates the HTML for an inline Help Button and Panel.
10 */
11 class WPSEO_Admin_Help_Panel {
12
13 /**
14 * Unique identifier of the element the inline help refers to, used as an identifier in the html.
15 *
16 * @var string
17 */
18 private $id;
19
20 /**
21 * The Help Button text. Needs a properly escaped string.
22 *
23 * @var string
24 */
25 private $help_button_text;
26
27 /**
28 * The Help Panel content. Needs a properly escaped string (might contain HTML).
29 *
30 * @var string
31 */
32 private $help_content;
33
34 /**
35 * Optional Whether to print out a container div element for the Help Panel, used for styling.
36 *
37 * @var string
38 */
39 private $wrapper;
40
41 /**
42 * Constructor.
43 *
44 * @param string $id Unique identifier of the element the inline help refers to, used as
45 * an identifier in the html.
46 * @param string $help_button_text The Help Button text. Needs a properly escaped string.
47 * @param string $help_content The Help Panel content. Needs a properly escaped string (might contain HTML).
48 * @param string $wrapper Optional Whether to print out a container div element for the Help Panel,
49 * used for styling.
50 * Pass a `has-wrapper` value to print out the container. Default: no container.
51 */
52 public function __construct( $id, $help_button_text, $help_content, $wrapper = '' ) {
53 $this->id = $id;
54 $this->help_button_text = $help_button_text;
55 $this->help_content = $help_content;
56 $this->wrapper = $wrapper;
57 }
58
59 /**
60 * Returns the html for the Help Button.
61 *
62 * @return string
63 */
64 public function get_button_html() {
65
66 if ( ! $this->id || ! $this->help_button_text || ! $this->help_content ) {
67 return '';
68 }
69
70 return sprintf(
71 ' <button type="button" class="yoast_help yoast-help-button dashicons" id="%1$s-help-toggle" aria-expanded="false" aria-controls="%1$s-help"><span class="yoast-help-icon" aria-hidden="true"></span><span class="screen-reader-text">%2$s</span></button>',
72 esc_attr( $this->id ),
73 $this->help_button_text
74 );
75 }
76
77 /**
78 * Returns the html for the Help Panel.
79 *
80 * @return string
81 */
82 public function get_panel_html() {
83
84 if ( ! $this->id || ! $this->help_button_text || ! $this->help_content ) {
85 return '';
86 }
87
88 $wrapper_start = '';
89 $wrapper_end = '';
90
91 if ( $this->wrapper === 'has-wrapper' ) {
92 $wrapper_start = '<div class="yoast-seo-help-container">';
93 $wrapper_end = '</div>';
94 }
95
96 return sprintf(
97 '%1$s<p id="%2$s-help" class="yoast-help-panel">%3$s</p>%4$s',
98 $wrapper_start,
99 esc_attr( $this->id ),
100 $this->help_content,
101 $wrapper_end
102 );
103 }
104 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 /**
9 * Determines the recommended replacement variables based on the context.
10 */
11 class WPSEO_Admin_Recommended_Replace_Vars {
12
13 /**
14 * The recommended replacement variables.
15 *
16 * @var array
17 */
18 protected $recommended_replace_vars = [
19 // Posts types.
20 'page' => [ 'sitename', 'title', 'sep', 'primary_category' ],
21 'post' => [ 'sitename', 'title', 'sep', 'primary_category' ],
22 // Homepage.
23 'homepage' => [ 'sitename', 'sitedesc', 'sep' ],
24 // Custom post type.
25 'custom_post_type' => [ 'sitename', 'title', 'sep' ],
26
27 // Taxonomies.
28 'category' => [ 'sitename', 'term_title', 'sep', 'term_hierarchy' ],
29 'post_tag' => [ 'sitename', 'term_title', 'sep' ],
30 'post_format' => [ 'sitename', 'term_title', 'sep', 'page' ],
31
32 // Custom taxonomy.
33 'term-in-custom-taxonomy' => [ 'sitename', 'term_title', 'sep', 'term_hierarchy' ],
34
35 // Settings - archive pages.
36 'author_archive' => [ 'sitename', 'title', 'sep', 'page' ],
37 'date_archive' => [ 'sitename', 'sep', 'date', 'page' ],
38 'custom-post-type_archive' => [ 'sitename', 'title', 'sep', 'page' ],
39
40 // Settings - special pages.
41 'search' => [ 'sitename', 'searchphrase', 'sep', 'page' ],
42 '404' => [ 'sitename', 'sep' ],
43 ];
44
45 /**
46 * Determines the page type of the current term.
47 *
48 * @param string $taxonomy The taxonomy name.
49 *
50 * @return string The page type.
51 */
52 public function determine_for_term( $taxonomy ) {
53 $recommended_replace_vars = $this->get_recommended_replacevars();
54 if ( array_key_exists( $taxonomy, $recommended_replace_vars ) ) {
55 return $taxonomy;
56 }
57
58 return 'term-in-custom-taxonomy';
59 }
60
61 /**
62 * Determines the page type of the current post.
63 *
64 * @param WP_Post $post A WordPress post instance.
65 *
66 * @return string The page type.
67 */
68 public function determine_for_post( $post ) {
69 if ( $post instanceof WP_Post === false ) {
70 return 'post';
71 }
72
73 if ( $post->post_type === 'page' && $this->is_homepage( $post ) ) {
74 return 'homepage';
75 }
76
77 $recommended_replace_vars = $this->get_recommended_replacevars();
78 if ( array_key_exists( $post->post_type, $recommended_replace_vars ) ) {
79 return $post->post_type;
80 }
81
82 return 'custom_post_type';
83 }
84
85 /**
86 * Determines the page type for a post type.
87 *
88 * @param string $post_type The name of the post_type.
89 * @param string $fallback The page type to fall back to.
90 *
91 * @return string The page type.
92 */
93 public function determine_for_post_type( $post_type, $fallback = 'custom_post_type' ) {
94 $page_type = $post_type;
95 $recommended_replace_vars = $this->get_recommended_replacevars();
96 $has_recommended_replacevars = $this->has_recommended_replace_vars( $recommended_replace_vars, $page_type );
97
98 if ( ! $has_recommended_replacevars ) {
99 return $fallback;
100 }
101
102 return $page_type;
103 }
104
105 /**
106 * Determines the page type for an archive page.
107 *
108 * @param string $name The name of the archive.
109 * @param string $fallback The page type to fall back to.
110 *
111 * @return string The page type.
112 */
113 public function determine_for_archive( $name, $fallback = 'custom-post-type_archive' ) {
114 $page_type = $name . '_archive';
115 $recommended_replace_vars = $this->get_recommended_replacevars();
116 $has_recommended_replacevars = $this->has_recommended_replace_vars( $recommended_replace_vars, $page_type );
117
118 if ( ! $has_recommended_replacevars ) {
119 return $fallback;
120 }
121
122 return $page_type;
123 }
124
125 /**
126 * Retrieves the recommended replacement variables for the given page type.
127 *
128 * @param string $page_type The page type.
129 *
130 * @return array The recommended replacement variables.
131 */
132 public function get_recommended_replacevars_for( $page_type ) {
133 $recommended_replace_vars = $this->get_recommended_replacevars();
134 $has_recommended_replace_vars = $this->has_recommended_replace_vars( $recommended_replace_vars, $page_type );
135
136 if ( ! $has_recommended_replace_vars ) {
137 return [];
138 }
139
140 return $recommended_replace_vars[ $page_type ];
141 }
142
143 /**
144 * Retrieves the recommended replacement variables.
145 *
146 * @return array The recommended replacement variables.
147 */
148 public function get_recommended_replacevars() {
149 /**
150 * Filter: Adds the possibility to add extra recommended replacement variables.
151 *
152 * @api array $additional_replace_vars Empty array to add the replacevars to.
153 */
154 $recommended_replace_vars = apply_filters( 'wpseo_recommended_replace_vars', $this->recommended_replace_vars );
155
156 if ( ! is_array( $recommended_replace_vars ) ) {
157 return $this->recommended_replace_vars;
158 }
159
160 return $recommended_replace_vars;
161 }
162
163 /**
164 * Returns whether the given page type has recommended replace vars.
165 *
166 * @param array $recommended_replace_vars The recommended replace vars
167 * to check in.
168 * @param string $page_type The page type to check.
169 *
170 * @return bool True if there are associated recommended replace vars.
171 */
172 private function has_recommended_replace_vars( $recommended_replace_vars, $page_type ) {
173 if ( ! isset( $recommended_replace_vars[ $page_type ] ) ) {
174 return false;
175 }
176
177 if ( ! is_array( $recommended_replace_vars[ $page_type ] ) ) {
178 return false;
179 }
180
181 return true;
182 }
183
184 /**
185 * Determines whether or not a post is the homepage.
186 *
187 * @param WP_Post $post The WordPress global post object.
188 *
189 * @return bool True if the given post is the homepage.
190 */
191 private function is_homepage( $post ) {
192 if ( $post instanceof WP_Post === false ) {
193 return false;
194 }
195
196 /*
197 * The page on front returns a string with normal WordPress interaction, while the post ID is an int.
198 * This way we make sure we always compare strings.
199 */
200 $post_id = (int) $post->ID;
201 $page_on_front = (int) get_option( 'page_on_front' );
202
203 return get_option( 'show_on_front' ) === 'page' && $page_on_front === $post_id;
204 }
205 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 * @since 1.8.0
7 */
8
9 /**
10 * Customizes user profile.
11 */
12 class WPSEO_Admin_User_Profile {
13
14 /**
15 * Class constructor.
16 */
17 public function __construct() {
18 add_action( 'show_user_profile', [ $this, 'user_profile' ] );
19 add_action( 'edit_user_profile', [ $this, 'user_profile' ] );
20 add_action( 'personal_options_update', [ $this, 'process_user_option_update' ] );
21 add_action( 'edit_user_profile_update', [ $this, 'process_user_option_update' ] );
22
23 add_action( 'update_user_meta', [ $this, 'clear_author_sitemap_cache' ], 10, 3 );
24 }
25
26 /**
27 * Clear author sitemap cache when settings are changed.
28 *
29 * @since 3.1
30 *
31 * @param int $meta_id The ID of the meta option changed.
32 * @param int $object_id The ID of the user.
33 * @param string $meta_key The key of the meta field changed.
34 */
35 public function clear_author_sitemap_cache( $meta_id, $object_id, $meta_key ) {
36 if ( $meta_key === '_yoast_wpseo_profile_updated' ) {
37 WPSEO_Sitemaps_Cache::clear( [ 'author' ] );
38 }
39 }
40
41 /**
42 * Updates the user metas that (might) have been set on the user profile page.
43 *
44 * @param int $user_id User ID of the updated user.
45 */
46 public function process_user_option_update( $user_id ) {
47 update_user_meta( $user_id, '_yoast_wpseo_profile_updated', time() );
48
49 if ( ! check_admin_referer( 'wpseo_user_profile_update', 'wpseo_nonce' ) ) {
50 return;
51 }
52
53 $wpseo_author_title = isset( $_POST['wpseo_author_title'] ) ? sanitize_text_field( wp_unslash( $_POST['wpseo_author_title'] ) ) : '';
54 $wpseo_author_metadesc = isset( $_POST['wpseo_author_metadesc'] ) ? sanitize_text_field( wp_unslash( $_POST['wpseo_author_metadesc'] ) ) : '';
55 $wpseo_noindex_author = isset( $_POST['wpseo_noindex_author'] ) ? sanitize_text_field( wp_unslash( $_POST['wpseo_noindex_author'] ) ) : '';
56 $wpseo_content_analysis_disable = isset( $_POST['wpseo_content_analysis_disable'] ) ? sanitize_text_field( wp_unslash( $_POST['wpseo_content_analysis_disable'] ) ) : '';
57 $wpseo_keyword_analysis_disable = isset( $_POST['wpseo_keyword_analysis_disable'] ) ? sanitize_text_field( wp_unslash( $_POST['wpseo_keyword_analysis_disable'] ) ) : '';
58 $wpseo_inclusive_language_analysis_disable = isset( $_POST['wpseo_inclusive_language_analysis_disable'] ) ? sanitize_text_field( wp_unslash( $_POST['wpseo_inclusive_language_analysis_disable'] ) ) : '';
59
60 update_user_meta( $user_id, 'wpseo_title', $wpseo_author_title );
61 update_user_meta( $user_id, 'wpseo_metadesc', $wpseo_author_metadesc );
62 update_user_meta( $user_id, 'wpseo_noindex_author', $wpseo_noindex_author );
63 update_user_meta( $user_id, 'wpseo_content_analysis_disable', $wpseo_content_analysis_disable );
64 update_user_meta( $user_id, 'wpseo_keyword_analysis_disable', $wpseo_keyword_analysis_disable );
65 update_user_meta( $user_id, 'wpseo_inclusive_language_analysis_disable', $wpseo_inclusive_language_analysis_disable );
66 }
67
68 /**
69 * Add the inputs needed for SEO values to the User Profile page.
70 *
71 * @param WP_User $user User instance to output for.
72 */
73 public function user_profile( $user ) {
74 wp_nonce_field( 'wpseo_user_profile_update', 'wpseo_nonce' );
75
76 require_once WPSEO_PATH . 'admin/views/user-profile.php';
77 }
78 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 /**
9 * Represents the utils for the admin.
10 */
11 class WPSEO_Admin_Utils {
12
13 /**
14 * Gets the install URL for the passed plugin slug.
15 *
16 * @param string $slug The slug to create an install link for.
17 *
18 * @return string The install URL. Empty string if the current user doesn't have the proper capabilities.
19 */
20 public static function get_install_url( $slug ) {
21 if ( ! current_user_can( 'install_plugins' ) ) {
22 return '';
23 }
24
25 return wp_nonce_url(
26 self_admin_url( 'update.php?action=install-plugin&plugin=' . dirname( $slug ) ),
27 'install-plugin_' . dirname( $slug )
28 );
29 }
30
31 /**
32 * Gets the activation URL for the passed plugin slug.
33 *
34 * @param string $slug The slug to create an activation link for.
35 *
36 * @return string The activation URL. Empty string if the current user doesn't have the proper capabilities.
37 */
38 public static function get_activation_url( $slug ) {
39 if ( ! current_user_can( 'install_plugins' ) ) {
40 return '';
41 }
42
43 return wp_nonce_url(
44 self_admin_url( 'plugins.php?action=activate&plugin_status=all&paged=1&s&plugin=' . $slug ),
45 'activate-plugin_' . $slug
46 );
47 }
48
49 /**
50 * Creates a link if the passed plugin is deemend a directly-installable plugin.
51 *
52 * @param array $plugin The plugin to create the link for.
53 *
54 * @return string The link to the plugin install. Returns the title if the plugin is deemed a Premium product.
55 */
56 public static function get_install_link( $plugin ) {
57 $install_url = self::get_install_url( $plugin['slug'] );
58
59 if ( $install_url === '' || ( isset( $plugin['premium'] ) && $plugin['premium'] === true ) ) {
60 return $plugin['title'];
61 }
62
63 return sprintf(
64 '<a href="%s">%s</a>',
65 $install_url,
66 $plugin['title']
67 );
68 }
69
70 /**
71 * Gets a visually hidden accessible message for links that open in a new browser tab.
72 *
73 * @return string The visually hidden accessible message.
74 */
75 public static function get_new_tab_message() {
76 return sprintf(
77 '<span class="screen-reader-text">%s</span>',
78 esc_html__( '(Opens in a new browser tab)', 'wordpress-seo' )
79 );
80 }
81 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 /**
9 * Represents a WPSEO asset
10 */
11 class WPSEO_Admin_Asset {
12
13 /**
14 * Constant used to identify file type as a JS file.
15 *
16 * @var string
17 */
18 const TYPE_JS = 'js';
19
20 /**
21 * Constant used to identify file type as a CSS file.
22 *
23 * @var string
24 */
25 const TYPE_CSS = 'css';
26
27 /**
28 * The name option identifier.
29 *
30 * @var string
31 */
32 const NAME = 'name';
33
34 /**
35 * The source option identifier.
36 *
37 * @var string
38 */
39 const SRC = 'src';
40
41 /**
42 * The dependencies option identifier.
43 *
44 * @var string
45 */
46 const DEPS = 'deps';
47
48 /**
49 * The version option identifier.
50 *
51 * @var string
52 */
53 const VERSION = 'version';
54
55 /* Style specific. */
56
57 /**
58 * The media option identifier.
59 *
60 * @var string
61 */
62 const MEDIA = 'media';
63
64 /**
65 * The rtl option identifier.
66 *
67 * @var string
68 */
69 const RTL = 'rtl';
70
71 /* Script specific. */
72
73 /**
74 * The "in footer" option identifier.
75 *
76 * @var string
77 */
78 const IN_FOOTER = 'in_footer';
79
80 /**
81 * Asset identifier.
82 *
83 * @var string
84 */
85 protected $name;
86
87 /**
88 * Path to the asset.
89 *
90 * @var string
91 */
92 protected $src;
93
94 /**
95 * Asset dependencies.
96 *
97 * @var string|array
98 */
99 protected $deps;
100
101 /**
102 * Asset version.
103 *
104 * @var string
105 */
106 protected $version;
107
108 /**
109 * For CSS Assets. The type of media for which this stylesheet has been defined.
110 *
111 * See https://www.w3.org/TR/CSS2/media.html#media-types.
112 *
113 * @var string
114 */
115 protected $media;
116
117 /**
118 * For JS Assets. Whether or not the script should be loaded in the footer.
119 *
120 * @var bool
121 */
122 protected $in_footer;
123
124 /**
125 * For CSS Assets. Whether this stylesheet is a right-to-left stylesheet.
126 *
127 * @var bool
128 */
129 protected $rtl;
130
131 /**
132 * File suffix.
133 *
134 * @var string
135 */
136 protected $suffix;
137
138 /**
139 * Default asset arguments.
140 *
141 * @var array
142 */
143 private $defaults = [
144 'deps' => [],
145 'in_footer' => true,
146 'rtl' => true,
147 'media' => 'all',
148 'version' => '',
149 'suffix' => '',
150 ];
151
152 /**
153 * Constructs an instance of the WPSEO_Admin_Asset class.
154 *
155 * @param array $args The arguments for this asset.
156 *
157 * @throws InvalidArgumentException Throws when no name or src has been provided.
158 */
159 public function __construct( array $args ) {
160 if ( ! isset( $args['name'] ) ) {
161 throw new InvalidArgumentException( 'name is a required argument' );
162 }
163
164 if ( ! isset( $args['src'] ) ) {
165 throw new InvalidArgumentException( 'src is a required argument' );
166 }
167
168 $args = array_merge( $this->defaults, $args );
169
170 $this->name = $args['name'];
171 $this->src = $args['src'];
172 $this->deps = $args['deps'];
173 $this->version = $args['version'];
174 $this->media = $args['media'];
175 $this->in_footer = $args['in_footer'];
176 $this->rtl = $args['rtl'];
177 $this->suffix = $args['suffix'];
178 }
179
180 /**
181 * Returns the asset identifier.
182 *
183 * @return string
184 */
185 public function get_name() {
186 return $this->name;
187 }
188
189 /**
190 * Returns the path to the asset.
191 *
192 * @return string
193 */
194 public function get_src() {
195 return $this->src;
196 }
197
198 /**
199 * Returns the asset dependencies.
200 *
201 * @return array|string
202 */
203 public function get_deps() {
204 return $this->deps;
205 }
206
207 /**
208 * Returns the asset version.
209 *
210 * @return string|null
211 */
212 public function get_version() {
213 if ( ! empty( $this->version ) ) {
214 return $this->version;
215 }
216
217 return null;
218 }
219
220 /**
221 * Returns the media type for CSS assets.
222 *
223 * @return string
224 */
225 public function get_media() {
226 return $this->media;
227 }
228
229 /**
230 * Returns whether a script asset should be loaded in the footer of the page.
231 *
232 * @return bool
233 */
234 public function is_in_footer() {
235 return $this->in_footer;
236 }
237
238 /**
239 * Returns whether this CSS has a RTL counterpart.
240 *
241 * @return bool
242 */
243 public function has_rtl() {
244 return $this->rtl;
245 }
246
247 /**
248 * Returns the file suffix.
249 *
250 * @return string
251 */
252 public function get_suffix() {
253 return $this->suffix;
254 }
255 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin\Bulk Editor
6 * @since 1.5.0
7 */
8
9 /**
10 * Implements table for bulk description editing.
11 */
12 class WPSEO_Bulk_Description_List_Table extends WPSEO_Bulk_List_Table {
13
14 /**
15 * Current type for this class will be (meta) description.
16 *
17 * @var string
18 */
19 protected $page_type = 'description';
20
21 /**
22 * Settings with are used in __construct.
23 *
24 * @var array
25 */
26 protected $settings = [
27 'singular' => 'wpseo_bulk_description',
28 'plural' => 'wpseo_bulk_descriptions',
29 'ajax' => true,
30 ];
31
32 /**
33 * The field in the database where meta field is saved.
34 *
35 * @var string
36 */
37 protected $target_db_field = 'metadesc';
38
39 /**
40 * The columns shown on the table.
41 *
42 * @return array
43 */
44 public function get_columns() {
45 $columns = [
46 'col_existing_yoast_seo_metadesc' => __( 'Existing Yoast Meta Description', 'wordpress-seo' ),
47 'col_new_yoast_seo_metadesc' => __( 'New Yoast Meta Description', 'wordpress-seo' ),
48 ];
49
50 return $this->merge_columns( $columns );
51 }
52
53 /**
54 * Parse the metadescription.
55 *
56 * @param string $column_name Column name.
57 * @param object $record Data object.
58 * @param string $attributes HTML attributes.
59 *
60 * @return string
61 */
62 protected function parse_page_specific_column( $column_name, $record, $attributes ) {
63 switch ( $column_name ) {
64 case 'col_new_yoast_seo_metadesc':
65 return sprintf(
66 '<textarea id="%1$s" name="%1$s" class="wpseo-new-metadesc" data-id="%2$s" aria-labelledby="col_new_yoast_seo_metadesc"></textarea>',
67 esc_attr( 'wpseo-new-metadesc-' . $record->ID ),
68 esc_attr( $record->ID )
69 );
70
71 case 'col_existing_yoast_seo_metadesc':
72 // @todo Inconsistent return/echo behavior R.
73 // I traced the escaping of the attributes to WPSEO_Bulk_List_Table::column_attributes. Alexander.
74 // The output of WPSEO_Bulk_List_Table::parse_meta_data_field is properly escaped.
75 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
76 echo $this->parse_meta_data_field( $record->ID, $attributes );
77 break;
78 }
79 }
80 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin\Bulk Editor
6 * @since 1.5.0
7 */
8
9 /**
10 * Implements table for bulk title editing.
11 */
12 class WPSEO_Bulk_Title_Editor_List_Table extends WPSEO_Bulk_List_Table {
13
14 /**
15 * Current type for this class will be title.
16 *
17 * @var string
18 */
19 protected $page_type = 'title';
20
21 /**
22 * Settings with are used in __construct.
23 *
24 * @var array
25 */
26 protected $settings = [
27 'singular' => 'wpseo_bulk_title',
28 'plural' => 'wpseo_bulk_titles',
29 'ajax' => true,
30 ];
31
32 /**
33 * The field in the database where meta field is saved.
34 *
35 * @var string
36 */
37 protected $target_db_field = 'title';
38
39 /**
40 * The columns shown on the table.
41 *
42 * @return array
43 */
44 public function get_columns() {
45
46 $columns = [
47 /* translators: %1$s expands to Yoast SEO */
48 'col_existing_yoast_seo_title' => sprintf( __( 'Existing %1$s Title', 'wordpress-seo' ), 'Yoast SEO' ),
49 /* translators: %1$s expands to Yoast SEO */
50 'col_new_yoast_seo_title' => sprintf( __( 'New %1$s Title', 'wordpress-seo' ), 'Yoast SEO' ),
51 ];
52
53 return $this->merge_columns( $columns );
54 }
55
56 /**
57 * Parse the title columns.
58 *
59 * @param string $column_name Column name.
60 * @param object $record Data object.
61 * @param string $attributes HTML attributes.
62 *
63 * @return string
64 */
65 protected function parse_page_specific_column( $column_name, $record, $attributes ) {
66
67 // Fill meta data if exists in $this->meta_data.
68 $meta_data = ( ! empty( $this->meta_data[ $record->ID ] ) ) ? $this->meta_data[ $record->ID ] : [];
69
70 switch ( $column_name ) {
71 case 'col_existing_yoast_seo_title':
72 // @todo Inconsistent return/echo behavior R.
73 // I traced the escaping of the attributes to WPSEO_Bulk_List_Table::column_attributes.
74 // The output of WPSEO_Bulk_List_Table::parse_meta_data_field is properly escaped.
75 // phpcs:ignore WordPress.Security.EscapeOutput
76 echo $this->parse_meta_data_field( $record->ID, $attributes );
77 break;
78
79 case 'col_new_yoast_seo_title':
80 return sprintf(
81 '<input type="text" id="%1$s" name="%1$s" class="wpseo-new-title" data-id="%2$s" aria-labelledby="col_new_yoast_seo_title" />',
82 'wpseo-new-title-' . $record->ID,
83 $record->ID
84 );
85 }
86
87 unset( $meta_data );
88 }
89 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 /**
9 * Collects the data from the added collection objects.
10 */
11 class WPSEO_Collector {
12
13 /**
14 * Holds the collections.
15 *
16 * @var WPSEO_Collection[]
17 */
18 protected $collections = [];
19
20 /**
21 * Adds a collection object to the collections.
22 *
23 * @param WPSEO_Collection $collection The collection object to add.
24 */
25 public function add_collection( WPSEO_Collection $collection ) {
26 $this->collections[] = $collection;
27 }
28
29 /**
30 * Collects the data from the collection objects.
31 *
32 * @return array The collected data.
33 */
34 public function collect() {
35 $data = [];
36
37 foreach ( $this->collections as $collection ) {
38 $data = array_merge( $data, $collection->get() );
39 }
40
41 return $data;
42 }
43
44 /**
45 * Returns the collected data as a JSON encoded string.
46 *
47 * @return false|string The encode string.
48 */
49 public function get_as_json() {
50 return WPSEO_Utils::format_json_encode( $this->collect() );
51 }
52 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 use Yoast\WP\SEO\Config\Schema_Types;
9 use Yoast\WP\SEO\Integrations\Settings_Integration;
10 use Yoast\WP\SEO\Integrations\Academy_Integration;
11
12 /**
13 * Class WPSEO_Admin_Pages.
14 *
15 * Class with functionality for the Yoast SEO admin pages.
16 */
17 class WPSEO_Admin_Pages {
18
19 /**
20 * The option in use for the current admin page.
21 *
22 * @var string
23 */
24 public $currentoption = 'wpseo';
25
26 /**
27 * Holds the asset manager.
28 *
29 * @var WPSEO_Admin_Asset_Manager
30 */
31 private $asset_manager;
32
33 /**
34 * Class constructor, which basically only hooks the init function on the init hook.
35 */
36 public function __construct() {
37 add_action( 'init', [ $this, 'init' ], 20 );
38 $this->asset_manager = new WPSEO_Admin_Asset_Manager();
39 }
40
41 /**
42 * Make sure the needed scripts are loaded for admin pages.
43 */
44 public function init() {
45 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
46 $page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
47 if ( $page === Settings_Integration::PAGE || $page === Academy_Integration::PAGE ) {
48 // Bail, this is managed in the Settings_Integration.
49 return;
50 }
51
52 add_action( 'admin_enqueue_scripts', [ $this, 'config_page_scripts' ] );
53 add_action( 'admin_enqueue_scripts', [ $this, 'config_page_styles' ] );
54 }
55
56 /**
57 * Loads the required styles for the config page.
58 */
59 public function config_page_styles() {
60 wp_enqueue_style( 'dashboard' );
61 wp_enqueue_style( 'thickbox' );
62 wp_enqueue_style( 'global' );
63 wp_enqueue_style( 'wp-admin' );
64 $this->asset_manager->enqueue_style( 'admin-css' );
65 $this->asset_manager->enqueue_style( 'monorepo' );
66
67 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
68 $page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
69 if ( $page === 'wpseo_licenses' ) {
70 $this->asset_manager->enqueue_style( 'tailwind' );
71 }
72 }
73
74 /**
75 * Loads the required scripts for the config page.
76 */
77 public function config_page_scripts() {
78 $this->asset_manager->enqueue_script( 'settings' );
79 wp_enqueue_script( 'dashboard' );
80 wp_enqueue_script( 'thickbox' );
81
82 $alert_dismissal_action = YoastSEO()->classes->get( \Yoast\WP\SEO\Actions\Alert_Dismissal_Action::class );
83 $dismissed_alerts = $alert_dismissal_action->all_dismissed();
84
85 $script_data = [
86 'userLanguageCode' => WPSEO_Language_Utils::get_language( \get_user_locale() ),
87 'dismissedAlerts' => $dismissed_alerts,
88 'isRtl' => is_rtl(),
89 'isPremium' => YoastSEO()->helpers->product->is_premium(),
90 'webinarIntroSettingsUrl' => WPSEO_Shortlinker::get( 'https://yoa.st/webinar-intro-settings' ),
91 'webinarIntroFirstTimeConfigUrl' => $this->get_webinar_shortlink(),
92 ];
93
94 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
95 $page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
96
97 if ( in_array( $page, [ WPSEO_Admin::PAGE_IDENTIFIER, 'wpseo_workouts' ], true ) ) {
98 wp_enqueue_media();
99
100 $script_data['media'] = [
101 'choose_image' => __( 'Use Image', 'wordpress-seo' ),
102 ];
103
104 $script_data['userEditUrl'] = add_query_arg( 'user_id', '{user_id}', admin_url( 'user-edit.php' ) );
105 }
106
107 if ( $page === 'wpseo_tools' ) {
108 $this->enqueue_tools_scripts();
109 }
110
111 $this->asset_manager->localize_script( 'settings', 'wpseoScriptData', $script_data );
112 $this->asset_manager->enqueue_user_language_script();
113 }
114
115 /**
116 * Retrieves some variables that are needed for replacing variables in JS.
117 *
118 * @deprecated 20.3
119 * @codeCoverageIgnore
120 *
121 * @return array The replacement and recommended replacement variables.
122 */
123 public function get_replace_vars_script_data() {
124 _deprecated_function( __METHOD__, 'Yoast SEO 20.3' );
125 $replace_vars = new WPSEO_Replace_Vars();
126 $recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars();
127 $editor_specific_replace_vars = new WPSEO_Admin_Editor_Specific_Replace_Vars();
128 $replace_vars_list = $replace_vars->get_replacement_variables_list();
129
130 return [
131 'replace_vars' => $replace_vars_list,
132 'recommended_replace_vars' => $recommended_replace_vars->get_recommended_replacevars(),
133 'editor_specific_replace_vars' => $editor_specific_replace_vars->get(),
134 'shared_replace_vars' => $editor_specific_replace_vars->get_generic( $replace_vars_list ),
135 'hidden_replace_vars' => $replace_vars->get_hidden_replace_vars(),
136 ];
137 }
138
139 /**
140 * Enqueues and handles all the tool dependencies.
141 */
142 private function enqueue_tools_scripts() {
143 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
144 $tool = isset( $_GET['tool'] ) && is_string( $_GET['tool'] ) ? sanitize_text_field( wp_unslash( $_GET['tool'] ) ) : '';
145
146 if ( empty( $tool ) ) {
147 $this->asset_manager->enqueue_script( 'yoast-seo' );
148 }
149
150 if ( $tool === 'bulk-editor' ) {
151 $this->asset_manager->enqueue_script( 'bulk-editor' );
152 }
153 }
154
155 /**
156 * Returns the appropriate shortlink for the Webinar.
157 *
158 * @return string The shortlink for the Webinar.
159 */
160 private function get_webinar_shortlink() {
161 if ( YoastSEO()->helpers->product->is_premium() ) {
162 return WPSEO_Shortlinker::get( 'https://yoa.st/webinar-intro-first-time-config-premium' );
163 }
164
165 return WPSEO_Shortlinker::get( 'https://yoa.st/webinar-intro-first-time-config' );
166 }
167 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin\Customizer
6 */
7
8 /**
9 * Class with functionality to support WP SEO settings in WordPress Customizer.
10 */
11 class WPSEO_Customizer {
12
13 /**
14 * Holds the customize manager.
15 *
16 * @var WP_Customize_Manager
17 */
18 protected $wp_customize;
19
20 /**
21 * Template for the setting IDs used for the customizer.
22 *
23 * @var string
24 */
25 private $setting_template = 'wpseo_titles[%s]';
26
27 /**
28 * Default arguments for the breadcrumbs customizer settings object.
29 *
30 * @var array
31 */
32 private $default_setting_args = [
33 'default' => '',
34 'type' => 'option',
35 'transport' => 'refresh',
36 ];
37
38 /**
39 * Default arguments for the breadcrumbs customizer control object.
40 *
41 * @var array
42 */
43 private $default_control_args = [
44 'label' => '',
45 'type' => 'text',
46 'section' => 'wpseo_breadcrumbs_customizer_section',
47 'settings' => '',
48 'context' => '',
49 ];
50
51 /**
52 * Construct Method.
53 */
54 public function __construct() {
55 add_action( 'customize_register', [ $this, 'wpseo_customize_register' ] );
56 }
57
58 /**
59 * Function to support WordPress Customizer.
60 *
61 * @param WP_Customize_Manager $wp_customize Manager class instance.
62 */
63 public function wpseo_customize_register( $wp_customize ) {
64 if ( ! WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ) ) {
65 return;
66 }
67
68 $this->wp_customize = $wp_customize;
69
70 $this->breadcrumbs_section();
71 $this->breadcrumbs_blog_show_setting();
72 $this->breadcrumbs_separator_setting();
73 $this->breadcrumbs_home_setting();
74 $this->breadcrumbs_prefix_setting();
75 $this->breadcrumbs_archiveprefix_setting();
76 $this->breadcrumbs_searchprefix_setting();
77 $this->breadcrumbs_404_setting();
78 }
79
80 /**
81 * Add the breadcrumbs section to the customizer.
82 */
83 private function breadcrumbs_section() {
84 $section_args = [
85 /* translators: %s is the name of the plugin */
86 'title' => sprintf( __( '%s Breadcrumbs', 'wordpress-seo' ), 'Yoast SEO' ),
87 'priority' => 999,
88 'active_callback' => [ $this, 'breadcrumbs_active_callback' ],
89 ];
90
91 $this->wp_customize->add_section( 'wpseo_breadcrumbs_customizer_section', $section_args );
92 }
93
94 /**
95 * Returns whether or not the breadcrumbs are active.
96 *
97 * @return bool
98 */
99 public function breadcrumbs_active_callback() {
100 return current_theme_supports( 'yoast-seo-breadcrumbs' ) || WPSEO_Options::get( 'breadcrumbs-enable' );
101 }
102
103 /**
104 * Adds the breadcrumbs show blog checkbox.
105 */
106 private function breadcrumbs_blog_show_setting() {
107 $index = 'breadcrumbs-display-blog-page';
108 $control_args = [
109 'label' => __( 'Show blog page in breadcrumbs', 'wordpress-seo' ),
110 'type' => 'checkbox',
111 'active_callback' => [ $this, 'breadcrumbs_blog_show_active_cb' ],
112 ];
113
114 $this->add_setting_and_control( $index, $control_args );
115 }
116
117 /**
118 * Returns whether or not to show the breadcrumbs blog show option.
119 *
120 * @return bool
121 */
122 public function breadcrumbs_blog_show_active_cb() {
123 return get_option( 'show_on_front' ) === 'page';
124 }
125
126 /**
127 * Adds the breadcrumbs separator text field.
128 */
129 private function breadcrumbs_separator_setting() {
130 $index = 'breadcrumbs-sep';
131 $control_args = [
132 'label' => __( 'Breadcrumbs separator:', 'wordpress-seo' ),
133 ];
134 $id = 'wpseo-breadcrumbs-separator';
135
136 $this->add_setting_and_control( $index, $control_args, $id );
137 }
138
139 /**
140 * Adds the breadcrumbs home anchor text field.
141 */
142 private function breadcrumbs_home_setting() {
143 $index = 'breadcrumbs-home';
144 $control_args = [
145 'label' => __( 'Anchor text for the homepage:', 'wordpress-seo' ),
146 ];
147
148 $this->add_setting_and_control( $index, $control_args );
149 }
150
151 /**
152 * Adds the breadcrumbs prefix text field.
153 */
154 private function breadcrumbs_prefix_setting() {
155 $index = 'breadcrumbs-prefix';
156 $control_args = [
157 'label' => __( 'Prefix for breadcrumbs:', 'wordpress-seo' ),
158 ];
159
160 $this->add_setting_and_control( $index, $control_args );
161 }
162
163 /**
164 * Adds the breadcrumbs archive prefix text field.
165 */
166 private function breadcrumbs_archiveprefix_setting() {
167 $index = 'breadcrumbs-archiveprefix';
168 $control_args = [
169 'label' => __( 'Prefix for archive pages:', 'wordpress-seo' ),
170 ];
171
172 $this->add_setting_and_control( $index, $control_args );
173 }
174
175 /**
176 * Adds the breadcrumbs search prefix text field.
177 */
178 private function breadcrumbs_searchprefix_setting() {
179 $index = 'breadcrumbs-searchprefix';
180 $control_args = [
181 'label' => __( 'Prefix for search result pages:', 'wordpress-seo' ),
182 ];
183
184 $this->add_setting_and_control( $index, $control_args );
185 }
186
187 /**
188 * Adds the breadcrumb 404 prefix text field.
189 */
190 private function breadcrumbs_404_setting() {
191 $index = 'breadcrumbs-404crumb';
192 $control_args = [
193 'label' => __( 'Breadcrumb for 404 pages:', 'wordpress-seo' ),
194 ];
195
196 $this->add_setting_and_control( $index, $control_args );
197 }
198
199 /**
200 * Adds the customizer setting and control.
201 *
202 * @param string $index Array key index to use for the customizer setting.
203 * @param array $control_args Customizer control object arguments.
204 * Only those different from the default need to be passed.
205 * @param string|null $id Optional. Customizer control object ID.
206 * Will default to 'wpseo-' . $index.
207 * @param array $custom_settings Optional. Customizer setting arguments.
208 * Only those different from the default need to be passed.
209 */
210 private function add_setting_and_control( $index, $control_args, $id = null, $custom_settings = [] ) {
211 $setting = sprintf( $this->setting_template, $index );
212 $control_args = array_merge( $this->default_control_args, $control_args );
213 $control_args['settings'] = $setting;
214
215 $settings_args = $this->default_setting_args;
216 if ( ! empty( $custom_settings ) ) {
217 $settings_args = array_merge( $settings_args, $custom_settings );
218 }
219
220 if ( ! isset( $id ) ) {
221 $id = 'wpseo-' . $index;
222 }
223
224 $this->wp_customize->add_setting( $setting, $settings_args );
225
226 $control = new WP_Customize_Control( $this->wp_customize, $id, $control_args );
227 $this->wp_customize->add_control( $control );
228 }
229 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 /**
9 * Represents the proxy for communicating with the database.
10 */
11 class WPSEO_Database_Proxy {
12
13 /**
14 * Holds the table name.
15 *
16 * @var string
17 */
18 protected $table_name;
19
20 /**
21 * Determines whether to suppress errors or not.
22 *
23 * @var bool
24 */
25 protected $suppress_errors = true;
26
27 /**
28 * Determines if this table is multisite.
29 *
30 * @var bool
31 */
32 protected $is_multisite_table = false;
33
34 /**
35 * Holds the last suppressed state.
36 *
37 * @var bool
38 */
39 protected $last_suppressed_state;
40
41 /**
42 * Holds the WordPress database object.
43 *
44 * @var wpdb
45 */
46 protected $database;
47
48 /**
49 * Sets the class attributes and registers the table.
50 *
51 * @param wpdb $database The database object.
52 * @param string $table_name The table name that is represented.
53 * @param bool $suppress_errors Should the errors be suppressed.
54 * @param bool $is_multisite_table Should the table be global in multisite.
55 */
56 public function __construct( $database, $table_name, $suppress_errors = true, $is_multisite_table = false ) {
57 $this->table_name = $table_name;
58 $this->suppress_errors = (bool) $suppress_errors;
59 $this->is_multisite_table = (bool) $is_multisite_table;
60 $this->database = $database;
61
62 // If the table prefix was provided, strip it as it's handled automatically.
63 $table_prefix = $this->get_table_prefix();
64 if ( ! empty( $table_prefix ) && strpos( $this->table_name, $table_prefix ) === 0 ) {
65 $this->table_prefix = substr( $this->table_name, strlen( $table_prefix ) );
66 }
67
68 if ( ! $this->is_table_registered() ) {
69 $this->register_table();
70 }
71 }
72
73 /**
74 * Inserts data into the database.
75 *
76 * @param array $data Data to insert.
77 * @param array|string|null $format Formats for the data.
78 *
79 * @return false|int Total amount of inserted rows or false on error.
80 */
81 public function insert( array $data, $format = null ) {
82 $this->pre_execution();
83
84 $result = $this->database->insert( $this->get_table_name(), $data, $format );
85
86 $this->post_execution();
87
88 return $result;
89 }
90
91 /**
92 * Updates data in the database.
93 *
94 * @param array $data Data to update on the table.
95 * @param array $where Where condition as key => value array.
96 * @param array|string|null $format Optional. Data prepare format.
97 * @param array|string|null $where_format Optional. Where prepare format.
98 *
99 * @return false|int False when the update request is invalid, int on number of rows changed.
100 */
101 public function update( array $data, array $where, $format = null, $where_format = null ) {
102 $this->pre_execution();
103
104 $result = $this->database->update( $this->get_table_name(), $data, $where, $format, $where_format );
105
106 $this->post_execution();
107
108 return $result;
109 }
110
111 /**
112 * Upserts data in the database.
113 *
114 * Performs an insert into and if key is duplicate it will update the existing record.
115 *
116 * @param array $data Data to update on the table.
117 * @param array|null $where Unused. Where condition as key => value array.
118 * @param array|string|null $format Optional. Data prepare format.
119 * @param array|string|null $where_format Optional. Where prepare format.
120 *
121 * @return false|int False when the upsert request is invalid, int on number of rows changed.
122 */
123 public function upsert( array $data, array $where = null, $format = null, $where_format = null ) {
124 if ( $where_format !== null ) {
125 _deprecated_argument( __METHOD__, '7.7.0', 'The where_format argument is deprecated' );
126 }
127
128 $this->pre_execution();
129
130 $update = [];
131 $keys = [];
132 $columns = array_keys( $data );
133 foreach ( $columns as $column ) {
134 $keys[] = '`' . $column . '`';
135 $update[] = sprintf( '`%1$s` = VALUES(`%1$s`)', $column );
136 }
137
138 $query = sprintf(
139 'INSERT INTO `%1$s` (%2$s) VALUES ( %3$s ) ON DUPLICATE KEY UPDATE %4$s',
140 $this->get_table_name(),
141 implode( ', ', $keys ),
142 implode( ', ', array_fill( 0, count( $data ), '%s' ) ),
143 implode( ', ', $update )
144 );
145
146 $result = $this->database->query(
147 $this->database->prepare(
148 $query,
149 array_values( $data )
150 )
151 );
152
153 $this->post_execution();
154
155 return $result;
156 }
157
158 /**
159 * Deletes a record from the database.
160 *
161 * @param array $where Where clauses for the query.
162 * @param array|string|null $format Formats for the data.
163 *
164 * @return false|int
165 */
166 public function delete( array $where, $format = null ) {
167 $this->pre_execution();
168
169 $result = $this->database->delete( $this->get_table_name(), $where, $format );
170
171 $this->post_execution();
172
173 return $result;
174 }
175
176 /**
177 * Executes the given query and returns the results.
178 *
179 * @param string $query The query to execute.
180 *
181 * @return array|object|null The resultset
182 */
183 public function get_results( $query ) {
184 $this->pre_execution();
185
186 $results = $this->database->get_results( $query );
187
188 $this->post_execution();
189
190 return $results;
191 }
192
193 /**
194 * Creates a table to the database.
195 *
196 * @param array $columns The columns to create.
197 * @param array $indexes The indexes to use.
198 *
199 * @return bool True when creation is successful.
200 */
201 public function create_table( array $columns, array $indexes = [] ) {
202 $create_table = sprintf(
203 'CREATE TABLE IF NOT EXISTS %1$s ( %2$s ) %3$s',
204 $this->get_table_name(),
205 implode( ',', array_merge( $columns, $indexes ) ),
206 $this->database->get_charset_collate()
207 );
208
209 $this->pre_execution();
210
211 $is_created = (bool) $this->database->query( $create_table );
212
213 $this->post_execution();
214
215 return $is_created;
216 }
217
218 /**
219 * Checks if there is an error.
220 *
221 * @return bool Returns true when there is an error.
222 */
223 public function has_error() {
224 return ( $this->database->last_error !== '' );
225 }
226
227 /**
228 * Executed before a query will be ran.
229 */
230 protected function pre_execution() {
231 if ( $this->suppress_errors ) {
232 $this->last_suppressed_state = $this->database->suppress_errors();
233 }
234 }
235
236 /**
237 * Executed after a query has been ran.
238 */
239 protected function post_execution() {
240 if ( $this->suppress_errors ) {
241 $this->database->suppress_errors( $this->last_suppressed_state );
242 }
243 }
244
245 /**
246 * Returns the full table name.
247 *
248 * @return string Full table name including prefix.
249 */
250 public function get_table_name() {
251 return $this->get_table_prefix() . $this->table_name;
252 }
253
254 /**
255 * Returns the prefix to use for the table.
256 *
257 * @return string The table prefix depending on the database context.
258 */
259 protected function get_table_prefix() {
260 if ( $this->is_multisite_table ) {
261 return $this->database->base_prefix;
262 }
263
264 return $this->database->get_blog_prefix();
265 }
266
267 /**
268 * Registers the table with WordPress.
269 *
270 * @return void
271 */
272 protected function register_table() {
273 $table_name = $this->table_name;
274 $full_table_name = $this->get_table_name();
275
276 $this->database->$table_name = $full_table_name;
277
278 if ( $this->is_multisite_table ) {
279 $this->database->ms_global_tables[] = $table_name;
280 return;
281 }
282
283 $this->database->tables[] = $table_name;
284 }
285
286 /**
287 * Checks if the table has been registered with WordPress.
288 *
289 * @return bool True if the table is registered, false otherwise.
290 */
291 protected function is_table_registered() {
292 if ( $this->is_multisite_table ) {
293 return in_array( $this->table_name, $this->database->ms_global_tables, true );
294 }
295
296 return in_array( $this->table_name, $this->database->tables, true );
297 }
298 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin\Export
6 */
7
8 /**
9 * Class WPSEO_Export.
10 *
11 * Class with functionality to export the WP SEO settings.
12 */
13 class WPSEO_Export {
14
15 /**
16 * Holds the nonce action.
17 *
18 * @var string
19 */
20 const NONCE_ACTION = 'wpseo_export';
21
22 /**
23 * Holds the export data.
24 *
25 * @var string
26 */
27 private $export = '';
28
29 /**
30 * Holds whether the export was a success.
31 *
32 * @var bool
33 */
34 public $success;
35
36 /**
37 * Handles the export request.
38 */
39 public function export() {
40 check_admin_referer( self::NONCE_ACTION );
41 $this->export_settings();
42 $this->output();
43 }
44
45 /**
46 * Outputs the export.
47 */
48 public function output() {
49 if ( ! WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ) ) {
50 esc_html_e( 'You do not have the required rights to export settings.', 'wordpress-seo' );
51 return;
52 }
53
54 echo '<p id="wpseo-settings-export-desc">';
55 printf(
56 /* translators: %1$s expands to Import settings */
57 esc_html__(
58 'Copy all these settings to another site\'s %1$s tab and click "%1$s" there.',
59 'wordpress-seo'
60 ),
61 esc_html__(
62 'Import settings',
63 'wordpress-seo'
64 )
65 );
66 echo '</p>';
67 /* translators: %1$s expands to Yoast SEO */
68 echo '<label for="wpseo-settings-export" class="yoast-inline-label">' . sprintf( __( 'Your %1$s settings:', 'wordpress-seo' ), 'Yoast SEO' ) . '</label><br />';
69 echo '<textarea id="wpseo-settings-export" rows="20" cols="100" aria-describedby="wpseo-settings-export-desc">' . esc_textarea( $this->export ) . '</textarea>';
70 }
71
72 /**
73 * Exports the current site's WP SEO settings.
74 */
75 private function export_settings() {
76 $this->export_header();
77
78 foreach ( WPSEO_Options::get_option_names() as $opt_group ) {
79 $this->write_opt_group( $opt_group );
80 }
81 }
82
83 /**
84 * Writes the header of the export.
85 */
86 private function export_header() {
87 $header = sprintf(
88 /* translators: %1$s expands to Yoast SEO, %2$s expands to Yoast.com */
89 esc_html__( 'These are settings for the %1$s plugin by %2$s', 'wordpress-seo' ),
90 'Yoast SEO',
91 'Yoast.com'
92 );
93 $this->write_line( '; ' . $header );
94 }
95
96 /**
97 * Writes a line to the export.
98 *
99 * @param string $line Line string.
100 * @param bool $newline_first Boolean flag whether to prepend with new line.
101 */
102 private function write_line( $line, $newline_first = false ) {
103 if ( $newline_first ) {
104 $this->export .= PHP_EOL;
105 }
106 $this->export .= $line . PHP_EOL;
107 }
108
109 /**
110 * Writes an entire option group to the export.
111 *
112 * @param string $opt_group Option group name.
113 */
114 private function write_opt_group( $opt_group ) {
115
116 $this->write_line( '[' . $opt_group . ']', true );
117
118 $options = get_option( $opt_group );
119
120 if ( ! is_array( $options ) ) {
121 return;
122 }
123
124 foreach ( $options as $key => $elem ) {
125 if ( is_array( $elem ) ) {
126 $count = count( $elem );
127 for ( $i = 0; $i < $count; $i++ ) {
128 $elem_check = isset( $elem[ $i ] ) ? $elem[ $i ] : null;
129 $this->write_setting( $key . '[]', $elem_check );
130 }
131 }
132 else {
133 $this->write_setting( $key, $elem );
134 }
135 }
136 }
137
138 /**
139 * Writes a settings line to the export.
140 *
141 * @param string $key Key string.
142 * @param string $val Value string.
143 */
144 private function write_setting( $key, $val ) {
145 if ( is_string( $val ) ) {
146 $val = '"' . $val . '"';
147 }
148 $this->write_line( $key . ' = ' . $val );
149 }
150 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 /**
9 * Exposes shortlinks in a global, so that we can pass them to our Javascript components.
10 */
11 class WPSEO_Expose_Shortlinks implements WPSEO_WordPress_Integration {
12
13 /**
14 * Array containing the keys and shortlinks.
15 *
16 * @var array
17 */
18 private $shortlinks = [
19 'shortlinks.advanced.allow_search_engines' => 'https://yoa.st/allow-search-engines',
20 'shortlinks.advanced.follow_links' => 'https://yoa.st/follow-links',
21 'shortlinks.advanced.meta_robots' => 'https://yoa.st/meta-robots-advanced',
22 'shortlinks.advanced.breadcrumbs_title' => 'https://yoa.st/breadcrumbs-title',
23 'shortlinks.metabox.schema.explanation' => 'https://yoa.st/400',
24 'shortlinks.metabox.schema.page_type' => 'https://yoa.st/402',
25 'shortlinks.sidebar.schema.explanation' => 'https://yoa.st/401',
26 'shortlinks.sidebar.schema.page_type' => 'https://yoa.st/403',
27 'shortlinks.focus_keyword_info' => 'https://yoa.st/focus-keyword',
28 'shortlinks.nofollow_sponsored' => 'https://yoa.st/nofollow-sponsored',
29 'shortlinks.snippet_preview_info' => 'https://yoa.st/snippet-preview',
30 'shortlinks.cornerstone_content_info' => 'https://yoa.st/1i9',
31 'shortlinks.upsell.social_preview.facebook' => 'https://yoa.st/social-preview-facebook',
32 'shortlinks.upsell.social_preview.twitter' => 'https://yoa.st/social-preview-twitter',
33 'shortlinks.upsell.sidebar.news' => 'https://yoa.st/get-news-sidebar',
34 'shortlinks.upsell.sidebar.focus_keyword_synonyms_link' => 'https://yoa.st/textlink-synonyms-popup-sidebar',
35 'shortlinks.upsell.sidebar.focus_keyword_synonyms_button' => 'https://yoa.st/keyword-synonyms-popup-sidebar',
36 'shortlinks.upsell.sidebar.premium_seo_analysis_button' => 'https://yoa.st/premium-seo-analysis-sidebar',
37 'shortlinks.upsell.sidebar.focus_keyword_additional_link' => 'https://yoa.st/textlink-keywords-popup-sidebar',
38 'shortlinks.upsell.sidebar.focus_keyword_additional_button' => 'https://yoa.st/add-keywords-popup-sidebar',
39 'shortlinks.upsell.sidebar.additional_link' => 'https://yoa.st/textlink-keywords-sidebar',
40 'shortlinks.upsell.sidebar.additional_button' => 'https://yoa.st/add-keywords-sidebar',
41 'shortlinks.upsell.sidebar.keyphrase_distribution' => 'https://yoa.st/keyphrase-distribution-sidebar',
42 'shortlinks.upsell.sidebar.word_complexity' => 'https://yoa.st/word-complexity-sidebar',
43 'shortlinks.upsell.metabox.news' => 'https://yoa.st/get-news-metabox',
44 'shortlinks.upsell.metabox.go_premium' => 'https://yoa.st/pe-premium-page',
45 'shortlinks.upsell.metabox.focus_keyword_synonyms_link' => 'https://yoa.st/textlink-synonyms-popup-metabox',
46 'shortlinks.upsell.metabox.focus_keyword_synonyms_button' => 'https://yoa.st/keyword-synonyms-popup',
47 'shortlinks.upsell.metabox.premium_seo_analysis_button' => 'https://yoa.st/premium-seo-analysis-metabox',
48 'shortlinks.upsell.metabox.focus_keyword_additional_link' => 'https://yoa.st/textlink-keywords-popup-metabox',
49 'shortlinks.upsell.metabox.focus_keyword_additional_button' => 'https://yoa.st/add-keywords-popup',
50 'shortlinks.upsell.metabox.additional_link' => 'https://yoa.st/textlink-keywords-metabox',
51 'shortlinks.upsell.metabox.additional_button' => 'https://yoa.st/add-keywords-metabox',
52 'shortlinks.upsell.metabox.keyphrase_distribution' => 'https://yoa.st/keyphrase-distribution-metabox',
53 'shortlinks.upsell.metabox.word_complexity' => 'https://yoa.st/word-complexity-metabox',
54 'shortlinks.upsell.gsc.create_redirect_button' => 'https://yoa.st/redirects',
55 'shortlinks.readability_analysis_info' => 'https://yoa.st/readability-analysis',
56 'shortlinks.inclusive_language_analysis_info' => 'https://yoa.st/inclusive-language-analysis',
57 'shortlinks.activate_premium_info' => 'https://yoa.st/activate-subscription',
58 'shortlinks.upsell.sidebar.morphology_upsell_metabox' => 'https://yoa.st/morphology-upsell-metabox',
59 'shortlinks.upsell.sidebar.morphology_upsell_sidebar' => 'https://yoa.st/morphology-upsell-sidebar',
60 'shortlinks.semrush.volume_help' => 'https://yoa.st/3-v',
61 'shortlinks.semrush.trend_help' => 'https://yoa.st/3-v',
62 'shortlinks.semrush.prices' => 'https://yoa.st/semrush-prices',
63 'shortlinks.semrush.premium_landing_page' => 'https://yoa.st/413',
64 'shortlinks.wincher.seo_performance' => 'https://yoa.st/wincher-integration',
65 'shortlinks-insights-estimated_reading_time' => 'https://yoa.st/4fd',
66 'shortlinks-insights-flesch_reading_ease' => 'https://yoa.st/34r',
67 'shortlinks-insights-flesch_reading_ease_sidebar' => 'https://yoa.st/4mf',
68 'shortlinks-insights-flesch_reading_ease_metabox' => 'https://yoa.st/4mg',
69 'shortlinks-insights-flesch_reading_ease_article' => 'https://yoa.st/34s',
70 'shortlinks-insights-keyword_research_link' => 'https://yoa.st/keyword-research-metabox',
71 'shortlinks-insights-upsell-sidebar-prominent_words' => 'https://yoa.st/prominent-words-upsell-sidebar',
72 'shortlinks-insights-upsell-metabox-prominent_words' => 'https://yoa.st/prominent-words-upsell-metabox',
73 'shortlinks-insights-upsell-elementor-prominent_words' => 'https://yoa.st/prominent-words-upsell-elementor',
74 'shortlinks-insights-word_count' => 'https://yoa.st/word-count',
75 'shortlinks-insights-upsell-sidebar-text_formality' => 'https://yoa.st/formality-upsell-sidebar',
76 'shortlinks-insights-upsell-metabox-text_formality' => 'https://yoa.st/formality-upsell-metabox',
77 'shortlinks-insights-upsell-elementor-text_formality' => 'https://yoa.st/formality-upsell-elementor',
78 'shortlinks-insights-text_formality_info_free' => 'https://yoa.st/formality-free',
79 'shortlinks-insights-text_formality_info_premium' => 'https://yoa.st/formality',
80 ];
81
82 /**
83 * Registers all hooks to WordPress.
84 *
85 * @return void
86 */
87 public function register_hooks() {
88 add_filter( 'wpseo_admin_l10n', [ $this, 'expose_shortlinks' ] );
89 }
90
91 /**
92 * Adds shortlinks to the passed array.
93 *
94 * @param array $input The array to add shortlinks to.
95 *
96 * @return array The passed array with the additional shortlinks.
97 */
98 public function expose_shortlinks( $input ) {
99 foreach ( $this->get_shortlinks() as $key => $shortlink ) {
100 $input[ $key ] = WPSEO_Shortlinker::get( $shortlink );
101 }
102
103 $input['default_query_params'] = WPSEO_Shortlinker::get_query_params();
104
105 return $input;
106 }
107
108 /**
109 * Retrieves the shortlinks.
110 *
111 * @return array The shortlinks.
112 */
113 private function get_shortlinks() {
114 if ( ! $this->is_term_edit() ) {
115 return $this->shortlinks;
116 }
117
118 $shortlinks = $this->shortlinks;
119
120 $shortlinks['shortlinks.upsell.metabox.focus_keyword_synonyms_link'] = 'https://yoa.st/textlink-synonyms-popup-metabox-term';
121 $shortlinks['shortlinks.upsell.metabox.focus_keyword_synonyms_button'] = 'https://yoa.st/keyword-synonyms-popup-term';
122 $shortlinks['shortlinks.upsell.metabox.focus_keyword_additional_link'] = 'https://yoa.st/textlink-keywords-popup-metabox-term';
123 $shortlinks['shortlinks.upsell.metabox.focus_keyword_additional_button'] = 'https://yoa.st/add-keywords-popup-term';
124 $shortlinks['shortlinks.upsell.metabox.additional_link'] = 'https://yoa.st/textlink-keywords-metabox-term';
125 $shortlinks['shortlinks.upsell.metabox.additional_button'] = 'https://yoa.st/add-keywords-metabox-term';
126 $shortlinks['shortlinks.upsell.sidebar.morphology_upsell_metabox'] = 'https://yoa.st/morphology-upsell-metabox-term';
127 $shortlinks['shortlinks.upsell.metabox.keyphrase_distribution'] = 'https://yoa.st/keyphrase-distribution-metabox-term';
128 $shortlinks['shortlinks.upsell.metabox.word_complexity'] = 'https://yoa.st/word-complexity-metabox-term';
129
130 return $shortlinks;
131 }
132
133 /**
134 * Checks if the current page is a term edit page.
135 *
136 * @return bool True when page is term edit.
137 */
138 private function is_term_edit() {
139 global $pagenow;
140
141 return WPSEO_Taxonomy::is_term_edit( $pagenow );
142 }
143 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Gutenberg_Compatibility
6 */
7
8 /**
9 * Class WPSEO_Gutenberg_Compatibility
10 */
11 class WPSEO_Gutenberg_Compatibility {
12
13 /**
14 * The currently released version of Gutenberg.
15 *
16 * @var string
17 */
18 const CURRENT_RELEASE = '15.7.0';
19
20 /**
21 * The minimally supported version of Gutenberg by the plugin.
22 *
23 * @var string
24 */
25 const MINIMUM_SUPPORTED = '15.7.0';
26
27 /**
28 * Holds the current version.
29 *
30 * @var string
31 */
32 protected $current_version = '';
33
34 /**
35 * WPSEO_Gutenberg_Compatibility constructor.
36 */
37 public function __construct() {
38 $this->current_version = $this->detect_installed_gutenberg_version();
39 }
40
41 /**
42 * Determines whether or not Gutenberg is installed.
43 *
44 * @return bool Whether or not Gutenberg is installed.
45 */
46 public function is_installed() {
47 return $this->current_version !== '';
48 }
49
50 /**
51 * Determines whether or not the currently installed version of Gutenberg is below the minimum supported version.
52 *
53 * @return bool True if the currently installed version is below the minimum supported version. False otherwise.
54 */
55 public function is_below_minimum() {
56 return version_compare( $this->current_version, $this->get_minimum_supported_version(), '<' );
57 }
58
59 /**
60 * Gets the currently installed version.
61 *
62 * @return string The currently installed version.
63 */
64 public function get_installed_version() {
65 return $this->current_version;
66 }
67
68 /**
69 * Determines whether or not the currently installed version of Gutenberg is the latest, fully compatible version.
70 *
71 * @return bool Whether or not the currently installed version is fully compatible.
72 */
73 public function is_fully_compatible() {
74 return version_compare( $this->current_version, $this->get_latest_release(), '>=' );
75 }
76
77 /**
78 * Gets the latest released version of Gutenberg.
79 *
80 * @return string The latest release.
81 */
82 protected function get_latest_release() {
83 return self::CURRENT_RELEASE;
84 }
85
86 /**
87 * Gets the minimum supported version of Gutenberg.
88 *
89 * @return string The minumum supported release.
90 */
91 protected function get_minimum_supported_version() {
92 return self::MINIMUM_SUPPORTED;
93 }
94
95 /**
96 * Detects the currently installed Gutenberg version.
97 *
98 * @return string The currently installed Gutenberg version. Empty if the version couldn't be detected.
99 */
100 protected function detect_installed_gutenberg_version() {
101 if ( defined( 'GUTENBERG_VERSION' ) ) {
102 return GUTENBERG_VERSION;
103 }
104
105 return '';
106 }
107 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 /**
9 * Loads the MyYoast proxy.
10 *
11 * This class registers a proxy page on `admin.php`. Which is reached with the `page=PAGE_IDENTIFIER` parameter.
12 * It will read external files and serves them like they are located locally.
13 */
14 class WPSEO_MyYoast_Proxy implements WPSEO_WordPress_Integration {
15
16 /**
17 * The page identifier used in WordPress to register the MyYoast proxy page.
18 *
19 * @var string
20 */
21 const PAGE_IDENTIFIER = 'wpseo_myyoast_proxy';
22
23 /**
24 * The cache control's max age. Used in the header of a successful proxy response.
25 *
26 * @var int
27 */
28 const CACHE_CONTROL_MAX_AGE = DAY_IN_SECONDS;
29
30 /**
31 * Registers the hooks when the user is on the right page.
32 *
33 * @codeCoverageIgnore
34 *
35 * @return void
36 */
37 public function register_hooks() {
38 if ( ! $this->is_proxy_page() ) {
39 return;
40 }
41
42 // Register the page for the proxy.
43 add_action( 'admin_menu', [ $this, 'add_proxy_page' ] );
44 add_action( 'admin_init', [ $this, 'handle_proxy_page' ] );
45 }
46
47 /**
48 * Registers the proxy page. It does not actually add a link to the dashboard.
49 *
50 * @codeCoverageIgnore
51 *
52 * @return void
53 */
54 public function add_proxy_page() {
55 add_dashboard_page( '', '', 'read', self::PAGE_IDENTIFIER, '' );
56 }
57
58 /**
59 * Renders the requested proxy page and exits to prevent the WordPress UI from loading.
60 *
61 * @codeCoverageIgnore
62 *
63 * @return void
64 */
65 public function handle_proxy_page() {
66 $this->render_proxy_page();
67
68 // Prevent the WordPress UI from loading.
69 exit;
70 }
71
72 /**
73 * Renders the requested proxy page.
74 *
75 * This is separated from the exits to be able to test it.
76 *
77 * @return void
78 */
79 public function render_proxy_page() {
80 $proxy_options = $this->determine_proxy_options();
81 if ( $proxy_options === [] ) {
82 // Do not accept any other file than implemented.
83 $this->set_header( 'HTTP/1.0 501 Requested file not implemented' );
84 return;
85 }
86
87 // Set the headers before serving the remote file.
88 $this->set_header( 'Content-Type: ' . $proxy_options['content_type'] );
89 $this->set_header( 'Cache-Control: max-age=' . self::CACHE_CONTROL_MAX_AGE );
90
91 try {
92 echo $this->get_remote_url_body( $proxy_options['url'] );
93 }
94 catch ( Exception $e ) {
95 /*
96 * Reset the file headers because the loading failed.
97 *
98 * Note: Due to supporting PHP 5.2 `header_remove` can not be used here.
99 * Overwrite the headers instead.
100 */
101 $this->set_header( 'Content-Type: text/plain' );
102 $this->set_header( 'Cache-Control: max-age=0' );
103
104 $this->set_header( 'HTTP/1.0 500 ' . $e->getMessage() );
105 }
106 }
107
108 /**
109 * Tries to load the given url via `wp_remote_get`.
110 *
111 * @codeCoverageIgnore
112 *
113 * @param string $url The url to load.
114 *
115 * @return string The body of the response.
116 *
117 * @throws Exception When `wp_remote_get` returned an error.
118 * @throws Exception When the response code is not 200.
119 */
120 protected function get_remote_url_body( $url ) {
121 $response = wp_remote_get( $url );
122
123 if ( $response instanceof WP_Error ) {
124 throw new Exception( 'Unable to retrieve file from MyYoast' );
125 }
126
127 if ( wp_remote_retrieve_response_code( $response ) !== 200 ) {
128 throw new Exception( 'Received unexpected response from MyYoast' );
129 }
130
131 return wp_remote_retrieve_body( $response );
132 }
133
134 /**
135 * Determines the proxy options based on the file and plugin version arguments.
136 *
137 * When the file is known it returns an array like this:
138 * <code>
139 * $array = array(
140 * 'content_type' => 'the content type'
141 * 'url' => 'the url, possibly with the plugin version'
142 * )
143 * </code>
144 *
145 * @return array Empty for an unknown file. See format above for known files.
146 */
147 protected function determine_proxy_options() {
148 if ( $this->get_proxy_file() === 'research-webworker' ) {
149 return [
150 'content_type' => 'text/javascript; charset=UTF-8',
151 'url' => 'https://my.yoast.com/api/downloads/file/analysis-worker?plugin_version=' . $this->get_plugin_version(),
152 ];
153 }
154
155 return [];
156 }
157
158 /**
159 * Checks if the current page is the MyYoast proxy page.
160 *
161 * @codeCoverageIgnore
162 *
163 * @return bool True when the page request parameter equals the proxy page.
164 */
165 protected function is_proxy_page() {
166 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
167 $page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
168 return $page === self::PAGE_IDENTIFIER;
169 }
170
171 /**
172 * Returns the proxy file from the HTTP request parameters.
173 *
174 * @codeCoverageIgnore
175 *
176 * @return string The sanitized file request parameter or an empty string if it does not exist.
177 */
178 protected function get_proxy_file() {
179 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
180 if ( isset( $_GET['file'] ) && is_string( $_GET['file'] ) ) {
181 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
182 return sanitize_text_field( wp_unslash( $_GET['file'] ) );
183 }
184 return '';
185 }
186
187 /**
188 * Returns the plugin version from the HTTP request parameters.
189 *
190 * @codeCoverageIgnore
191 *
192 * @return string The sanitized plugin_version request parameter or an empty string if it does not exist.
193 */
194 protected function get_plugin_version() {
195 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
196 if ( isset( $_GET['plugin_version'] ) && is_string( $_GET['plugin_version'] ) ) {
197 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
198 $plugin_version = sanitize_text_field( wp_unslash( $_GET['plugin_version'] ) );
199 // Replace slashes to secure against requiring a file from another path.
200 return str_replace( [ '/', '\\' ], '_', $plugin_version );
201 }
202 return '';
203 }
204
205 /**
206 * Sets the HTTP header.
207 *
208 * This is a tiny helper function to enable better testing.
209 *
210 * @codeCoverageIgnore
211 *
212 * @param string $header The header to set.
213 *
214 * @return void
215 */
216 protected function set_header( $header ) {
217 header( $header );
218 }
219 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin\Options\Tabs
6 */
7
8 /**
9 * Class WPSEO_Option_Tab.
10 */
11 class WPSEO_Option_Tab {
12
13 /**
14 * Name of the tab.
15 *
16 * @var string
17 */
18 private $name;
19
20 /**
21 * Label of the tab.
22 *
23 * @var string
24 */
25 private $label;
26
27 /**
28 * Optional arguments.
29 *
30 * @var array
31 */
32 private $arguments;
33
34 /**
35 * WPSEO_Option_Tab constructor.
36 *
37 * @param string $name Name of the tab.
38 * @param string $label Localized label of the tab.
39 * @param array $arguments Optional arguments.
40 */
41 public function __construct( $name, $label, array $arguments = [] ) {
42 $this->name = sanitize_title( $name );
43 $this->label = $label;
44 $this->arguments = $arguments;
45 }
46
47 /**
48 * Gets the name.
49 *
50 * @return string The name.
51 */
52 public function get_name() {
53 return $this->name;
54 }
55
56 /**
57 * Gets the label.
58 *
59 * @return string The label.
60 */
61 public function get_label() {
62 return $this->label;
63 }
64
65 /**
66 * Retrieves whether the tab needs a save button.
67 *
68 * @return bool True whether the tabs needs a save button.
69 */
70 public function has_save_button() {
71 return (bool) $this->get_argument( 'save_button', true );
72 }
73
74 /**
75 * Retrieves whether the tab hosts beta functionalities.
76 *
77 * @return bool True whether the tab hosts beta functionalities.
78 */
79 public function is_beta() {
80 return (bool) $this->get_argument( 'beta', false );
81 }
82
83 /**
84 * Retrieves whether the tab hosts premium functionalities.
85 *
86 * @return bool True whether the tab hosts premium functionalities.
87 */
88 public function is_premium() {
89 return (bool) $this->get_argument( 'premium', false );
90 }
91
92 /**
93 * Gets the option group.
94 *
95 * @return string The option group.
96 */
97 public function get_opt_group() {
98 return $this->get_argument( 'opt_group' );
99 }
100
101 /**
102 * Retrieves the variable from the supplied arguments.
103 *
104 * @param string $variable Variable to retrieve.
105 * @param string|mixed $default_value Default to use when variable not found.
106 *
107 * @return mixed|string The retrieved variable.
108 */
109 protected function get_argument( $variable, $default_value = '' ) {
110 return array_key_exists( $variable, $this->arguments ) ? $this->arguments[ $variable ] : $default_value;
111 }
112 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin\Options\Tabs
6 */
7
8 use Yoast\WP\SEO\Presenters\Admin\Beta_Badge_Presenter;
9 use Yoast\WP\SEO\Presenters\Admin\Premium_Badge_Presenter;
10
11 /**
12 * Class WPSEO_Option_Tabs_Formatter.
13 */
14 class WPSEO_Option_Tabs_Formatter {
15
16 /**
17 * Retrieves the path to the view of the tab.
18 *
19 * @param WPSEO_Option_Tabs $option_tabs Option Tabs to get base from.
20 * @param WPSEO_Option_Tab $tab Tab to get name from.
21 *
22 * @return string
23 */
24 public function get_tab_view( WPSEO_Option_Tabs $option_tabs, WPSEO_Option_Tab $tab ) {
25 return WPSEO_PATH . 'admin/views/tabs/' . $option_tabs->get_base() . '/' . $tab->get_name() . '.php';
26 }
27
28 /**
29 * Outputs the option tabs.
30 *
31 * @param WPSEO_Option_Tabs $option_tabs Option Tabs to get tabs from.
32 */
33 public function run( WPSEO_Option_Tabs $option_tabs ) {
34
35 echo '<h2 class="nav-tab-wrapper" id="wpseo-tabs">';
36 foreach ( $option_tabs->get_tabs() as $tab ) {
37 $label = esc_html( $tab->get_label() );
38
39 if ( $tab->is_beta() ) {
40 $label = '<span style="margin-right:4px;">' . $label . '</span>' . new Beta_Badge_Presenter( $tab->get_name() );
41 }
42 elseif ( $tab->is_premium() ) {
43 $label = '<span style="margin-right:4px;">' . $label . '</span>' . new Premium_Badge_Presenter( $tab->get_name() );
44 }
45
46 printf(
47 '<a class="nav-tab" id="%1$s" href="%2$s">%3$s</a>',
48 esc_attr( $tab->get_name() . '-tab' ),
49 esc_url( '#top#' . $tab->get_name() ),
50 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: we do this on purpose
51 $label
52 );
53 }
54 echo '</h2>';
55
56 foreach ( $option_tabs->get_tabs() as $tab ) {
57 $identifier = $tab->get_name();
58
59 $class = 'wpseotab ' . ( $tab->has_save_button() ? 'save' : 'nosave' );
60 printf( '<div id="%1$s" class="%2$s">', esc_attr( $identifier ), esc_attr( $class ) );
61
62 $tab_filter_name = sprintf( '%s_%s', $option_tabs->get_base(), $tab->get_name() );
63
64 /**
65 * Allows to override the content that is display on the specific option tab.
66 *
67 * @internal For internal Yoast SEO use only.
68 *
69 * @api string|null The content that should be displayed for this tab. Leave empty for default behaviour.
70 *
71 * @param WPSEO_Option_Tabs $option_tabs The registered option tabs.
72 * @param WPSEO_Option_Tab $tab The tab that is being displayed.
73 */
74 $option_tab_content = apply_filters( 'wpseo_option_tab-' . $tab_filter_name, null, $option_tabs, $tab );
75 if ( ! empty( $option_tab_content ) ) {
76 echo wp_kses_post( $option_tab_content );
77 }
78
79 if ( empty( $option_tab_content ) ) {
80 // Output the settings view for all tabs.
81 $tab_view = $this->get_tab_view( $option_tabs, $tab );
82
83 if ( is_file( $tab_view ) ) {
84 $yform = Yoast_Form::get_instance();
85 require $tab_view;
86 }
87 }
88
89 echo '</div>';
90 }
91 }
92 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin\Options\Tabs
6 */
7
8 /**
9 * Class WPSEO_Option_Tabs.
10 */
11 class WPSEO_Option_Tabs {
12
13 /**
14 * Tabs base.
15 *
16 * @var string
17 */
18 private $base;
19
20 /**
21 * The tabs in this group.
22 *
23 * @var array
24 */
25 private $tabs = [];
26
27 /**
28 * Name of the active tab.
29 *
30 * @var string
31 */
32 private $active_tab = '';
33
34 /**
35 * WPSEO_Option_Tabs constructor.
36 *
37 * @codeCoverageIgnore
38 *
39 * @param string $base Base of the tabs.
40 * @param string $active_tab Currently active tab.
41 */
42 public function __construct( $base, $active_tab = '' ) {
43 $this->base = sanitize_title( $base );
44
45 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
46 $tab = isset( $_GET['tab'] ) && is_string( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : '';
47 $this->active_tab = empty( $tab ) ? $active_tab : $tab;
48 }
49
50 /**
51 * Get the base.
52 *
53 * @return string
54 */
55 public function get_base() {
56 return $this->base;
57 }
58
59 /**
60 * Add a tab.
61 *
62 * @param WPSEO_Option_Tab $tab Tab to add.
63 *
64 * @return $this
65 */
66 public function add_tab( WPSEO_Option_Tab $tab ) {
67 $this->tabs[] = $tab;
68
69 return $this;
70 }
71
72 /**
73 * Get active tab.
74 *
75 * @return WPSEO_Option_Tab|null Get the active tab.
76 */
77 public function get_active_tab() {
78 if ( empty( $this->active_tab ) ) {
79 return null;
80 }
81
82 $active_tabs = array_filter( $this->tabs, [ $this, 'is_active_tab' ] );
83 if ( ! empty( $active_tabs ) ) {
84 $active_tabs = array_values( $active_tabs );
85 if ( count( $active_tabs ) === 1 ) {
86 return $active_tabs[0];
87 }
88 }
89
90 return null;
91 }
92
93 /**
94 * Is the tab the active tab.
95 *
96 * @param WPSEO_Option_Tab $tab Tab to check for active tab.
97 *
98 * @return bool
99 */
100 public function is_active_tab( WPSEO_Option_Tab $tab ) {
101 return ( $tab->get_name() === $this->active_tab );
102 }
103
104 /**
105 * Get all tabs.
106 *
107 * @return WPSEO_Option_Tab[]
108 */
109 public function get_tabs() {
110 return $this->tabs;
111 }
112
113 /**
114 * Display the tabs.
115 *
116 * @param Yoast_Form $yform Yoast Form needed in the views.
117 */
118 public function display( Yoast_Form $yform ) {
119 $formatter = new WPSEO_Option_Tabs_Formatter();
120 $formatter->run( $this, $yform );
121 }
122 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 /**
9 * Class WPSEO_presenter_paper.
10 */
11 class WPSEO_Paper_Presenter {
12
13 /**
14 * Title of the paper.
15 *
16 * @var string
17 */
18 private $title;
19
20 /**
21 * The view variables.
22 *
23 * @var array
24 */
25 private $settings;
26
27 /**
28 * The path to the view file.
29 *
30 * @var string
31 */
32 private $view_file;
33
34 /**
35 * WPSEO_presenter_paper constructor.
36 *
37 * @param string $title The title of the paper.
38 * @param string|null $view_file Optional. The path to the view file. Use the content setting
39 * if do not wish to use a view file.
40 * @param array $settings Optional. Settings for the paper.
41 */
42 public function __construct( $title, $view_file = null, array $settings = [] ) {
43 $defaults = [
44 'paper_id' => null,
45 'paper_id_prefix' => 'wpseo-',
46 'collapsible' => false,
47 'collapsible_header_class' => '',
48 'expanded' => false,
49 'help_text' => '',
50 'title_after' => '',
51 'class' => '',
52 'content' => '',
53 'view_data' => [],
54 ];
55
56 $this->settings = wp_parse_args( $settings, $defaults );
57 $this->title = $title;
58 $this->view_file = $view_file;
59 }
60
61 /**
62 * Renders the collapsible paper and returns it as a string.
63 *
64 * @return string The rendered paper.
65 */
66 public function get_output() {
67 $view_variables = $this->get_view_variables();
68
69 extract( $view_variables, EXTR_SKIP );
70
71 $content = $this->settings['content'];
72
73 if ( $this->view_file !== null ) {
74 ob_start();
75 require $this->view_file;
76 $content = ob_get_clean();
77 }
78
79 ob_start();
80 require WPSEO_PATH . 'admin/views/paper-collapsible.php';
81 $rendered_output = ob_get_clean();
82
83 return $rendered_output;
84 }
85
86 /**
87 * Retrieves the view variables.
88 *
89 * @return array The view variables.
90 */
91 private function get_view_variables() {
92 if ( $this->settings['help_text'] instanceof WPSEO_Admin_Help_Panel === false ) {
93 $this->settings['help_text'] = new WPSEO_Admin_Help_Panel( '', '', '' );
94 }
95
96 $view_variables = [
97 'class' => $this->settings['class'],
98 'collapsible' => $this->settings['collapsible'],
99 'collapsible_config' => $this->collapsible_config(),
100 'collapsible_header_class' => $this->settings['collapsible_header_class'],
101 'title_after' => $this->settings['title_after'],
102 'help_text' => $this->settings['help_text'],
103 'view_file' => $this->view_file,
104 'title' => $this->title,
105 'paper_id' => $this->settings['paper_id'],
106 'paper_id_prefix' => $this->settings['paper_id_prefix'],
107 'yform' => Yoast_Form::get_instance(),
108 ];
109
110 return array_merge( $this->settings['view_data'], $view_variables );
111 }
112
113 /**
114 * Retrieves the collapsible config based on the settings.
115 *
116 * @return array The config.
117 */
118 protected function collapsible_config() {
119 if ( empty( $this->settings['collapsible'] ) ) {
120 return [
121 'toggle_icon' => '',
122 'class' => '',
123 'expanded' => '',
124 ];
125 }
126
127 if ( ! empty( $this->settings['expanded'] ) ) {
128 return [
129 'toggle_icon' => 'dashicons-arrow-up-alt2',
130 'class' => 'toggleable-container',
131 'expanded' => 'true',
132 ];
133 }
134
135 return [
136 'toggle_icon' => 'dashicons-arrow-down-alt2',
137 'class' => 'toggleable-container toggleable-container-hidden',
138 'expanded' => 'false',
139 ];
140 }
141 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Plugin_Availability
6 */
7
8 /**
9 * Class WPSEO_Plugin_Availability
10 */
11 class WPSEO_Plugin_Availability {
12
13 /**
14 * Holds the plugins.
15 *
16 * @var array
17 */
18 protected $plugins = [];
19
20 /**
21 * Registers the plugins so we can access them.
22 */
23 public function register() {
24 $this->register_yoast_plugins();
25 $this->register_yoast_plugins_status();
26 }
27
28 /**
29 * Registers all the available Yoast SEO plugins.
30 */
31 protected function register_yoast_plugins() {
32 $this->plugins = [
33 'yoast-seo-premium' => [
34 'url' => WPSEO_Shortlinker::get( 'https://yoa.st/1y7' ),
35 'title' => 'Yoast SEO Premium',
36 'description' => sprintf(
37 /* translators: %1$s expands to Yoast SEO */
38 __( 'The premium version of %1$s with more features & support.', 'wordpress-seo' ),
39 'Yoast SEO'
40 ),
41 'installed' => false,
42 'slug' => 'wordpress-seo-premium/wp-seo-premium.php',
43 'version_sync' => true,
44 'premium' => true,
45 ],
46
47 'video-seo-for-wordpress-seo-by-yoast' => [
48 'url' => WPSEO_Shortlinker::get( 'https://yoa.st/1y8' ),
49 'title' => 'Video SEO',
50 'description' => __( 'Optimize your videos to show them off in search results and get more clicks!', 'wordpress-seo' ),
51 'installed' => false,
52 'slug' => 'wpseo-video/video-seo.php',
53 'version_sync' => true,
54 'premium' => true,
55 ],
56
57 'yoast-news-seo' => [
58 'url' => WPSEO_Shortlinker::get( 'https://yoa.st/1y9' ),
59 'title' => 'News SEO',
60 'description' => __( 'Are you in Google News? Increase your traffic from Google News by optimizing for it!', 'wordpress-seo' ),
61 'installed' => false,
62 'slug' => 'wpseo-news/wpseo-news.php',
63 'version_sync' => true,
64 'premium' => true,
65 ],
66
67 'local-seo-for-yoast-seo' => [
68 'url' => WPSEO_Shortlinker::get( 'https://yoa.st/1ya' ),
69 'title' => 'Local SEO',
70 'description' => __( 'Rank better locally and in Google Maps, without breaking a sweat!', 'wordpress-seo' ),
71 'installed' => false,
72 'slug' => 'wordpress-seo-local/local-seo.php',
73 'version_sync' => true,
74 'premium' => true,
75 ],
76
77 'yoast-woocommerce-seo' => [
78 'url' => WPSEO_Shortlinker::get( 'https://yoa.st/1o0' ),
79 'title' => 'Yoast WooCommerce SEO',
80 'description' => sprintf(
81 /* translators: %1$s expands to Yoast SEO */
82 __( 'Seamlessly integrate WooCommerce with %1$s and get extra features!', 'wordpress-seo' ),
83 'Yoast SEO'
84 ),
85 '_dependencies' => [
86 'WooCommerce' => [
87 'slug' => 'woocommerce/woocommerce.php',
88 ],
89 ],
90 'installed' => false,
91 'slug' => 'wpseo-woocommerce/wpseo-woocommerce.php',
92 'version_sync' => true,
93 'premium' => true,
94 ],
95 ];
96 }
97
98 /**
99 * Sets certain plugin properties based on WordPress' status.
100 */
101 protected function register_yoast_plugins_status() {
102
103 foreach ( $this->plugins as $name => $plugin ) {
104
105 $plugin_slug = $plugin['slug'];
106 $plugin_path = WP_PLUGIN_DIR . '/' . $plugin_slug;
107
108 if ( file_exists( $plugin_path ) ) {
109 $plugin_data = get_plugin_data( $plugin_path, false, false );
110 $this->plugins[ $name ]['installed'] = true;
111 $this->plugins[ $name ]['version'] = $plugin_data['Version'];
112 $this->plugins[ $name ]['active'] = is_plugin_active( $plugin_slug );
113 }
114 }
115 }
116
117 /**
118 * Checks whether or not a plugin is known within the Yoast SEO collection.
119 *
120 * @param string $plugin The plugin to search for.
121 *
122 * @return bool Whether or not the plugin is exists.
123 */
124 protected function plugin_exists( $plugin ) {
125 return isset( $this->plugins[ $plugin ] );
126 }
127
128 /**
129 * Gets all the possibly available plugins.
130 *
131 * @return array Array containing the information about the plugins.
132 */
133 public function get_plugins() {
134 return $this->plugins;
135 }
136
137 /**
138 * Gets a specific plugin. Returns an empty array if it cannot be found.
139 *
140 * @param string $plugin The plugin to search for.
141 *
142 * @return array The plugin properties.
143 */
144 public function get_plugin( $plugin ) {
145 if ( ! $this->plugin_exists( $plugin ) ) {
146 return [];
147 }
148
149 return $this->plugins[ $plugin ];
150 }
151
152 /**
153 * Gets the version of the plugin.
154 *
155 * @param array $plugin The information available about the plugin.
156 *
157 * @return string The version associated with the plugin.
158 */
159 public function get_version( $plugin ) {
160 if ( ! isset( $plugin['version'] ) ) {
161 return '';
162 }
163
164 return $plugin['version'];
165 }
166
167 /**
168 * Checks if there are dependencies available for the plugin.
169 *
170 * @param array $plugin The information available about the plugin.
171 *
172 * @return bool Whether or not there is a dependency present.
173 */
174 public function has_dependencies( $plugin ) {
175 return ( isset( $plugin['_dependencies'] ) && ! empty( $plugin['_dependencies'] ) );
176 }
177
178 /**
179 * Gets the dependencies for the plugin.
180 *
181 * @param array $plugin The information available about the plugin.
182 *
183 * @return array Array containing all the dependencies associated with the plugin.
184 */
185 public function get_dependencies( $plugin ) {
186 if ( ! $this->has_dependencies( $plugin ) ) {
187 return [];
188 }
189
190 return $plugin['_dependencies'];
191 }
192
193 /**
194 * Checks if all dependencies are satisfied.
195 *
196 * @param array $plugin The information available about the plugin.
197 *
198 * @return bool Whether or not the dependencies are satisfied.
199 */
200 public function dependencies_are_satisfied( $plugin ) {
201 if ( ! $this->has_dependencies( $plugin ) ) {
202 return true;
203 }
204
205 $dependencies = $this->get_dependencies( $plugin );
206 $installed_dependencies = array_filter( $dependencies, [ $this, 'is_dependency_available' ] );
207
208 return count( $installed_dependencies ) === count( $dependencies );
209 }
210
211 /**
212 * Checks whether or not one of the plugins is properly installed and usable.
213 *
214 * @param array $plugin The information available about the plugin.
215 *
216 * @return bool Whether or not the plugin is properly installed.
217 */
218 public function is_installed( $plugin ) {
219 if ( empty( $plugin ) ) {
220 return false;
221 }
222
223 return $this->is_available( $plugin );
224 }
225
226 /**
227 * Gets all installed plugins.
228 *
229 * @return array The installed plugins.
230 */
231 public function get_installed_plugins() {
232 $installed = [];
233
234 foreach ( $this->plugins as $plugin_key => $plugin ) {
235 if ( $this->is_installed( $plugin ) ) {
236 $installed[ $plugin_key ] = $plugin;
237 }
238 }
239
240 return $installed;
241 }
242
243 /**
244 * Checks for the availability of the plugin.
245 *
246 * @param array $plugin The information available about the plugin.
247 *
248 * @return bool Whether or not the plugin is available.
249 */
250 public function is_available( $plugin ) {
251 return isset( $plugin['installed'] ) && $plugin['installed'] === true;
252 }
253
254 /**
255 * Checks whether a dependency is available.
256 *
257 * @param array $dependency The information about the dependency to look for.
258 *
259 * @return bool Whether or not the dependency is available.
260 */
261 public function is_dependency_available( $dependency ) {
262 return isset( get_plugins()[ $dependency['slug'] ] );
263 }
264
265 /**
266 * Gets the names of the dependencies.
267 *
268 * @param array $plugin The plugin to get the dependency names from.
269 *
270 * @return array Array containing the names of the associated dependencies.
271 */
272 public function get_dependency_names( $plugin ) {
273 if ( ! $this->has_dependencies( $plugin ) ) {
274 return [];
275 }
276
277 return array_keys( $plugin['_dependencies'] );
278 }
279
280 /**
281 * Gets an array of plugins that have defined dependencies.
282 *
283 * @return array Array of the plugins that have dependencies.
284 */
285 public function get_plugins_with_dependencies() {
286 return array_filter( $this->plugins, [ $this, 'has_dependencies' ] );
287 }
288
289 /**
290 * Determines whether or not a plugin is active.
291 *
292 * @param string $plugin The plugin slug to check.
293 *
294 * @return bool Whether or not the plugin is active.
295 */
296 public function is_active( $plugin ) {
297 return is_plugin_active( $plugin );
298 }
299
300 /**
301 * Determines whether or not a plugin is a Premium product.
302 *
303 * @param array $plugin The plugin to check.
304 *
305 * @return bool Whether or not the plugin is a Premium product.
306 */
307 public function is_premium( $plugin ) {
308 return isset( $plugin['premium'] ) && $plugin['premium'] === true;
309 }
310 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 * @since 1.7.0
7 */
8
9 use Yoast\WP\SEO\Config\Conflicting_Plugins;
10
11 /**
12 * Contains list of conflicting plugins.
13 */
14 class WPSEO_Plugin_Conflict extends Yoast_Plugin_Conflict {
15
16 /**
17 * The plugins must be grouped per section.
18 *
19 * It's possible to check for each section if there are conflicting plugin.
20 *
21 * NOTE: when changing this array, be sure to update the array in Conflicting_Plugins_Service too.
22 *
23 * @var array
24 */
25 protected $plugins = [
26 // The plugin which are writing OG metadata.
27 'open_graph' => Conflicting_Plugins::OPEN_GRAPH_PLUGINS,
28 'xml_sitemaps' => Conflicting_Plugins::XML_SITEMAPS_PLUGINS,
29 'cloaking' => Conflicting_Plugins::CLOAKING_PLUGINS,
30 'seo' => Conflicting_Plugins::SEO_PLUGINS,
31 ];
32
33 /**
34 * Overrides instance to set with this class as class.
35 *
36 * @param string $class_name Optional class name.
37 *
38 * @return Yoast_Plugin_Conflict
39 */
40 public static function get_instance( $class_name = __CLASS__ ) {
41 return parent::get_instance( $class_name );
42 }
43
44 /**
45 * After activating any plugin, this method will be executed by a hook.
46 *
47 * If the activated plugin is conflicting with ours a notice will be shown.
48 *
49 * @param string|bool $plugin Optional plugin basename to check.
50 */
51 public static function hook_check_for_plugin_conflicts( $plugin = false ) {
52 // The instance of the plugin.
53 $instance = self::get_instance();
54
55 // Only add the plugin as an active plugin if $plugin isn't false.
56 if ( $plugin && is_string( $plugin ) ) {
57 $instance->add_active_plugin( $instance->find_plugin_category( $plugin ), $plugin );
58 }
59
60 $plugin_sections = [];
61
62 // Only check for open graph problems when they are enabled.
63 if ( WPSEO_Options::get( 'opengraph' ) ) {
64 /* translators: %1$s expands to Yoast SEO, %2$s: 'Facebook' plugin name of possibly conflicting plugin with regard to creating OpenGraph output. */
65 $plugin_sections['open_graph'] = __( 'Both %1$s and %2$s create Open Graph output, which might make Facebook, Twitter, LinkedIn and other social networks use the wrong texts and images when your pages are being shared.', 'wordpress-seo' )
66 . '<br/><br/>'
67 . '<a class="button" href="' . admin_url( 'admin.php?page=wpseo_page_settings#/site-features#card-wpseo_social-opengraph' ) . '">'
68 /* translators: %1$s expands to Yoast SEO. */
69 . sprintf( __( 'Configure %1$s\'s Open Graph settings', 'wordpress-seo' ), 'Yoast SEO' )
70 . '</a>';
71 }
72
73 // Only check for XML conflicts if sitemaps are enabled.
74 if ( WPSEO_Options::get( 'enable_xml_sitemap' ) ) {
75 /* translators: %1$s expands to Yoast SEO, %2$s: 'Google XML Sitemaps' plugin name of possibly conflicting plugin with regard to the creation of sitemaps. */
76 $plugin_sections['xml_sitemaps'] = __( 'Both %1$s and %2$s can create XML sitemaps. Having two XML sitemaps is not beneficial for search engines and might slow down your site.', 'wordpress-seo' )
77 . '<br/><br/>'
78 . '<a class="button" href="' . admin_url( 'admin.php?page=wpseo_page_settings#/site-features#card-wpseo-enable_xml_sitemap' ) . '">'
79 /* translators: %1$s expands to Yoast SEO. */
80 . sprintf( __( 'Toggle %1$s\'s XML Sitemap', 'wordpress-seo' ), 'Yoast SEO' )
81 . '</a>';
82 }
83
84 /* translators: %2$s expands to 'RS Head Cleaner' plugin name of possibly conflicting plugin with regard to differentiating output between search engines and normal users. */
85 $plugin_sections['cloaking'] = __( 'The plugin %2$s changes your site\'s output and in doing that differentiates between search engines and normal users, a process that\'s called cloaking. We highly recommend that you disable it.', 'wordpress-seo' );
86
87 /* translators: %1$s expands to Yoast SEO, %2$s: 'SEO' plugin name of possibly conflicting plugin with regard to the creation of duplicate SEO meta. */
88 $plugin_sections['seo'] = __( 'Both %1$s and %2$s manage the SEO of your site. Running two SEO plugins at the same time is detrimental.', 'wordpress-seo' );
89
90 $instance->check_plugin_conflicts( $plugin_sections );
91 }
92 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 /**
9 * Class WPSEO_Premium_popup.
10 */
11 class WPSEO_Premium_Popup {
12
13 /**
14 * An unique identifier for the popup
15 *
16 * @var string
17 */
18 private $identifier = '';
19
20 /**
21 * The heading level of the title of the popup.
22 *
23 * @var string
24 */
25 private $heading_level = '';
26
27 /**
28 * The title of the popup.
29 *
30 * @var string
31 */
32 private $title = '';
33
34 /**
35 * The content of the popup.
36 *
37 * @var string
38 */
39 private $content = '';
40
41 /**
42 * The URL for where the button should link to.
43 *
44 * @var string
45 */
46 private $url = '';
47
48 /**
49 * Wpseo_Premium_Popup constructor.
50 *
51 * @param string $identifier An unique identifier for the popup.
52 * @param string $heading_level The heading level for the title of the popup.
53 * @param string $title The title of the popup.
54 * @param string $content The content of the popup.
55 * @param string $url The URL for where the button should link to.
56 */
57 public function __construct( $identifier, $heading_level, $title, $content, $url ) {
58 $this->identifier = $identifier;
59 $this->heading_level = $heading_level;
60 $this->title = $title;
61 $this->content = $content;
62 $this->url = $url;
63 }
64
65 /**
66 * Returns the premium popup as an HTML string.
67 *
68 * @param bool $popup Show this message as a popup show it straight away.
69 *
70 * @return string
71 */
72 public function get_premium_message( $popup = true ) {
73 // Don't show in Premium.
74 if ( defined( 'WPSEO_PREMIUM_FILE' ) ) {
75 return '';
76 }
77
78 $assets_uri = trailingslashit( plugin_dir_url( WPSEO_FILE ) );
79
80 /* translators: %s expands to Yoast SEO Premium */
81 $cta_text = esc_html( sprintf( __( 'Get %s', 'wordpress-seo' ), 'Yoast SEO Premium' ) );
82 $new_tab_message = '<span class="screen-reader-text">' . esc_html__( '(Opens in a new browser tab)', 'wordpress-seo' ) . '</span>';
83 $caret_icon = '<span aria-hidden="true" class="yoast-button-upsell__caret"></span>';
84 $classes = '';
85 if ( $popup ) {
86 $classes = ' hidden';
87 }
88 $micro_copy = __( '1 year free support and updates included!', 'wordpress-seo' );
89
90 $popup = <<<EO_POPUP
91 <div id="wpseo-{$this->identifier}-popup" class="wpseo-premium-popup wp-clearfix$classes">
92 <img class="alignright wpseo-premium-popup-icon" src="{$assets_uri}packages/js/images/Yoast_SEO_Icon.svg" width="150" height="150" alt="Yoast SEO"/>
93 <{$this->heading_level} id="wpseo-contact-support-popup-title" class="wpseo-premium-popup-title">{$this->title}</{$this->heading_level}>
94 {$this->content}
95 <a id="wpseo-{$this->identifier}-popup-button" class="yoast-button-upsell" href="{$this->url}" target="_blank">
96 {$cta_text} {$new_tab_message} {$caret_icon}
97 </a><br/>
98 <small>{$micro_copy}</small>
99 </div>
100 EO_POPUP;
101
102 return $popup;
103 }
104 }
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 /**
9 * Class WPSEO_Premium_Upsell_Admin_Block
10 */
11 class WPSEO_Premium_Upsell_Admin_Block {
12
13 /**
14 * Hook to display the block on.
15 *
16 * @var string
17 */
18 protected $hook;
19
20 /**
21 * Identifier to use in the dismissal functionality.
22 *
23 * @var string
24 */
25 protected $identifier = 'premium_upsell';
26
27 /**
28 * Registers which hook the block will be displayed on.
29 *
30 * @param string $hook Hook to display the block on.
31 */
32 public function __construct( $hook ) {
33 $this->hook = $hook;
34 }
35
36 /**
37 * Registers WordPress hooks.
38 *
39 * @return void
40 */
41 public function register_hooks() {
42 add_action( $this->hook, [ $this, 'render' ] );
43 }
44
45 /**
46 * Renders the upsell block.
47 *
48 * @return void
49 */
50 public function render() {
51 $url = WPSEO_Shortlinker::get( 'https://yoa.st/17h' );
52
53 $arguments = [
54 '<strong>' . esc_html__( 'Multiple keyphrases', 'wordpress-seo' ) . '</strong>: ' . esc_html__( 'Increase your SEO reach', 'wordpress-seo' ),
55 '<strong>' . esc_html__( 'No more dead links', 'wordpress-seo' ) . '</strong>: ' . esc_html__( 'Easy redirect manager', 'wordpress-seo' ),
56 '<strong>' . esc_html__( 'Superfast internal linking suggestions', 'wordpress-seo' ) . '</strong>',
57 '<strong>' . esc_html__( 'Social media preview', 'wordpress-seo' ) . '</strong>: ' . esc_html__( 'Facebook & Twitter', 'wordpress-seo' ),
58 '<strong>' . esc_html__( '24/7 email support', 'wordpress-seo' ) . '</strong>',
59 '<strong>' . esc_html__( 'No ads!', 'wordpress-seo' ) . '</strong>',
60 ];
61
62 $arguments_html = implode( '', array_map( [ $this, 'get_argument_html' ], $arguments ) );
63
64 $class = $this->get_html_class();
65
66 /* translators: %s expands to Yoast SEO Premium */
67 $button_text = sprintf( esc_html__( 'Get %s', 'wordpress-seo' ), 'Yoast SEO Premium' );
68 $button_text .= '<span class="screen-reader-text">' . esc_html__( '(Opens in a new browser tab)', 'wordpress-seo' ) . '</span>' .
69 '<span aria-hidden="true" class="yoast-button-upsell__caret"></span>';
70
71 $upgrade_button = sprintf(
72 '<a id="%1$s" class="yoast-button-upsell" data-action="load-nfd-ctb" data-ctb-id="f6a84663-465f-4cb5-8ba5-f7a6d72224b2" href="%2$s" target="_blank">%3$s</a>',
73 esc_attr( 'wpseo-' . $this->identifier . '-popup-button' ),
74 esc_url( $url ),
75 $button_text
76 );
77
78 echo '<div class="' . esc_attr( $class ) . '">';
79
80 echo '<div>';
81 echo '<h2 class="' . esc_attr( $class . '--header' ) . '">' .
82 sprintf(
83 /* translators: %s expands to Yoast SEO Premium */
84 esc_html__( 'Upgrade to %s', 'wordpress-seo' ),
85 'Yoast SEO Premium'
86 ) .
87 '</h2>';
88
89 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Correctly escaped in $this->get_argument_html() method.
90 echo '<ul class="' . esc_attr( $class . '--motivation' ) . '">' . $arguments_html . '</ul>';
91
92 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Correctly escaped in $upgrade_button and $button_text above.
93 echo '<p>' . $upgrade_button . '</p>';
94 echo '</div>';
95
96 echo '</div>';
97 }
98
99 /**
100 * Formats the argument to a HTML list item.
101 *
102 * @param string $argument The argument to format.
103 *
104 * @return string Formatted argument in HTML.
105 */
106 protected function get_argument_html( $argument ) {
107 $class = $this->get_html_class();
108
109 return sprintf(
110 '<li><div class="%1$s">%2$s</div></li>',
111 esc_attr( $class . '--argument' ),
112 $argument
113 );
114 }
115
116 /**
117 * Returns the HTML base class to use.
118 *
119 * @return string The HTML base class.
120 */
121 protected function get_html_class() {
122 return 'yoast_' . $this->identifier;
123 }
124 }
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.