WordPress.org

Make WordPress Core

Ticket #32075: 32075-improved-patch-v8.patch

File 32075-improved-patch-v8.patch, 17.2 KB (added by jrf, 5 years ago)

Minor tidying up

Line 
1From f2628465439c7882cec5f1d2e8090929fa00320c Mon Sep 17 00:00:00 2001
2Date: Sun, 10 Apr 2016 16:07:39 +0200
3Subject: [PATCH] Prevent WP setting the memory limit to a value lower than it
4 currently is.
5
6Also fixes a bug in how the memory limits were tested in the first place.
7
8Includes unit tests for the newly added functions in load.php.
9---
10 src/wp-admin/admin.php                            | 16 +---
11 src/wp-admin/includes/file.php                    |  3 +-
12 src/wp-admin/includes/image-edit.php              |  3 +-
13 src/wp-includes/class-wp-image-editor-gd.php      | 10 +--
14 src/wp-includes/class-wp-image-editor-imagick.php |  3 +-
15 src/wp-includes/default-constants.php             | 43 +++++-----
16 src/wp-includes/deprecated.php                    |  3 +-
17 src/wp-includes/functions.php                     | 95 +++++++++++++++++++++++
18 src/wp-includes/load.php                          | 58 ++++++++++++++
19 tests/phpunit/tests/functions.php                 | 16 ++++
20 tests/phpunit/tests/load.php                      | 72 +++++++++++++++++
21 11 files changed, 271 insertions(+), 51 deletions(-)
22 create mode 100644 tests/phpunit/tests/load.php
23
24diff --git a/src/wp-admin/admin.php b/src/wp-admin/admin.php
25index a54ca21..6c01415 100644
26--- a/src/wp-admin/admin.php
27+++ b/src/wp-admin/admin.php
28@@ -138,21 +138,7 @@ else
29        require(ABSPATH . 'wp-admin/menu.php');
30 
31 if ( current_user_can( 'manage_options' ) ) {
32-       /**
33-        * Filter the maximum memory limit available for administration screens.
34-        *
35-        * This only applies to administrators, who may require more memory for tasks like updates.
36-        * Memory limits when processing images (uploaded or edited by users of any role) are
37-        * handled separately.
38-        *
39-        * The WP_MAX_MEMORY_LIMIT constant specifically defines the maximum memory limit available
40-        * when in the administration back end. The default is 256M, or 256 megabytes of memory.
41-        *
42-        * @since 3.0.0
43-        *
44-        * @param string 'WP_MAX_MEMORY_LIMIT' The maximum WordPress memory limit. Default 256M.
45-        */
46-       @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) );
47+       wp_raise_memory_limit( 'admin' );
48 }
49 
50 /**
51diff --git a/src/wp-admin/includes/file.php b/src/wp-admin/includes/file.php
52index 72f5e22..4b718a7 100644
53--- a/src/wp-admin/includes/file.php
54+++ b/src/wp-admin/includes/file.php
55@@ -558,8 +558,7 @@ function unzip_file($file, $to) {
56                return new WP_Error('fs_unavailable', __('Could not access filesystem.'));
57 
58        // Unzip can use a lot of memory, but not this much hopefully
59-       /** This filter is documented in wp-admin/admin.php */
60-       @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) );
61+       wp_raise_memory_limit( 'admin' );
62 
63        $needed_dirs = array();
64        $to = trailingslashit($to);
65diff --git a/src/wp-admin/includes/image-edit.php b/src/wp-admin/includes/image-edit.php
66index 8947f53..a954d24 100644
67--- a/src/wp-admin/includes/image-edit.php
68+++ b/src/wp-admin/includes/image-edit.php
69@@ -586,8 +586,7 @@ function image_edit_apply_changes( $image, $changes ) {
70 function stream_preview_image( $post_id ) {
71        $post = get_post( $post_id );
72 
73-       /** This filter is documented in wp-admin/admin.php */
74-       @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) );
75+       wp_raise_memory_limit( 'admin' );
76 
77        $img = wp_get_image_editor( _load_image_to_edit_path( $post_id ) );
78 
79diff --git a/src/wp-includes/class-wp-image-editor-gd.php b/src/wp-includes/class-wp-image-editor-gd.php
80index 2093c6b..9cb756d 100644
81--- a/src/wp-includes/class-wp-image-editor-gd.php
82+++ b/src/wp-includes/class-wp-image-editor-gd.php
83@@ -96,16 +96,8 @@ class WP_Image_Editor_GD extends WP_Image_Editor {
84                if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) )
85                        return new WP_Error( 'error_loading_image', __('File doesn’t exist?'), $this->file );
86 
87-               /**
88-                * Filter the memory limit allocated for image manipulation.
89-                *
90-                * @since 3.5.0
91-                *
92-                * @param int|string $limit Maximum memory limit to allocate for images. Default WP_MAX_MEMORY_LIMIT.
93-                *                          Accepts an integer (bytes), or a shorthand string notation, such as '256M'.
94-                */
95                // Set artificially high because GD uses uncompressed images in memory
96-               @ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) );
97+               wp_raise_memory_limit( 'image' );
98 
99                $this->image = @imagecreatefromstring( file_get_contents( $this->file ) );
100 
101diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php
102index 7632cbb..5372eaa 100644
103--- a/src/wp-includes/class-wp-image-editor-imagick.php
104+++ b/src/wp-includes/class-wp-image-editor-imagick.php
105@@ -137,9 +137,8 @@ class WP_Image_Editor_Imagick extends WP_Image_Editor {
106                if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) )
107                        return new WP_Error( 'error_loading_image', __('File doesn’t exist?'), $this->file );
108 
109-               /** This filter is documented in wp-includes/class-wp-image-editor-imagick.php */
110                // Even though Imagick uses less PHP memory than GD, set higher limit for users that have low PHP.ini limits
111-               @ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) );
112+               wp_raise_memory_limit( 'image' );
113 
114                try {
115                        $this->image = new Imagick( $this->file );
116diff --git a/src/wp-includes/default-constants.php b/src/wp-includes/default-constants.php
117index c9092bd..cb790fb 100644
118--- a/src/wp-includes/default-constants.php
119+++ b/src/wp-includes/default-constants.php
120@@ -17,36 +17,39 @@
121 function wp_initial_constants() {
122        global $blog_id;
123 
124-       // set memory limits
125-       if ( !defined('WP_MEMORY_LIMIT') ) {
126-               if ( is_multisite() ) {
127-                       define('WP_MEMORY_LIMIT', '64M');
128+       $current_limit     = @ini_get( 'memory_limit' );
129+       $current_limit_int = wp_php_ini_bytes_to_int( $current_limit );
130+
131+       // Define memory limits.
132+       if ( ! defined( 'WP_MEMORY_LIMIT' ) ) {
133+               if ( false === wp_is_ini_value_changable( 'memory_limit' ) ) {
134+                       define( 'WP_MEMORY_LIMIT', $current_limit );
135+               } elseif ( is_multisite() ) {
136+                       define( 'WP_MEMORY_LIMIT', '64M' );
137                } else {
138-                       define('WP_MEMORY_LIMIT', '40M');
139+                       define( 'WP_MEMORY_LIMIT', '40M' );
140                }
141        }
142 
143        if ( ! defined( 'WP_MAX_MEMORY_LIMIT' ) ) {
144-               define( 'WP_MAX_MEMORY_LIMIT', '256M' );
145+               if ( false === wp_is_ini_value_changable( 'memory_limit' ) ) {
146+                       define( 'WP_MAX_MEMORY_LIMIT', $current_limit );
147+               } elseif ( -1 === $current_limit_int || $current_limit_int > 268435456 ) {
148+                       define( 'WP_MAX_MEMORY_LIMIT', $current_limit );
149+               } else {
150+                       define( 'WP_MAX_MEMORY_LIMIT', '256M' );
151+               }
152+       }
153+
154+       // Set memory limits.
155+       $wp_limit_int = wp_php_ini_bytes_to_int( WP_MEMORY_LIMIT );
156+       if ( -1 !== $current_limit_int && ( -1 === $wp_limit_int || $wp_limit_int > $current_limit_int ) ) {
157+               @ini_set( 'memory_limit', WP_MEMORY_LIMIT );
158        }
159 
160        if ( ! isset($blog_id) )
161                $blog_id = 1;
162 
163-       // set memory limits.
164-       if ( function_exists( 'memory_get_usage' ) ) {
165-               $current_limit = @ini_get( 'memory_limit' );
166-               $current_limit_int = intval( $current_limit );
167-               if ( false !== strpos( $current_limit, 'G' ) )
168-                       $current_limit_int *= 1024;
169-               $wp_limit_int = intval( WP_MEMORY_LIMIT );
170-               if ( false !== strpos( WP_MEMORY_LIMIT, 'G' ) )
171-                       $wp_limit_int *= 1024;
172-
173-               if ( -1 != $current_limit && ( -1 == WP_MEMORY_LIMIT || $current_limit_int < $wp_limit_int ) )
174-                       @ini_set( 'memory_limit', WP_MEMORY_LIMIT );
175-       }
176-
177        if ( !defined('WP_CONTENT_DIR') )
178                define( 'WP_CONTENT_DIR', ABSPATH . 'wp-content' ); // no trailing slash, full paths only - WP_CONTENT_URL is defined further down
179 
180diff --git a/src/wp-includes/deprecated.php b/src/wp-includes/deprecated.php
181index f79761b..0247d4a 100644
182--- a/src/wp-includes/deprecated.php
183+++ b/src/wp-includes/deprecated.php
184@@ -3176,7 +3176,8 @@ function wp_load_image( $file ) {
185                return __('The GD image library is not installed.');
186 
187        // Set artificially high because GD uses uncompressed images in memory
188-       @ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) );
189+       wp_raise_memory_limit( 'image' );
190+
191        $image = imagecreatefromstring( file_get_contents( $file ) );
192 
193        if ( !is_resource( $image ) )
194diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php
195index 0e720ee..f3bef58 100644
196--- a/src/wp-includes/functions.php
197+++ b/src/wp-includes/functions.php
198@@ -5280,3 +5280,98 @@ function mysql_to_rfc3339( $date_string ) {
199        // Strip timezone information
200        return preg_replace( '/(?:Z|[+-]\d{2}(?::\d{2})?)$/', '', $formatted );
201 }
202+
203+/**
204+ * Attempts to raise the PHP memory limit for memory intensive processes.
205+ *
206+ * Allows only to raise the existing limit and prevents lowering it.
207+ *
208+ * @since 4.6.0
209+ *
210+ * @param string $context Context in which the function is called.
211+ *                        Either 'admin' or 'image'. Defaults to 'admin'.
212+ */
213+function wp_raise_memory_limit( $context = 'admin' ) {
214+       // Exit early if the limit cannot be changed.
215+       if ( false === wp_is_ini_value_changable( 'memory_limit' ) ) {
216+               return;
217+       }
218+
219+       $current_limit     = @ini_get( 'memory_limit' );
220+       $current_limit_int = wp_php_ini_bytes_to_int( $current_limit );
221+
222+       if ( -1 === $current_limit_int ) {
223+               return;
224+       }
225+
226+       $wp_max_limit     = WP_MAX_MEMORY_LIMIT;
227+       $wp_max_limit_int = wp_php_ini_bytes_to_int( $wp_max_limit );
228+       $filtered_limit   = $wp_max_limit;
229+
230+       switch ( $context ) {
231+               case 'admin':
232+                       /**
233+                        * Filter the memory limit available for administration screens.
234+                        *
235+                        * This only applies to administrators, who may require more memory for tasks like updates.
236+                        * Memory limits when processing images (uploaded or edited by users of any role) are
237+                        * handled separately.
238+                        *
239+                        * The WP_MAX_MEMORY_LIMIT constant specifically defines the maximum memory limit available
240+                        * when in the administration back end. The default is 256M (256 megabytes
241+                        * of memory) or the original `memory_limit` php.ini value if this is higher.
242+                        *
243+                        * @since 3.0.0
244+                        * @since 4.6.0 The default takes the original `memory_limit` into account.
245+                        *
246+                        * @param int|string $filtered_limit The maximum WordPress memory limit.
247+                        *                                   Accepts an integer (bytes), or a shorthand string
248+                        *                                   notation, such as '256M'.
249+                        */
250+                       $filtered_limit = apply_filters( 'admin_memory_limit', $filtered_limit );
251+                       break;
252+
253+               case 'image':
254+                       /**
255+                        * Filter the memory limit allocated for image manipulation.
256+                        *
257+                        * @since 3.5.0
258+                        * @since 4.6.0 The default takes the original `memory_limit` into account.
259+                        *
260+                        * @param int|string $filtered_limit Maximum memory limit to allocate for images.
261+                        *                                   Default 256M or the original php.ini memory_limit,
262+                        *                                   whichever is higher.
263+                        *                                   Accepts an integer (bytes), or a shorthand string
264+                        *                                   notation, such as '256M'.
265+                        */
266+                       $filtered_limit = apply_filters( 'image_memory_limit', $filtered_limit );
267+                       break;
268+
269+               default:
270+                       /**
271+                        * Filter the memory limit allocated for arbitrary contexts.
272+                        *
273+                        * The dynamic portion of the hook name, `$context`, refers to an arbitrary
274+                        * context passed on calling the function. This allows for plugins to define
275+                        * their own contexts for raising the memory limit.
276+                        *
277+                        * @since 4.6.0
278+                        *
279+                        * @param int|string $filtered_limit Maximum memory limit to allocate for images.
280+                        *                                   Default 256M or the original php.ini memory_limit,
281+                        *                                   whichever is higher.
282+                        *                                   Accepts an integer (bytes), or a shorthand string
283+                        *                                   notation, such as '256M'.
284+                        */
285+                       $filtered_limit = apply_filters( "{$context}_memory_limit", $filtered_limit );
286+                       break;
287+       }
288+
289+       $filtered_limit_int = wp_php_ini_bytes_to_int( $filtered_limit );
290+
291+       if ( -1 === $filtered_limit_int || ( $filtered_limit_int > $wp_max_limit_int && $filtered_limit_int > $current_limit_int ) ) {
292+               @ini_set( 'memory_limit', $filtered_limit );
293+       } elseif ( -1 === $wp_max_limit_int || $wp_max_limit_int > $current_limit_int ) {
294+               @ini_set( 'memory_limit', $wp_max_limit );
295+       }
296+}
297diff --git a/src/wp-includes/load.php b/src/wp-includes/load.php
298index 2f2d95b..93add7b 100644
299--- a/src/wp-includes/load.php
300+++ b/src/wp-includes/load.php
301@@ -895,3 +895,61 @@ function wp_installing( $is_installing = null ) {
302 
303        return (bool) $installing;
304 }
305+
306+/**
307+ * Converts a PHP ini shorthand byte value to an integer byte value.
308+ *
309+ * @since 4.6.0
310+ *
311+ * @see http://php.net/manual/en/function.ini-get.php
312+ * @see http://php.net/manual/en/faq.using.php#faq.using.shorthandbytes
313+ *
314+ * @param string $value An PHP ini byte value, either shorthand or ordinary.
315+ * @return int Value in bytes.
316+ */
317+function wp_php_ini_bytes_to_int( $value ) {
318+       $value = trim( $value );
319+       $last  = strtolower( $value[ strlen( $value ) - 1 ] );
320+
321+       switch( $last ) {
322+               // Note: the `break` statement is left out on purpose!
323+               case 'g':
324+                       $value *= 1024;
325+               case 'm':
326+                       $value *= 1024;
327+               case 'k':
328+                       $value *= 1024;
329+               default:
330+                       // Left empty on purpose.
331+                       break;
332+       }
333+
334+       // Deal with large (float) values which run into the maximum integer size.
335+       if ( PHP_INT_MAX < $value ) {
336+               $value = PHP_INT_MAX;
337+       }
338+       return (int) $value;
339+}
340+
341+/**
342+ * Determines whether a PHP ini value is changable at runtime.
343+ *
344+ * @since 4.6.0
345+ *
346+ * @see http://php.net/manual/en/function.ini-get-all.php
347+ *
348+ * @param string $setting The name of the ini setting to check.
349+ * @return bool True if the value is changable at runtime. False otherwise.
350+ */
351+function wp_is_ini_value_changable( $setting ) {
352+       static $ini_all;
353+
354+       if ( ! isset( $ini_all ) ) {
355+               $ini_all = ini_get_all();
356+       }
357+
358+       if ( isset( $ini_all[ $setting ]['access'] ) && ( INI_ALL === $ini_all[ $setting ]['access'] || INI_USER === $ini_all[ $setting ]['access'] ) ) {
359+               return true;
360+       }
361+       return false;
362+}
363diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php
364index 6d6e177..023e81e 100644
365--- a/tests/phpunit/tests/functions.php
366+++ b/tests/phpunit/tests/functions.php
367@@ -813,4 +813,20 @@ class Tests_Functions extends WP_UnitTestCase {
368                        array( '2016-03-02T19:13:00', '16-03-02 19:13' )
369                );
370        }
371+
372+       /**
373+        * Test raising the memory limit.
374+        *
375+        * {@internal Unfortunately as the default for 'WP_MAX_MEMORY_LIMIT' in the
376+        * test suite is -1, we can not test the memory limit negotiations.}}
377+        *
378+        * @ticket 32075
379+        */
380+       function test_wp_raise_memory_limit() {
381+               ini_set( 'memory_limit', '40M' );
382+               wp_raise_memory_limit();
383+               $this->assertEquals( '-1', ini_get( 'memory_limit' ) );
384+       }
385 }
386diff --git a/tests/phpunit/tests/load.php b/tests/phpunit/tests/load.php
387new file mode 100644
388index 0000000..84133ff
389--- /dev/null
390+++ b/tests/phpunit/tests/load.php
391@@ -0,0 +1,72 @@
392+<?php
393+
394+/**
395+ * @group load.php
396+ */
397+class Tests_Load extends WP_UnitTestCase {
398+
399+       /**
400+        * Test converting PHP ini byte values to integer byte values.
401+        *
402+        * @dataProvider data_wp_php_ini_bytes_to_int
403+        */
404+       function test_wp_php_ini_bytes_to_int( $value, $expected ) {
405+               $this->assertSame( $expected, wp_php_ini_bytes_to_int( $value ) );
406+       }
407+
408+       function data_wp_php_ini_bytes_to_int() {
409+               $array = array(
410+                       // Integer input
411+                       array( -1, -1 ), // = no memory limit
412+                       array( 8388608, 8388608 ), // 8M
413+
414+                       // String input (memory limit shorthand values)
415+                       array( '32k', 32768 ),
416+                       array( '64K', 65536 ),
417+                       array( '128m', 134217728 ),
418+                       array( '256M', 268435456 ),
419+                       array( '1g', 1073741824 ),
420+                       array( '1024', 1024 ), // No letter will be interpreted as integer value.
421+
422+                       // Edge cases
423+                       array( 'g', 0 ),
424+                       array( 'null', 0 ),
425+                       array( 'off', 0 ),
426+               );
427+
428+               // Test while running into maximum integer size limit on 32bit systems.
429+               if ( 2147483647 === PHP_INT_MAX ) {
430+                       $array[] = array( '2G', 2147483647 );
431+                       $array[] = array( '4G', 2147483647 );
432+               } else {
433+                       $array[] = array( '2G', 2147483648 );
434+                       $array[] = array( '4G', 4294967296 );
435+               }
436+
437+               return $array;
438+       }
439+
440+       /**
441+        * Test the determining of the changability of a PHP ini value.
442+        *
443+        * @dataProvider data_wp_is_ini_value_changable
444+        */
445+       function test_wp_is_ini_value_changable( $setting, $expected ) {
446+               $this->assertSame( $expected, wp_is_ini_value_changable( $setting ) );
447+       }
448+
449+       function data_wp_is_ini_value_changable() {
450+               $array = array(
451+                       array( 'memory_limit', true ), // PHP_INI_ALL
452+                       array( 'log_errors', true ), // PHP_INI_ALL
453+                       array( 'upload_max_filesize', false ), // PHP_INI_PERDIR
454+                       array( 'upload_tmp_dir', false ), // PHP_INI_SYSTEM
455+               );
456+
457+               if ( extension_loaded( 'Tidy' ) && version_compare( PHP_VERSION, '7.0.0', '>' ) ) {
458+                       $array[] = array( 'tidy.clean_output', true ); // PHP_INI_USER
459+               }
460+
461+               return $array;
462+       }
463+}
464--
4651.9.4.msysgit.2
466