From 11759999b2e013e574cc5eb7c3743e22793eacbf Mon Sep 17 00:00:00 2001
From: jrfnl <github_nospam@adviesenzo.nl>
Date: Thu, 10 Dec 2015 10:58:06 +0100
Subject: [PATCH] Improved patch for issue #32075.

Prevent WP setting the memory limit to a value lower than it currently is.
Also fixes a bug in how the memory limits were tested in the first place.
---
 src/wp-admin/admin.php                            | 17 +--------
 src/wp-admin/includes/file.php                    |  4 +-
 src/wp-admin/includes/image-edit.php              |  4 +-
 src/wp-includes/class-wp-image-editor-gd.php      | 11 +-----
 src/wp-includes/class-wp-image-editor-imagick.php |  4 +-
 src/wp-includes/default-constants.php             | 31 ++++++++--------
 src/wp-includes/deprecated.php                    |  4 +-
 src/wp-includes/functions.php                     | 45 +++++++++++++++++++++++
 src/wp-includes/load.php                          | 26 +++++++++++++
 tests/phpunit/tests/functions.php                 | 16 ++++++++
 10 files changed, 115 insertions(+), 47 deletions(-)

diff --git a/src/wp-admin/admin.php b/src/wp-admin/admin.php
index b1fa18f..6bfaf1c 100644
--- a/src/wp-admin/admin.php
+++ b/src/wp-admin/admin.php
@@ -138,21 +138,8 @@ else
 	require(ABSPATH . 'wp-admin/menu.php');
 
 if ( current_user_can( 'manage_options' ) ) {
-	/**
-	 * Filter the maximum memory limit available for administration screens.
-	 *
-	 * This only applies to administrators, who may require more memory for tasks like updates.
-	 * Memory limits when processing images (uploaded or edited by users of any role) are
-	 * handled separately.
-	 *
-	 * The WP_MAX_MEMORY_LIMIT constant specifically defines the maximum memory limit available
-	 * when in the administration back-end. The default is 256M, or 256 megabytes of memory.
-	 *
-	 * @since 3.0.0
-	 *
-	 * @param string 'WP_MAX_MEMORY_LIMIT' The maximum WordPress memory limit. Default 256M.
-	 */
-	@ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) );
+	/** The 'admin_memory_limit' filter is documented in wp-includes/functions.php */
+	wp_raise_memory_limit( 'admin_memory_limit' );
 }
 
 /**
diff --git a/src/wp-admin/includes/file.php b/src/wp-admin/includes/file.php
index b8052eb..e108b71 100644
--- a/src/wp-admin/includes/file.php
+++ b/src/wp-admin/includes/file.php
@@ -546,8 +546,8 @@ function unzip_file($file, $to) {
 		return new WP_Error('fs_unavailable', __('Could not access filesystem.'));
 
 	// Unzip can use a lot of memory, but not this much hopefully
-	/** This filter is documented in wp-admin/admin.php */
-	@ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) );
+	/** The 'admin_memory_limit' filter is documented in wp-includes/functions.php */
+	wp_raise_memory_limit( 'admin_memory_limit' );
 
 	$needed_dirs = array();
 	$to = trailingslashit($to);
diff --git a/src/wp-admin/includes/image-edit.php b/src/wp-admin/includes/image-edit.php
index 4015f30..031c73f 100644
--- a/src/wp-admin/includes/image-edit.php
+++ b/src/wp-admin/includes/image-edit.php
@@ -557,8 +557,8 @@ function image_edit_apply_changes( $image, $changes ) {
 function stream_preview_image( $post_id ) {
 	$post = get_post( $post_id );
 
-	/** This filter is documented in wp-admin/admin.php */
-	@ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) );
+	/** The 'admin_memory_limit' filter is documented in wp-includes/functions.php */
+	wp_raise_memory_limit( 'admin_memory_limit' );
 
 	$img = wp_get_image_editor( _load_image_to_edit_path( $post_id ) );
 
diff --git a/src/wp-includes/class-wp-image-editor-gd.php b/src/wp-includes/class-wp-image-editor-gd.php
index 2093c6b..f60d645 100644
--- a/src/wp-includes/class-wp-image-editor-gd.php
+++ b/src/wp-includes/class-wp-image-editor-gd.php
@@ -96,16 +96,9 @@ class WP_Image_Editor_GD extends WP_Image_Editor {
 		if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) )
 			return new WP_Error( 'error_loading_image', __('File doesn&#8217;t exist?'), $this->file );
 
-		/**
-		 * Filter the memory limit allocated for image manipulation.
-		 *
-		 * @since 3.5.0
-		 *
-		 * @param int|string $limit Maximum memory limit to allocate for images. Default WP_MAX_MEMORY_LIMIT.
-		 *                          Accepts an integer (bytes), or a shorthand string notation, such as '256M'.
-		 */
 		// Set artificially high because GD uses uncompressed images in memory
