Tuxedo Big File Uploads - Version 2.0.2

Version Description

Download this release

Release Info

Developer uglyrobot
Plugin Icon 128x128 Tuxedo Big File Uploads
Version 2.0.2
Comparing to
See all releases

Code changes from version 2.0.1 to 2.0.2

assets/js/block-notice.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { __ } = wp.i18n;
2
+
3
+ function BFUGettextFilter( translation, text, domain ) {
4
+ if ( text === 'This file exceeds the maximum upload size for this site.' ) {
5
+ return translation + ' ' + __('To upload larger files use the upload tab via the Media Library link.', 'tuxedo-big-file-uploads');
6
+ }
7
+ return translation;
8
+ }
9
+
10
+ wp.hooks.addFilter(
11
+ 'i18n.gettext',
12
+ 'infinite-uploads/bfu/upload-notice',
13
+ BFUGettextFilter
14
+ );
classes/class-review-notice.php ADDED
@@ -0,0 +1,518 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Helper library to as for a wp.org review.
4
+ *
5
+ * Review notice will be shown using WordPress admin notices after
6
+ * a specified time of plugin/theme use.
7
+ * This is mainly developed to reuse on my plugins but anyone can
8
+ * use it as a library.
9
+ *
10
+ * @author Joel James <me@joelsays.com>
11
+ * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
12
+ * @copyright Copyright (c) 2021, Joel James
13
+ * @link https://github.com/duckdev/wp-review-notice/
14
+ * @subpackage Pages
15
+ */
16
+
17
+ // Should be called only by WordPress.
18
+ defined( 'WPINC' ) || die;
19
+
20
+ /**
21
+ * Class Notice.
22
+ *
23
+ * Main class that handles review notice.
24
+ */
25
+ class Big_File_Uploads_Review_Notice {
26
+
27
+ /**
28
+ * Prefix for all options and keys.
29
+ *
30
+ * Override only when required.
31
+ *
32
+ * @var string $prefix
33
+ *
34
+ * @since 1.0.0
35
+ */
36
+ private $prefix = '';
37
+
38
+ /**
39
+ * Plugin name to show in review.
40
+ *
41
+ * @var string $name
42
+ *
43
+ * @since 1.0.0
44
+ */
45
+ private $name = '';
46
+
47
+ /**
48
+ * Plugin slug in https://wordpress.org/plugins/{slug}.
49
+ *
50
+ * @var string $slug
51
+ *
52
+ * @since 1.0.0
53
+ */
54
+ private $slug = '';
55
+
56
+ /**
57
+ * Minimum no. of days to show the notice after.
58
+ *
59
+ * Currently we support only days.
60
+ *
61
+ * @var int $days
62
+ *
63
+ * @since 1.0.0
64
+ */
65
+ private $days = 7;
66
+
67
+ /**
68
+ * WP admin page screen IOs to show notice in.
69
+ *
70
+ * If it's empty, we will show it on all pages.
71
+ *
72
+ * @var array $screens
73
+ *
74
+ * @since 1.0.0
75
+ */
76
+ private $screens = array();
77
+
78
+ /**
79
+ * Notice classes to set additional classes.
80
+ *
81
+ * By default we use WP info notice class.
82
+ *
83
+ * @var array $classes
84
+ *
85
+ * @since 1.0.0
86
+ */
87
+ private $classes = array( 'notice', 'notice-info' );
88
+
89
+ /**
90
+ * Minimum capability for the user to see and dismiss notice.
91
+ *
92
+ * @see https://wordpress.org/support/article/roles-and-capabilities/
93
+ *
94
+ * @var string $cap
95
+ *
96
+ * @since 1.0.0
97
+ */
98
+ private $cap = 'manage_options';
99
+
100
+ /**
101
+ * Text domain for translations.
102
+ *
103
+ * @var string $domain
104
+ *
105
+ * @since 1.0.0
106
+ */
107
+ private $domain = '';
108
+
109
+ /**
110
+ * Create new notice instance with provided options.
111
+ *
112
+ * Do not use any hooks to run these functions because
113
+ * we don't know in which hook and priority everyone is
114
+ * going to initialize this notice.
115
+ *
116
+ * @param string $slug WP.org slug for plugin.
117
+ * @param string $name Name of plugin.
118
+ * @param array $options Array of options (@see Big_File_Uploads_Review_Notice::get()).
119
+ *
120
+ * @since 4.0.0
121
+ * @access private
122
+ *
123
+ * @return void
124
+ */
125
+ private function __construct( $slug, $name, array $options ) {
126
+ // Only for admin side.
127
+ if ( is_admin() ) {
128
+ // Setup options.
129
+ $this->setup( $slug, $name, $options );
130
+
131
+ // Process actions.
132
+ $this->actions();
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Create and get new notice instance.
138
+ *
139
+ * Use this to setup new plugin notice to avoid multiple instances
140
+ * of same plugin notice.
141
+ * If you provide wrong slug, please note we will still link to the
142
+ * wrong wp.org plugin page for reviews.
143
+ *
144
+ * @param string $slug WP.org slug for plugin.
145
+ * @param string $name Name of plugin.
146
+ * @param array $options {
147
+ * Array of options.
148
+ *
149
+ * @type int $days No. of days after the notice is shown.
150
+ * @type array $screens WP screen IDs to show notice.
151
+ * Leave empty to show in all pages (not recommended).
152
+ * @type string $cap User capability to show notice.
153
+ * Make sure to use proper capability for multisite.
154
+ * @type array $classes Additional class names for notice.
155
+ * @type string $domain Text domain for translations.
156
+ * @type string $prefix To override default option prefix.
157
+ * }
158
+ *
159
+ * @since 1.0.0
160
+ * @access public
161
+ *
162
+ * @return Big_File_Uploads_Review_Notice
163
+ */
164
+ public static function get( $slug, $name, array $options ) {
165
+ static $notices = array();
166
+
167
+ // Create new instance if not already created.
168
+ if ( ! isset( $notices[ $slug ] ) || ! $notices[ $slug ] instanceof Big_File_Uploads_Review_Notice ) {
169
+ $notices[ $slug ] = new self( $slug, $name, $options );
170
+ }
171
+
172
+ return $notices[ $slug ];
173
+ }
174
+
175
+ /**
176
+ * Render the review notice.
177
+ *
178
+ * Review notice will be rendered only if all these conditions met:
179
+ * > Current screen is an allowed screen (@see Big_File_Uploads_Review_Notice::in_screen())
180
+ * > Current user has the required capability (@see Big_File_Uploads_Review_Notice::is_capable())
181
+ * > It's time to show the notice (@see Big_File_Uploads_Review_Notice::is_time())
182
+ * > User has not dismissed the notice (@see Big_File_Uploads_Review_Notice::is_dismissed())
183
+ *
184
+ * @since 1.0.0
185
+ * @access public
186
+ *
187
+ * @return void
188
+ */
189
+ public function render() {
190
+ // Check conditions.
191
+ if ( ! $this->can_show() ) {
192
+ return;
193
+ }
194
+
195
+ // Get current user data.
196
+ $current_user = wp_get_current_user();
197
+ // Make sure we have name.
198
+ $name = empty( $current_user->display_name ) ? __( 'friend', $this->domain ) : ucwords( $current_user->display_name );
199
+ ?>
200
+ <style>
201
+ #bfu-reviews-notice {
202
+ display: flex;
203
+ justify-content: center;
204
+ align-items: center;
205
+ }
206
+ #bfu-reviews-notice img {
207
+ display: block;
208
+ float: left;
209
+ margin: 10px 0;
210
+ }
211
+ #bfu-reviews-notice div {
212
+ margin-left: 10px;
213
+ }
214
+ #bfu-review-buttons a {
215
+ background-color: rgba(38, 169, 224, 1);
216
+ color: white;
217
+ border-radius: 5px;
218
+ border: 2px solid rgba(38, 169, 224, 1);
219
+ display: inline-block;
220
+ padding: .5em 1em;
221
+ text-decoration: none;
222
+ margin-right: 10px;
223
+ font-weight: bold;
224
+ }
225
+ #bfu-review-buttons a:hover, #bfu-review-buttons a:active, #bfu-review-buttons a:focus {
226
+ background-color: rgba(38, 169, 224, 0.7) !important;
227
+ border-color: transparent;
228
+ color: white;
229
+ }
230
+
231
+ #bfu-review-buttons .bfu-btn-later {
232
+ background-color: transparent;
233
+ color: rgba(38, 169, 224, 1);
234
+ font-weight: normal;
235
+ }
236
+ .bfu-btn-dismiss {
237
+ color: #EE7C1E;
238
+ }
239
+ .bfu-btn-dismiss:hover, .bfu-btn-dismiss:active, .bfu-btn-dismiss:focus {
240
+ color: rgba(238, 124, 30, 0.7) !important;
241
+ }
242
+ </style>
243
+ <div id="bfu-reviews-notice" class="<?php echo esc_attr( $this->get_classes() ); ?>">
244
+ <img src="<?php echo esc_url( plugins_url( '/assets/img/bfu-logo-sm.png', dirname( __FILE__ ) ) ); ?>" alt="Big File Uploads Logo" height="100" />
245
+ <div>
246
+ <p>
247
+ <?php
248
+ printf(
249
+ // translators: %1$s Current user's name, %2$s Plugin name, %3$d.
250
+ esc_html__( 'Hey %1$s, %2$s has been helping you upload large files for a while now – that’s awesome! If you love it, would you rate it? Giving your favorite free plugins a 5-star rating helps developers like us maintain and build free tools. Thank you for the support!', $this->domain ),
251
+ esc_html( $name ),
252
+ '<strong>' . esc_html( $this->name ) . '</strong>'
253
+ );
254
+ ?>
255
+ </p>
256
+ <p id="bfu-review-buttons">
257
+ <a href="https://wordpress.org/support/plugin/<?php echo esc_html( $this->slug ); ?>/reviews/#new-post" target="_blank">
258
+ <?php esc_html_e( 'You deserve it!', $this->domain ); ?>
259
+ </a>
260
+ <a class="bfu-btn-later" href="<?php echo esc_url( add_query_arg( $this->key( 'action' ), 'later' ) ); ?>">
261
+ <?php esc_html_e( 'Maybe later', $this->domain ); ?>
262
+ </a>
263
+ </p>
264
+ <p>
265
+ <a class="bfu-btn-dismiss" href="<?php echo esc_url( add_query_arg( $this->key( 'action' ), 'dismiss' ) ); ?>">
266
+ <?php esc_html_e( 'Leave me alone', $this->domain ); ?>
267
+ </a>
268
+ </p>
269
+ </div>
270
+ </div>
271
+ <?php
272
+ }
273
+
274
+ /**
275
+ * Check if it's time to show the notice.
276
+ *
277
+ * Based on the day provided, we will check if the current
278
+ * timestamp exceeded or reached the notice time.
279
+ *
280
+ * @since 1.0.0
281
+ * @access protected
282
+ * @uses get_site_option()
283
+ * @uses update_site_option()
284
+ *
285
+ * @return bool
286
+ */
287
+ protected function is_time() {
288
+ // Get the notice time.
289
+ $time = get_site_option( $this->key( 'time' ) );
290
+
291
+ // If not set, set now and bail.
292
+ if ( empty( $time ) ) {
293
+ $time = time() + ( $this->days * DAY_IN_SECONDS );
294
+ // Set to future.
295
+ update_site_option( $this->key( 'time' ), $time );
296
+
297
+ return true;
298
+ }
299
+
300
+ // Check if time passed or reached.
301
+ return (int) $time <= time();
302
+ }
303
+
304
+ /**
305
+ * Check if the notice is already dismissed.
306
+ *
307
+ * If a user has dismissed the notice, do not show
308
+ * notice to the current user again.
309
+ * We store the flag in current user's meta data.
310
+ *
311
+ * @since 1.0.0
312
+ * @access protected
313
+ * @uses get_user_meta()
314
+ *
315
+ * @return bool
316
+ */
317
+ protected function is_dismissed() {
318
+ // Get current user.
319
+ $current_user = wp_get_current_user();
320
+
321
+ // Check if current item is dismissed.
322
+ return (bool) get_user_meta(
323
+ $current_user->ID,
324
+ $this->key( 'dismissed' ),
325
+ true
326
+ );
327
+ }
328
+
329
+ /**
330
+ * Check if current user has the capability.
331
+ *
332
+ * Before showing and processing the notice actions,
333
+ * current user should have the capability to do so.
334
+ *
335
+ * @since 1.0.0
336
+ * @uses current_user_can()
337
+ * @access protected
338
+ *
339
+ * @return bool
340
+ */
341
+ protected function is_capable() {
342
+ return current_user_can( $this->cap );
343
+ }
344
+
345
+ /**
346
+ * Check if the current screen is allowed.
347
+ *
348
+ * Make sure the current page's screen ID is in
349
+ * allowed IDs list before showing a notice.
350
+ * If no screen ID is set, we will allow it in
351
+ * all pages (not recommended).
352
+ *
353
+ * @since 1.0.0
354
+ * @access protected
355
+ * @uses get_current_screen()
356
+ *
357
+ * @return bool
358
+ */
359
+ protected function in_screen() {
360
+ // If not screen ID is set, show everywhere.
361
+ if ( empty( $this->screens ) ) {
362
+ return true;
363
+ }
364
+
365
+ // Get current screen.
366
+ $screen = get_current_screen();
367
+
368
+ // Check if current screen id is allowed.
369
+ return ! empty( $screen->id ) && in_array( $screen->id, $this->screens, true );
370
+ }
371
+
372
+ /**
373
+ * Get the class names for notice div.
374
+ *
375
+ * Notice is using WordPress admin notices info notice styles.
376
+ * You can pass additional class names to customize it for your
377
+ * requirements in `classes` option when creating notice instance.
378
+ *
379
+ * @see https://developer.wordpress.org/reference/hooks/admin_notices/
380
+ *
381
+ * @since 1.0.0
382
+ * @access protected
383
+ *
384
+ * @return string
385
+ */
386
+ protected function get_classes() {
387
+ // Required classes.
388
+ $classes = array( 'notice', 'notice-info' );
389
+
390
+ // Add extra classes.
391
+ if ( ! empty( $this->classes ) && is_array( $this->classes ) ) {
392
+ $classes = array_merge( $classes, $this->classes );
393
+ $classes = array_unique( $classes );
394
+ }
395
+
396
+ return implode( ' ', $classes );
397
+ }
398
+
399
+ /**
400
+ * Check if we can show the notice.
401
+ *
402
+ * > Current screen is an allowed screen (@see Big_File_Uploads_Review_Notice::in_screen())
403
+ * > Current user has the required capability (@see Big_File_Uploads_Review_Notice::is_capable())
404
+ * > It's time to show the notice (@see Big_File_Uploads_Review_Notice::is_time())
405
+ * > User has not dismissed the notice (@see Big_File_Uploads_Review_Notice::is_dismissed())
406
+ *
407
+ * @since 1.0.0
408
+ * @access protected
409
+ *
410
+ * @return bool
411
+ */
412
+ protected function can_show() {
413
+ return (
414
+ $this->in_screen() &&
415
+ $this->is_capable() &&
416
+ $this->is_time() &&
417
+ ! $this->is_dismissed()
418
+ );
419
+ }
420
+
421
+ /**
422
+ * Process the notice actions.
423
+ *
424
+ * If current user is capable process actions.
425
+ * > Later: Extend the time to show the notice.
426
+ * > Dismiss: Hide the notice to current user.
427
+ *
428
+ * @since 1.0.0
429
+ * @access protected
430
+ *
431
+ * @return void
432
+ */
433
+ protected function actions() {
434
+ // Only if required.
435
+ if ( ! $this->in_screen() || ! $this->is_capable() ) {
436
+ return;
437
+ }
438
+
439
+ // Get the current review action.
440
+ $action = filter_input( INPUT_GET, $this->key( 'action' ), FILTER_SANITIZE_STRING );
441
+ do_action( 'qm/debug', $action );
442
+ switch ( $action ) {
443
+ case 'later':
444
+ // Let's show after 2 times of days.
445
+ $time = time() + ( $this->days * DAY_IN_SECONDS * 2 );
446
+ update_site_option( $this->key( 'time' ), $time );
447
+ break;
448
+ case 'dismiss':
449
+ // Do not show again to this user.
450
+ update_user_meta(
451
+ get_current_user_id(),
452
+ $this->key( 'dismissed' ),
453
+ true
454
+ );
455
+ break;
456
+ }
457
+ }
458
+
459
+ /**
460
+ * Setup notice options to initialize class.
461
+ *
462
+ * Make sure the required options are set before
463
+ * initializing the class.
464
+ *
465
+ * @param string $slug WP.org slug for plugin.
466
+ * @param string $name Name of plugin.
467
+ * @param array $options Array of options (@see Big_File_Uploads_Review_Notice::get()).
468
+ *
469
+ * @since 1.0.0
470
+ * @access private
471
+ *
472
+ * @return void
473
+ */
474
+ private function setup( $slug, $name, array $options ) {
475
+ // Plugin name is required.
476
+ if ( empty( $name ) || empty( $slug ) ) {
477
+ return;
478
+ }
479
+
480
+ // Merge options.
481
+ $options = wp_parse_args(
482
+ $options,
483
+ array(
484
+ 'days' => 7,
485
+ 'screens' => array(),
486
+ 'cap' => 'manage_options',
487
+ 'classes' => array(),
488
+ 'domain' => 'tuxedo-big-file-uploads',
489
+ )
490
+ );
491
+
492
+ // Set options.
493
+ $this->slug = (string) $slug;
494
+ $this->name = (string) $name;
495
+ $this->cap = (string) $options['cap'];
496
+ $this->days = (int) $options['days'];
497
+ $this->screens = (array) $options['screens'];
498
+ $this->classes = (array) $options['classes'];
499
+ $this->domain = (string) $options['domain'];
500
+ $this->prefix = str_replace( '-', '_', $this->slug );
501
+ }
502
+
503
+ /**
504
+ * Create key by prefixing option name.
505
+ *
506
+ * Use this to create proper key for options.
507
+ *
508
+ * @param string $key Key.
509
+ *
510
+ * @since 1.0.0
511
+ * @access protected
512
+ *
513
+ * @return string
514
+ */
515
+ private function key( $key ) {
516
+ return $this->prefix . '_reviews_' . $key;
517
+ }
518
+ }
readme.txt CHANGED
@@ -1,15 +1,15 @@
1
  === Big File Uploads - Increase Maximum File Upload Size ===
