PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 27.9
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v27.9
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
wordpress-seo / admin / class-yoast-plugin-conflict.php

class-yoast-plugin-conflict.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 27.9, at admin/class-yoast-plugin-conflict.php

341 lines 10.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 * @since 1.7.0
7 */
8
9 /**
10 * Base class for handling plugin conflicts.
11 */
12 class Yoast_Plugin_Conflict {
13
14 /**
15 * The plugins must be grouped per section.
16 *
17 * It's possible to check for each section if there are conflicting plugins.
18 *
19 * @var array
20 */
21 protected $plugins = [];
22
23 /**
24 * All the current active plugins will be stored in this private var.
25 *
26 * @var array
27 */
28 protected $all_active_plugins = [];
29
30 /**
31 * After searching for active plugins that are in $this->plugins the active plugins will be stored in this
32 * property.
33 *
34 * @var array
35 */
36 protected $active_conflicting_plugins = [];
37
38 /**
39 * Property for holding instance of itself.
40 *
41 * @var Yoast_Plugin_Conflict
42 */
43 protected static $instance;
44
45 /**
46 * For the use of singleton pattern. Create instance of itself and return this instance.
47 *
48 * @param string $class_name Give the classname to initialize. If classname is
49 * false (empty) it will use it's own __CLASS__.
50 *
51 * @return Yoast_Plugin_Conflict
52 */
53 public static function get_instance( $class_name = '' ) {
54
55 if ( self::$instance === null ) {
56 if ( ! is_string( $class_name ) || $class_name === '' ) {
57 $class_name = self::class;
58 }
59
60 self::$instance = new $class_name();
61 }
62
63 return self::$instance;
64 }
65
66 /**
67 * Setting instance, all active plugins and search for active plugins.
68 *
69 * Protected constructor to prevent creating a new instance of the
70 * *Singleton* via the `new` operator from outside this class.
71 */
72 protected function __construct() {
73 // Set active plugins.
74 $this->all_active_plugins = get_option( 'active_plugins' );
75
76 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
77 if ( isset( $_GET['action'] ) && is_string( $_GET['action'] ) ) {
78 // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information and only comparing the variable in a condition.
79 $action = wp_unslash( $_GET['action'] );
80 if ( $action === 'deactivate' ) {
81 $this->remove_deactivated_plugin();
82 }
83 }
84
85 // Search for active plugins.
86 $this->search_active_plugins();
87 }
88
89 /**
90 * Check if there are conflicting plugins for given $plugin_section.
91 *
92 * @param string $plugin_section Type of plugin conflict (such as Open Graph or sitemap).
93 *
94 * @return bool
95 */
96 public function check_for_conflicts( $plugin_section ) {
97
98 static $sections_checked;
99
100 // Return early if there are no active conflicting plugins at all.
101 if ( empty( $this->active_conflicting_plugins ) ) {
102 return false;
103 }
104
105 $sections_checked ??= [];
106
107 if ( ! in_array( $plugin_section, $sections_checked, true ) ) {
108 $sections_checked[] = $plugin_section;
109 return ( ! empty( $this->active_conflicting_plugins[ $plugin_section ] ) );
110 }
111
112 return false;
113 }
114
115 /**
116 * Checks for given $plugin_sections for conflicts.
117 *
118 * @param array $plugin_sections Set of sections.
119 *
120 * @return void
121 */
122 public function check_plugin_conflicts( $plugin_sections ) {
123 foreach ( $plugin_sections as $plugin_section => $readable_plugin_section ) {
124 // Check for conflicting plugins and show error if there are conflicts.
125 if ( $this->check_for_conflicts( $plugin_section ) ) {
126 $this->set_error( $plugin_section, $readable_plugin_section );
127 }
128 }
129
130 // List of all active sections.
131 $sections = array_keys( $plugin_sections );
132 // List of all sections.
133 $all_plugin_sections = array_keys( $this->plugins );
134
135 /*
136 * Get all sections that are inactive.
137 * These plugins need to be cleared.
138 *
139 * This happens when Sitemaps or OpenGraph implementations toggle active/disabled.
140 */
141 $inactive_sections = array_diff( $all_plugin_sections, $sections );
142 if ( ! empty( $inactive_sections ) ) {
143 foreach ( $inactive_sections as $section ) {
144 array_walk( $this->plugins[ $section ], [ $this, 'clear_error' ] );
145 }
146 }
147
148 // For active sections clear errors for inactive plugins.
149 foreach ( $sections as $section ) {
150 // By default, clear errors for all plugins of the section.
151 $inactive_plugins = $this->plugins[ $section ];
152
153 // If there are active plugins, filter them from being cleared.
154 if ( isset( $this->active_conflicting_plugins[ $section ] ) ) {
155 $inactive_plugins = array_diff( $this->plugins[ $section ], $this->active_conflicting_plugins[ $section ] );
156 }
157
158 array_walk( $inactive_plugins, [ $this, 'clear_error' ] );
159 }
160 }
161
162 /**
163 * Setting an error on the screen.
164 *
165 * @param string $plugin_section Type of conflict group (such as Open Graph or sitemap).
166 * @param string $readable_plugin_section This is the value for the translation.
167 *
168 * @return void
169 */
170 protected function set_error( $plugin_section, $readable_plugin_section ) {
171
172 $notification_center = Yoast_Notification_Center::get();
173
174 foreach ( $this->active_conflicting_plugins[ $plugin_section ] as $plugin_file ) {
175
176 $plugin_name = $this->get_plugin_name( $plugin_file );
177
178 $error_message = '';
179 /* translators: %1$s: 'Facebook & Open Graph' plugin name(s) of possibly conflicting plugin(s), %2$s to Yoast SEO */
180 $error_message .= '<p>' . sprintf( __( 'The %1$s plugin might cause issues when used in conjunction with %2$s.', 'wordpress-seo' ), '<em>' . $plugin_name . '</em>', 'Yoast SEO' ) . '</p>';
181 $error_message .= '<p>' . sprintf( $readable_plugin_section, 'Yoast SEO', $plugin_name ) . '</p>';
182
183 /* translators: %s: 'Facebook' plugin name of possibly conflicting plugin */
184 $error_message .= '<a class="button button-primary" href="' . wp_nonce_url( 'plugins.php?action=deactivate&amp;plugin=' . $plugin_file . '&amp;plugin_status=all', 'deactivate-plugin_' . $plugin_file ) . '">' . sprintf( __( 'Deactivate %s', 'wordpress-seo' ), $this->get_plugin_name( $plugin_file ) ) . '</a> ';
185
186 $identifier = $this->get_notification_identifier( $plugin_file );
187
188 // Add the message to the notifications center.
189 $notification_center->add_notification(
190 new Yoast_Notification(
191 $error_message,
192 [
193 'type' => Yoast_Notification::ERROR,
194 'id' => 'wpseo-conflict-' . $identifier,
195 ],
196 ),
197 );
198 }
199 }
200
201 /**
202 * Clear the notification for a plugin.
203 *
204 * @param string $plugin_file Clear the optional notification for this plugin.
205 *
206 * @return void
207 */
208 public function clear_error( $plugin_file ) {
209 $identifier = $this->get_notification_identifier( $plugin_file );
210
211 $notification_center = Yoast_Notification_Center::get();
212 $notification_center->remove_notification_by_id( 'wpseo-conflict-' . $identifier );
213 }
214
215 /**
216 * Loop through the $this->plugins to check if one of the plugins is active.
217 *
218 * This method will store the active plugins in $this->active_plugins.
219 *
220 * @return void
221 */
222 protected function search_active_plugins() {
223 foreach ( $this->plugins as $plugin_section => $plugins ) {
224 $this->check_plugins_active( $plugins, $plugin_section );
225 }
226 }
227
228 /**
229 * Loop through plugins and check if each plugin is active.
230 *
231 * @param array $plugins Set of plugins.
232 * @param string $plugin_section Type of conflict group (such as Open Graph or sitemap).
233 *
234 * @return void
235 */
236 protected function check_plugins_active( $plugins, $plugin_section ) {
237 foreach ( $plugins as $plugin ) {
238 if ( $this->check_plugin_is_active( $plugin ) ) {
239 $this->add_active_plugin( $plugin_section, $plugin );
240 }
241 }
242 }
243
244 /**
245 * Check if given plugin exists in array with all_active_plugins.
246 *
247 * @param string $plugin Plugin basename string.
248 *
249 * @return bool
250 */
251 protected function check_plugin_is_active( $plugin ) {
252 return in_array( $plugin, $this->all_active_plugins, true );
253 }
254
255 /**
256 * Add plugin to the list of active plugins.
257 *
258 * This method will check first if key $plugin_section exists, if not it will create an empty array
259 * If $plugin itself doesn't exist it will be added.
260 *
261 * @param string $plugin_section Type of conflict group (such as Open Graph or sitemap).
262 * @param string $plugin Plugin basename string.
263 *
264 * @return void
265 */
266 protected function add_active_plugin( $plugin_section, $plugin ) {
267 if ( ! array_key_exists( $plugin_section, $this->active_conflicting_plugins ) ) {
268 $this->active_conflicting_plugins[ $plugin_section ] = [];
269 }
270
271 if ( ! in_array( $plugin, $this->active_conflicting_plugins[ $plugin_section ], true ) ) {
272 $this->active_conflicting_plugins[ $plugin_section ][] = $plugin;
273 }
274 }
275
276 /**
277 * Search in $this->plugins for the given $plugin.
278 *
279 * If there is a result it will return the plugin category.
280 *
281 * @param string $plugin Plugin basename string.
282 *
283 * @return int|string
284 */
285 protected function find_plugin_category( $plugin ) {
286 foreach ( $this->plugins as $plugin_section => $plugins ) {
287 if ( in_array( $plugin, $plugins, true ) ) {
288 return $plugin_section;
289 }
290 }
291 }
292
293 /**
294 * Get plugin name from file.
295 *
296 * @param string $plugin Plugin path relative to plugins directory.
297 *
298 * @return string|bool Plugin name or false when no name is set.
299 */
300 protected function get_plugin_name( $plugin ) {
301 $plugin_details = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
302
303 if ( $plugin_details['Name'] !== '' ) {
304 return $plugin_details['Name'];
305 }
306
307 return false;
308 }
309
310 /**
311 * When being in the deactivation process the currently deactivated plugin has to be removed.
312 *
313 * @return void
314 */
315 private function remove_deactivated_plugin() {
316 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: On the deactivation screen the nonce is already checked by WordPress itself.
317 if ( ! isset( $_GET['plugin'] ) || ! is_string( $_GET['plugin'] ) ) {
318 return;
319 }
320
321 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: On the deactivation screen the nonce is already checked by WordPress itself.
322 $deactivated_plugin = sanitize_text_field( wp_unslash( $_GET['plugin'] ) );
323 $key_to_remove = array_search( $deactivated_plugin, $this->all_active_plugins, true );
324
325 if ( $key_to_remove !== false ) {
326 unset( $this->all_active_plugins[ $key_to_remove ] );
327 }
328 }
329
330 /**
331 * Get the identifier from the plugin file.
332 *
333 * @param string $plugin_file Plugin file to get Identifier from.
334 *
335 * @return string
336 */
337 private function get_notification_identifier( $plugin_file ) {
338 return md5( $plugin_file );
339 }
340 }
341