PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 18.7
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v18.7
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 18.7, at admin/class-yoast-plugin-conflict.php

355 lines 10.5 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 ( \is_null( self::$instance ) ) {
56 if ( ! \is_string( $class_name ) || $class_name === '' ) {
57 $class_name = __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 of this class.
71 */
72 protected function __construct() {
73 // Set active plugins.
74 $this->all_active_plugins = \get_option( 'active_plugins' );
75
76 if ( \filter_input( INPUT_GET, 'action' ) === 'deactivate' ) {
77 $this->remove_deactivated_plugin();
78 }
79
80 // Search for active plugins.
81 $this->search_active_plugins();
82 }
83
84 /**
85 * Check if there are conflicting plugins for given $plugin_section.
86 *
87 * @param string $plugin_section Type of plugin conflict (such as Open Graph or sitemap).
88 *
89 * @return bool
90 */
91 public function check_for_conflicts( $plugin_section ) {
92
93 static $sections_checked;
94
95 // Return early if there are no active conflicting plugins at all.
96 if ( empty( $this->active_conflicting_plugins ) ) {
97 return false;
98 }
99
100 if ( $sections_checked === null ) {
101 $sections_checked = [];
102 }
103
104 if ( ! \in_array( $plugin_section, $sections_checked, true ) ) {
105 $sections_checked[] = $plugin_section;
106 $has_conflicts = ( ! empty( $this->active_conflicting_plugins[ $plugin_section ] ) );
107
108 return $has_conflicts;
109 }
110
111 return false;
112 }
113
114 /**
115 * Getting all the conflicting plugins and return them as a string.
116 *
117 * This method will loop through all conflicting plugins to get the details of each plugin. The plugin name
118 * will be taken from the details to parse a comma separated string, which can be use for by example a notice
119 *
120 * @deprecated 17.7 This method is unused and will be removed in the future
121 * @codeCoverageIgnore
122 *
123 * @param string $plugin_section Plugin conflict type (such as Open Graph or sitemap).
124 *
125 * @return string
126 */
127 public function get_conflicting_plugins_as_string( $plugin_section ) {
128 if ( ! \function_exists( 'get_plugin_data' ) ) {
129 require_once ABSPATH . 'wp-admin/includes/plugin.php';
130 }
131
132 // Getting the active plugins by given section.
133 $plugins = $this->active_conflicting_plugins[ $plugin_section ];
134
135 $plugin_names = [];
136 foreach ( $plugins as $plugin ) {
137 $name = $this->get_plugin_name( $plugin );
138 if ( ! empty( $name ) ) {
139 $plugin_names[] = '<em>' . $name . '</em>';
140 }
141 }
142 unset( $plugins, $plugin );
143
144 if ( ! empty( $plugin_names ) ) {
145 return \implode( ' &amp; ', $plugin_names );
146 }
147 }
148
149 /**
150 * Checks for given $plugin_sections for conflicts.
151 *
152 * @param array $plugin_sections Set of sections.
153 */
154 public function check_plugin_conflicts( $plugin_sections ) {
155 foreach ( $plugin_sections as $plugin_section => $readable_plugin_section ) {
156 // Check for conflicting plugins and show error if there are conflicts.
157 if ( $this->check_for_conflicts( $plugin_section ) ) {
158 $this->set_error( $plugin_section, $readable_plugin_section );
159 }
160 }
161
162 // List of all active sections.
163 $sections = \array_keys( $plugin_sections );
164 // List of all sections.
165 $all_plugin_sections = \array_keys( $this->plugins );
166
167 /*
168 * Get all sections that are inactive.
169 * These plugins need to be cleared.
170 *
171 * This happens when Sitemaps or OpenGraph implementations toggle active/disabled.
172 */
173 $inactive_sections = \array_diff( $all_plugin_sections, $sections );
174 if ( ! empty( $inactive_sections ) ) {
175 foreach ( $inactive_sections as $section ) {
176 \array_walk( $this->plugins[ $section ], [ $this, 'clear_error' ] );
177 }
178 }
179
180 // For active sections clear errors for inactive plugins.
181 foreach ( $sections as $section ) {
182 // By default clear errors for all plugins of the section.
183 $inactive_plugins = $this->plugins[ $section ];
184
185 // If there are active plugins, filter them from being cleared.
186 if ( isset( $this->active_conflicting_plugins[ $section ] ) ) {
187 $inactive_plugins = \array_diff( $this->plugins[ $section ], $this->active_conflicting_plugins[ $section ] );
188 }
189
190 \array_walk( $inactive_plugins, [ $this, 'clear_error' ] );
191 }
192 }
193
194 /**
195 * Setting an error on the screen.
196 *
197 * @param string $plugin_section Type of conflict group (such as Open Graph or sitemap).
198 * @param string $readable_plugin_section This is the value for the translation.
199 */
200 protected function set_error( $plugin_section, $readable_plugin_section ) {
201
202 $notification_center = Yoast_Notification_Center::get();
203
204 foreach ( $this->active_conflicting_plugins[ $plugin_section ] as $plugin_file ) {
205
206 $plugin_name = $this->get_plugin_name( $plugin_file );
207
208 $error_message = '';
209 /* translators: %1$s: 'Facebook & Open Graph' plugin name(s) of possibly conflicting plugin(s), %2$s to Yoast SEO */
210 $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>';
211 $error_message .= '<p>' . sprintf( $readable_plugin_section, 'Yoast SEO', $plugin_name ) . '</p>';
212
213 /* translators: %s: 'Facebook' plugin name of possibly conflicting plugin */
214 $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> ';
215
216 $identifier = $this->get_notification_identifier( $plugin_file );
217
218 // Add the message to the notifications center.
219 $notification_center->add_notification(
220 new Yoast_Notification(
221 $error_message,
222 [
223 'type' => Yoast_Notification::ERROR,
224 'id' => 'wpseo-conflict-' . $identifier,
225 ]
226 )
227 );
228 }
229 }
230
231 /**
232 * Clear the notification for a plugin.
233 *
234 * @param string $plugin_file Clear the optional notification for this plugin.
235 */
236 public function clear_error( $plugin_file ) {
237 $identifier = $this->get_notification_identifier( $plugin_file );
238
239 $notification_center = Yoast_Notification_Center::get();
240 $notification_center->remove_notification_by_id( 'wpseo-conflict-' . $identifier );
241 }
242
243 /**
244 * Loop through the $this->plugins to check if one of the plugins is active.
245 *
246 * This method will store the active plugins in $this->active_plugins.
247 */
248 protected function search_active_plugins() {
249 foreach ( $this->plugins as $plugin_section => $plugins ) {
250 $this->check_plugins_active( $plugins, $plugin_section );
251 }
252 }
253
254 /**
255 * Loop through plugins and check if each plugin is active.
256 *
257 * @param array $plugins Set of plugins.
258 * @param string $plugin_section Type of conflict group (such as Open Graph or sitemap).
259 */
260 protected function check_plugins_active( $plugins, $plugin_section ) {
261 foreach ( $plugins as $plugin ) {
262 if ( $this->check_plugin_is_active( $plugin ) ) {
263 $this->add_active_plugin( $plugin_section, $plugin );
264 }
265 }
266 }
267
268 /**
269 * Check if given plugin exists in array with all_active_plugins.
270 *
271 * @param string $plugin Plugin basename string.
272 *
273 * @return bool
274 */
275 protected function check_plugin_is_active( $plugin ) {
276 return \in_array( $plugin, $this->all_active_plugins, true );
277 }
278
279 /**
280 * Add plugin to the list of active plugins.
281 *
282 * This method will check first if key $plugin_section exists, if not it will create an empty array
283 * If $plugin itself doesn't exist it will be added.
284 *
285 * @param string $plugin_section Type of conflict group (such as Open Graph or sitemap).
286 * @param string $plugin Plugin basename string.
287 */
288 protected function add_active_plugin( $plugin_section, $plugin ) {
289 if ( ! \array_key_exists( $plugin_section, $this->active_conflicting_plugins ) ) {
290 $this->active_conflicting_plugins[ $plugin_section ] = [];
291 }
292
293 if ( ! \in_array( $plugin, $this->active_conflicting_plugins[ $plugin_section ], true ) ) {
294 $this->active_conflicting_plugins[ $plugin_section ][] = $plugin;
295 }
296 }
297
298 /**
299 * Search in $this->plugins for the given $plugin.
300 *
301 * If there is a result it will return the plugin category.
302 *
303 * @param string $plugin Plugin basename string.
304 *
305 * @return int|string
306 */
307 protected function find_plugin_category( $plugin ) {
308 foreach ( $this->plugins as $plugin_section => $plugins ) {
309 if ( \in_array( $plugin, $plugins, true ) ) {
310 return $plugin_section;
311 }
312 }
313 }
314
315 /**
316 * Get plugin name from file.
317 *
318 * @param string $plugin Plugin path relative to plugins directory.
319 *
320 * @return string|bool Plugin name or false when no name is set.
321 */
322 protected function get_plugin_name( $plugin ) {
323 $plugin_details = \get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
324
325 if ( $plugin_details['Name'] !== '' ) {
326 return $plugin_details['Name'];
327 }
328
329 return false;
330 }
331
332 /**
333 * When being in the deactivation process the currently deactivated plugin has to be removed.
334 */
335 private function remove_deactivated_plugin() {
336 $deactivated_plugin = \filter_input( INPUT_GET, 'plugin' );
337 $key_to_remove = \array_search( $deactivated_plugin, $this->all_active_plugins, true );
338
339 if ( $key_to_remove !== false ) {
340 unset( $this->all_active_plugins[ $key_to_remove ] );
341 }
342 }
343
344 /**
345 * Get the identifier from the plugin file.
346 *
347 * @param string $plugin_file Plugin file to get Identifier from.
348 *
349 * @return string
350 */
351 private function get_notification_identifier( $plugin_file ) {
352 return \md5( $plugin_file );
353 }
354 }
355