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

Yoast / wordpress-seo / fe4d74ae81b231d2986738b4e4648c24370c79ea

26 Feb 2025 02:54PM UTC coverage: 45.177% (-5.5%) from 50.712%
fe4d74ae81b231d2986738b4e4648c24370c79ea

push

github

enricobattocchi
Drop compatibility with PHP 7.2 and 7.3

15990 of 35394 relevant lines covered (45.18%)

4.08 hits per line

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

0.0
/admin/class-yoast-plugin-conflict.php
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 = 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
                if ( $sections_checked === null ) {
×
106
                        $sections_checked = [];
×
107
                }
108

109
                if ( ! in_array( $plugin_section, $sections_checked, true ) ) {
×
110
                        $sections_checked[] = $plugin_section;
×
111
                        return ( ! empty( $this->active_conflicting_plugins[ $plugin_section ] ) );
×
112
                }
113

114
                return false;
×
115
        }
116

117
        /**
118
         * Checks for given $plugin_sections for conflicts.
119
         *
120
         * @param array $plugin_sections Set of sections.
121
         *
122
         * @return void
123
         */
124
        public function check_plugin_conflicts( $plugin_sections ) {
×
125
                foreach ( $plugin_sections as $plugin_section => $readable_plugin_section ) {
×
126
                        // Check for conflicting plugins and show error if there are conflicts.
127
                        if ( $this->check_for_conflicts( $plugin_section ) ) {
×
128
                                $this->set_error( $plugin_section, $readable_plugin_section );
×
129
                        }
130
                }
131

132
                // List of all active sections.
133
                $sections = array_keys( $plugin_sections );
×
134
                // List of all sections.
135
                $all_plugin_sections = array_keys( $this->plugins );
×
136

137
                /*
138
                 * Get all sections that are inactive.
139
                 * These plugins need to be cleared.
140
                 *
141
                 * This happens when Sitemaps or OpenGraph implementations toggle active/disabled.
142
                 */
143
                $inactive_sections = array_diff( $all_plugin_sections, $sections );
×
144
                if ( ! empty( $inactive_sections ) ) {
×
145
                        foreach ( $inactive_sections as $section ) {
×
146
                                array_walk( $this->plugins[ $section ], [ $this, 'clear_error' ] );
×
147
                        }
148
                }
149

150
                // For active sections clear errors for inactive plugins.
151
                foreach ( $sections as $section ) {
×
152
                        // By default, clear errors for all plugins of the section.
153
                        $inactive_plugins = $this->plugins[ $section ];
×
154

155
                        // If there are active plugins, filter them from being cleared.
156
                        if ( isset( $this->active_conflicting_plugins[ $section ] ) ) {
×
157
                                $inactive_plugins = array_diff( $this->plugins[ $section ], $this->active_conflicting_plugins[ $section ] );
×
158
                        }
159

160
                        array_walk( $inactive_plugins, [ $this, 'clear_error' ] );
×
161
                }
162
        }
163

164
        /**
165
         * Setting an error on the screen.
166
         *
167
         * @param string $plugin_section          Type of conflict group (such as Open Graph or sitemap).
168
         * @param string $readable_plugin_section This is the value for the translation.
169
         *
170
         * @return void
171
         */
172
        protected function set_error( $plugin_section, $readable_plugin_section ) {
×
173

174
                $notification_center = Yoast_Notification_Center::get();
×
175

176
                foreach ( $this->active_conflicting_plugins[ $plugin_section ] as $plugin_file ) {
×
177

178
                        $plugin_name = $this->get_plugin_name( $plugin_file );
×
179

180
                        $error_message = '';
×
181
                        /* translators: %1$s: 'Facebook & Open Graph' plugin name(s) of possibly conflicting plugin(s), %2$s to Yoast SEO */
182
                        $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>';
×
183
                        $error_message .= '<p>' . sprintf( $readable_plugin_section, 'Yoast SEO', $plugin_name ) . '</p>';
×
184

185
                        /* translators: %s: 'Facebook' plugin name of possibly conflicting plugin */
186
                        $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> ';
×
187

188
                        $identifier = $this->get_notification_identifier( $plugin_file );
×
189

190
                        // Add the message to the notifications center.
191
                        $notification_center->add_notification(
×
192
                                new Yoast_Notification(
×
193
                                        $error_message,
×
194
                                        [
×
195
                                                'type' => Yoast_Notification::ERROR,
×
196
                                                'id'   => 'wpseo-conflict-' . $identifier,
×
197
                                        ]
×
198
                                )
×
199
                        );
×
200
                }
201
        }
202

203
        /**
204
         * Clear the notification for a plugin.
205
         *
206
         * @param string $plugin_file Clear the optional notification for this plugin.
207
         *
208
         * @return void
209
         */
