PluginProbe
Upload Larger Plugins / 1.8
Upload Larger Plugins v1.8
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 1.8, at upload-larger-plugins.php

469 lines 19.1 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: 1.8
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', '1.8');
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('args' => 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, 1024*1024*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 // 'flash_swf_url' => includes_url('js/plupload/plupload.flash.swf'),
319 // 'silverlight_xap_url' => includes_url('js/plupload/plupload.silverlight.xap'),
320
321 # WP 3.9 updated to plupload 2.0 - https://core.trac.wordpress.org/ticket/25663
322 if (is_file(ABSPATH.'wp-includes/js/plupload/Moxie.swf')) {
323 $plupload_init['flash_swf_url'] = includes_url('js/plupload/Moxie.swf');
324 } else {
325 $plupload_init['flash_swf_url'] = includes_url('js/plupload/plupload.flash.swf');
326 }
327
328 if (is_file(ABSPATH.'wp-includes/js/plupload/Moxie.xap')) {
329 $plupload_init['silverlight_xap_url'] = includes_url('js/plupload/Moxie.xap');
330 } else {
331 $plupload_init['silverlight_xap_url'] = includes_url('js/plupload/plupload.silverlight.swf');
332 }
333
334 ?><script type="text/javascript">
335 var ulp_plupload_config=<?php echo json_encode($plupload_init); ?>;
336 </script>
337 <style type="text/css">
338 .drag-drop #drag-drop-area {
339 border: 4px dashed #ddd;
340 height: 200px;
341 }
342 #filelist {
343 width: 100%;
344 }
345 #filelist .file {
346 padding: 5px;
347 background: #ececec;
348 border: solid 1px #ccc;
349 margin: 4px 0;
350 }
351 #filelist .fileprogress {
352 width: 0%;
353 background: #f6a828;
354 height: 5px;
355 }
356 </style>
357 <?php
358
359 }
360
361 public function install_plugins_pre_upload() {
362 // Unhook the default uploader (works on WP < 4.6 only)
363 remove_action('install_plugins_upload', 'install_plugins_upload');
364 }
365
366 /**
367 * If hooked, runs upon the WP action install_plugins_upload
368 *
369 */
370 public function install_plugins_upload() {
371
372 echo '<div class="upload-plugin">';
373
374 require(ABSPATH.WPINC.'/version.php');
375
376 if (version_compare($wp_version, '4.5.9999', '<')) { ?>
377
378 <!-- Upload form from Upload Larger Plugins -->
379 <h4><?php
380 esc_html_e('Install a plugin in .zip format'); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- string from WordPress core
381 ?></h4>
382
383 <?php } ?>
384
385 <p class="install-help" style="text-align:left; margin-bottom: 6px;">
386
387 <?php
388
389 $upload_dir = untrailingslashit(get_temp_dir());
390 if (!$this->really_is_writable($upload_dir)) {
391 // translators: directory path
392 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
393 } else {
394 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
395 }
396
397 echo '</p>';
398
399 if (version_compare($wp_version, '3.3', '<')) {
400 // translators: WordPress, version number
401 echo '<em>'.sprintf(esc_html__('This feature requires %1$s version %2$s or later', 'upload-larger-plugins'), 'WordPress', '3.3').'</em>';
402 } else {
403 ?>
404 <div id="plupload-upload-ui" class="drag-drop" style="width: 70%;">
405 <div id="drag-drop-area">
406 <div class="drag-drop-inside">
407 <p class="drag-drop-info"><?php esc_html_e('Drop plugin zip here', 'upload-larger-plugins'); ?></p>
408 <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>
409 <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>
410 </div>
411 </div>
412 <div id="filelist">
413 </div>
414 </div>
415 <?php
416 }
417 ?>
418
419 </div>
420
421 <?php
422 /*
423 <div style="display:none;">
424 <form method="post" enctype="multipart/form-data" class="wp-upload-form" action="<?php echo self_admin_url('update.php?action=upload-plugin'); ?>">
425 <?php wp_nonce_field( 'plugin-upload'); ?>
426 <input type="file" id="pluginzip" name="pluginzip" />
427 <?php submit_button( __( 'Install Now' ), 'button', 'install-plugin-submit', false ); ?>
428 </form>
429 </div>
430 */
431 }
432
433 /**
434 * Find out whether we really can write to a particular folder
435 *
436 * @param String $dir - the folder path
437 *
438 * @return Boolean - the result
439 */
440 private function really_is_writable($dir) {
441
442 // phpcs:disable WordPress.WP.AlternativeFunctions -- incorrect filesystem suggestions
443
444 // Suppress warnings, since if the user is dumping warnings to screen, then invalid JavaScript results and the screen breaks.
445 if (!@is_writable($dir)) return false;// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
446 // Found a case - GoDaddy server, Windows, PHP 5.2.17 - where is_writable returned true, but writing failed
447 $rand_file = "$dir/test-".md5(rand().time()).".txt"; // phpcs:ignore WordPress.WP.AlternativeFunctions.rand_rand -- cryptographic randomness not required
448 while (file_exists($rand_file)) {
449 $rand_file = "$dir/test-".md5(rand().time()).".txt"; // phpcs:ignore WordPress.WP.AlternativeFunctions.rand_rand -- cryptographic randomness not required
450 }
451 $ret = @file_put_contents($rand_file, 'testing...');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
452 @unlink($rand_file);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
453 return ($ret > 0);
454
455 // phpcs:enable WordPress.WP.AlternativeFunctions
456
457 }
458
459 public function action_links($links, $file) {
460 if ($file == UPLOADLARGERPLUGINS_SLUG."/".basename(__FILE__)) {
461 array_unshift( $links,
462 '<a href="options-general.php?page=upload_larger_plugins">'.__('Settings', 'upload-larger-plugins').'</a>'
463 );
464 }
465 return $links;
466 }
467
468 }
469