Subversion Repositories web.active

Rev

Rev 22 | Go to most recent revision | Details | Last modification | View Log

Rev Author Line No. Line
1 mjordaan 1
<?php namespace ProcessWire;
2
 
3
/**
4
 * An Inputfield for handling file uploads
5
 *
6
 * @property string $extensions Allowed file extensions, space separated
7
 * @property int $maxFiles Maximum number of files allowed
8
 * @property int $maxFilesize Maximum file size
9
 * @property bool $useTags Whether or not tags are enabled
10
 * @property string $tagsList Predefined tags
11
 * @property bool|int $unzip Whether or not unzip is enabled
12
 * @property bool|int $overwrite Whether or not overwrite mode is enabled
13
 * @property int $descriptionRows Number of rows for description field (default=1, 0=disable)
14
 * @property string $destinationPath Destination path for uploaded file
15
 * @property string $itemClass Class name(s) for each file item (default=InputfieldFileItem ui-widget ui-widget-content)
16
 * @property bool|int $noUpload Set to true or 1 to disable uploading to this field
17
 * @property bool|int $noLang Set to true or 1 to disable multi-language descriptions
18
 * @property bool|int $noAjax Set to true or 1 to disable ajax uploading
19
 * @property int $uploadOnlyMode Set to true or 1 to disable existing file list display, or 2 to also prevent file from having 'temp' status.
20
 * @property bool|int $noCollapseItem Set to true to disable collapsed items (like for LanguageTranslator tool or other things that add tools to files)
21
 * @property bool|int $noShortName Set to true to disable shortened filenames in output
22
 * @property bool|int $noCustomButton Set to true to disable use of the styled <input type='file'>
23
 * @property Pagefiles|Pagefile|null $value
24
 *
25
 * @method string renderItem($pagefile, $id, $n)
26
 * @method string renderList($value)
27
 * @method string renderUpload($value)
28
 * @method void fileAdded(Pagefile $pagefile)
29
 * @method array extractMetadata(Pagefile $pagefile, array $metadata = array())
30
 * @method void processInputAddFile($filename)
31
 * @method void processInputDeleteFile(Pagefile $pagefile)
32
 * @method bool processInputFile(WireInputData $input, Pagefile $pagefile, $n)
33
 * @method bool processItemInputfields(Pagefile $pagefile, InputfieldWrapper $inputfields, $id, WireInputData $input)
34
 *
35
 */
