From 54c3b8dfbd9f410bbedfa9592073d5579815e5f8 Mon Sep 17 00:00:00 2001
From: Christian Foellmann <foellmann@foe-services.de>
Date: Mon, 23 Dec 2013 01:07:53 +0100
Subject: [PATCH] add plugin headers 'Core', 'PHP' and functions to check
 against environment variables

---
 wp-admin/css/colors.css                           |   5 +-
 wp-admin/includes/class-wp-plugins-list-table.php |  74 ++++++++++++---
 wp-admin/includes/plugin.php                      | 106 +++++++++++++++++++++-
 3 files changed, 169 insertions(+), 16 deletions(-)

diff --git a/wp-admin/css/colors.css b/wp-admin/css/colors.css
index f26c716..522964f 100644
--- a/wp-admin/css/colors.css
+++ b/wp-admin/css/colors.css
@@ -824,6 +824,8 @@ tr.active + tr.plugin-update-tr .plugin-update {
 .plugins .active.update td,
 .plugins .active.update th,
 tr.active.update + tr.plugin-update-tr .plugin-update,
+.plugins .inactive.incompatible td,
+.plugins .inactive.incompatible th,
 #activity-widget #the-comment-list .unapproved {
 	background-color: #fefaf7;
 }
@@ -849,7 +851,8 @@ tr.active.update + tr.plugin-update-tr .plugin-update,
 }
 
 .plugins .active.update th.check-column,
-.plugins .active.update + .plugin-update-tr .plugin-update {
+.plugins .active.update + .plugin-update-tr .plugin-update,
+.plugins .inactive.incompatible th.check-column {
 	border-left: 4px solid #d54e21;
 }
 
diff --git a/wp-admin/includes/class-wp-plugins-list-table.php b/wp-admin/includes/class-wp-plugins-list-table.php
index 9e64849..1230fb3 100644
--- a/wp-admin/includes/class-wp-plugins-list-table.php
+++ b/wp-admin/includes/class-wp-plugins-list-table.php
@@ -18,8 +18,9 @@ class WP_Plugins_List_Table extends WP_List_Table {
 		) );
 
 		$status = 'all';
-		if ( isset( $_REQUEST['plugin_status'] ) && in_array( $_REQUEST['plugin_status'], array( 'active', 'inactive', 'recently_activated', 'upgrade', 'mustuse', 'dropins', 'search' ) ) )
+		if ( isset( $_REQUEST['plugin_status'] ) && in_array( $_REQUEST['plugin_status'], array( 'active', 'inactive', 'recently_activated', 'upgrade', 'mustuse', 'dropins', 'search', 'incompatible' ) ) ) {
 			$status = $_REQUEST['plugin_status'];
+		}
 
 		if ( isset($_REQUEST['s']) )
 			$_SERVER['REQUEST_URI'] = add_query_arg('s', wp_unslash($_REQUEST['s']) );
@@ -48,7 +49,8 @@ class WP_Plugins_List_Table extends WP_List_Table {
 			'recently_activated' => array(),
 			'upgrade' => array(),
 			'mustuse' => array(),
-			'dropins' => array()
+			'dropins' => array(),
+			'incompatible' => array(),
 		);
 
 		$screen = $this->screen;
@@ -58,7 +60,10 @@ class WP_Plugins_List_Table extends WP_List_Table {
 				$plugins['mustuse'] = get_mu_plugins();
 			if ( apply_filters( 'show_advanced_plugins', true, 'dropins' ) )
 				$plugins['dropins'] = get_dropins();
-
+			if ( apply_filters( 'show_advanced_plugins', true, 'incompatible' ) ) {
+				$plugins['incompatible'] = get_incompatible_plugins();
+			}
+			
 			if ( current_user_can( 'update_plugins' ) ) {
 				$current = get_site_transient( 'update_plugins' );
 				foreach ( (array) $plugins['all'] as $plugin_file => $plugin_data ) {
@@ -68,6 +73,10 @@ class WP_Plugins_List_Table extends WP_List_Table {
 					}
 				}
 			}
+		} elseif ( is_multisite() && !is_network_admin() && current_user_can( 'manage_network_plugins' ) ) {
+			if ( apply_filters( 'show_advanced_plugins', true, 'incompatible' ) ) {
+				$plugins['incompatible'] = get_incompatible_plugins();
+			}
 		}
 
 		set_transient( 'plugin_slugs', array_keys( $plugins['all'] ), DAY_IN_SECONDS );
@@ -87,12 +96,14 @@ class WP_Plugins_List_Table extends WP_List_Table {
 				unset( $plugins['all'][ $plugin_file ] );
 			} elseif ( ! $screen->in_admin( 'network' ) && is_plugin_active_for_network( $plugin_file ) ) {
 				unset( $plugins['all'][ $plugin_file ] );
-			} elseif ( ( ! $screen->in_admin( 'network' ) && is_plugin_active( $plugin_file ) )
-				|| ( $screen->in_admin( 'network' ) && is_plugin_active_for_network( $plugin_file ) ) ) {
+			} elseif ( !is_plugin_compatible( $plugin_file ) ) {
+				unset( $plugins['all'][ $plugin_file ] );
+			} elseif ( ( ! $screen->in_admin( 'network' ) && is_plugin_active( $plugin_file ) )	|| ( $screen->in_admin( 'network' ) && is_plugin_active_for_network( $plugin_file ) ) ) {
 				$plugins['active'][ $plugin_file ] = $plugin_data;
 			} else {
-				if ( ! $screen->in_admin( 'network' ) && isset( $recently_activated[ $plugin_file ] ) ) // Was the plugin recently activated?
+				if ( ! $screen->in_admin( 'network' ) && isset( $recently_activated[ $plugin_file ] ) ) {// Was the plugin recently activated?
 					$plugins['recently_activated'][ $plugin_file ] = $plugin_data;
+				}
 				$plugins['inactive'][ $plugin_file ] = $plugin_data;
 			}
 		}
