class-sv-wc-framework-bootstrap.php 14.3 KB
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 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
<?php
/**
 * WooCommerce Plugin Framework
 *
 * This source file is subject to the GNU General Public License v3.0
 * that is bundled with this package in the file license.txt.
 * It is also available through the world-wide-web at this URL:
 * http://www.gnu.org/licenses/gpl-3.0.html
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@skyverge.com so we can send you a copy immediately.
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade the plugin to newer
 * versions in the future. If you wish to customize the plugin for your
 * needs please refer to http://www.skyverge.com
 *
 * @package   SkyVerge/WooCommerce/Plugin/Classes
 * @author    SkyVerge
 * @copyright Copyright (c) 2013-2016, SkyVerge, Inc.
 * @license   http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
 */

defined( 'ABSPATH' ) or exit;

if ( ! class_exists( 'SV_WC_Framework_Bootstrap' ) ) :


/**
 * # SkyVerge WooCommerce Plugin Framework Bootstrap
 *
 * The purpose of this class is to find and load the highest versioned
 * framework of the activated framework plugins, and then initialize any
 * compatible framework plugins.
 *
 * @since 2.0.0
 */
class SV_WC_Framework_Bootstrap {


	/** @var SV_WC_Framework_Bootstrap The single instance of the class */
	protected static $instance = null;

	/** @var array registered framework plugins */
	protected $registered_plugins = array();

	/** @var array registered and active framework plugins */
	protected $active_plugins = array();

	/** @var array of plugins that need to be updated due to an outdated framework */
	protected $incompatible_framework_plugins = array();

	/** @var array of plugins that require a newer version of WC */
	protected $incompatible_wc_version_plugins = array();

	/** @var array of plugins that require a newer version of WP */
	protected $incompatible_wp_version_plugins = array();


	/**
	 * Hidden constructor
	 *
	 * @since 2.0.0
	 */
	private function __construct() {

		// load framework plugins once all plugins are loaded
		add_action( 'plugins_loaded', array( $this, 'load_framework_plugins' ) );

		// deactivate backwards-incompatible framework plugins if the admin isn't ready to upgrade old plugins
		add_action( 'admin_init', array( $this, 'maybe_deactivate_framework_plugins' ) );
	}


	/**
	 * Instantiate the class singleton
	 *
	 * @since 2.0.0
	 * @return SV_WC_Framework_Bootstrap singleton instance
	 */
	public static function instance() {
		if ( is_null( self::$instance ) ) {
			self::$instance = new self();
		}

		return self::$instance;
	}


	/**
	 * Register a frameworked plugin
	 *
	 * @since 2.0.0
	 * @param string $version the framework version
	 * @param string $plugin_name the plugin name
	 * @param string $path the plugin path
	 * @param callable $callback function to initialize the plugin
	 * @param array $args optional plugin arguments.  Possible arguments: 'is_payment_gateway', 'backwards_compatible'
	 */
	public function register_plugin( $version, $plugin_name, $path, $callback, $args = array() ) {
		$this->registered_plugins[] = array( 'version' => $version, 'plugin_name' => $plugin_name, 'path' => $path, 'callback' => $callback, 'args' => $args );
	}


	/**
	 * Loads all registered framework plugins, first initializing the plugin
	 * framework by loading the highest versioned one.
	 *
	 * @since 2.0.0
	 */
	public function load_framework_plugins() {

		// first sort the registered plugins by framework version
		usort( $this->registered_plugins, array( $this, 'compare_frameworks' ) );

		$loaded_framework = null;

		foreach ( $this->registered_plugins as $plugin ) {

			// load the first found (highest versioned) plugin framework class
			if ( ! class_exists( 'SV_WC_Plugin' ) ) {
				require_once( $this->get_plugin_path( $plugin['path'] ) . '/lib/skyverge/woocommerce/class-sv-wc-plugin.php' );
				$loaded_framework = $plugin;

				// the loaded plugin is always considered active (for the
				// purposes of handling conflicts between this and other plugins
				// with incompatible framework versions)
				$this->active_plugins[] = $plugin;
			}

			// if the loaded version of the framework has a backwards compatibility requirement
			//  which is not met by the current plugin add an admin notice and move on without
			//  loading the plugin
			if ( ! empty( $loaded_framework['args']['backwards_compatible'] ) && version_compare( $loaded_framework['args']['backwards_compatible'], $plugin['version'], '>' ) ) {

				$this->incompatible_framework_plugins[] = $plugin;

				// next plugin
				continue;
			}

			// if a plugin defines a minimum WC version which is not met, render a notice and skip loading the plugin
			if ( ! empty( $plugin['args']['minimum_wc_version'] ) && version_compare( $this->get_wc_version(), $plugin['args']['minimum_wc_version'], '<' ) ) {

				$this->incompatible_wc_version_plugins[] = $plugin;

				// next plugin
				continue;
			}

			// if a plugin defines a minimum WP version which is not met, render a notice and skip loading the plugin
			if ( ! empty( $plugin['args']['minimum_wp_version'] ) && version_compare( get_bloginfo( 'version' ), $plugin['args']['minimum_wp_version'], '<' ) ) {

				$this->incompatible_wp_version_plugins[] = $plugin;

				// next plugin
				continue;
			}

			// add this plugin to the active list
			if ( ! in_array( $plugin, $this->active_plugins ) ) {
				$this->active_plugins[] = $plugin;
			}

			// load the first found (highest versioned) payment gateway framework class, as needed
			if ( isset( $plugin['args']['is_payment_gateway'] ) && ! class_exists( 'SV_WC_Payment_Gateway' ) ) {
				require_once( $this->get_plugin_path( $plugin['path'] ) . '/lib/skyverge/woocommerce/payment-gateway/class-sv-wc-payment-gateway-plugin.php' );
			}

			// initialize the plugin
			$plugin['callback']();
		}

		// render update notices
		if ( ( $this->incompatible_framework_plugins || $this->incompatible_wc_version_plugins || $this->incompatible_wp_version_plugins ) && is_admin() && ! is_ajax() && ! has_action( 'admin_notices', array( $this, 'render_update_notices' ) ) ) {

			add_action( 'admin_notices', array( $this, 'render_update_notices' ) );
		}

		/**
		 * WC Plugin Framework Plugins Loaded Action.
		 *
		 * Fired when all frameworked plugins are loaded. Frameworked plugins can
		 * hook into this action rather than `plugins_loaded`/`woocommerce_loaded`
		 * as needed.
		 *
		 * @since 2.0.0
		 */
		do_action( 'sv_wc_framework_plugins_loaded' );
	}