2
 
3
  Plugin Name: Big File Uploads - Increase Maximum File Upload Size
4
- Version: 2.0.1
5
  Author: Infinite Uploads
6
  Author URI: https://infiniteuploads.com/?utm_source=wordpress.org&utm_medium=readme&utm_campaign=bfu_readme&utm_term=author_uri
7
  Contributors: uglyrobot, jdailey, andtrev
8
  Tags: increase file size limit, increase upload limit, max upload file size, post max size, upload limit, file upload, files uploader, ftp, video uploader, AJAX
9
  Requires at least: 5.3
10
- Tested up to: 5.9
11
- Stable tag: 2.0.1
12
- Requires PHP: 5.5
13
  License: GPLv2
14
  License URI: https://www.gnu.org/licenses/gpl-2.0.html
15
 
@@ -111,6 +111,14 @@ No. [Infinite Uploads](https://wordpress.org/plugins/infinite-uploads/) is an op
111
 
112
  == Changelog ==
113
 
 
 
 
 
 
 
 
 
114
  2.0.1 - 2021-6-30
115
  ----------------------------------------------------------------------
116
  - Bug fix: Sometimes the upgrade notice showed in wrong places in the admin area. props Nick H.
1
  === Big File Uploads - Increase Maximum File Upload Size ===
2
 
3
  Plugin Name: Big File Uploads - Increase Maximum File Upload Size
4
+ Version: 2.0.2
5
  Author: Infinite Uploads
6
  Author URI: https://infiniteuploads.com/?utm_source=wordpress.org&utm_medium=readme&utm_campaign=bfu_readme&utm_term=author_uri
7
  Contributors: uglyrobot, jdailey, andtrev
8
  Tags: increase file size limit, increase upload limit, max upload file size, post max size, upload limit, file upload, files uploader, ftp, video uploader, AJAX
9
  Requires at least: 5.3
10
+ Tested up to: 6.0
11
+ Stable tag: 2.0.2
12
+ Requires PHP: 5.6
13
  License: GPLv2
14
  License URI: https://www.gnu.org/licenses/gpl-2.0.html
15
 
111
 
112
  == Changelog ==
113
 
114
+ 2.0.2 - 2022-2-03
115
+ ----------------------------------------------------------------------
116
+ - Fix: Conflicts with some theme builders like Themify.
117
+ - Fix: Fail with error message instead of showing success with partially uploaded big files missing chunks.
118
+ - Optimize default chunk size to limit requests.
119
+ - Add a review on wordpress.org timed notice
120
+ - Smoother Gutenberg editor support with a custom error message directing to use the media library uploader.
121
+
122
  2.0.1 - 2021-6-30
123
  ----------------------------------------------------------------------
124
  - Bug fix: Sometimes the upgrade notice showed in wrong places in the admin area. props Nick H.
tuxedo_big_file_uploads.php CHANGED
@@ -2,7 +2,7 @@
2
  /**
3
  * Plugin Name: Big File Uploads
4
  * Description: Enable large file uploads in the built-in WordPress media uploader via multipart uploads, and set maximum upload file size to any value based on user role. Uploads can be as large as available disk space allows.
5
- * Version: 2.0.1
6
  * Author: Infinite Uploads
7
  * Author URI: https://infiniteuploads.com/?utm_source=bfu_plugin&utm_medium=plugin&utm_campaign=bfu_plugin&utm_content=meta
8
  * Network: true
@@ -34,7 +34,7 @@ if ( ! defined( 'ABSPATH' ) ) {
34
  die();
35
  }
36
 
37
- define( 'BIG_FILE_UPLOADS_VERSION', '2.0.1' );
38
 
39
  /**
40
  * Big File Uploads manager class.
@@ -100,6 +100,7 @@ class BigFileUploads {
100
  $this->max_upload_size = wp_max_upload_size();
101
 
102
  add_action( 'plugins_loaded', array( $this, 'load_textdomain' ) );
 
103
  add_filter( 'plupload_init', array( $this, 'filter_plupload_settings' ) );
104
  add_filter( 'upload_post_params', array( $this, 'filter_plupload_params' ) );
105
  add_filter( 'plupload_default_settings', array( $this, 'filter_plupload_settings' ) );
@@ -108,6 +109,9 @@ class BigFileUploads {
108
  //add_filter( 'ext2type', array( $this, 'filter_ext_types' ) );
109
  add_action( 'wp_ajax_bfu_chunker', array( $this, 'ajax_chunk_receiver' ) );
110
  add_action( 'post-upload-ui', array( $this, 'upload_output' ) );
 
 
 
111
 
112
  //single site
113
  add_action( 'admin_menu', [ &$this, 'admin_menu' ] );
@@ -165,11 +169,19 @@ class BigFileUploads {
165
  */
166
  public function filter_plupload_settings( $plupload_settings ) {
167
 
 
 
 
 
 
 
 
168
  if ( ! defined( 'BIG_FILE_UPLOADS_CHUNK_SIZE_KB' ) ) {
169
- define( 'BIG_FILE_UPLOADS_CHUNK_SIZE_KB', 2048 );
170
  }
 
171
  if ( ! defined( 'BIG_FILE_UPLOADS_RETRIES' ) ) {
172
- define( 'BIG_FILE_UPLOADS_RETRIES', 3 );
173
  }
174
 
175
  $plupload_settings['url'] = admin_url( 'admin-ajax.php' );
@@ -193,6 +205,32 @@ class BigFileUploads {
193
  load_plugin_textdomain( $domain, false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
194
  }
195
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  /**
197
  * Return max upload size.
198
  *
@@ -224,18 +262,60 @@ class BigFileUploads {
224
  }
225
 
226
  /**
227
- * Output html on the upload form.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  *
229
  * @since 2.0
230
  */
231
  public function upload_output() {
232
- if ( ! current_user_can( $this->capability ) ) {
 
233
  return;
234
  }
235
  ?>
236
  <script type="text/javascript">
237
- jQuery(".max-upload-size").append(' <small><a style="text-decoration:none;" href="<?php echo esc_url( $this->settings_url() ); ?>"><?php esc_html_e( 'Change', 'tuxedo-big-file-uploads' ); ?></a></small>');
 
 
 
 
 
 
 
 
 
 
 
238
 
 
239
  <?php
240
  $dismissed = get_user_option( 'bfu_notice_dismissed', get_current_user_id() );
241
  if ( ! class_exists( 'Infinite_Uploads' ) && ! $dismissed ) {
@@ -374,6 +454,10 @@ class BigFileUploads {
374
  * Based on code by Davit Barbakadze
375
  * https://gist.github.com/jayarjo/5846636
376
  *
 
 
 
 
377
  * @since 1.2.0
378
  */
379
  public function ajax_chunk_receiver() {
@@ -386,51 +470,72 @@ class BigFileUploads {
386
 
387
  /** Authenticate user. */
388
  if ( ! is_user_logged_in() || ! current_user_can( 'upload_files' ) ) {
389
- die();
390
  }
391
  check_admin_referer( 'media-form' );
392
 
393
  /** Check and get file chunks. */
394
- $chunk = isset( $_REQUEST['chunk'] ) ? intval( $_REQUEST['chunk'] ) : 0;
 
395
  $chunks = isset( $_REQUEST['chunks'] ) ? intval( $_REQUEST['chunks'] ) : 0;
396
 
397
  /** Get file name and path + name. */
398
  $fileName = isset( $_REQUEST['name'] ) ? $_REQUEST['name'] : $_FILES['async-upload']['name'];
399
- $filePath = dirname( $_FILES['async-upload']['tmp_name'] ) . '/' . md5( $fileName );
400
 
401
  $tuxbfu_max_upload_size = $this->get_upload_limit();
402
- if ( file_exists( "{$filePath}.part" ) && filesize( "{$filePath}.part" ) + filesize( $_FILES['async-upload']['tmp_name'] ) > $tuxbfu_max_upload_size ) {
403
 
404
  if ( ! $chunks || $chunk == $chunks - 1 ) {
405
- @unlink( "{$filePath}.part" );
406
 
407
  if ( ! isset( $_REQUEST['short'] ) || ! isset( $_REQUEST['type'] ) ) {
408
-
409
  echo wp_json_encode( array(
410
  'success' => false,
411
  'data' => array(
412
  'message' => __( 'The file size has exceeded the maximum file size setting.', 'tuxedo-big-file-uploads' ),
413
- 'filename' => $_FILES['async-upload']['name'],
414
  ),
415
  ) );
416
  wp_die();
417
-
418
  } else {
419
-
420
- echo '<div class="error-div error">
421
- <a class="dismiss" href="#" onclick="jQuery(this).parents(\'div.media-item\').slideUp(200, function(){jQuery(this).remove();});">' . __( 'Dismiss' ) . '</a>
422
- <strong>' . sprintf( __( '&#8220;%s&#8221; has failed to upload.' ), esc_html( $_FILES['async-upload']['name'] ) ) . '<br />' . __( 'The file size has exceeded the maximum file size setting.', 'tuxedo-big-file-uploads' ) . '</strong></div>';
423
-
 
 
 
 
 
 
 
 
 
 
424
  }
425
 
426
  }
427
 
428
  die();
 
429
 
 
 
 
 
430
  }
431
 
432
  /** Open temp file. */
433
- $out = @fopen( "{$filePath}.part", $chunk == 0 ? 'wb' : 'ab' );
 
 
 
 
 
 
 
434
  if ( $out ) {
435
 
436
  /** Read binary input stream and append it to temp file. */
@@ -444,25 +549,81 @@ class BigFileUploads {
444
  /** Failed to open input stream. */
445
  /** Attempt to clean up unfinished output. */
446
  @fclose( $out );
447
- @unlink( "{$filePath}.part" );
448
- die();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
449
  }
450
 
451
  @fclose( $in );
452
  @fclose( $out );
453
-
454
  @unlink( $_FILES['async-upload']['tmp_name'] );
455
-
456
  } else {
457
  /** Failed to open output stream. */
458
- die();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
459
  }
460
 
461
  /** Check if file has finished uploading all parts. */
462
  if ( ! $chunks || $chunk == $chunks - 1 ) {
463
 
464
  /** Recreate upload in $_FILES global and pass off to WordPress. */
465
- rename( "{$filePath}.part", $_FILES['async-upload']['tmp_name'] );
466
  $_FILES['async-upload']['name'] = $fileName;
467
  $_FILES['async-upload']['size'] = filesize( $_FILES['async-upload']['tmp_name'] );
468
  //$wp_filetype = wp_check_filetype_and_ext( $_FILES['async-upload']['tmp_name'], $_FILES['async-upload']['tmp_name'] );
@@ -488,27 +649,42 @@ class BigFileUploads {
488
 
489
  $id = media_handle_upload( 'async-upload', $post_id );
490
  if ( is_wp_error( $id ) ) {
491
- echo '<div class="error-div error">
492
- <a class="dismiss" href="#" onclick="jQuery(this).parents(\'div.media-item\').slideUp(200, function(){jQuery(this).remove();});">' . __( 'Dismiss' ) . '</a>
493
- <strong>' . sprintf( __( '&#8220;%s&#8221; has failed to upload.' ), esc_html( $_FILES['async-upload']['name'] ) ) . '</strong><br />' .
494
- esc_html( $id->get_error_message() ) . '</div>';
 
 
 
 
 
 
 
 
 
495
  exit;
496
  }
497
 
498
- if ( isset( $_REQUEST['short'] ) && $_REQUEST['short'] ) {
499
  // Short form response - attachment ID only.
500
  echo $id;
501
- } elseif ( isset( $_REQUEST['type'] ) ) {
502
- // Long form response - big chunk o html.
503
  $type = $_REQUEST['type'];
504
 
505
  /**
506
- * Filter the returned ID of an uploaded attachment.
 
 
 
 
507
  *
508
- * The dynamic portion of the hook name, `$type`, refers to the attachment type,
509
- * such as 'image', 'audio', 'video', 'file', etc.
 
 
510
  *
511
- * @since 1.2.0
512
  *
513
  * @param int $id Uploaded attachment ID.
514
  */
2
  /**
3
  * Plugin Name: Big File Uploads
4
  * Description: Enable large file uploads in the built-in WordPress media uploader via multipart uploads, and set maximum upload file size to any value based on user role. Uploads can be as large as available disk space allows.
5
+ * Version: 2.0.2
6
  * Author: Infinite Uploads
7
  * Author URI: https://infiniteuploads.com/?utm_source=bfu_plugin&utm_medium=plugin&utm_campaign=bfu_plugin&utm_content=meta
8
  * Network: true
34
  die();
35
  }
36
 
37
+ define( 'BIG_FILE_UPLOADS_VERSION', '2.0.2' );
38
 
39
  /**
40
  * Big File Uploads manager class.
100
  $this->max_upload_size = wp_max_upload_size();
101
 
102
  add_action( 'plugins_loaded', array( $this, 'load_textdomain' ) );
103
+ add_action( 'admin_notices', array( $this, 'init_review_notice' ) );
104
  add_filter( 'plupload_init', array( $this, 'filter_plupload_settings' ) );
105
  add_filter( 'upload_post_params', array( $this, 'filter_plupload_params' ) );
106
  add_filter( 'plupload_default_settings', array( $this, 'filter_plupload_settings' ) );
109
  //add_filter( 'ext2type', array( $this, 'filter_ext_types' ) );
110
  add_action( 'wp_ajax_bfu_chunker', array( $this, 'ajax_chunk_receiver' ) );
111
  add_action( 'post-upload-ui', array( $this, 'upload_output' ) );
112
+ add_action( 'enqueue_block_editor_assets', array( $this, 'gutenberg_notice' ) );
113
+ add_filter( 'block_editor_settings_all', array( $this, 'gutenberg_size_filter' ) );
114
+
115
 
116
  //single site
117
  add_action( 'admin_menu', [ &$this, 'admin_menu' ] );
169
  */
170
  public function filter_plupload_settings( $plupload_settings ) {
171
 
172
+ $max_chunk = ( MB_IN_BYTES * 20 ); //20MB max chunk size (to avoid timeouts)
173
+ if ( $max_chunk > $this->max_upload_size ) {
174
+ $default_chunk = ( $this->max_upload_size * 0.8 ) / KB_IN_BYTES;
175
+ } else {
176
+ $default_chunk = $max_chunk / KB_IN_BYTES;
177
+ }
178
+ //define( 'BIG_FILE_UPLOADS_CHUNK_SIZE_KB', 512 );//TODO remove
179
  if ( ! defined( 'BIG_FILE_UPLOADS_CHUNK_SIZE_KB' ) ) {
180
+ define( 'BIG_FILE_UPLOADS_CHUNK_SIZE_KB', $default_chunk );
181
  }
182
+
183
  if ( ! defined( 'BIG_FILE_UPLOADS_RETRIES' ) ) {
184
+ define( 'BIG_FILE_UPLOADS_RETRIES', 1 );
185
  }
186
 
187
  $plupload_settings['url'] = admin_url( 'admin-ajax.php' );
205
  load_plugin_textdomain( $domain, false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
206
  }
207
 
208
+ /**
209
+ * Load Localization files.
210
+ *
211
+ * @since 1.0.0
212
+ */
213
+ public function init_review_notice() {
214
+
215
+ require_once dirname( __FILE__ ) . '/classes/class-review-notice.php';
216
+
217
+ // Setup notice.
218
+ $notice = Big_File_Uploads_Review_Notice::get(
219
+ 'tuxedo-big-file-uploads', // Plugin slug on wp.org (eg: hello-dolly).
220
+ __( 'Big File Uploads', 'tuxedo-big-file-uploads' ), // Plugin name (eg: Hello Dolly).
221
+ array(
222
+ 'days' => 14,
223
+ 'screens' => [ 'plugins', 'settings_page_big_file_uploads', 'upload' ],
224
+ 'cap' => 'install_plugins',
225
+ 'domain' => 'tuxedo-big-file-uploads',
226
+ 'prefix' => 'bfu'
227
+ ) // Notice options.
228
+ );
229
+
230
+ // Render notice.
231
+ $notice->render();
232
+ }
233
+
234
  /**
235
  * Return max upload size.
236
  *
262
  }
263
 
264
  /**
265
+ * Add the js to Gutenberg to add our custom upload size notice.
266
+ *
267
+ * @return void
268
+ */
269
+ function gutenberg_notice() {
270
+ wp_enqueue_script(
271
+ 'bfu-block-upload-notice',
272
+ plugin_dir_url( __FILE__ ) . 'assets/js/block-notice.js',
273
+ [ 'wp-blocks', 'wp-i18n', 'wp-element', 'wp-editor' ],
274
+ BIG_FILE_UPLOADS_VERSION
275
+ );
276
+
277
+ wp_set_script_translations( 'bfu-block-upload-notice', 'tuxedo-big-file-uploads' );
278
+ }
279
+
280
+ /**
281
+ * Always pass the original size limit to Gutenberg so it can show our error (BFU only works inside media library via plupload).
282
+ *
283
+ * @param $editor_settings
284
+ *
285
+ * @return mixed
286
+ */
287
+ function gutenberg_size_filter( $editor_settings ) {
288
+ $editor_settings['maxUploadFileSize'] = $this->max_upload_size;
289
+
290
+ return $editor_settings;
291
+ }
292
+
293
+ /**
294
+ * Enqueue html on the upload form.
295
  *
296
  * @since 2.0
297
  */
298
  public function upload_output() {
299
+ global $pagenow;
300
+ if ( ! current_user_can( $this->capability ) || is_null( $pagenow ) || ! in_array( $pagenow, array( 'post-new.php', 'post.php', 'upload.php', 'media-new.php' ) ) ) {
301
  return;
302
  }
303
  ?>
304
  <script type="text/javascript">
305
+ //When each chunk is uploaded, check if there were any errors or not and stop the rest
306
+ jQuery(function() {
307
+ if ( typeof uploader !== 'undefined' ) {
308
+ uploader.bind('ChunkUploaded', function (up, file, response) {
309
+ //Stop the upload!
310
+ if (response.status === 202) {
311
+ up.removeFile(file);
312
+ uploadSuccess(file, response.response);
313
+ }
314
+ });
315
+ }
316
+ });
317
 
318
+ jQuery(".max-upload-size").append(' <small><a style="text-decoration:none;" href="<?php echo esc_url( $this->settings_url() ); ?>"><?php esc_html_e( 'Change', 'tuxedo-big-file-uploads' ); ?></a></small>');
319
  <?php
320
  $dismissed = get_user_option( 'bfu_notice_dismissed', get_current_user_id() );
321
  if ( ! class_exists( 'Infinite_Uploads' ) && ! $dismissed ) {
454
  * Based on code by Davit Barbakadze
455
  * https://gist.github.com/jayarjo/5846636
456
  *
457
+ * Mirrors /wp-admin/async-upload.php
458
+ *
459
+ * @todo Figure out a way to stop furthur chunks from uploading when there is an error in gutenberg
460
+ *
461
  * @since 1.2.0
462
  */
463
  public function ajax_chunk_receiver() {
470
 
471
  /** Authenticate user. */
472
  if ( ! is_user_logged_in() || ! current_user_can( 'upload_files' ) ) {
473
+ wp_die( __( 'Sorry, you are not allowed to upload files.' ) );
474
  }
475
  check_admin_referer( 'media-form' );
476
 
477
  /** Check and get file chunks. */
478
+ $chunk = isset( $_REQUEST['chunk'] ) ? intval( $_REQUEST['chunk'] ) : 0; //zero index
479
+ $current_part = $chunk + 1;
480
  $chunks = isset( $_REQUEST['chunks'] ) ? intval( $_REQUEST['chunks'] ) : 0;
481
 
482
  /** Get file name and path + name. */
483
  $fileName = isset( $_REQUEST['name'] ) ? $_REQUEST['name'] : $_FILES['async-upload']['name'];
484
+ $filePath = dirname( $_FILES['async-upload']['tmp_name'] ) . '/bfu-' . md5( $fileName ) . '.part';
485
 
486
  $tuxbfu_max_upload_size = $this->get_upload_limit();
487
+ if ( file_exists( $filePath ) && filesize( $filePath ) + filesize( $_FILES['async-upload']['tmp_name'] ) > $tuxbfu_max_upload_size ) {
488
 
489
  if ( ! $chunks || $chunk == $chunks - 1 ) {
490
+ @unlink( $filePath );
491
 
492
  if ( ! isset( $_REQUEST['short'] ) || ! isset( $_REQUEST['type'] ) ) {
 
493
  echo wp_json_encode( array(
494
  'success' => false,
495
  'data' => array(
496
  'message' => __( 'The file size has exceeded the maximum file size setting.', 'tuxedo-big-file-uploads' ),
497
+ 'filename' => $fileName,
498
  ),
499
  ) );
500
  wp_die();
 
501
  } else {
502
+ status_header( 202 );
503
+ printf(
504
+ '<div class="error-div error">%s <strong>%s</strong><br />%s</div>',
505
+ sprintf(
506
+ '<button type="button" class="dismiss button-link" onclick="jQuery(this).parents(\'div.media-item\').slideUp(200, function(){jQuery(this).remove();});">%s</button>',
507
+ __( 'Dismiss' )
508
+ ),
509
+ sprintf(
510
+ /* translators: %s: Name of the file that failed to upload. */
511
+ __( '&#8220;%s&#8221; has failed to upload.' ),
512
+ esc_html( $fileName )
513
+ ),
514
+ __( 'The file size has exceeded the maximum file size setting.', 'tuxedo-big-file-uploads' )
515
+ );
516
+ exit;
517
  }
518
 
519
  }
520
 
521
  die();
522
+ }
523
 
524
+ //debugging
525
+ if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
526
+ $size = file_exists( $filePath ) ? size_format( filesize( $filePath ), 3 ) : '0 B';
527
+ error_log( "BFU: Processing \"$fileName\" part $current_part of $chunks as $filePath. $size processed so far." );
528
  }
529
 
530
  /** Open temp file. */
531
+ if ( $chunk == 0 ) {
532
+ $out = @fopen( $filePath, 'wb');
533
+ } elseif ( is_writable( $filePath ) ) { //
534
+ $out = @fopen( $filePath, 'ab' );
535
+ } else {
536
+ $out = false;
537
+ }
538
+
539
  if ( $out ) {
540
 
541
  /** Read binary input stream and append it to temp file. */
549
  /** Failed to open input stream. */
550
  /** Attempt to clean up unfinished output. */
551
  @fclose( $out );
552
+ @unlink( $filePath );
553
+ error_log( "BFU: Error reading uploaded part $current_part of $chunks." );
554
+
555
+ if ( ! isset( $_REQUEST['short'] ) || ! isset( $_REQUEST['type'] ) ) {
556
+ echo wp_json_encode(
557
+ array(
558
+ 'success' => false,
559
+ 'data' => array(
560
+ 'message' => sprintf( __( 'There was an error reading uploaded part %d of %d.', 'tuxedo-big-file-uploads' ), $current_part, $chunks ),
561
+ 'filename' => esc_html( $fileName ),
562
+ ),
563
+ )
564
+ );
565
+ wp_die();
566
+ } else {
567
+ status_header( 202 );
568
+ printf(
569
+ '<div class="error-div error">%s <strong>%s</strong><br />%s</div>',
570
+ sprintf(
571
+ '<button type="button" class="dismiss button-link" onclick="jQuery(this).parents(\'div.media-item\').slideUp(200, function(){jQuery(this).remove();});">%s</button>',
572
+ __( 'Dismiss' )
573
+ ),
574
+ sprintf(
575
+ /* translators: %s: Name of the file that failed to upload. */
576
+ __( '&#8220;%s&#8221; has failed to upload.' ),
577
+ esc_html( $fileName )
578
+ ),
579
+ sprintf( __( 'There was an error reading uploaded part %d of %d.', 'tuxedo-big-file-uploads' ), $current_part, $chunks )
580
+ );
581
+ exit;
582
+ }
583
  }
584
 
585
  @fclose( $in );
586
  @fclose( $out );
 
587
  @unlink( $_FILES['async-upload']['tmp_name'] );
 
588
  } else {
589
  /** Failed to open output stream. */
590
+ error_log( "BFU: Failed to open output stream $filePath to write part $current_part of $chunks." );
591
+
592
+ if ( ! isset( $_REQUEST['short'] ) || ! isset( $_REQUEST['type'] ) ) {
593
+ echo wp_json_encode(
594
+ array(
595
+ 'success' => false,
596
+ 'data' => array(
597
+ 'message' => sprintf( __( 'There was an error opening the temp file %s for writing. Available temp directory space may be exceeded or the temp file was cleaned up before the upload completed.', 'tuxedo-big-file-uploads' ), esc_html( $filePath ) ),
598
+ 'filename' => esc_html( $fileName ),
599
+ ),
600
+ )
601
+ );
602
+ wp_die();
603
+ } else {
604
+ status_header( 202 );
605
+ printf(
606
+ '<div class="error-div error">%s <strong>%s</strong><br />%s</div>',
607
+ sprintf(
608
+ '<button type="button" class="dismiss button-link" onclick="jQuery(this).parents(\'div.media-item\').slideUp(200, function(){jQuery(this).remove();});">%s</button>',
609
+ __( 'Dismiss' )
610
+ ),
611
+ sprintf(
612
+ /* translators: %s: Name of the file that failed to upload. */
613
+ __( '&#8220;%s&#8221; has failed to upload.' ),
614
+ esc_html( $fileName )
615
+ ),
616
+ sprintf( __( 'There was an error opening the temp file %s for writing. Available temp directory space may be exceeded or the temp file was cleaned up before the upload completed.', 'tuxedo-big-file-uploads' ), esc_html( $filePath ) )
617
+ );
618
+ exit;
619
+ }
620
  }
621
 
622
  /** Check if file has finished uploading all parts. */
623
  if ( ! $chunks || $chunk == $chunks - 1 ) {
624
 
625
  /** Recreate upload in $_FILES global and pass off to WordPress. */
626
+ rename( $filePath, $_FILES['async-upload']['tmp_name'] );
627
  $_FILES['async-upload']['name'] = $fileName;
628
  $_FILES['async-upload']['size'] = filesize( $_FILES['async-upload']['tmp_name'] );
629
  //$wp_filetype = wp_check_filetype_and_ext( $_FILES['async-upload']['tmp_name'], $_FILES['async-upload']['tmp_name'] );
649
 
650
  $id = media_handle_upload( 'async-upload', $post_id );
651
  if ( is_wp_error( $id ) ) {
652
+ printf(
653
+ '<div class="error-div error">%s <strong>%s</strong><br />%s</div>',
654
+ sprintf(
655
+ '<button type="button" class="dismiss button-link" onclick="jQuery(this).parents(\'div.media-item\').slideUp(200, function(){jQuery(this).remove();});">%s</button>',
656
+ __( 'Dismiss' )
657
+ ),
658
+ sprintf(
659
+ /* translators: %s: Name of the file that failed to upload. */
660
+ __( '&#8220;%s&#8221; has failed to upload.' ),
661
+ esc_html( $_FILES['async-upload']['name'] )
662
+ ),
663
+ esc_html( $id->get_error_message() )
664
+ );
665
  exit;
666
  }
667
 
668
+ if ( $_REQUEST['short'] ) {
669
  // Short form response - attachment ID only.
670
  echo $id;
671
+ } else {
672
+ // Long form response - big chunk of HTML.
673
  $type = $_REQUEST['type'];
674
 
675
  /**
676
+ * Filters the returned ID of an uploaded attachment.
677
+ *
678
+ * The dynamic portion of the hook name, `$type`, refers to the attachment type.
679
+ *
680
+ * Possible hook names include:
681
  *
682
+ * - `async_upload_audio`
683
+ * - `async_upload_file`
684
+ * - `async_upload_image`
685
+ * - `async_upload_video`
686
  *
687
+ * @since 2.5.0
688
  *
689
  * @param int $id Uploaded attachment ID.
690
  */