PluginProbe
Upload Larger Plugins / trunk
Upload Larger Plugins vtrunk
2.1 trunk 1.0 1.1 1.2 1.3 1.4 1.4.1 1.5 1.6 1.7 1.8 2.0
upload-larger-plugins / upload-larger-plugins.php

upload-larger-plugins.php in Upload Larger Plugins trunk, at upload-larger-plugins.php

467 lines 19.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 Plugin Name: Upload Larger Plugins
4 Version: 2.1
5 Description: Allow plugins larger than the PHP-defined limit to be uploaded.
6 Author: David Anderson
7 Donate: https://david.dw-perspective.org.uk/donate
8 Author URI: https://david.dw-perspective.org.uk
9 Text Domain: upload-larger-plugins
10 License: MIT
11 */
12
13 if (!defined('ABSPATH')) die('No direct access');
14
15 // Globals
16 define('UPLOADLARGERPLUGINS_VERSION', '2.1');
17 define('UPLOADLARGERPLUGINS_SLUG', "upload-larger-plugins");
18 define('UPLOADLARGERPLUGINS_DIR', dirname(realpath(__FILE__)));
19 define('UPLOADLARGERPLUGINS_URL', plugins_url('', __FILE__));
20
21 $simba_upload_larger_plugins = new Simba_Upload_Larger_Plugins();
22
23 class Simba_Upload_Larger_Plugins {
24
25 private $upload_dir;
26 private $upload_basedir;
27
28 /**
29 * Plugin constructor
30 */
31 public function __construct() {
32 //add_filter('plugin_action_links', array($this, 'action_links'), 10, 2 );
33 add_action('install_plugins_upload', array($this, 'install_plugins_upload'), 9, 1);
34 add_action('install_plugins_pre_upload', array($this, 'install_plugins_pre_upload'));
35 add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts'));
36 add_action('plugins_loaded', array($this, 'load_translations'));
37 add_action('admin_head', array($this, 'admin_head'));
38 add_action('wp_ajax_ulp_plupload_action', array($this, 'ulp_plupload_action'));
39 add_action('admin_init', array($this, 'admin_init'));
40 // This filter only exists on WP 3.7+. We used to use it then... but then WP 4.6.1 broke our method, so we've reverted to the pre-WP-3.7 method
41 // add_filter('upgrader_pre_download', array($this, 'upgrader_pre_download'), 10, 3);
42 // This action allows us to tweak the link URL on WP 5.5+
43 add_filter('install_plugin_overwrite_actions', array($this, 'install_plugin_overwrite_actions'));
44 }
45
46 /**
47 * Called by the WP filter install_plugin_overwrite_actions (WP 5.5+)
48 *
49 * @param Array $install_actions Array of plugin action links.
50 *
51 * @return Array - modified array
52 */
53 public function install_plugin_overwrite_actions($install_actions) {
54
55 // phpcs:disable WordPress.Security.NonceVerification -- handled by WP core
56
57 if (!empty($install_actions['overwrite_plugin']) && false !== strpos($install_actions['overwrite_plugin'], 'action=upload-plugin&amp;')) {
58
59 if (!empty($_GET['plugincksha1']) && !empty($_GET['overridebd']) && !empty($_GET['package']) && current_user_can('install_plugins')) {
60
61 // WP 5.5 already uses "package=0"; so we have to replace that with the proper name that will work with our upload directory
62 $install_actions['overwrite_plugin'] = str_replace('action=upload-plugin&amp;', 'action=upload-plugin&amp;plugincksha1='.urlencode($_GET['plugincksha1']).'&amp;overridebd='.urlencode(stripslashes($_GET['overridebd'])).'&amp;package='.urlencode(stripslashes($_GET['package'])).'&amp;', $install_actions['overwrite_plugin']); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- the worst that can happen is providing an invalid checksum which then gets blocked anyway
63
64 $install_actions['overwrite_plugin'] = str_replace('package=0&amp;', '', $install_actions['overwrite_plugin']);
65 }
66
67 }
68
69 // phpcs:enable WordPress.Security.NonceVerification
70 return $install_actions;
71 }
72
73 /**
74 * Called by the WP action admin_init. Used to continue when a completed upload from our widget has occurred.
75 */
76 public function admin_init() {
77
78 // Check if parameters present indicate our action
79 if (empty($_GET['plugincksha1']) || empty($_GET['overridebd']) || !isset($_GET['package']) || !current_user_can('install_plugins')) return; // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.NonceVerification.Recommended -- wrong recommendations; checksum is being used
80
81 /*
82 Old note:
83
84 The rest of the code's purpose is to work-around the lack of the upgrader_pre_download filter before WP 3.7
85 The below would work on >= 3.7 too; but there, we use a more elegant/direct method.
86
87 New situation:
88 WP 4.6.1 - https://build.trac.wordpress.org/changeset/38466 - introduced a change which prevents upgrader_pre_download from working. So, this way is back.
89 */
90
91 // require(ABSPATH.WPINC.'/version.php');
92 // if (version_compare($wp_version, '3.7', '>=')) return;
93
94 $package = (isset($_GET['fpackage']) && is_numeric($_GET['package'])) ? stripslashes($_GET['fpackage']) : stripslashes($_GET['package']); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- false positive
95
96 $upgrader = new stdClass;
97 $upgrader->strings = array('download_failed' => __('Error when trying to find uploaded file', 'upload-larger-plugins'));
98 $try_file = $this->upgrader_pre_download(false, $package, $upgrader);
99
100 // The File_Upload_Upgrader object eventually gets constructed with this (where $urlholder = 'package', and $uploads = wp_upload_dir())
101 //File_Upload_Upgrader::filename = $_GET[$urlholder];
102 //File_Upload_Upgrader::package = $uploads['basedir'] . '/' . $this->filename;
103
104 if (!(($uploads = wp_upload_dir()) && false === $uploads['error'])) return;
105
106 if (is_string($try_file) && file_exists($try_file)) {
107 $upload_dir = untrailingslashit(get_temp_dir());
108 // if (!is_writable($upload_dir)) return;
109 $this->upload_basedir = $upload_dir;
110 add_filter('upload_dir', array($this, 'upload_dir'));
111 add_action('upgrader_process_complete', array($this, 'upgrader_process_complete'));
112 }
113 }
114
115 // Only hooked on WP < 3.7
116 public function upgrader_process_complete() {
117 remove_filter('upload_dir', array($this, 'upload_dir'));
118 }
119
120 public function upgrader_pre_download($result, $package, $upgrader) {
121
122 //phpcs:disable WordPress.Security.NonceVerification.Recommended -- checksum used
123
124 if (empty($_GET['plugincksha1']) || empty($_GET['overridebd'])) return $result;
125 $upload_dir = untrailingslashit(get_temp_dir());
126
127 // Sanity checks
128 if ($upload_dir != $_GET['overridebd']) return new WP_Error('download_failed', $upgrader->strings['download_failed']);
129 $try_file = $upload_dir.'/'.basename($package);
130
131 if (!file_exists($try_file) || sha1_file($try_file) != $_GET['plugincksha1']) return new WP_Error('download_failed', $upgrader->strings['download_failed']);
132
133 //phpcs:enable WordPress.Security.NonceVerification.Recommended
134
135 return $try_file;
136 }
137
138 /**
139 * @return Boolean
140 */
141 private function is_our_page_and_authorised() {
142 if (!current_user_can('install_plugins')) return false;
143
144 require(ABSPATH.WPINC.'/version.php');
145
146 global $pagenow;
147 // On WP 4.6, there is no longer an upload 'tab' - it's a slide-down instead
148
149 return ($pagenow != 'plugin-install.php' || (version_compare($wp_version, '4.5.9999', '<') && (!isset($_REQUEST['tab']) || 'upload' != $_REQUEST['tab']))) ? false : true; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- false positive
150
151 }
152
153 /**
154 * Runs upon the WP action admin_enqueue_scripts
155 */
156 public function admin_enqueue_scripts() {
157
158 if (!$this->is_our_page_and_authorised()) return;
159
160 wp_enqueue_script('ulp-admin-ui', UPLOADLARGERPLUGINS_URL.'/admin.js', array('jquery', 'plupload-all'), '1', array('in_footer' => true));
161
162 wp_localize_script('ulp-admin-ui', 'ulplion', array(
163 'notarchive' => __('This file does not appear to be a zip file.', 'upload-larger-plugins'),
164 'notarchive2' => '<p>'.__('This file does not appear to be a zip file.', 'upload-larger-plugins').'</p>',
165 'uploaderror' => __('Upload error:', 'upload-larger-plugins'),
166 'makesure' => __('(make sure that you were trying to upload a zip file', 'upload-larger-plugins'),
167 'uploaderr' => __('Upload error', 'upload-larger-plugins'),
168 'jsonnotunderstood' => __('Error: the server sent us a response (JSON) which we did not understand.', 'upload-larger-plugins'),
169 'error' => __('Error:', 'upload-larger-plugins')
170 ));
171
172 }
173
174 /**
175 * Runs upon the WP action plugins_loaded
176 */
177 public function load_translations() {
178 // Tell WordPress where to find the translations
179 load_plugin_textdomain('upload-larger-plugins', false, basename(dirname(__FILE__)).'/languages/');
180 }
181
182 /**
183 * Used by the WP filter upload_dir
184 *
185 * @param Array $uploads
186 *
187 * @return Array
188 */
189 public function upload_dir($uploads) {
190 if (!empty($this->upload_dir)) $uploads['path'] = $this->upload_dir;
191 if (!empty($this->upload_basedir)) $uploads['basedir'] = $this->upload_basedir;
192 return $uploads;
193 }
194
195 /**
196 * Runs upon the AJAX event ulp_plupload_action
197 */
198 public function ulp_plupload_action() {
199
200 // phpcs:disable WordPress.WP.AlternativeFunctions -- incorrect advice on filesystem functions
201 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- destination is not DB
202
203 @set_time_limit(900); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- mere opinion
204
205 if (!current_user_can('install_plugins') || !isset($_FILES['async-upload']) || !isset($_POST['name'])) return;
206 check_ajax_referer('uploadlargerplugins-uploader');
207
208 $upload_dir = untrailingslashit(get_temp_dir());
209 if (!is_writable($upload_dir)) exit;
210 $this->upload_dir = $upload_dir;
211
212 add_filter('upload_dir', array($this, 'upload_dir'));
213 // handle file upload
214
215 $farray = array('test_form' => true, 'action' => 'ulp_plupload_action');
216
217 $farray['test_type'] = false;
218 $farray['ext'] = 'zip';
219 $farray['type'] = 'application/zip';
220
221 // if (isset($_POST['chunks'])) {
222 //
223 // } else {
224 // # Over-write - that's OK.
225 // $farray['unique_filename_callback'] = array($this, 'unique_filename_callback');
226 // }
227
228 $status = wp_handle_upload(
229 $_FILES['async-upload'],
230 $farray
231 );
232 remove_filter('upload_dir', array($this, 'upload_dir'));
233
234 if (isset($status['error'])) {
235 echo json_encode(array('e' => $status['error']));
236 exit;
237 }
238
239 // Should be a no-op
240 $name = basename(stripslashes($_POST['name'])); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- false positive
241
242 // If this was the chunk, then we should instead be concatenating onto the final file
243 if (isset($_POST['chunks']) && isset($_POST['chunk']) && preg_match('/^[0-9]+$/', $_POST['chunk'])) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- known not to contain any slash
244 // A random element is added, because otherwise it is theoretically possible for another user to upload into a shared temporary directory in between the upload and install, and over-write
245 $final_file = $name;
246 rename($status['file'], $upload_dir.'/'.$final_file.'.'.$_POST['chunk'].'.zip.tmp'); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- already known to contain no slash
247 $status['file'] = $upload_dir.'/'.$final_file.'.'.$_POST['chunk'].'.zip.tmp'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- known to contain no slash
248
249 // Final chunk? If so, then stich it all back together
250 if ($_POST['chunk'] == $_POST['chunks']-1) {
251 if ($wh = fopen($upload_dir.'/'.$final_file, 'wb')) {
252 for ($i=0 ; $i<$_POST['chunks']; $i++) {
253 $rf = $upload_dir.'/'.$final_file.'.'.$i.'.zip.tmp';
254 if ($rh = fopen($rf, 'rb')) {
255 while ($line = fread($rh, 32768)) fwrite($wh, $line);
256 fclose($rh);
257 @unlink($rf);
258 }
259 }
260 fclose($wh);
261 $status['file'] = $upload_dir.'/'.$final_file;
262 }
263 }
264
265 }
266
267 $response = array();
268 if (!isset($_POST['chunks']) || (isset($_POST['chunk']) && $_POST['chunk'] == $_POST['chunks']-1)) {
269 $file = basename($status['file']);
270 if (!preg_match('/\.zip$/i', $file, $matches)) {
271 @unlink($status['file']);
272 echo json_encode(array('e' => __('Error:', 'upload-larger-plugins').' '.__('This file does not appear to be a zip file.', 'upload-larger-plugins')));
273 exit;
274 }
275 }
276
277 // send the redirect URL
278 $response['m'] = admin_url('update.php?action=upload-plugin&overridebd='.urlencode(dirname($status['file'])).'&plugincksha1='.sha1_file($status['file']).'&_wpnonce='.wp_create_nonce( 'plugin-upload' ).'&package='.urlencode(basename($status['file'])));
279 echo json_encode($response);
280 exit;
281
282 // phpcs:enable WordPress.WP.AlternativeFunctions, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
283 }
284
285 /**
286 * Runs upon the WP action admin_head
287 */
288 public function admin_head() {
289
290 if (!$this->is_our_page_and_authorised()) return;
291
292 $chunk_size = min(wp_max_upload_size()-1024, 1048576*2-1024);
293
294 # The multiple_queues argument is ignored in plupload 2.x (WP3.9+) - https://make.wordpress.org/core/2014/04/11/plupload-2-x-in-wordpress-3-9/
295 # max_file_size is also in filters as of plupload 2.x, but in its default position is still supported for backwards-compatibility. Likewise, our use of filters.extensions below is supported by a backwards-compatibility option (the current way is filters.mime-types.extensions
296
297 $plupload_init = array(
298 'runtimes' => 'html5,flash,silverlight,html4',
299 'browse_button' => 'plupload-browse-button',
300 'container' => 'plupload-upload-ui',
301 'drop_element' => 'drag-drop-area',
302 'file_data_name' => 'async-upload',
303 'multiple_queues' => false,
304 'max_file_count' => 1,
305 'max_file_size' => '100Gb',
306 'chunk_size' => $chunk_size.'b',
307 'url' => admin_url('admin-ajax.php'),
308 'filters' => array(array('title' => __('Allowed Files'), 'extensions' => 'zip')), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- string from WordPress core
309 'multipart' => true,
310 'multi_selection' => false,
311 'urlstream_upload' => true,
312 // additional post data to send to our ajax hook
313 'multipart_params' => array(
314 '_ajax_nonce' => wp_create_nonce('uploadlargerplugins-uploader'),
315 'action' => 'ulp_plupload_action'
316 )
317 );
318
319 // WP 3.9 updated to plupload 2.0 - https://core.trac.wordpress.org/ticket/25663
320 if (is_file(ABSPATH.WPINC.'/js/plupload/Moxie.swf')) {
321 $plupload_init['flash_swf_url'] = includes_url('js/plupload/Moxie.swf');
322 } elseif (is_file(ABSPATH.WPINC.'/js/plupload/plupload.flash.swf')) {
323 $plupload_init['flash_swf_url'] = includes_url('js/plupload/plupload.flash.swf');
324 }
325
326 if (is_file(ABSPATH.WPINC.'/js/plupload/Moxie.xap')) {
327 $plupload_init['silverlight_xap_url'] = includes_url('js/plupload/Moxie.xap');
328 } elseif (is_file(ABSPATH.WPINC.'/js/plupload/plupload.silverlight.swf')) {
329 $plupload_init['silverlight_xap_url'] = includes_url('js/plupload/plupload.silverlight.swf');
330 }
331
332 ?><script>
333 var ulp_plupload_config=<?php echo json_encode($plupload_init); ?>;
334 </script>
335 <style type="text/css">
336 .drag-drop #drag-drop-area {
337 border: 4px dashed #ddd;
338 height: 200px;
339 }
340 #filelist {
341 width: 100%;
342 }
343 #filelist .file {
344 padding: 5px;
345 background: #ececec;
346 border: solid 1px #ccc;
347 margin: 4px 0;
348 }
349 #filelist .fileprogress {
350 width: 0%;
351 background: #f6a828;
352 height: 5px;
353 }
354 </style>
355 <?php
356
357 }
358
359 public function install_plugins_pre_upload() {
360 // Unhook the default uploader (works on WP < 4.6 only)
361 remove_action('install_plugins_upload', 'install_plugins_upload');
362 }
363
364 /**
365 * If hooked, runs upon the WP action install_plugins_upload
366 *
367 */
368 public function install_plugins_upload() {
369
370 echo '<div class="upload-plugin">';
371
372 require(ABSPATH.WPINC.'/version.php');
373
374 if (version_compare($wp_version, '4.5.9999', '<')) { ?>
375
376 <!-- Upload form from Upload Larger Plugins -->
377 <h4><?php
378 esc_html_e('Install a plugin in .zip format'); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- string from WordPress core
379 ?></h4>
380
381 <?php } ?>
382
383 <p class="install-help" style="text-align:left; margin-bottom: 6px;">
384
385 <?php
386
387 $upload_dir = untrailingslashit(get_temp_dir());
388 if (!$this->really_is_writable($upload_dir)) {
389 // translators: directory path
390 echo '<strong>'.sprintf(esc_html__("Your hosting's temporary directory (%s) is not writable (as verified by attempting to write to it). You need to fix this (asking your hosting company for help if necessary) to be able to upload any plugins.", 'upload-larger-plugins'), $upload_dir).'</strong>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- false positive
391 } else {
392 esc_html_e('If you have a plugin in a .zip format, you may install it by uploading it here.').'<br>'; // phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- string from WordPress core
393 }
394
395 echo '</p>';
396
397 if (version_compare($wp_version, '3.3', '<')) {
398 // translators: WordPress, version number
399 echo '<em>'.sprintf(esc_html__('This feature requires %1$s version %2$s or later', 'upload-larger-plugins'), 'WordPress', '3.3').'</em>';
400 } else {
401 ?>
402 <div id="plupload-upload-ui" class="drag-drop" style="width: 70%;">
403 <div id="drag-drop-area">
404 <div class="drag-drop-inside">
405 <p class="drag-drop-info"><?php esc_html_e('Drop plugin zip here', 'upload-larger-plugins'); ?></p>
406 <p><?php echo esc_html_x('or', 'Uploader: Drop plugin zip here - or - Select File'); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- string from WordPress core ?></p>
407 <p class="drag-drop-buttons"><input id="plupload-browse-button" type="button" value="<?php echo esc_attr(__('Select File', 'upload-larger-plugins')); ?>" class="button" /></p>
408 </div>
409 </div>
410 <div id="filelist">
411 </div>
412 </div>
413 <?php
414 }
415 ?>
416
417 </div>
418
419 <?php
420 /*
421 <div style="display:none;">
422 <form method="post" enctype="multipart/form-data" class="wp-upload-form" action="<?php echo self_admin_url('update.php?action=upload-plugin'); ?>">
423 <?php wp_nonce_field( 'plugin-upload'); ?>
424 <input type="file" id="pluginzip" name="pluginzip" />
425 <?php submit_button( __( 'Install Now' ), 'button', 'install-plugin-submit', false ); ?>
426 </form>
427 </div>
428 */
429 }
430
431 /**
432 * Find out whether we really can write to a particular folder
433 *
434 * @param String $dir - the folder path
435 *
436 * @return Boolean - the result
437 */
438 private function really_is_writable($dir) {
439
440 // phpcs:disable WordPress.WP.AlternativeFunctions -- incorrect filesystem suggestions
441
442 // Suppress warnings, since if the user is dumping warnings to screen, then invalid JavaScript results and the screen breaks.
443 if (!@is_writable($dir)) return false;// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
444 // Found a case - GoDaddy server, Windows, PHP 5.2.17 - where is_writable returned true, but writing failed
445 $rand_file = "$dir/test-".md5(rand().time()).".txt"; // phpcs:ignore WordPress.WP.AlternativeFunctions.rand_rand -- cryptographic randomness not required
446 while (file_exists($rand_file)) {
447 $rand_file = "$dir/test-".md5(rand().time()).".txt"; // phpcs:ignore WordPress.WP.AlternativeFunctions.rand_rand -- cryptographic randomness not required
448 }
449 $ret = @file_put_contents($rand_file, 'testing...');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
450 @unlink($rand_file);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
451 return ($ret > 0);
452
453 // phpcs:enable WordPress.WP.AlternativeFunctions
454
455 }
456
457 public function action_links($links, $file) {
458 if ($file == UPLOADLARGERPLUGINS_SLUG."/".basename(__FILE__)) {
459 array_unshift($links,
460 '<a href="options-general.php?page=upload_larger_plugins">'.__('Settings', 'upload-larger-plugins').'</a>'
461 );
462 }
463 return $links;
464 }
465
466 }
467