@@ -177,7 +188,7 @@ class WP_Plugins_List_Table extends WP_List_Table {
 		global $status;
 
 		return array(
-			'cb'          => !in_array( $status, array( 'mustuse', 'dropins' ) ) ? '<input type="checkbox" />' : '',
+			'cb'          => !in_array( $status, array( 'mustuse', 'dropins' ) ) || !( 'incompatible' == $status && is_multisite() && is_network_admin() ) ? '<input type="checkbox" />' : '',
 			'name'        => __( 'Plugin' ),
 			'description' => __( 'Description' ),
 		);
@@ -208,6 +219,9 @@ class WP_Plugins_List_Table extends WP_List_Table {
 				case 'inactive':
 					$text = _n( 'Inactive <span class="count">(%s)</span>', 'Inactive <span class="count">(%s)</span>', $count );
 					break;
+				case 'incompatible':
+					$text = _n( 'Incompatible <span class="count">(%s)</span>', 'Incompatible <span class="count">(%s)</span>', $count );
+					break;
 				case 'mustuse':
 					$text = _n( 'Must-Use <span class="count">(%s)</span>', 'Must-Use <span class="count">(%s)</span>', $count );
 					break;
@@ -235,13 +249,33 @@ class WP_Plugins_List_Table extends WP_List_Table {
 		global $status;
 
 		$actions = array();
+		
+		if ( 'incompatible' == $status ) {
+			if ( is_multisite() ) {
+				if ( is_network_admin() && current_user_can( 'manage_network_plugins' ) ) {
+					$actions['delete-selected'] = __( 'Delete' );
+					$actions['update-selected'] = __( 'Update' );
+					return $actions;
+				} else {
+					return $actions;
+				}
+			} else {
+				if ( current_user_can( 'update_plugins' ) ) {
+					$actions['update-selected'] = __( 'Update' );
+				}
+				if ( current_user_can( 'delete_plugins' ) && ( 'active' != $status ) ) {
+					$actions['delete-selected'] = __( 'Delete' );
+				}
+				return $actions;
+			} 
+		}
 
 		if ( 'active' != $status )
 			$actions['activate-selected'] = $this->screen->in_admin( 'network' ) ? __( 'Network Activate' ) : __( 'Activate' );
 
 		if ( 'inactive' != $status && 'recent' != $status )
 			$actions['deactivate-selected'] = $this->screen->in_admin( 'network' ) ? __( 'Network Deactivate' ) : __( 'Deactivate' );
-
+		
 		if ( !is_multisite() || $this->screen->in_admin( 'network' ) ) {
 			if ( current_user_can( 'update_plugins' ) )
 				$actions['update-selected'] = __( 'Update' );
@@ -262,20 +296,24 @@ class WP_Plugins_List_Table extends WP_List_Table {
 	}
 
 	function extra_tablenav( $which ) {
-		global $status;
+		global $status, $wp_version;
 
-		if ( ! in_array($status, array('recently_activated', 'mustuse', 'dropins') ) )
+		if ( ! in_array( $status, array( 'recently_activated', 'mustuse', 'dropins', 'incompatible' ) ) )
 			return;
 
 		echo '<div class="alignleft actions">';
-
+		
 		if ( ! $this->screen->in_admin( 'network' ) && 'recently_activated' == $status )
 			submit_button( __( 'Clear List' ), 'button', 'clear-recent-list', false );
 		elseif ( 'top' == $which && 'mustuse' == $status )
 			echo '<p>' . sprintf( __( 'Files in the <code>%s</code> directory are executed automatically.' ), str_replace( ABSPATH, '/', WPMU_PLUGIN_DIR ) ) . '</p>';
 		elseif ( 'top' == $which && 'dropins' == $status )
 			echo '<p>' . sprintf( __( 'Drop-ins are advanced plugins in the <code>%s</code> directory that replace WordPress functionality when present.' ), str_replace( ABSPATH, '', WP_CONTENT_DIR ) ) . '</p>';
-
+		elseif ( 'bottom' == $which && 'incompatible' == $status ) {
+			if ( is_multisite() && is_network_admin() && current_user_can( 'manage_network' ) || !is_multisite() && current_user_can( 'install_plugins' ) ) {
+				echo '<p> Your PHP version is: ' . PHP_VERSION . ' | Your WP version is: ' . $wp_version . '</p>'; //@todo
+			}
+		}
 		echo '</div>';
 	}
 
@@ -330,6 +368,13 @@ class WP_Plugins_List_Table extends WP_List_Table {
 			}
 			if ( $plugin_data['Description'] )
 				$description .= '<p>' . $plugin_data['Description'] . '</p>';
+		} elseif ( 'incompatible' == $context ) {
+			$is_active = false;
+			if ( is_network_admin() && current_user_can( 'manage_network_plugins' ) ) {
+				$actions['delete'] = '<a href="' . wp_nonce_url('plugins.php?action=delete-selected&amp;checked[]=' . $plugin_file . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'bulk-plugins') . '" title="' . esc_attr__( 'Delete this plugin' ) . '" class="delete">' . __( 'Delete' ) . '</a>';
+			} elseif ( !is_multisite() && current_user_can( 'delete_plugins' ) ) {
+				$actions['delete'] = '<a href="' . wp_nonce_url('plugins.php?action=delete-selected&amp;checked[]=' . $plugin_file . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'bulk-plugins') . '" title="' . esc_attr__( 'Delete this plugin' ) . '" class="delete">' . __( 'Delete' ) . '</a>';
+			}
 		} else {
 			if ( $screen->in_admin( 'network' ) )
 				$is_active = is_plugin_active_for_network( $plugin_file );
@@ -367,8 +412,9 @@ class WP_Plugins_List_Table extends WP_List_Table {
 
 		$class = $is_active ? 'active' : 'inactive';
 		$checkbox_id =  "checkbox_" . md5($plugin_data['Name']);
-		if ( in_array( $status, array( 'mustuse', 'dropins' ) ) ) {
+		if ( in_array( $status, array( 'mustuse', 'dropins' ) ) || 'incompatible' == $context ) {
 			$checkbox = '';
+			$class .= ' incompatible';
 		} else {
 			$checkbox = "<label class='screen-reader-text' for='" . $checkbox_id . "' >" . sprintf( __( 'Select %s' ), $plugin_data['Name'] ) . "</label>"
 				. "<input type='checkbox' name='checked[]' value='" . esc_attr( $plugin_file ) . "' id='" . $checkbox_id . "' />";
@@ -430,7 +476,7 @@ class WP_Plugins_List_Table extends WP_List_Table {
 		}
 
 		echo "</tr>";
-
+		
 		do_action( 'after_plugin_row', $plugin_file, $plugin_data, $status );
 		do_action( "after_plugin_row_$plugin_file", $plugin_file, $plugin_data, $status );
 	}
diff --git a/wp-admin/includes/plugin.php b/wp-admin/includes/plugin.php
index e85ac5b..f423676 100644
--- a/wp-admin/includes/plugin.php
+++ b/wp-admin/includes/plugin.php
@@ -47,6 +47,8 @@
  *		'TextDomain' - Plugin's text domain for localization.
  *		'DomainPath' - Plugin's relative directory path to .mo files.
  *		'Network' - Boolean. Whether the plugin can only be activated network wide.
+ *		'Core' - The minimum WordPress version required.
+ *		'PHP' - The minimal PHP version required.
  *
  * Some users have issues with opening large files and manipulating the contents
  * for want is usually the first 1kiB or 2kiB. This function stops pulling in
@@ -83,6 +85,8 @@ function get_plugin_data( $plugin_file, $markup = true, $translate = true ) {
 		'Network' => 'Network',
 		// Site Wide Only is deprecated in favor of Network.
 		'_sitewide' => 'Site Wide Only',
+		'Core' => 'Core',
+		'PHP' => 'PHP',
 	);
 
 	$plugin_data = get_file_data( $plugin_file, $default_headers, 'plugin' );
@@ -94,7 +98,7 @@ function get_plugin_data( $plugin_file, $markup = true, $translate = true ) {
 	}
 	$plugin_data['Network'] = ( 'true' == strtolower( $plugin_data['Network'] ) );
 	unset( $plugin_data['_sitewide'] );
-
+	
 	if ( $markup || $translate ) {
 		$plugin_data = _get_plugin_data_markup_translate( $plugin_file, $plugin_data, $markup, $translate );
 	} else {
@@ -346,6 +350,28 @@ function get_mu_plugins() {
 }
 
 /**
+ * @todo 
+ *
+ * @todo 
+ *
+ * @since 3.9.0
+ * @return array @todo Key is the mu-plugin file path and the value is an array of the mu-plugin data.
+ */
+function get_incompatible_plugins() {
+	$incompatible_plugins = array();
+	$wp_plugins = get_plugins();
+	
+	foreach ( $wp_plugins as $plugin => $plugin_data ) {
+		if ( is_plugin_incompatible( $plugin ) ) {
+			$incompatible_plugins[ $plugin ] = $plugin_data;
+		}
+	}
+
+	uasort( $incompatible_plugins, '_sort_uname_callback' );
+	return $incompatible_plugins;
+}
+
+/**
  * Callback to sort array by a 'Name' key.
  *
  * @since 3.1.0
@@ -454,6 +480,84 @@ function is_plugin_inactive( $plugin ) {
 }
 
 /**
+ * Check whether the plugin is compatible to current environment.
+ * 
+ * Prevent activation if required WP core version or PHP version is not met or
+ * plugin is incompatible to network wide activation.
+ *
+ * @since 3.9.0
+ * 
+ * @global int $wp_version The current version of this particular WP instance
+ * 
+ * @param string $plugin Base plugin path from plugins directory.
+ * @return bool True if compatible. False if incompatible.
+ */
+function is_plugin_compatible( $plugin ) {
+	
+	$plugin_file = WP_PLUGIN_DIR . '/' . $plugin;
+		
+	if ( is_plugin_compatible_to_php( $plugin_file ) && is_plugin_compatible_to_core( $plugin_file ) ) {	
+		return true;
+	} else {
+		return false;
+	}
+	
+}
+
+/**
+ * @todo
+ * 
+ * Reverse of iis_plugin_compatible(). Used as a callback.
+ *
+ * @since 3.9.0
+ * 
+ * @param string $plugin Base plugin path from plugins directory.
+ * @return bool True if incompatible. False if compatible.
+ */
+function is_plugin_incompatible( $plugin ) {
+	return !is_plugin_compatible( $plugin );
+}
+
+/**
+ * Check whether the plugin is compatible to the current PHP version.
+ *
+ * @since 3.9.0
+ * 
+ * @param string $plugin Base plugin path from plugins directory.
+ * @return bool True if compatible. False if incompatible.
+ */
+function is_plugin_compatible_to_php( $plugin ) {
+	$plugin_data = get_plugin_data( $plugin );
+	
+	if ( isset( $plugin_data['PHP'] ) ) {
+		return version_compare( PHP_VERSION, $plugin_data['PHP'], '>=' );
+	} else {
+		return true;
+	}
+}
+
+/**
+ * Check whether the plugin is compatible to the current WP version.
+ *
+ * @since 3.9.0
+ * 
+ * @global int $wp_version The current version of this particular WP instance
+ * 
+ * @param string $plugin Base plugin path from plugins directory.
+ * @return bool True if compatible. False if incompatible.
+ */
+function is_plugin_compatible_to_core( $plugin ) {
+	$plugin_data = get_plugin_data( $plugin );
+	
+	if ( isset( $plugin_data['Core'] ) ) {
+		global $wp_version;
+		return version_compare( $wp_version, $plugin_data['Core'], '>=' );
+	} else {
+		return true;
+	}
+}
+
+/**
  * Check whether the plugin is active for the entire network.
  *
  * @since 3.0.0
-- 
1.8.4.msysgit.0

