class-yoast-plugin-conflict.php 9.81 KB
Newer Older
imac's avatar
imac committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
<?php
/**
 * @package WPSEO\Admin
 * @since      1.7.0
 */

/**
 * Base class for handling plugin conflicts.
 */
class Yoast_Plugin_Conflict {

	/**
	 * The plugins must be grouped per section.
	 *
	 * It's possible to check for each section if there are conflicting plugin
	 *
	 * @var array
	 */
	protected $plugins = array();

	/**
	 * All the current active plugins will be stored in this private var
	 *
	 * @var array
	 */
	protected $all_active_plugins = array();

	/**
	 * After searching for active plugins that are in $this->plugins the active plugins will be stored in this
	 * property
	 *
	 * @var array
	 */
	protected $active_plugins = array();

	/**
	 * Property for holding instance of itself
	 *
	 * @var Yoast_Plugin_Conflict
	 */
	protected static $instance;

	/**
	 * For the use of singleton pattern. Create instance of itself and return his instance
	 *
	 * @param string $class_name Give the classname to initialize. If classname is false (empty) it will use it's own __CLASS__.
	 *
	 * @return Yoast_Plugin_Conflict
	 */
	public static function get_instance( $class_name = '' ) {

		if ( is_null( self::$instance ) ) {
			if ( ! is_string( $class_name ) || $class_name === '' ) {
				$class_name = __CLASS__;
			}

			self::$instance = new $class_name();
		}

		return self::$instance;
	}

	/**
	 * Setting instance, all active plugins and search for active plugins
	 *
	 * Protected constructor to prevent creating a new instance of the
	 * *Singleton* via the `new` operator from outside of this class.
	 */
	protected function __construct() {
		// Set active plugins.
		$this->all_active_plugins = get_option( 'active_plugins' );

		if ( filter_input( INPUT_GET, 'action' ) === 'deactivate' ) {
			$this->remove_deactivated_plugin();
		}

		// Search for active plugins.
		$this->search_active_plugins();
	}

	/**
	 * Check if there are conflicting plugins for given $plugin_section
	 *
	 * @param string $plugin_section Type of plugin conflict (such as Open Graph or sitemap).
	 *
	 * @return bool
	 */
	public function check_for_conflicts( $plugin_section ) {

		static $sections_checked;

		if ( $sections_checked === null ) {
			$sections_checked = array();
		}

		if ( ! in_array( $plugin_section, $sections_checked ) ) {
			$sections_checked[] = $plugin_section;
			$has_conflicts      = ( ! empty( $this->active_plugins[ $plugin_section ] ) );

			return $has_conflicts;
		}

		return false;
	}

	/**
	 * Getting all the conflicting plugins and return them as a string.
	 *
	 * This method will loop through all conflicting plugins to get the details of each plugin. The plugin name
	 * will be taken from the details to parse a comma separated string, which can be use for by example a notice
	 *
	 * @param string $plugin_section Plugin conflict type (such as Open Graph or sitemap).
	 *
	 * @return string
	 */
	public function get_conflicting_plugins_as_string( $plugin_section ) {
		if ( ! function_exists( 'get_plugin_data' ) ) {
			require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
		}

		// Getting the active plugins by given section.
		$plugins = $this->active_plugins[ $plugin_section ];

		$plugin_names = array();
		foreach ( $plugins as $plugin ) {
			if ( $name = WPSEO_Utils::get_plugin_name( $plugin ) ) {
				$plugin_names[] = '<em>' . $name . '</em>';
			}
		}
		unset( $plugins, $plugin );

		if ( ! empty( $plugin_names ) ) {
			return implode( ' &amp; ', $plugin_names );
		}
	}