210
        public function clear_error( $plugin_file ) {
×
211
                $identifier = $this->get_notification_identifier( $plugin_file );
×
212

213
                $notification_center = Yoast_Notification_Center::get();
×
214
                $notification_center->remove_notification_by_id( 'wpseo-conflict-' . $identifier );
×
215
        }
216

217
        /**
218
         * Loop through the $this->plugins to check if one of the plugins is active.
219
         *
220
         * This method will store the active plugins in $this->active_plugins.
221
         *
222
         * @return void
223
         */
224
        protected function search_active_plugins() {
×
225
                foreach ( $this->plugins as $plugin_section => $plugins ) {
×
226
                        $this->check_plugins_active( $plugins, $plugin_section );
×
227
                }
228
        }
229

230
        /**
231
         * Loop through plugins and check if each plugin is active.
232
         *
233
         * @param array  $plugins        Set of plugins.
234
         * @param string $plugin_section Type of conflict group (such as Open Graph or sitemap).
235
         *
236
         * @return void
237
         */
238
        protected function check_plugins_active( $plugins, $plugin_section ) {
×
239
                foreach ( $plugins as $plugin ) {
×
240
                        if ( $this->check_plugin_is_active( $plugin ) ) {
×
241
                                $this->add_active_plugin( $plugin_section, $plugin );
×
242
                        }
243
                }
244
        }
245

246
        /**
247
         * Check if given plugin exists in array with all_active_plugins.
248
         *
249
         * @param string $plugin Plugin basename string.
250
         *
251
         * @return bool
252
         */
253
        protected function check_plugin_is_active( $plugin ) {
×
254
                return in_array( $plugin, $this->all_active_plugins, true );
×
255
        }
256

257
        /**
258
         * Add plugin to the list of active plugins.
259
         *
260
         * This method will check first if key $plugin_section exists, if not it will create an empty array
261
         * If $plugin itself doesn't exist it will be added.
262
         *
263
         * @param string $plugin_section Type of conflict group (such as Open Graph or sitemap).
264
         * @param string $plugin         Plugin basename string.
265
         *
266
         * @return void
267
         */
268
        protected function add_active_plugin( $plugin_section, $plugin ) {
×
269
                if ( ! array_key_exists( $plugin_section, $this->active_conflicting_plugins ) ) {
×
270
                        $this->active_conflicting_plugins[ $plugin_section ] = [];
×
271
                }
272

273
                if ( ! in_array( $plugin, $this->active_conflicting_plugins[ $plugin_section ], true ) ) {
×
274
                        $this->active_conflicting_plugins[ $plugin_section ][] = $plugin;
×
275
                }
276
        }
277

278
        /**
279
         * Search in $this->plugins for the given $plugin.
280
         *
281
         * If there is a result it will return the plugin category.
282
         *
283
         * @param string $plugin Plugin basename string.
284
         *
285
         * @return int|string
286
         */
287
        protected function find_plugin_category( $plugin ) {
×
288
                foreach ( $this->plugins as $plugin_section => $plugins ) {
×
289
                        if ( in_array( $plugin, $plugins, true ) ) {
×
290
                                return $plugin_section;
×
291
                        }
292
                }
293
        }
294

295
        /**
296
         * Get plugin name from file.
297
         *
298
         * @param string $plugin Plugin path relative to plugins directory.
299
         *
300
         * @return string|bool Plugin name or false when no name is set.
301
         */
302
        protected function get_plugin_name( $plugin ) {
×
303
                $plugin_details = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
×
304

305
                if ( $plugin_details['Name'] !== '' ) {
×
306
                        return $plugin_details['Name'];
×
307
                }
308

309
                return false;
×
310
        }
311

312
        /**
313
         * When being in the deactivation process the currently deactivated plugin has to be removed.
314
         *
315
         * @return void
316
         */
317
        private function remove_deactivated_plugin() {
×
318
                // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: On the deactivation screen the nonce is already checked by WordPress itself.
319
                if ( ! isset( $_GET['plugin'] ) || ! is_string( $_GET['plugin'] ) ) {
×
320
                        return;
×
321
                }
322

323
                // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: On the deactivation screen the nonce is already checked by WordPress itself.
324
                $deactivated_plugin = sanitize_text_field( wp_unslash( $_GET['plugin'] ) );
×
325
                $key_to_remove      = array_search( $deactivated_plugin, $this->all_active_plugins, true );
×
326

327
                if ( $key_to_remove !== false ) {
×
328
                        unset( $this->all_active_plugins[ $key_to_remove ] );
×
329
                }
330
        }
331

332
        /**
333
         * Get the identifier from the plugin file.
334
         *
335
         * @param string $plugin_file Plugin file to get Identifier from.
336
         *
337
         * @return string
338
         */
339
        private function get_notification_identifier( $plugin_file ) {
×
340
                return md5( $plugin_file );
×
341
        }
342
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc