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

450 lines 16.4 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.7
5 Plugin URI: https://wordpress.org/plugins/upload-larger-plugins
6 Description: Allow plugins larger than the PHP-defined limit to be uploaded.
7 Author: David Anderson
8 Donate: https://david.dw-perspective.org.uk/donate
9 Author URI: https://david.dw-perspective.org.uk
10 Text Domain: upload-larger-plugins
11 License: MIT
12 */
13
14 if (!defined('ABSPATH')) die('No direct access');
15
16 // Globals
17 define('UPLOADLARGERPLUGINS_VERSION', '1.7');
18 define('UPLOADLARGERPLUGINS_SLUG', "upload-larger-plugins");
19 define('UPLOADLARGERPLUGINS_DIR', dirname(realpath(__FILE__)));
20 define('UPLOADLARGERPLUGINS_URL', plugins_url('', __FILE__));
21
22 $simba_upload_larger_plugins = new Simba_Upload_Larger_Plugins();
23
24 class Simba_Upload_Larger_Plugins {
25
26 private $upload_dir;
27 private $upload_basedir;
28
29 /**
30 * Plugin constructor
31 */
32 public function __construct() {
33 //add_filter('plugin_action_links', array($this, 'action_links'), 10, 2 );
34 add_action('install_plugins_upload', array($this, 'install_plugins_upload'), 9, 1);
35 add_action('install_plugins_pre_upload', array($this, 'install_plugins_pre_upload'));
36 add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts'));
37 add_action('plugins_loaded', array($this, 'load_translations'));
38 add_action('admin_head', array($this, 'admin_head'));
39 add_action('wp_ajax_ulp_plupload_action', array($this, 'ulp_plupload_action'));
40 add_action('admin_init', array($this, 'admin_init'));
41 // 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
42 // add_filter('upgrader_pre_download', array($this, 'upgrader_pre_download'), 10, 3);
43 // This action allows us to tweak the link URL on WP 5.5+
44 add_filter('install_plugin_overwrite_actions', array($this, 'install_plugin_overwrite_actions'));
45 }
46
47 /**
48 * Called by the WP filter install_plugin_overwrite_actions (WP 5.5+)
49 *
50 * @param Array $install_actions Array of plugin action links.
51 *
52 * @return Array - modified array
53 */
54 public function install_plugin_overwrite_actions($install_actions) {
55
56 if (!empty($install_actions['overwrite_plugin']) && false !== strpos($install_actions['overwrite_plugin'], 'action=upload-plugin&amp;')) {
57
58 if (!empty($_GET['plugincksha1']) && !empty($_GET['overridebd']) && !empty($_GET['package']) && current_user_can('install_plugins')) {
59
60 // WP 5.5 already uses "package=0"; so we have to replace that with the proper name that will work with our upload directory
61 $install_actions['overwrite_plugin'] = str_replace('action=upload-plugin&amp;', 'action=upload-plugin&amp;plugincksha1='.urlencode($_GET['plugincksha1']).'&amp;overridebd='.urlencode($_GET['overridebd']).'&amp;package='.urlencode($_GET['package']).'&amp;', $install_actions['overwrite_plugin']);
62
63 $install_actions['overwrite_plugin'] = str_replace('package=0&amp;', '', $install_actions['overwrite_plugin']);
64 }
65
66 }
67
68 // error_log(print_r($install_actions, true));
69
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;
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']);
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 if (empty($_GET['plugincksha1']) || empty($_GET['overridebd'])) return $result;
123 $upload_dir = untrailingslashit(get_temp_dir());
124
125 // Sanity checks
126 if ($upload_dir != $_GET['overridebd']) return new WP_Error('download_failed', $upgrader->strings['download_failed']);
127 $try_file = $upload_dir.'/'.basename($package);
128
129 if (!file_exists($try_file) || sha1_file($try_file) != $_GET['plugincksha1']) return new WP_Error('download_failed', $upgrader->strings['download_failed']);
130
131 return $try_file;
132 }
133
134 /**
135 * @return Boolean
136 */
137 private function is_our_page_and_authorised() {
138 if (!current_user_can('install_plugins')) return false;
139
140 require(ABSPATH.WPINC.'/version.php');
141
142 global $pagenow;
143 // On WP 4.6, there is no longer an upload 'tab' - it's a slide-down instead
144
145 return ($pagenow != 'plugin-install.php' || (version_compare($wp_version, '4.5.9999', '<') && (!isset($_REQUEST['tab']) || 'upload' != $_REQUEST['tab']))) ? false : true;
146
147 }
148
149 /**
150 * Runs upon the WP action admin_enqueue_scripts
151 */
152 public function admin_enqueue_scripts() {
153
154 if (!$this->is_our_page_and_authorised()) return;
155
156 wp_enqueue_script('ulp-admin-ui', UPLOADLARGERPLUGINS_URL.'/admin.js', array('jquery', 'plupload-all'), '1');
157
158 wp_localize_script('ulp-admin-ui', 'ulplion', array(
159 'notarchive' => __('This file does not appear to be a zip file.', 'upload-larger-plugins'),
160 'notarchive2' => '<p>'.__('This file does not appear to be a zip file.', 'upload-larger-plugins').'</p>',
161 'uploaderror' => __('Upload error:', 'upload-larger-plugins'),
162 'makesure' => __('(make sure that you were trying to upload a zip file', 'upload-larger-plugins'),
163 'uploaderr' => __('Upload error', 'upload-larger-plugins'),
164 'jsonnotunderstood' => __('Error: the server sent us a response (JSON) which we did not understand.', 'upload-larger-plugins'),
165 'error' => __('Error:', 'upload-larger-plugins')
166 ));
167
168 }
169
170 /**
171 * Runs upon the WP action plugins_loaded
172 */
173 public function load_translations() {
174 // Tell WordPress where to find the translations
175 load_plugin_textdomain('upload-larger-plugins', false, basename(dirname(__FILE__)).'/languages/');
176 }
177
178 /**
179 * Used by the WP filter upload_dir
180 *
181 * @param Array $uploads
182 *
183 * @return Array
184 */
185 public function upload_dir($uploads) {
186 if (!empty($this->upload_dir)) $uploads['path'] = $this->upload_dir;
187 if (!empty($this->upload_basedir)) $uploads['basedir'] = $this->upload_basedir;
188 return $uploads;
189 }
190
191 /**
192 * Runs upon the AJAX event ulp_plupload_action
193 */
194 public function ulp_plupload_action() {
195
196 @set_time_limit(900);
197
198 if (!current_user_can('install_plugins')) return;
199 check_ajax_referer('uploadlargerplugins-uploader');
200
201 $upload_dir = untrailingslashit(get_temp_dir());
202 if (!is_writable($upload_dir)) exit;
203 $this->upload_dir = $upload_dir;
204
205 add_filter('upload_dir', array($this, 'upload_dir'));
206 // handle file upload
207
208 $farray = array('test_form' => true, 'action' => 'ulp_plupload_action');
209
210 $farray['test_type'] = false;
211 $farray['ext'] = 'zip';
212 $farray['type'] = 'application/zip';
213
214 // if (isset($_POST['chunks'])) {
215 //
216 // } else {
217 // # Over-write - that's OK.
218 // $farray['unique_filename_callback'] = array($this, 'unique_filename_callback');
219 // }
220
221 $status = wp_handle_upload(
222 $_FILES['async-upload'],
223 $farray
224 );
225 remove_filter('upload_dir', array($this, 'upload_dir'));
226
227 if (isset($status['error'])) {
228 echo json_encode(array('e' => $status['error']));
229 exit;
230 }
231
232 // Should be a no-op
233 $name = basename($_POST['name']);
234
235 // If this was the chunk, then we should instead be concatenating onto the final file
236 if (isset($_POST['chunks']) && isset($_POST['chunk']) && preg_match('/^[0-9]+$/',$_POST['chunk'])) {
237 # 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
238 $final_file = $name;
239 rename($status['file'], $upload_dir.'/'.$final_file.'.'.$_POST['chunk'].'.zip.tmp');
240 $status['file'] = $upload_dir.'/'.$final_file.'.'.$_POST['chunk'].'.zip.tmp';
241
242 // Final chunk? If so, then stich it all back together
243 if ($_POST['chunk'] == $_POST['chunks']-1) {
244 if ($wh = fopen($upload_dir.'/'.$final_file, 'wb')) {
245 for ($i=0 ; $i<$_POST['chunks']; $i++) {
246 $rf = $upload_dir.'/'.$final_file.'.'.$i.'.zip.tmp';
247 if ($rh = fopen($rf, 'rb')) {
248 while ($line = fread($rh, 32768)) fwrite($wh, $line);
249 fclose($rh);
250 @unlink($rf);
251 }
252 }
253 fclose($wh);
254 $status['file'] = $upload_dir.'/'.$final_file;
255 }
256 }
257
258 }
259
260 $response = array();
261 if (!isset($_POST['chunks']) || (isset($_POST['chunk']) && $_POST['chunk'] == $_POST['chunks']-1)) {
262 $file = basename($status['file']);
263 if (!preg_match('/\.zip$/i', $file, $matches)) {
264 @unlink($status['file']);
265 echo json_encode(array('e' => sprintf(__('Error: %s', 'upload-larger-plugins'), __('This file does not appear to be a zip file.', 'upload-larger-plugins'))));
266 exit;
267 }
268 }
269
270 // send the redirect URL
271 $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'])));
272 echo json_encode($response);
273 exit;
274 }
275
276 /**
277 * Runs upon the WP action admin_head
278 */
279 public function admin_head() {
280
281 if (!$this->is_our_page_and_authorised()) return;
282
283 $chunk_size = min(wp_max_upload_size()-1024, 1024*1024*2-1024);
284
285 # 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/
286 # 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
287
288 $plupload_init = array(
289 'runtimes' => 'html5,flash,silverlight,html4',
290 'browse_button' => 'plupload-browse-button',
291 'container' => 'plupload-upload-ui',
292 'drop_element' => 'drag-drop-area',
293 'file_data_name' => 'async-upload',
294 'multiple_queues' => false,
295 'max_file_count' => 1,
296 'max_file_size' => '100Gb',
297 'chunk_size' => $chunk_size.'b',
298 'url' => admin_url('admin-ajax.php'),
299 'filters' => array(array('title' => __('Allowed Files'), 'extensions' => 'zip')),
300 'multipart' => true,
301 'multi_selection' => false,
302 'urlstream_upload' => true,
303 // additional post data to send to our ajax hook
304 'multipart_params' => array(
305 '_ajax_nonce' => wp_create_nonce('uploadlargerplugins-uploader'),
306 'action' => 'ulp_plupload_action'
307 )
308 );
309 // 'flash_swf_url' => includes_url('js/plupload/plupload.flash.swf'),
310 // 'silverlight_xap_url' => includes_url('js/plupload/plupload.silverlight.xap'),
311
312 # WP 3.9 updated to plupload 2.0 - https://core.trac.wordpress.org/ticket/25663
313 if (is_file(ABSPATH.'wp-includes/js/plupload/Moxie.swf')) {
314 $plupload_init['flash_swf_url'] = includes_url('js/plupload/Moxie.swf');
315 } else {
316 $plupload_init['flash_swf_url'] = includes_url('js/plupload/plupload.flash.swf');
317 }
318
319 if (is_file(ABSPATH.'wp-includes/js/plupload/Moxie.xap')) {
320 $plupload_init['silverlight_xap_url'] = includes_url('js/plupload/Moxie.xap');
321 } else {
322 $plupload_init['silverlight_xap_url'] = includes_url('js/plupload/plupload.silverlight.swf');
323 }
324
325 ?><script type="text/javascript">
326 var ulp_plupload_config=<?php echo json_encode($plupload_init); ?>;
327 </script>
328 <style type="text/css">
329 .drag-drop #drag-drop-area {
330 border: 4px dashed #ddd;
331 height: 200px;
332 }
333 #filelist {
334 width: 100%;
335 }
336 #filelist .file {
337 padding: 5px;
338 background: #ececec;
339 border: solid 1px #ccc;
340 margin: 4px 0;
341 }
342 #filelist .fileprogress {
343 width: 0%;
344 background: #f6a828;
345 height: 5px;
346 }
347 </style>
348 <?php
349
350 }
351
352 public function install_plugins_pre_upload() {
353 // Unhook the default uploader (works on WP < 4.6 only)
354 remove_action('install_plugins_upload', 'install_plugins_upload');
355 }
356
357 /**
358 * If hooked, runs upon the WP action install_plugins_upload
359 *
360 */
361 public function install_plugins_upload() {
362
363 echo '<div class="upload-plugin">';
364
365 require(ABSPATH.WPINC.'/version.php');
366
367 if (version_compare($wp_version, '4.5.9999', '<')) { ?>
368
369 <!-- Upload form from Upload Larger Plugins -->
370 <h4><?php _e('Install a plugin in .zip format'); ?></h4>
371
372 <?php } ?>
373
374 <p class="install-help" style="text-align:left; margin-bottom: 6px;">
375
376 <?php
377
378 $upload_dir = untrailingslashit(get_temp_dir());
379 if (!$this->really_is_writable($upload_dir)) {
380 echo '<strong>'.sprintf(__("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>';
381 } else {
382 _e('If you have a plugin in a .zip format, you may install it by uploading it here.').'<br>';
383 }
384
385 echo '</p>';
386
387 if (version_compare($wp_version, '3.3', '<')) {
388 echo '<em>'.sprintf(__('This feature requires %s version %s or later', 'upload-larger-plugins'), 'WordPress', '3.3').'</em>';
389 } else {
390 ?>
391 <div id="plupload-upload-ui" class="drag-drop" style="width: 70%;">
392 <div id="drag-drop-area">
393 <div class="drag-drop-inside">
394 <p class="drag-drop-info"><?php _e('Drop plugin zip here', 'upload-larger-plugins'); ?></p>
395 <p><?php _ex('or', 'Uploader: Drop plugin zip here - or - Select File'); ?></p>
396 <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>
397 </div>
398 </div>
399 <div id="filelist">
400 </div>
401 </div>
402 <?php
403 }
404 ?>
405
406 </div>
407
408 <?php
409 /*
410 <div style="display:none;">
411 <form method="post" enctype="multipart/form-data" class="wp-upload-form" action="<?php echo self_admin_url('update.php?action=upload-plugin'); ?>">
412 <?php wp_nonce_field( 'plugin-upload'); ?>
413 <input type="file" id="pluginzip" name="pluginzip" />
414 <?php submit_button( __( 'Install Now' ), 'button', 'install-plugin-submit', false ); ?>
415 </form>
416 </div>
417 */
418 }
419
420 /**
421 * Find out whether we really can write to a particular folder
422 *
423 * @param String $dir - the folder path
424 *
425 * @return Boolean - the result
426 */
427 private function really_is_writable($dir) {
428 // Suppress warnings, since if the user is dumping warnings to screen, then invalid JavaScript results and the screen breaks.
429 if (!@is_writable($dir)) return false;// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
430 // Found a case - GoDaddy server, Windows, PHP 5.2.17 - where is_writable returned true, but writing failed
431 $rand_file = "$dir/test-".md5(rand().time()).".txt";
432 while (file_exists($rand_file)) {
433 $rand_file = "$dir/test-".md5(rand().time()).".txt";
434 }
435 $ret = @file_put_contents($rand_file, 'testing...');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
436 @unlink($rand_file);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
437 return ($ret > 0);
438 }
439
440 public function action_links($links, $file) {
441 if ($file == UPLOADLARGERPLUGINS_SLUG."/".basename(__FILE__)) {
442 array_unshift( $links,
443 '<a href="options-general.php?page=upload_larger_plugins">'.__('Settings', 'upload-larger-plugins').'</a>'
444 );
445 }
446 return $links;
447 }
448
449 }
450