36
class InputfieldFile extends Inputfield implements InputfieldItemList, InputfieldHasSortableValue {
37
 
38
	public static function getModuleInfo() {
39
		return array(
40
			'title' => __('Files', __FILE__), // Module Title
41
			'summary' => __('One or more file uploads (sortable)', __FILE__), // Module Summary
42
			'version' => 126,
43
			'permanent' => true, 
44
			);
45
	}
46
 
47
	/**
48
	 * Cache of responses we'll be sending on ajax requests
49
	 *
50
	 */
51
	protected $ajaxResponses = array();
52
 
53
	/**
54
	 * Was a file replaced? 
55
	 *
56
	 */
57
	protected $singleFileReplacement = false;
58
 
59
	/**
60
	 * Saved instanceof WireUpload in case API retrieval is needed (see getWireUpload() method)
61
	 *
62
	 */
63
	protected $wireUpload = null;
64
 
65
	/**
66
	 * Set to the current Pagefile item when doing iteration
67
	 * 
68
	 * @var Pagefile|null
69
	 * 
70
	 */
71
	protected $currentItem = null;
72
 
73
	/**
74
	 * True when field should behave in an upload only mode
75
	 * 
76
	 * @var bool|int
77
	 * 
78
	 */
79
	protected $uploadOnlyMode = 0;
80
 
81
	/**
82
	 * This is true when we are only rendering the value rather than the inputs
83
	 * 
84
	 * @var bool
85
	 * 
86
	 */
87
	protected $renderValueMode = false;
88
 
89
	/**
90
	 * True when in ajax mode
91
	 * 
92
	 * @var bool
93
	 * 
94
	 */
95
	protected $isAjax = false;
96
 
97
	/**
98
	 * Admin theme specific settings
99
	 * 
100
	 * @var array
101
	 * 
102
	 */
103
	protected $themeSettings = array();
104
 
105
	/**
106
	 * Commonly used text labels, translated, indexed by label name
107
	 * 
108
	 * @var array
109
	 * 
110
	 */
111
	protected $labels = array();
112
 
113
	/**
114
	 * Cached value of Fieldgroup used for Pagefile custom fields, as used by getItemInputfields() method
115
	 *
116
	 * @var Fieldgroup|null|bool Null when not yet known, false when known not applicable, Fieldgroup when known and in use
117
	 *
118
	 */
119
	protected $itemFieldgroup = null;
120
 
121
	/**
122
	 * Initialize the InputfieldFile
123
	 *
124
	 */
125
	public function init() {
126
		parent::init();
127
 
128
		// note: these two fields originate from FieldtypeFile. 
129
		// Initializing them here ensures this Inputfield has the values set automatically.
130
		$this->set('extensions', '');
131
		$this->set('maxFiles', 0); 
132
		$this->set('maxFilesize', 0); 
133
		$this->set('useTags', 0);
134
		$this->set('tagsList', ''); 
135
 
136
		// native to this Inputfield
137
		$this->set('unzip', 0); 
138
		$this->set('overwrite', 0); 
139
		$this->set('descriptionRows', 1); 
140
		$this->set('destinationPath', ''); 
141
		$this->set('itemClass', 'InputfieldFileItem ui-widget ui-widget-content'); 
142
		$this->set('noUpload', 0); // set to 1 to disable uploading to this field
143
		$this->set('noLang', 0); 
144
		$this->set('noAjax', 0); // disable ajax uploading
145
		$this->set('noCollapseItem', 0);
146
		$this->set('noShortName', 0);
147
		$this->set('noCustomButton', false);
148
		$this->attr('type', 'file'); 
149
 
150
		$this->labels = array(
151
			'description' => $this->_('Description'), 
152
			'tags' => $this->_('Tags'),
153
			'drag-drop' => $this->_('drag and drop files in here'), 
154
			'delete' => $this->_('Delete'),
155
			'choose-file' => $this->_('Choose File'),
156
			'choose-files' => $this->_('Choose Files'),
157
		);
158
 
159
		$this->isAjax = $this->wire('input')->get('InputfieldFileAjax') 
160
			|| $this->wire('input')->get('reloadInputfieldAjax')
161
			|| $this->wire('input')->get('renderInputfieldAjax');
162
 
163
		$this->setMaxFilesize(trim(ini_get('post_max_size'))); 
164
		$this->uploadOnlyMode = (int) $this->wire('input')->get('uploadOnlyMode');
165
		$this->addClass('InputfieldItemList', 'wrapClass');
166
		$this->addClass('InputfieldHasFileList', 'wrapClass');
167
 
168
		$themeDefaults = array(
169
			'error' => "<span class='ui-state-error-text'>{out}</span>",
170
		);
171
		$themeSettings = $this->wire('config')->InputfieldFile;
172
		$this->themeSettings = is_array($themeSettings) ? array_merge($themeDefaults, $themeSettings) : $themeDefaults;
173
	}
174
 
175
	public function get($key) {
176
		if($key === 'renderValueMode') return $this->renderValueMode;
177
		if($key === 'singleFileReplacement') return $this->singleFileReplacement;
178
		if($key === 'descriptionFieldLabel') return $this->labels['description'];
179
		if($key === 'tagsFieldLabel') return $this->labels['tags'];
180
		if($key === 'deleteLabel') return $this->labels['delete'];
181
		if($key === 'themeSettings') return $this->themeSettings;
182
		return parent::get($key);
183
	}
184
 
185
	public function set($key, $value) {
186
		if($key == 'maxFilesize') return $this->setMaxFilesize($value);
187
		return parent::set($key, $value); 
188
	}
189
 
190
	/**
191
	 * Set the max file size in bytes or use string like "30m", "2g" "500k"
192
	 * 
193
	 * @param int|string $filesize
194
	 * @return $this
195
	 * 
196
	 */
197
	public function setMaxFilesize($filesize) {
198
		$max = $this->strToBytes($filesize);
199
		$phpMax = $this->strToBytes(ini_get('upload_max_filesize'));
200
		if($phpMax < $max) $max = $phpMax;	
201
		$this->maxFilesize = $max; 
202
		return $this;
203
	}
204
 
205
	/**
206
	 * Convert string like "32M" to bytes (integer)
207
	 * 
208
	 * @param string|int $filesize
209
	 * @return int
210
	 * 
211
	 */
212
	protected function strToBytes($filesize) {
213
		if(ctype_digit("$filesize")) {
214
			$bytes = (int) $filesize;
215
		} else {
216
			$filesize = rtrim($filesize, 'bB'); // convert mb=>m, gb=>g, kb=>k
217
			$last = strtolower(substr($filesize, -1));
218
			if(ctype_alpha($last)) $filesize = rtrim($filesize, $last);
219
			$filesize = (int) $filesize;
220
			if($last == 'g') {
221
				$bytes = (($filesize * 1024) * 1024) * 1024;
222
			} else if($last == 'm') {
223
				$bytes = ($filesize * 1024) * 1024;
224
			} else if($last == 'k') {
225
				$bytes = $filesize * 1024;
226
			} else if($filesize > 0) {
227
				$bytes = $filesize;
228
			} else {
229
				$bytes = (5 * 1024) * 1024;
230
			}
231
		}
232
		return $bytes; 
233
	}
234
 
235
	/**
236
	 * Per Inputfield interface, returns true when this field is empty
237
	 *
238
	 */
239
	public function isEmpty() {
240
		return !wireCount($this->value);
241
	}
242
 
243
	/**
244
	 * Set an attribute
245
	 * 
246
	 * @param array|string $key
247
	 * @param array|int|string $value
248
	 * @return Inputfield|InputfieldFile
249
	 * 
250
	 */
251
	public function setAttribute($key, $value) {
252
		if($key == 'value') {
253
			if($value instanceof Pagefile) {
254
				// if given a Pagefile rather than a Pagefiles, use the Pagefiles instead
255
				$value = $value->pagefiles; 
256
			}
257
		}
258
		return parent::setAttribute($key, $value);
259
	}
260
 
261
	/**
262
	 * Check to ensure that the containing form as an 'enctype' attr needed for uploading files
263
	 *
264
	 */
265
	protected function checkFormEnctype() {
266
		$parent = $this->parent;
267
		while($parent) {
268
			if($parent->attr('method') == 'post') {
269
				if(!$parent->attr('enctype')) $parent->attr('enctype', 'multipart/form-data');
270
				break;
271
			}
272
			$parent = $parent->parent; 
273
		}
274
	}
275
 
276
	/**
277
	 * Set the parent of this Inputfield
278
	 *
279
	 * @param InputfieldWrapper $parent
280
	 * @return $this
281
	 *
282
	 */
283
	public function setParent(InputfieldWrapper $parent) {
284
		parent::setParent($parent); 
285
		$this->checkFormEnctype();
286
		return $this;
287
	}
288
 
289
	/**
290
	 * Get the unique 'id' attribute for the given Pagefile
291
	 * 
292
	 * @param Pagefile $pagefile
293
	 * @return string
294
	 * 
295
	 */
296
	protected function pagefileId(Pagefile $pagefile) {
297
		return $this->name . "_" . $pagefile->hash; 
298
	}
299
 
300
	/**
301
	 * Render a description input for the given Pagefile
302
	 * 
303
	 * @param Pagefile $pagefile
304
	 * @param string $id 
305
	 * @param int $n
306
	 * @return string
307
	 * 
308
	 */
309
	protected function renderItemDescriptionField(Pagefile $pagefile, $id, $n) {
310
 
311
		if($n) {}
312
		$out = '';
313
		$tabs = '';
314
		static $hasLangTabs = null;
315
		static $langTabSettings = array();
316
 
317
		if($this->renderValueMode) {
318
			if($this->wire('languages')) {
319
				$description = $pagefile->description($this->wire('user')->language);
320
			} else {
321
				$description = $pagefile->description;
322
			}
323
			if(strlen($description)) $description = 
324
				"<div class='InputfieldFileDescription detail'>" . $this->wire('sanitizer')->entities1($description) . "</div>";
325
			return $description;
326
		}
327
 
328
		if($this->descriptionRows > 0) {
329
 
330
			$userLanguage = $this->wire('user')->language;
331
			$languages = $this->noLang ? null : $this->wire('languages');
332
			$defaultDescriptionFieldLabel = $this->wire('sanitizer')->entities1($this->labels['description']);
333
 
334
			if(!$userLanguage || !$languages || $languages->count() < 2) {
335
				$numLanguages = 0;
336
				$languages = array(null);
337
			} else {
338
				$numLanguages = $languages->count();
339
				if(is_null($hasLangTabs)) {
340
					$hasLangTabs = $this->wire('modules')->isInstalled('LanguageTabs');
341
					if($hasLangTabs) {
342
						/** @var LanguageTabs $languageTabs */
343
						$languageTabs = $this->wire('modules')->getModule('LanguageTabs');
344
						$langTabSettings = $languageTabs->getSettings();
345
					}
346
				}
347
			}
348
 
349
			foreach($languages as $language) {
350
 
351
				$descriptionFieldName = "description_$id";
352
				$descriptionFieldLabel = $defaultDescriptionFieldLabel;
353
				$labelClass = "detail";
354
				$attrStr = '';
355
 
356
				if($language) {
357
					$tabField = empty($langTabSettings['tabField']) ? 'title' : $langTabSettings['tabField'];
358
					$descriptionFieldLabel = (string) $language->getUnformatted($tabField);
359
					if(empty($descriptionFieldLabel)) $descriptionFieldLabel = $language->get('name');
360
					$descriptionFieldLabel = $this->wire('sanitizer')->entities($descriptionFieldLabel);
361
					if(!$language->isDefault()) $descriptionFieldName = "description{$language->id}_$id";
362
					$labelClass .= ' LanguageSupportLabel';
363
					if(!$languages->editable($language)) {
364
						$labelClass .= ' LanguageNotEditable';
365
						$descriptionFieldLabel = "<s>$descriptionFieldLabel</s>";
366
					}
367
					$tabID = "langTab_{$id}__$language";
368
					$aClass = "langTab$language";
369
					if(!empty($langTabSettings['aClass'])) $aClass .= " " . $langTabSettings['aClass'];
370
					$tabs .= "<li><a data-lang='$language' class='$aClass' href='#$tabID'>$descriptionFieldLabel</a></li>";
371
					$out .= "<div class='InputfieldFileDescription LanguageSupport' data-language='$language' id='$tabID'>"; // open wrapper
372
				} else {
373
					$out .= "<div class='InputfieldFileDescription'>"; // open wrapper
374
					$attrStr = "placeholder='$descriptionFieldLabel&hellip;'";
375
					$labelClass = 'detail pw-hidden';
376
				}
377
 
378
				$attrStr = "name='$descriptionFieldName' id='$descriptionFieldName' $attrStr";
379
 
380
				$out .= "<label for='$descriptionFieldName' class='$labelClass'>$descriptionFieldLabel</label>";
381
 
382
				$description = $this->wire('sanitizer')->entities($pagefile->description($language));
383
 
384
				if($this->descriptionRows > 1) {
385
					$out .= "<textarea $attrStr rows='$this->descriptionRows'>$description</textarea>";
386
				} else {
387
					$out .= "<input type='text' $attrStr value='$description' />";
388
				}
389
 
390
				$out .= "</div>"; // close wrapper
391
			}
392
 
393
			if($numLanguages && $hasLangTabs) {
394
				$ulClass = empty($langTabSettings['ulClass']) ? '' : " class='$langTabSettings[ulClass]'";
395
				$ulAttr = empty($langTabSettings['ulAttrs']) ? '' : " $langTabSettings[ulAttrs]";
396
				$out = "<div class='hasLangTabs langTabsContainer'><div class='langTabs'><ul $ulAttr$ulClass>$tabs</ul>$out</div></div>";
397
				if($this->isAjax) $out .= "<script>setupLanguageTabs($('#wrap_" . $this->attr('id') . "'));</script>";
398
			}
399
 
400
		}
401
 
402
		if($this->useTags) $out .= $this->renderItemTagsField($pagefile, $id, $n); 
403
 
404
		return $out;
405
	}
406
 
407
	/**
408
	 * Render the tags input for the given Pagefile
409
	 * 
410
	 * @param Pagefile $pagefile
411
	 * @param string $id
412
	 * @param int $n
413
	 * @return string
414
	 * 
415
	 */
416
	protected function renderItemTagsField(Pagefile $pagefile, $id, $n) {
417
 
418
		if($n) {}
419
		$tagsLabel = $this->wire('sanitizer')->entities($this->labels['tags']) . '&hellip;';
420
		$tagsStr = $this->wire('sanitizer')->entities($pagefile->tags);
421
		$tagsAttr = '';
422
 
423
		if($this->useTags >= FieldtypeFile::useTagsPredefined) {
424
			// select predefined
425
			$tagsClass = 'InputfieldFileTagsSelect';
426
			$tagsAttr = "data-cfgname='InputfieldFileTags_{$this->hasField->name}' ";
427
 
428
		} else {
429
			// text input
430
			$tagsClass = 'InputfieldFileTagsInput';
431
		}
432
 
433
		$out = 
434
			"<div class='InputfieldFileTags'>" . 
435
				"<label for='tags_$id' class='detail pw-hidden'>$tagsLabel</label>" . 
436
				"<input type='text' name='tags_$id' id='tags_$id' value='$tagsStr' " . 
437
					"placeholder='$tagsLabel' class='$tagsClass' $tagsAttr/>" . 
438
			"</div>";
439
 
440
		return $out;
441
	}
442
 
443
	/**
444
	 * Get a basename for the file, possibly shortened, suitable for display in InputfieldFileList
445
	 * 
446
	 * @param Pagefile $pagefile
447
	 * @param int $maxLength
448
	 * @return string
449
	 * 
450
	 */
451
	public function getDisplayBasename(Pagefile $pagefile, $maxLength = 25) {
452
		$displayName = $pagefile->basename;
453
		if($this->noShortName) return $displayName;
454
		if(strlen($displayName) > $maxLength) {
455
			$ext = ".$pagefile->ext";
456
			$maxLength -= (strlen($ext) + 1);
457
			$displayName = basename($displayName, $ext);
458
			$displayName = substr($displayName, 0, $maxLength);
459
			$displayName .= "&hellip;" . ltrim($ext, '.');
460
		}
461
		return $displayName; 	
462
	}
463
 
464
	/**
465
	 * Render markup for a file item
466
	 * 
467
	 * @param Pagefile $pagefile
468
	 * @param string $id
469
	 * @param int $n
470
	 * @return string
471
	 * 
472
	 */
473
	protected function ___renderItem($pagefile, $id, $n) {
474
 
475
		$displayName = $this->getDisplayBasename($pagefile);
476
		$deleteLabel = $this->labels['delete'];
477
 
478
		$out = 
479
			"<p class='InputfieldFileInfo InputfieldItemHeader ui-state-default ui-widget-header'>" . 
480
			wireIconMarkupFile($pagefile->basename, "fa-fw HideIfEmpty") . '&nbsp;' . 
481
			"<a class='InputfieldFileName' title='$pagefile->basename' target='_blank' href='{$pagefile->url}'>$displayName</a> " . 
482
			"<span class='InputfieldFileStats'>" . str_replace(' ', '&nbsp;', $pagefile->filesizeStr) . "</span> ";
483
 
484
		if(!$this->renderValueMode) $out .=
485
			"<label class='InputfieldFileDelete'>" . 
486
				"<input type='checkbox' name='delete_$id' value='1' title='$deleteLabel' />" . 
487
				"<i class='fa fa-fw fa-trash'></i></label>";
488
 
489
		$description = $this->renderItemDescriptionField($pagefile, $id, $n);
490
		$class = 'InputfieldFileData ';
491
		$class .= $description ? 'description ui-widget-content' : 'InputfieldFileFields';
492
 
493
		$out .= "</p><div class='$class'>" . $description;
494
 
495
		$inputfields = $this->getItemInputfields($pagefile);
496
		if($inputfields) $out .= $inputfields->render();
497
 
498
		if(!$this->renderValueMode) {
499
			$out .= "<input class='InputfieldFileSort' type='text' name='sort_$id' value='$n' />";
500
		}
501
 
502
		$out .= "</div>";
503
 
504
		return $out; 
505
	}
506
 
507
	/**
508
	 * Wrap output of files list item
509
	 * 
510
	 * @param string $out
511
	 * @return string
512
	 * 
513
	 */
514
	protected function renderItemWrap($out) {
515
		// note: using currentItem rather than a new argument since there are now a few modules extending
516
		// this one and if they implement their own calls to this method or version of this method then 
517
		// they will get strict notices from php if we add a new argument here. 
518
		$item = $this->currentItem; 
519
		$id = $item && !$this->renderValueMode ? " id='file_$item->hash'" : "";
520
		return "<li$id class='{$this->itemClass}'>$out</li>";
521
	}
522
 
523
	/**
524
	 * Render files list ready
525
	 * 
526
	 * @param Pagefiles|null $value
527
	 * @throws WireException
528
	 * @throws WirePermissionException
529
	 * 
530
	 */
531
	protected function renderListReady($value) {
532
		if(!$this->renderValueMode) {
533
			// if just rendering the files list (as opposed to saving it), delete any temp files that may have accumulated
534
			if(!$this->overwrite && !count($_POST) && !$this->isAjax && !$this->uploadOnlyMode) {
535
				// don't delete files when in render single field or fields mode
536
				if(!$this->wire('input')->get('field') && !$this->wire('input')->get('fields')) {
537
					if($value instanceof Pagefiles) $value->deleteAllTemp();
538
				}
539
			}
540
		}
541
	}
542
 
543
	/**
544
	 * Render files list
545
	 * 
546
	 * @param Pagefiles|null $value
547
	 * @return string
548
	 * 
549
	 */
550
	protected function ___renderList($value) {
551
 
552
		if(!$value) return '';
553
		$out = '';
554
		$n = 0; 
555
 
556
		$this->renderListReady($value);
557
 
558
		if(!$this->uploadOnlyMode && WireArray::iterable($value)) {
559
			foreach($value as $k => $pagefile) {
560
				$id = $this->pagefileId($pagefile);
561
				$this->currentItem = $pagefile;
562
				$out .= $this->renderItemWrap($this->renderItem($pagefile, $id, $n++));
563
			}
564
		}
565
 
566
		$class = 'InputfieldFileList ui-helper-clearfix';
567
		if($this->overwrite && !$this->renderValueMode) $class .= " InputfieldFileOverwrite";
568
		if($out) $out = "<ul class='$class'>$out</ul>";
569
 
570
		return $out; 
571
	}
572
 
573
	/**
574
	 * Render upload area
575
	 * 
576
	 * @param Pagefiles|null $value
577
	 * @return string
578
	 * 
579
	 */
580
	protected function ___renderUpload($value) {
581
		if($value) {}
582
		if($this->noUpload || $this->renderValueMode) return '';
583
 
584
		// enables user to choose more than one file
585
		if($this->maxFiles != 1) $this->setAttribute('multiple', 'multiple'); 
586
 
587
		$attrs = $this->getAttributes();
588
		unset($attrs['value']); 
589
		if(substr($attrs['name'], -1) != ']') $attrs['name'] .= '[]';
590
 
591
		$extensions = $this->extensions;
592
		if($this->unzip && !$this->maxFiles) $extensions .= ' zip';
593
		$formatExtensions = $this->formatExtensions($extensions);
594
		$chooseLabel = $this->labels['choose-file'];
595
		$dragDropLabel = $this->labels['drag-drop'];
596
		$attrStr = $this->getAttributesString($attrs);
597
 
598
		$out =
599
			"<div " .
600
				"data-maxfilesize='$this->maxFilesize' " .
601
				"data-extensions='$extensions' " .
602
				"data-fieldname='$attrs[name]' " .
603
				"class='InputfieldFileUpload'>
604
				";
605
 
606
		if($this->getSetting('noCustomButton')) {
607
			$out .= "<input $attrStr>";
608
 
609
		} else {
610
			$out .= "
611
					<div class='InputMask ui-button ui-state-default'>
612
						<span class='ui-button-text'>
613
							<i class='fa fa-fw fa-folder-open-o'></i>$chooseLabel
614
						</span>
615
						<input $attrStr>
616
					</div>
617
					";
618
		}
619
 
620
		$out .= "  		
621
				<span class='InputfieldFileValidExtensions detail'>$formatExtensions</span>
622
				<input type='hidden' class='InputfieldFileMaxFiles' value='$this->maxFiles' />
623
			";
624
 
625
		if(!$this->noAjax) $out .= "
626
				<span class='AjaxUploadDropHere description'>
627
					<span>
628
						<i class='fa fa-cloud-upload'></i>&nbsp;$dragDropLabel
629
					</span>
630
				</span>
631
			";	
632
 
633
		$out .= "</div>"; // .InputfieldFileUpload
634
 
635
		return $out; 
636
	}
637
 
638
	public function renderReady(Inputfield $parent = null, $renderValueMode = false) {
639
 
640
		/** @var Config $config */
641
		$config = $this->wire('config');
642
 
643
		$this->addClass('InputfieldNoFocus', 'wrapClass');
644
		if(!$renderValueMode) $this->addClass('InputfieldHasUpload', 'wrapClass');
645
 
646
		if($this->useTags) {
647
			$this->wire('modules')->get('JqueryUI')->use('selectize');
648
			$this->addClass('InputfieldFileHasTags', 'wrapClass');
649
			if($this->useTags >= FieldtypeFile::useTagsPredefined && $this->hasField) {
650
				// predefined tags
651
				$fieldName = $this->hasField->name;
652
				$jsName = "InputfieldFileTags_$fieldName";
653
				$allowUserTags = $this->useTags & FieldtypeFile::useTagsNormal;
654
				$data = $config->js($jsName);
655
				if(!is_array($data)) $data = array();
656
				if(empty($data['tags'])) {
657
					$tags = array();
658
					foreach(explode(' ', (string) $this->get('tagsList')) as $tag) {
659
						$tag = trim($tag);
660
						if(!strlen($tag)) continue;
661
						$tags[strtolower($tag)] = $tag;
662
					}
663
					if($allowUserTags) {
664
						$pagefiles = $this->val();
665
						if($pagefiles instanceof Pagefiles) {
666
							$_tags = $pagefiles->tags(true);
667
							if(count($_tags)) $tags = array_merge($tags, $_tags);
668
						}
669
					}
670
					$data['tags'] = array_values($tags);
671
					$data['allowUserTags'] = $allowUserTags;
672
					$config->js($jsName, $data); 
673
				}
674
				$this->wrapAttr('data-configName', $jsName);
675
			} else {
676
				// regular tags text input
677
			}
678
		}
679
 
680
		$data = $config->js('InputfieldFile');
681
		if(!is_array($data)) $data = array();
682
		if(empty($data['labels'])) $data['labels'] = array();
683
		if(empty($data['labels']['bad-ext'])) {
684
			$data['labels']['bad-ext'] = $this->_('Unsupported file extension, please use only: EXTENSIONS');
685
			$data['labels']['too-big'] = $this->_('File is too big - maximum allowed size is MAX_KB kb');
686
			$config->js('InputfieldFile', $data); 
687
		}
688
 
689
		$this->getItemInputfields(); // custom fields ready
690
 
691
		return parent::renderReady($parent, $renderValueMode); 
692
	}
693
 
694
	public function ___render() {
695
		if(!$this->extensions) $this->error($this->_('No file extensions are defined for this field.')); 
696
		$numItems = wireCount($this->value);
697
		if($this->allowCollapsedItems()) $this->addClass('InputfieldItemListCollapse', 'wrapClass');
698
		if($numItems == 0) {
699
			$this->addClass('InputfieldFileEmpty', 'wrapClass');
700
		} else if($numItems == 1) {
701
			$this->addClass('InputfieldFileSingle', 'wrapClass');
702
		} else {
703
			$this->addClass('InputfieldFileMultiple', 'wrapClass');
704
		}
705
		return $this->renderList($this->value) . $this->renderUpload($this->value);
706
	}
707
 
708
	public function ___renderValue() {
709
		$this->renderValueMode = true; 
710
		$out = $this->render();
711
		$this->renderValueMode = false;
712
		return $out; 
713
	}
714
 
715
	protected function ___fileAdded(Pagefile $pagefile) {
716
		if($this->noUpload) return;
717
 
718
		$isValid = $this->wire('sanitizer')->validateFile($pagefile->filename(), array(
719
			'pagefile' => $pagefile
720
		));
721
 
722
		if($isValid === false) {
723
			$errors = $this->wire('sanitizer')->errors('clear array');
724
			throw new WireException(
725
				$this->_('File failed validation') . 
726
				(count($errors) ? ": " . implode(', ', $errors) : "")
727
			);
728
		} else if($isValid === null) {
729
			// there was no validator available for this file type
730
		}
731
 
732
		$message = $this->_('Added file:') . " {$pagefile->basename}"; // Label that precedes an added filename
733
 
734
		if($this->isAjax && !$this->noAjax) {
735
			$n = count($this->value); 
736
			if($n) $n--; // for sorting
737
			$this->currentItem = $pagefile; 
738
			$markup = $this->fileAddedGetMarkup($pagefile, $n);
739
			$this->ajaxResponse(false, $message, $pagefile->url, $pagefile->filesize(), $markup); 
740
		} else {
741
			$this->message($message); 
742
		}
743
 
744
		$pagefile->createdUser = $this->wire('user');
745
		$pagefile->modifiedUser = $this->wire('user');
746
	}
747
 
748
	protected function fileAddedGetMarkup(Pagefile $pagefile, $n) {
749
		return $this->renderItemWrap($this->renderItem($pagefile, $this->pagefileId($pagefile), $n));	
750
	}
751
 
752
	/**
753
	 * Given a Pagefile return array of meta data pulled from it
754
	 * 
755
	 * @param Pagefile $pagefile
756
	 * @param array $metadata Existing metadata, if applicable
757
	 * @return array Associative array of meta data (i.e. description and tags)
758
	 * 
759
	 */
760
	protected function ___extractMetadata(Pagefile $pagefile, array $metadata = array()) {
761
 
762
		$metadata['description'] = $pagefile->description;
763
 
764
		/** @var Languages $languages */
765
		$languages = $this->wire('languages');
766
		if($languages && !$this->noLang) {
767
			foreach($languages as $language) {
768
				if($language->isDefault()) continue;
769
				$metadata["description$language->id"] = $pagefile->description($language);
770
			}
771
		}
772
 
773
		$metadata['tags'] = $pagefile->tags;
774
		$filedata = $pagefile->filedata();
775
		if(count($filedata)) $metadata['filedata'] = $filedata;
776
 
777
		return $metadata;
778
	}
779
 
780
	/**
781
	 * Process input to add a file
782
	 * 
783
	 * @param string $filename
784
	 * @throws WireException
785
	 * 
786
	 */
787
	protected function ___processInputAddFile($filename) {
788
 
789
		$total = count($this->value); 
790
		$metadata = array();
791
		$rm = null;
792
 
793
		if($this->maxFiles > 1 && $total >= $this->maxFiles) return; 
794
 
795
		// allow replacement of file if maxFiles is 1
796
		if($this->maxFiles == 1 && $total) {
797
			$pagefile = $this->value->first();
798
			$metadata = $this->extractMetadata($pagefile, $metadata);
799
			$rm = true; 
800
			if($filename == $pagefile->basename) {
801
				// use overwrite mode rather than replace mode when single file and same filename
802
				if($this->overwrite) $rm = false;
803
			}
804
			if($rm) {
805
				if($this->overwrite) $this->processInputDeleteFile($pagefile);
806
				$this->singleFileReplacement = true; 
807
			}
808
		} 
809
 
810
		if($this->overwrite) {
811
			$pagefile = $this->value->get($filename); 
812
			clearstatcache();
813
			if($pagefile) {
814
				// already have a file of the same name
815
				if($pagefile instanceof Pageimage) $pagefile->removeVariations(); 
816
				$metadata = $this->extractMetadata($pagefile, $metadata);
817
			} else {
818
				// we don't have a file with the same name as the one that was uploaded
819
				// file must be in another files field on the same page, that could be problematic
820
				$ul = $this->getWireUpload();
821
				// see if any files were overwritten that weren't part of our field
822
				// if so, we need to restore them and issue an error
823
				$err = false;
824
				foreach($ul->getOverwrittenFiles() as $bakFile => $newFile) {
825
					if(basename($newFile) != $filename) continue; 
826
					$this->wire('files')->unlink($newFile); 	
827
					$this->wire('files')->rename($bakFile, $newFile); // restore
828
					$ul->error(sprintf($this->_('Refused file %s because it is already on the file system and owned by a different field.'), $filename)); 
829
					$err = true; 
830
				}
831
				if($err) return;
832
			}
833
		}
834
 
835
		$this->value->add($filename); 
836
		/** @var Pagefile $item */
837
		$item = $this->value->last();
838
 
839
		try {
840
			foreach($metadata as $key => $val) {
841
				if($val) $item->$key = $val;
842
			}
843
			// items saved in ajax or uploadOnly mode are temporary till saved in non-ajax/non-uploadOnly
844
			if($this->isAjax && !$this->overwrite) {
845
				if($this->wire('input')->get('InputfieldFileAjax') !== 'noTemp') {
846
					$item->isTemp(true);
847
				}
848
			}
849
			$this->fileAdded($item); 
850
		} catch(\Exception $e) {
851
			$item->unlink();
852
			$this->value->remove($item); 
853
			throw new WireException($e->getMessage()); 
854
		}
855
	}
856
 
857
	/**
858
	 * Process input to delete a Pagefile item
859
	 * 
860
	 * @param Pagefile $pagefile
861
	 * 
862
	 */
863
	protected function ___processInputDeleteFile(Pagefile $pagefile) {
864
		$fileLabel = $this->wire('config')->debug ? $pagefile->url() : $pagefile->name;
865
		$this->message($this->_("Deleted file:") . " $fileLabel"); // Label that precedes a deleted filename
866
		$this->value->delete($pagefile); 
867
		$this->trackChange('value');
868
	}
869
 
870
	/**
871
	 * Process input for one Pagefile
872
	 * 
873
	 * @param WireInputData $input
874
	 * @param Pagefile $pagefile
875
	 * @param int $n
876
	 * @return bool
877
	 * 
878
	 */
879
	protected function ___processInputFile(WireInputData $input, Pagefile $pagefile, $n) {
880
 
881
		$saveFields = false; // allow custom Inputfields to be saved?
882
		$changed = false; // are there any changes to this file?
883
		$id = $this->name . '_' . $pagefile->hash;
884
 
885
		if($this->uploadOnlyMode) {
886
			// skip files that aren't present as just uploaded
887
			$key = "sort_$id";
888
			if($input->$key === null) return false;
889
		}
890
 
891
		// replace (currently only used by InputfieldImage)
892
		$key = "replace_$id";
893
		$replace = $input->$key;
894
		if($replace) {
895
			if(strpos($replace, '?') !== false) {
896
				list($replace, $unused) = explode('?', $replace);
897
				if($unused) {}
898
			}
899
			$replaceFile = $this->value->getFile($replace);
900
			if($replaceFile && $replaceFile instanceof Pagefile) {
901
				$this->processInputDeleteFile($replaceFile);
902
				if(strtolower($pagefile->ext()) == strtolower($replaceFile->ext())) {
903
					$this->value->rename($pagefile, $replaceFile->name);
904
				}
905
				$changed = true; 
906
			}
907
		}
908
 
909
		// rename (currently only used by InputfieldImage)
910
		$key = "rename_$id";
911
		$rename = $input->$key;
912
		if(strlen($rename) && $rename != $pagefile->basename(false)) {
913
			$name = $pagefile->basename();
914
			$rename .= "." . $pagefile->ext();
915
			// cleanBasename($basename, $originalize = false, $allowDots = true, $translate = false) 
916
			$rename = $pagefile->pagefiles->cleanBasename($rename, true, true, true);
917
			$message = sprintf($this->_('Renamed file "%1$s" to "%2$s"'), $name, $rename);
918
			if($pagefile->rename($rename) !== false) {
919
				$this->message($message);
920
				$changed = true; 
921
			} else {
922
				$this->warning($this->_('Failed') . " - $message"); 
923
			}
924
		}
925
 
926
		// description and tags
927
		$languages = $this->noLang ? null : $this->wire('languages');
928
		$keys = $languages ? array('tags') : array('description', 'tags'); 
929
 
930
		foreach($keys as $key) { 
931
			if(isset($input[$key . '_' . $id])) { 
932
				$value = $input[$key . '_' . $id]; 
933
				if(is_array($value)) $value = implode(' ', $value);
934
				$value = trim($value); 
935
				if($value != $pagefile->$key) {
936
					$pagefile->$key = $value; 
937
					$changed = true; 
938
				}
939
			}
940
		}
941
 
942
		// multi-language descriptions
943
		if($languages) foreach($languages as $language) {
944
			if(!$languages->editable($language)) continue; 
945
			$key = $language->isDefault() ? "description_$id" : "description{$language->id}_$id";
946
			if(!isset($input[$key])) continue; 
947
			$value = trim($input[$key]); 
948
			if($value != $pagefile->description($language)) {
949
				$pagefile->description($language, $value); 
950
				$changed = true; 
951
			}
952
		}
953
 
954
		if($this->uploadOnlyMode) {
955
			if($this->uploadOnlyMode === 2) {
956
				$sort = 0; // ensures an isTemp(false) call occurs below
957
			} else {
958
				$sort = null;
959
			}
960
			$changed = true;
961
		} else {
962
			$key = "sort_$id";
963
			$sort = $input->$key;
964
			if($sort !== null) {
965
				$sort = (int) $sort; 
966
				$pagefile->set('sort', $sort);
967
				if($n !== $sort) $changed = true;
968
				$saveFields = true;
969
			}
970
		}
971
 
972
		if($saveFields) {
973
			// save custom Inputfields
974
			$inputfields = $this->getItemInputfields($pagefile);
975
			if($inputfields && $this->processItemInputfields($pagefile, $inputfields, $id, $input)) $changed = true;
976
		}
977
 
978
		if(isset($input['delete_' . $id])) {
979
			$this->processInputDeleteFile($pagefile); 
980
			$changed = true; 
981
 
982
		} else if(!$this->isAjax && !$this->overwrite && $pagefile->isTemp() && $sort !== null) {
983
			// if page saved with temporary items when not ajax, those temporary items become non-temp
984
			$pagefile->isTemp(false);
985
			// @todo should the next statement instead be this below?
986
			// if($this->maxFiles > 0) while(count($this->value) > $this->>maxFiles) { ... } ?
987
			if($this->maxFiles == 1) while(count($this->value) > 1) {
988
				$item = $this->value->first();
989
				$this->value->remove($item);
990
			}
991
			$changed = true;
992
		}
993
 
994
		return $changed; 
995
	}
996
 
997
	/**
998
	 * Process custom Inputfields for Pagefile item
999
	 * 
1000
	 * @param Pagefile $pagefile
1001
	 * @param InputfieldWrapper $inputfields
1002
	 * @param string $id Pagefile ID string
1003
	 * @param WireInputData $input
1004
	 * @return bool True if changes detected, false if not
1005
	 * @since 3.0.142
1006
	 * 
1007
	 */
1008
	protected function ___processItemInputfields(Pagefile $pagefile, InputfieldWrapper $inputfields, $id, WireInputData $input) {
1009
 
1010
		$changed = false;
1011
		$inputfields->resetTrackChanges(true);
1012
		$inputfields->processInput($input);
1013
 
1014
		foreach($inputfields->getAll() as $f) {
1015
			/** @var Inputfield $f */
1016
			foreach($f->getErrors(true) as $error) {
1017
				$f->error("$this->label ($pagefile->name): $error");
1018
			}
1019
			if(!$f->isChanged() && !$pagefile->isTemp()) {
1020
				continue;
1021
			}
1022
			$name = str_replace("_$id", '', $f->attr('name'));
1023
			if($f->getSetting('useLanguages')) {
1024
				$value = $pagefile->getFieldValue($name);
1025
				if(is_object($value)) $value->setFromInputfield($f);
1026
			} else {
1027
				$value = $f->val();
1028
			}
1029
			$pagefile->setFieldValue($name, $value, true);
1030
			$changed = true;
1031
		}
1032
 
1033
		return $changed;
1034
	}
1035
 
1036
	/**
1037
	 * Process input
1038
	 * 
1039
	 * @param WireInputData $input
1040
	 * @return self
1041
	 * 
1042
	 */
1043
	public function ___processInput(WireInputData $input) {
1044
 
1045
		if(is_null($this->value)) $this->value = $this->wire(new Pagefiles($this->wire('page')));
1046
		if(!$this->destinationPath) $this->destinationPath = $this->value->path();
1047
 
1048
		if(!$this->destinationPath || !is_dir($this->destinationPath)) {
1049
			return $this->error($this->_("destinationPath is empty or does not exist"));
1050
		}
1051
		if(!is_writable($this->destinationPath)) {
1052
			return $this->error($this->_("destinationPath is not writable"));
1053
		}
1054
 
1055
		$changed = false; 
1056
		$total = count($this->value); 
1057
 
1058
		if(!$this->noUpload) { 
1059
 
1060
			if($this->maxFiles <= 1 || $total < $this->maxFiles) { 
1061
 
1062
				$ul = $this->getWireUpload();
1063
				$ul->setName($this->attr('name')); 
1064
				$ul->setDestinationPath($this->destinationPath); 
1065
				$ul->setOverwrite($this->overwrite); 
1066
				$ul->setAllowAjax($this->noAjax ? false : true);
1067
				if($this->maxFilesize) $ul->setMaxFileSize($this->maxFilesize); 
1068
 
1069
				if($this->maxFiles == 1) {
1070
					$ul->setMaxFiles(1); 
1071
 
1072
				} else if($this->maxFiles) {
1073
					$maxFiles = $this->maxFiles - $total; 
1074
					$ul->setMaxFiles($maxFiles); 
1075
 
1076
				} else if($this->unzip) { 
1077
					$ul->setExtractArchives(true); 
1078
				}
1079
 
1080
				$ul->setValidExtensions(explode(' ', trim($this->extensions))); 
1081
 
1082
				foreach($ul->execute() as $filename) {
1083
					$this->processInputAddFile($filename); 
1084
					$changed = true; 
1085
				}
1086
 
1087
				if($this->isAjax && !$this->noAjax) foreach($ul->getErrors() as $error) { 
1088
					$this->ajaxResponse(true, $error); 
1089
				}
1090
 
1091
			} else if($this->maxFiles) {
1092
				// over the limit
1093
				$this->ajaxResponse(true, $this->_("Max file upload limit reached")); 
1094
			}
1095
		}
1096
 
1097
		$n = 0; 
1098
 
1099
		foreach($this->value as $pagefile) {
1100
			if($this->processInputFile($input, $pagefile, $n)) $changed = true; 
1101
			$n++; 
1102
		}
1103
 
1104
		if($changed) {
1105
			$this->value->sort('sort'); 
1106
			$this->trackChange('value'); 
1107
		}
1108
 
1109
		if(count($this->ajaxResponses) && $this->isAjax) {
1110
			echo $this->renderAjaxResponse();
1111
		}
1112
 
1113
		return $this; 
1114
	}
1115
 
1116
	/**
1117
	 * Render JSON response to AJAX request
1118
	 * 
1119
	 * @return string
1120
	 * 
1121
	 */
1122
	protected function renderAjaxResponse() {
1123
		if($this->wire('input')->get('ckeupload')) {
1124
			// https://docs.ckeditor.com/ckeditor4/docs/#!/guide/dev_file_upload
1125
			$a = $this->ajaxResponses[0];
1126
			$response = array(
1127
				'uploaded' => $a['error'] ? 0 : 1,
1128
				'fileName' => basename($a['file']),
1129
				'url' => $a['file'],
1130
				'ajaxResponse' => $a, // for InputfieldImage.js
1131
			);
1132
			if($a['error']) {
1133
				$response['error'] = array(
1134
					'message' => $a['message']
1135
				);
1136
			}
1137
			return json_encode($response);
1138
		} else {
1139
			return json_encode($this->ajaxResponses);
1140
		}
1141
	}
1142
 
1143
	/**
1144
	 * Send an ajax response
1145
	 *
1146
	 * @param bool $error Whether it was successful
1147
	 * @param string $message Message you want to return
1148
	 * @param string $file Full path and filename or blank if not applicable
1149
	 * @param string $size 
1150
	 * @param string $markup
1151
	 *
1152
	 */
1153
	protected function ajaxResponse($error, $message, $file = '', $size = '', $markup = '') {
1154
		$response = array(
1155
			'error' => $error, 
1156
			'message' => $message, 
1157
			'file' => $file,
1158
			'size' => $size,
1159
			'markup' => $markup, 
1160
			'replace' => $this->singleFileReplacement,
1161
			'overwrite' => $this->overwrite
1162
			);
1163
 
1164
		$this->ajaxResponses[] = $response; 
1165
	}
1166
 
1167
	/**
1168
	 * Return the current WireUpload instance or create a new one if not yet created
1169
	 *
1170
	 * @return WireUpload
1171
	 *
1172
	 */
1173
	public function getWireUpload() {
1174
		if(is_null($this->wireUpload)) $this->wireUpload = $this->wire(new WireUpload($this->attr('name'))); 
1175
		return $this->wireUpload; 
1176
	}
1177
 
1178
	/**
1179
	 * Template method: allow items to be collapsed?
1180
	 *
1181
	 * @return bool
1182
	 *
1183
	 */
1184
	protected function allowCollapsedItems() {
1185
		$allow = $this->descriptionRows == 0 && !$this->useTags && !$this->noCollapseItem;
1186
		if($allow && $this->hasField) {
1187
			/** @var FieldtypeFile $fieldtype */
1188
			$fieldtype = $this->hasField->type;
1189
			if($fieldtype->getFieldsTemplate($this->hasField)) $allow = false;
1190
		}
1191
		return $allow;
1192
	}
1193
 
1194
	/**
1195
	 * Format list of file extensions for output with upload field
1196
	 *
1197
	 * @param string $extensions
1198
	 * @return string
1199
	 *
1200
	 */
1201
	protected function formatExtensions($extensions) {
1202
		return $this->wire('sanitizer')->entities(str_replace(' ', ', ', trim($extensions)));
1203
	}
1204
 
1205
	/**
1206
	 * Get custom Inputfields for editing given Pagefile 
1207
	 * 
1208
	 * @param Pagefile|null $item Specify Pagefile item, or omit to prepare for render ready
1209
	 * @return bool|InputfieldWrapper
1210
	 * @since 3.0.142
1211
	 * 
1212
	 */
1213
	public function getItemInputfields(Pagefile $item = null) {
1214
 
1215
		/** @var Pagefiles $pagefiles */
1216
		$value = $this->val();
1217
		$pagefiles = $value instanceof Pagefile ? $value->pagefiles : $value;
1218
 
1219
		if(!$pagefiles instanceof Pagefiles) {
1220
			// no value present on this Inputfield
1221
			return false;
1222
		}
1223
 
1224
		if($this->itemFieldgroup === false) {
1225
			// item fieldgroup already determined not in use
1226
			return false;
1227
		}
1228
 
1229
		if($this->itemFieldgroup === null) {
1230
			// item fieldgroup not yet determined
1231
			$this->itemFieldgroup = false;
1232
			$template = $pagefiles->getFieldsTemplate();
1233
			if(!$template) return false;
1234
			$this->itemFieldgroup = $template->fieldgroup;
1235
		}
1236
 
1237
		/** @var Page $page */
1238
		$page = $pagefiles->getFieldsPage();
1239
		$id = $item ? ('_' . $this->pagefileId($item)) : '';
1240
 
1241
		$inputfields = $this->itemFieldgroup->getPageInputfields($page, $id, '', false); 
1242
		if(!$inputfields) return false;
1243
 
1244
		/** @var Languages|null $languages */
1245
		$languages = $this->wire('languages');
1246
 
1247
		foreach($inputfields->getAll() as $f) {
1248
 
1249
			if(!$item) {
1250
				// prepare inputfields for render rather than populating them
1251
				$f->renderReady();
1252
				continue;
1253
			}
1254
 
1255
			/** @var Inputfield $f */
1256
			$name = str_replace($id, '', $f->name);
1257
			$value = $item ? $item->getFieldValue($name) : null; 
1258
			if($value === null) continue;
1259
 
1260
			if($languages && $f->getSetting('useLanguages') && $value instanceof LanguagesValueInterface) {
1261
				foreach($languages as $language) {
1262
					$v = $value->getLanguageValue($language->id);
1263
					if($language->isDefault()) $f->val($v);
1264
					$f->set("value$language->id", $v);
1265
				}
1266
			} else if($f instanceof InputfieldCheckbox) {
1267
				if($value) $f->attr('checked', 'checked'); 
1268
			} else {
1269
				$f->val($value);
1270
			}
1271
 
1272
			if($f->className() === 'InputfieldCKEditor') {
1273
				// CKE does not like being placed in file/image fields.
1274
				// I'm sure it's possible, but needs more work and debugging, so it's disabled for now.
1275
				$allow = false;
1276
			} else {
1277
				$allow = true;
1278
			}
1279
 
1280
			if(!$allow) {
1281
				$inputfields->remove($f);
1282
				$this->prependMarkup =
1283
					"<p class='ui-state-error-text'>" .
1284
					sprintf($this->_('Field “%1$s” type “%2$s” is not supported in field “%3$s”'), $f->label, $f->className(), $this->label) .
1285
					'</p>';
1286
				$f->getParent()->remove($f);
1287
			}
1288
		}
1289
 
1290
		return $inputfields;
1291
	}
1292
 
1293
	/**
1294
	 * Configuration settings for InputfieldFile
1295
	 * 
1296
	 * @return InputfieldWrapper
1297
	 * 
1298
	 */
1299
	public function ___getConfigInputfields() {
1300
		$inputfields = parent::___getConfigInputfields();
1301
 
1302
		/** @var InputfieldCheckbox $f */
1303
		$f = $this->modules->get("InputfieldCheckbox"); 
1304
		$f->attr('name', 'unzip'); 
1305
		$f->attr('value', 1); 
1306
		$f->setAttribute('checked', $this->unzip ? 'checked' : ''); 
1307
		$f->label = $this->_('Decompress ZIP files?');
1308
		$f->description = $this->_("If checked, ZIP archives will be decompressed and all valid files added as uploads (if supported by the hosting environment). Max files must be set to 0 (no max) in order for ZIP uploads to be functional."); // Decompress ZIP files description
1309
		$f->collapsed = Inputfield::collapsedBlank;
1310
		$inputfields->append($f);
1311
 
1312
		$f = $this->modules->get("InputfieldCheckbox");
1313
		$f->attr('name', 'overwrite');
1314
		$f->label = $this->_('Overwrite existing files?');
1315
		$f->description = $this->_('If checked, a file uploaded with the same name as an existing file will replace the existing file (description and tags will remain). If not checked, uploaded filenames will be renamed to be unique.'); // Overwrite description
1316
		$f->notes = $this->_('Please note that when this option is enabled, AJAX-uploaded files are saved with the page immediately at upload, rather than when you click "save". As a result, you may wish to leave this option unchecked unless you have a specific need for it.'); // Overwrite notes
1317
		if($this->overwrite) $f->attr('checked', 'checked');
1318
		$f->collapsed = Inputfield::collapsedBlank;
1319
		$inputfields->append($f);
1320
 
1321
		$f = $this->modules->get("InputfieldInteger"); 
1322
		$f->attr('name', 'descriptionRows'); 
1323
		$f->attr('value', $this->descriptionRows !== null ? (int) $this->descriptionRows : 1); 
1324
		//$f->minValue = 0; 
1325
		//$f->maxValue = 30; 
1326
		$f->label = $this->_('Number of rows for description field?');
1327
		$f->description = $this->_("Enter the number of rows available for the file description field, or enter 0 to not have a description field."); // Number of rows description
1328
		$inputfields->append($f); 
1329
 
1330
		if($this->wire('languages') && $this->descriptionRows >= 1) {
1331
			$f = $this->modules->get("InputfieldCheckbox"); 
1332
			$f->attr('name', 'noLang'); 
1333
			$f->attr('value', 1); 
1334
			$f->setAttribute('checked', $this->noLang ? 'checked' : ''); 
1335
			$f->label = $this->_('Disable multi-language descriptions?');
1336
			$f->description = $this->_('By default, descriptions are multi-language when you have Language Support installed. If you want to disable multi-language descriptions, check this box.'); // Disable multi-language description
1337
			$inputfields->append($f); 
1338
		}
1339
 
1340
		return $inputfields; 	
1341
	}
1342
 
1343
 
1344
 
1345
}