	/** Admin methods ******************************************************/


	/**
	 * Deactivate backwards-incompatible framework plugins, which will allow
	 * plugins with an older version of the framework to be active. Useful when
	 * the admin isn't ready to upgrade older plugins yet needs them to still
	 * function (e.g. a payment gateway)
	 *
	 * @since 4.0.0
	 */
	public function maybe_deactivate_framework_plugins() {

		if ( isset( $_GET['sv_wc_framework_deactivate_newer'] ) ) {
			if ( 'yes' == $_GET['sv_wc_framework_deactivate_newer'] ) {

				// don't want to just deactivate all active plugins willy-nilly if there's no incompatible plugins
				if ( count( $this->incompatible_framework_plugins ) == 0 ) {
					return;
				}

				$plugins = array();

				foreach ( $this->active_plugins as $plugin ) {
					$plugins[] = plugin_basename( $plugin['path'] );
				}

				// deactivate all "active" frameworked plugins, these will be the newest, backwards-incompatible ones
				deactivate_plugins( $plugins );

				// redirect to the inactive plugin admin page, with a message indicating the number of plugins deactivated
				wp_redirect( admin_url( 'plugins.php?plugin_status=inactive&sv_wc_framework_deactivate_newer=' . count( $plugins ) ) );
				exit;
			} else {
				// we're on the inactive plugin page and we've deactivated one or more plugins
				add_action( 'admin_notices', array( $this, 'render_deactivation_notice' ) );
			}
		}
	}


	/**
	 * Render a notice with a count of the backwards incompatible frameworked
	 * plugins that were deactivated
	 *
	 * @since 4.0.0
	 */
	public function render_deactivation_notice() {
		echo '<div class="updated"><p>';
		echo $_GET['sv_wc_framework_deactivate_newer'] > 1 ?
			sprintf( 'Deactivated %d plugins', $_GET['sv_wc_framework_deactivate_newer'] ) :
			'Deactivated one plugin';
		echo '</p></div>';
	}