	/**
	 * Checks for given $plugin_sections for conflicts
	 *
	 * @param array $plugin_sections Set of sections.
	 */
	public function check_plugin_conflicts( $plugin_sections ) {
		foreach ( $plugin_sections as $plugin_section => $readable_plugin_section ) {
			// Check for conflicting plugins and show error if there are conflicts.
			if ( $this->check_for_conflicts( $plugin_section ) ) {
				$this->set_error( $plugin_section, $readable_plugin_section );
			}
		}

		// List of all active sections.
		$sections = array_keys( $plugin_sections );
		// List of all sections.
		$all_plugin_sections = array_keys( $this->plugins );

		/*
		 * Get all sections that are inactive.
		 * These plugins need to be cleared.
		 *
		 * This happens when Sitemaps or OpenGraph implementations toggle active/disabled.
		 */
		$inactive_sections = array_diff( $all_plugin_sections, $sections );
		if ( ! empty( $inactive_sections ) ) {
			foreach ( $inactive_sections as $section ) {
				array_walk( $this->plugins[ $section ], array( $this, 'clear_error' ) );
			}
		}

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

			// If there are active plugins, filter them from being cleared.
			if ( isset( $this->active_plugins[ $section ] ) ) {
				$inactive_plugins = array_diff( $this->plugins[ $section ], $this->active_plugins[ $section ] );
			}

			array_walk( $inactive_plugins, array( $this, 'clear_error' ) );
		}
	}

	/**
	 * Setting an error on the screen
	 *
	 * @param string $plugin_section          Type of conflict group (such as Open Graph or sitemap).
	 * @param string $readable_plugin_section This is the value for the translation.
	 */
	protected function set_error( $plugin_section, $readable_plugin_section ) {

		$notification_center = Yoast_Notification_Center::get();

		foreach ( $this->active_plugins[ $plugin_section ] as $plugin_file ) {

			$plugin_name = WPSEO_Utils::get_plugin_name( $plugin_file );

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

			/* translators: %s: 'Facebook' plugin name of possibly conflicting plugin */
			$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' ), WPSEO_Utils::get_plugin_name( $plugin_file ) ) . '</a> ';

			$identifier = $this->get_notification_identifier( $plugin_file );

			// Add the message to the notifications center.
			$notification_center->add_notification(
				new Yoast_Notification(
					$error_message,
					array(
						'type' => Yoast_Notification::ERROR,
						'id'   => 'wpseo-conflict-' . $identifier,
					)
				)
			);
		}
	}

	/**
	 * Clear the notification for a plugin
	 *
	 * @param string $plugin_file Clear the optional notification for this plugin.
	 */
	protected function clear_error( $plugin_file ) {
		$identifier = $this->get_notification_identifier( $plugin_file );

		$notification_center = Yoast_Notification_Center::get();
		$notification = $notification_center->get_notification_by_id( 'wpseo-conflict-' . $identifier );

		if ( $notification ) {
			$notification_center->remove_notification( $notification );
		}
	}

	/**
	 * Loop through the $this->plugins to check if one of the plugins is active.
	 *
	 * This method will store the active plugins in $this->active_plugins.
	 */
	protected function search_active_plugins() {
		foreach ( $this->plugins as $plugin_section => $plugins ) {
			$this->check_plugins_active( $plugins, $plugin_section );
		}
	}

	/**
	 * Loop through plugins and check if each plugin is active
	 *
	 * @param array  $plugins        Set of plugins.
	 * @param string $plugin_section Type of conflict group (such as Open Graph or sitemap).
	 */
	protected function check_plugins_active( $plugins, $plugin_section ) {
		foreach ( $plugins as $plugin ) {
			if ( $this->check_plugin_is_active( $plugin ) ) {
				$this->add_active_plugin( $plugin_section, $plugin );
			}
		}
	}


	/**
	 * Check if given plugin exists in array with all_active_plugins
	 *
	 * @param string $plugin Plugin basename string.
	 *
	 * @return bool
	 */
	protected function check_plugin_is_active( $plugin ) {
		return in_array( $plugin, $this->all_active_plugins );
	}

	/**
	 * Add plugin to the list of active plugins.
	 *
	 * This method will check first if key $plugin_section exists, if not it will create an empty array
	 * If $plugin itself doesn't exist it will be added.
	 *
	 * @param string $plugin_section Type of conflict group (such as Open Graph or sitemap).
	 * @param string $plugin         Plugin basename string.
	 */
	protected function add_active_plugin( $plugin_section, $plugin ) {

		if ( ! array_key_exists( $plugin_section, $this->active_plugins ) ) {
			$this->active_plugins[ $plugin_section ] = array();
		}

		if ( ! in_array( $plugin, $this->active_plugins[ $plugin_section ] ) ) {
			$this->active_plugins[ $plugin_section ][] = $plugin;
		}
	}

	/**
	 * Search in $this->plugins for the given $plugin
	 *
	 * If there is a result it will return the plugin category
	 *
	 * @param string $plugin Plugin basename string.
	 *
	 * @return int|string
	 */
	protected function find_plugin_category( $plugin ) {

		foreach ( $this->plugins as $plugin_section => $plugins ) {
			if ( in_array( $plugin, $plugins ) ) {
				return $plugin_section;
			}
		}

	}

	/**
	 * When being in the deactivation process the currently deactivated plugin has to be removed.
	 */
	private function remove_deactivated_plugin() {
		$deactivated_plugin = filter_input( INPUT_GET, 'plugin' );
		$key_to_remove      = array_search( $deactivated_plugin, $this->all_active_plugins );

		if ( $key_to_remove !== false ) {
			unset( $this->all_active_plugins[ $key_to_remove ] );
		}
	}

	/**
	 * Get the identifier from the plugin file
	 *
	 * @param string $plugin_file Plugin file to get Identifier from.
	 *
	 * @return string
	 */
	private function get_notification_identifier( $plugin_file ) {
		return md5( $plugin_file );
	}
}