-		@ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) );
+		/** The 'image_memory_limit' filter is documented in wp-includes/functions.php */
+		wp_raise_memory_limit( 'image_memory_limit' );
 
 		$this->image = @imagecreatefromstring( file_get_contents( $this->file ) );
 
diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php
index a14fa40..992187b 100644
--- a/src/wp-includes/class-wp-image-editor-imagick.php
+++ b/src/wp-includes/class-wp-image-editor-imagick.php
@@ -129,9 +129,9 @@ class WP_Image_Editor_Imagick extends WP_Image_Editor {
 		if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) )
 			return new WP_Error( 'error_loading_image', __('File doesn&#8217;t exist?'), $this->file );
 
-		/** This filter is documented in wp-includes/class-wp-image-editor-imagick.php */
 		// Even though Imagick uses less PHP memory than GD, set higher limit for users that have low PHP.ini limits
-		@ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) );
+		/** The 'image_memory_limit' filter is documented in wp-includes/functions.php */
+		wp_raise_memory_limit( 'image_memory_limit' );
 
 		try {
 			$this->image = new Imagick( $this->file );
diff --git a/src/wp-includes/default-constants.php b/src/wp-includes/default-constants.php
index d7a0e4e..8ceb45a 100644
--- a/src/wp-includes/default-constants.php
+++ b/src/wp-includes/default-constants.php
@@ -17,7 +17,7 @@
 function wp_initial_constants() {
 	global $blog_id;
 
-	// set memory limits
+	// Define memory limits.
 	if ( !defined('WP_MEMORY_LIMIT') ) {
 		if ( is_multisite() ) {
 			define('WP_MEMORY_LIMIT', '64M');
@@ -26,27 +26,26 @@ function wp_initial_constants() {
 		}
 	}
 
+	$current_limit     = @ini_get( 'memory_limit' );
+	$current_limit_int = wp_php_ini_bytes_to_int( $current_limit );
+
 	if ( ! defined( 'WP_MAX_MEMORY_LIMIT' ) ) {
-		define( 'WP_MAX_MEMORY_LIMIT', '256M' );
+		if ( -1 === $current_limit_int || $current_limit_int > 268435456 ) {
+			define( 'WP_MAX_MEMORY_LIMIT', $current_limit );
+		} else {
+			define( 'WP_MAX_MEMORY_LIMIT', '256M' );
+		}
+	}
+
+	// Set memory limits.
+	$wp_limit_int = wp_php_ini_bytes_to_int( WP_MEMORY_LIMIT );
+	if ( -1 !== $current_limit_int && ( -1 === $wp_limit_int || $wp_limit_int > $current_limit_int ) ) {
+		@ini_set( 'memory_limit', WP_MEMORY_LIMIT );
 	}
 
 	if ( ! isset($blog_id) )
 		$blog_id = 1;
 
-	// set memory limits.
-	if ( function_exists( 'memory_get_usage' ) ) {
-		$current_limit = @ini_get( 'memory_limit' );
-		$current_limit_int = intval( $current_limit );
-		if ( false !== strpos( $current_limit, 'G' ) )
-			$current_limit_int *= 1024;
-		$wp_limit_int = intval( WP_MEMORY_LIMIT );
-		if ( false !== strpos( WP_MEMORY_LIMIT, 'G' ) )
-			$wp_limit_int *= 1024;
-
-		if ( -1 != $current_limit && ( -1 == WP_MEMORY_LIMIT || $current_limit_int < $wp_limit_int ) )
-			@ini_set( 'memory_limit', WP_MEMORY_LIMIT );
-	}
-
 	if ( !defined('WP_CONTENT_DIR') )
 		define( 'WP_CONTENT_DIR', ABSPATH . 'wp-content' ); // no trailing slash, full paths only - WP_CONTENT_URL is defined further down
 
diff --git a/src/wp-includes/deprecated.php b/src/wp-includes/deprecated.php
index 571de24..c1b41bd 100644
--- a/src/wp-includes/deprecated.php
+++ b/src/wp-includes/deprecated.php
@@ -3134,7 +3134,9 @@ function wp_load_image( $file ) {
 		return __('The GD image library is not installed.');
 
 	// Set artificially high because GD uses uncompressed images in memory
-	@ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) );
+	/** The 'image_memory_limit' filter is documented in wp-includes/functions.php */
+	wp_raise_memory_limit( 'image_memory_limit' );
+
 	$image = imagecreatefromstring( file_get_contents( $file ) );
 
 	if ( !is_resource( $image ) )
diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php
index e3fb353..948fe8b 100644
--- a/src/wp-includes/functions.php
+++ b/src/wp-includes/functions.php
@@ -5166,3 +5166,48 @@ function mysql_to_rfc3339( $date_string ) {
 	// Strip timezone information
 	return preg_replace( '/(?:Z|[+-]\d{2}(?::\d{2})?)$/', '', $formatted );
 }
+
+/**
+ * WP raise memory limit.
+ *
+ * Raises the PHP memory limit for memory intensive processes.
+ * Only allows filter to raise and not lower the exciting limit.
+ *
+ * @param string $filter Name of the filter to apply. Defaults to 'admin_memory_limit'.
+ */
+function wp_raise_memory_limit( $filter = 'admin_memory_limit' ) {
+	$current_limit     = @ini_get( 'memory_limit' );
+	$current_limit_int = wp_php_ini_bytes_to_int( $current_limit );
+
+	if ( -1 === $current_limit_int ) {
+		return;
+	}
+
+	$wp_max_limit     = WP_MAX_MEMORY_LIMIT;
+	$wp_max_limit_int = wp_php_ini_bytes_to_int( $wp_max_limit );
+
+	/**
+	 * Filter the desired PHP memory limit before raising it.
+	 *
+	 * The WP_MAX_MEMORY_LIMIT constant specifically defines the maximum memory limit
+	 * available when in the administration back-end. The default is 256M (256 megabytes
+	 * of memory) or the original `memory_limit` php.ini value if this is higher.
+	 *
+	 * @since 3.0.0
+	 *
+	 * Filters which get passed in are:
+	 * 'admin_memory_limit' - Filter the memory limit available for administration screens.
+	 * 'image_memory_limit' - Filter the memory limit allocated for image manipulation.
+	 *
+	 * @param string $filter       The name of the filter to apply. Passed in.
+	 * @param string $wp_max_limit The maximum WordPress memory limit. Default 256M.
+	 */
+	$filtered_limit     = apply_filters( $filter, $wp_max_limit );
+	$filtered_limit_int = wp_php_ini_bytes_to_int( $filtered_limit );
+
+	if ( -1 === $filtered_limit_int || ( $filtered_limit_int > $wp_max_limit_int && $filtered_limit_int > $current_limit_int ) ) {
+		@ini_set( 'memory_limit', $filtered_limit );
+	} elseif ( -1 === $wp_max_limit_int || $wp_max_limit_int > $current_limit_int ) {
+		@ini_set( 'memory_limit', $wp_max_limit );
+	}
+}
diff --git a/src/wp-includes/load.php b/src/wp-includes/load.php
index 9d247ba..017558b 100644
--- a/src/wp-includes/load.php
+++ b/src/wp-includes/load.php
@@ -892,3 +892,29 @@ function wp_installing( $is_installing = null ) {
 
 	return (bool) $installing;
 }
+
+/**
+ * Convert a PHP ini shorthand byte value to an integer byte value.
+ *
+ * @see http://php.net/manual/en/function.ini-get.php
+ * @see http://php.net/manual/en/faq.using.php#faq.using.shorthandbytes
+ *
+ * @param string $value An PHP ini byte value, either shorthand or ordinary.
+ * @return int Value in bytes.
+ */
+function wp_php_ini_bytes_to_int( $value ) {
+	$value = trim( $value );
+	$last  = strtolower( $value[ strlen( $value ) - 1 ] );
+	
+	switch( $last ) {
+		// Note: the `break` statement is left out on purpose!
+		case 'g':
+			$value *= 1024;
+		case 'm':
+			$value *= 1024;
+		case 'k':
+			$value *= 1024;
+	}
+
+	return (int) $value;
+}
diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php
index 631b303..2c4ab82 100644
--- a/tests/phpunit/tests/functions.php
+++ b/tests/phpunit/tests/functions.php
@@ -717,4 +717,20 @@ class Tests_Functions extends WP_UnitTestCase {
 		the_date( 'Y', 'before ', ' after', false );
 		$this->assertEquals( '', ob_get_clean() );
 	}
+
+	/**
+	 * Test raising the memory limit.
+	 *
+	 * Unfortunately as the default for 'WP_MAX_MEMORY_LIMIT' in the test suite is -1, we can not
+	 * test the memory limit negotiations.
+	 *
+	 * @ticket 32075
+	 */
+	function test_wp_raise_memory_limit() {
+		$original = ini_get( 'memory_limit' );
+
+		ini_set( 'memory_limit', '40M' );
+		wp_raise_memory_limit();
+		$this->assertEquals( '-1', ini_get( 'memory_limit' ) );
+	}
 }
-- 
1.9.4.msysgit.2