	/**
	 * Render a notice to update any plugins with incompatible framework
	 * versions, or incompatiblities with the current WooCommerce or WordPress
	 * versions
	 *
	 * @since 2.0.0
	 */
	public function render_update_notices() {

		// must update plugin notice
		if ( ! empty( $this->incompatible_framework_plugins ) ) {

			$plugin_count = count( $this->incompatible_framework_plugins );

			echo '<div class="error">';

				// describe the problem
				echo '<p>';
					echo esc_html( _n( 'The following plugin is inactive because it is out of date and requires a newer version to function properly:', 'The following plugins are inactive because they are out of date and require a newer version to function properly:', $plugin_count, 'woocommerce-plugin-framework' ) );
				echo '</p>';

				// add a incompatible plugin list
				echo '<ul>';
					foreach ( $this->incompatible_framework_plugins as $plugin ) {
						printf( '<li>%s</li>', $plugin['plugin_name'] );
					}
				echo '</ul>';

				// describe the way to fix it
				echo '<p>';
					printf(
						/* translators: Placeholders: %1$s - <a> tag, %2$s - </a> tag, %3$s - <em> tag, %4$s - </em> tag, %5$s - <a> tag, %6$s - </a> tag */
						esc_html( _n( 'To reactivate, please %1$supdate the above plugin (recommended)%2$s %3$sor%4$s %5$sdeactivate the following%6$s:', 'To reactivate, please %1$supdate the above plugins (recommended)%2$s %3$sor%4$s %5$sdeactivate the following%6$s:', $plugin_count, 'woocommerce-plugin-framework' ) ),
						'<a href="' . esc_url( admin_url( 'update-core.php' ) ) . '">', '</a>',
						'<em>', '</em>',
						'<a href="' . esc_url( admin_url( 'plugins.php?sv_wc_framework_deactivate_newer=yes' ) ) . '">', '</a>'
					);
				echo '</p>';

				// add the list of active plugins
				echo '<ul>';
					foreach ( $this->active_plugins as $plugin ) {
						printf( '<li>%s</li>', $plugin['plugin_name'] );
					}
				echo '</ul>';

			echo '</div>';
		}

		// must update WC notice
		if ( ! empty( $this->incompatible_wc_version_plugins ) ) {

			printf( '<div class="error"><p>%s</p><ul>', count( $this->incompatible_wc_version_plugins ) > 1 ? esc_html__( 'The following plugins are inactive because they require a newer version of WooCommerce:', 'woocommerce-plugin-framework' ) : esc_html__( 'The following plugin is inactive because it requires a newer version of WooCommerce:', 'woocommerce-plugin-framework' ) );

			foreach ( $this->incompatible_wc_version_plugins as $plugin ) {

				/* translators: Placeholders: %1$s - plugin name, %2$s - WooCommerce version number */
				echo '<li>' . sprintf( esc_html__( '%1$s requires WooCommerce %2$s or newer', 'woocommerce-plugin-framework' ), $plugin['plugin_name'], $plugin['args']['minimum_wc_version'] ) . '</li>';
			}

			/* translators: Placeholders: %1$s - <a> tag, %2$s - </a> tag */
			echo '</ul><p>' . sprintf( esc_html__( 'Please %1$supdate WooCommerce%2$s', 'woocommerce-plugin-framework' ), '<a href="' . admin_url( 'update-core.php' ) . '">', '&nbsp;&raquo;</a>' ) . '</p></div>';
		}

		// must update WP notice
		if ( ! empty( $this->incompatible_wp_version_plugins ) ) {

			printf( '<div class="error"><p>%s</p><ul>', count( $this->incompatible_wp_version_plugins ) > 1 ? 'The following plugins are inactive because they require a newer version of WordPress:' : 'The following plugin is inactive because it requires a newer version of WordPress:' );

			foreach ( $this->incompatible_wp_version_plugins as $plugin ) {
				printf( '<li>%s requires WordPress %s or newer</li>', $plugin['plugin_name'], $plugin['args']['minimum_wp_version'] );
			}

			echo '</ul><p>Please <a href="' . admin_url( 'update-core.php' ) . '">update WordPress&nbsp;&raquo;</a></p></div>';
		}
	}


	/** Helper methods ******************************************************/


	/**
	 * Is the WooCommerce plugin installed and active? This method is handy for
	 * frameworked plugins that are listed on wordpress.org and thus don't have
	 * access to the Woo Helper functions bundled with WooThemes-listed plugins.
	 *
	 * Notice: For now you can't rely on this method being available, since the
	 * bootstrap class is the only piece of the framework which is loaded
	 * simply according to the lexical order of plugin directories. Therefore
	 * to use, you should first check that this method exists, or if you really
	 * need to check for WooCommerce being active, define your own method.
	 *
	 * @since 4.0.0
	 * @return boolean true if the WooCommerce plugin is installed and active
	 */
	public static function is_woocommerce_active() {

		$active_plugins = (array) get_option( 'active_plugins', array() );

		if ( is_multisite() ) {
			$active_plugins = array_merge( $active_plugins, get_site_option( 'active_sitewide_plugins', array() ) );
		}

		return in_array( 'woocommerce/woocommerce.php', $active_plugins ) || array_key_exists( 'woocommerce/woocommerce.php', $active_plugins );
	}


	/**
	 * Compare the two framework versions.  Returns -1 if $a is less than $b, 0 if
	 * they're equal, and 1 if $a is greater than $b
	 *
	 * @since 2.0.0
	 * @param array $a first registered plugin to compare
	 * @param array $b second registered plugin to compare
	 * @return int -1 if $a is less than $b, 0 if they're equal, and 1 if $a is greater than $b
	 */
	public function compare_frameworks( $a, $b ) {
		// compare versions without the operator argument, so we get a -1, 0 or 1 result
		return version_compare( $b['version'], $a['version'] );
	}


	/**
	 * Returns the plugin path for the given $file
	 *
	 * @since 2.0.0
	 * @param string $file the file
	 * @return string plugin path
	 */
	public function get_plugin_path( $file ) {
		return untrailingslashit( plugin_dir_path( $file ) );
	}


	/**
	 * Returns the WooCommerce version number, backwards compatible to
	 * WC 1.5
	 *
	 * @since 3.0.0
	 * @return null|string
	 */
	protected function get_wc_version() {

		if ( defined( 'WC_VERSION' )          && WC_VERSION )          return WC_VERSION;
		if ( defined( 'WOOCOMMERCE_VERSION' ) && WOOCOMMERCE_VERSION ) return WOOCOMMERCE_VERSION;

		return null;
	}

}


// instantiate the class
SV_WC_Framework_Bootstrap::instance();

endif;