1418 lines
42 KiB
Text
1418 lines
42 KiB
Text
|
<?php namespace ProcessWire;
|
||
|
|
||
|
/**
|
||
|
* An Inputfield for handling file uploads
|
||
|
*
|
||
|
* @property string $extensions Allowed file extensions, space separated
|
||
|
* @property array $okExtensions File extensions that are whitelisted if any in $extensions are problematic. (3.0.167+)
|
||
|
* @property int $maxFiles Maximum number of files allowed
|
||
|
* @property int $maxFilesize Maximum file size
|
||
|
* @property bool $useTags Whether or not tags are enabled
|
||
|
* @property string $tagsList Predefined tags
|
||
|
* @property bool|int $unzip Whether or not unzip is enabled
|
||
|
* @property bool|int $overwrite Whether or not overwrite mode is enabled
|
||
|
* @property int $descriptionRows Number of rows for description field (default=1, 0=disable)
|
||
|
* @property string $destinationPath Destination path for uploaded file
|
||
|
* @property string $itemClass Class name(s) for each file item (default=InputfieldFileItem ui-widget ui-widget-content)
|
||
|
* @property bool|int $noUpload Set to true or 1 to disable uploading to this field
|
||
|
* @property bool|int $noLang Set to true or 1 to disable multi-language descriptions
|
||
|
* @property bool|int $noAjax Set to true or 1 to disable ajax uploading
|
||
|
* @property int $uploadOnlyMode Set to true or 1 to disable existing file list display, or 2 to also prevent file from having 'temp' status.
|
||
|
* @property bool|int $noCollapseItem Set to true to disable collapsed items (like for LanguageTranslator tool or other things that add tools to files)
|
||
|
* @property bool|int $noShortName Set to true to disable shortened filenames in output
|
||
|
* @property bool|int $noCustomButton Set to true to disable use of the styled <input type='file'>
|
||
|
* @property Pagefiles|Pagefile|null $value
|
||
|
*
|
||
|
* @method string renderItem($pagefile, $id, $n)
|
||
|
* @method string renderList($value)
|
||
|
* @method string renderUpload($value)
|
||
|
* @method void fileAdded(Pagefile $pagefile)
|
||
|
* @method array extractMetadata(Pagefile $pagefile, array $metadata = array())
|
||
|
* @method void processInputAddFile($filename)
|
||
|
* @method void processInputDeleteFile(Pagefile $pagefile)
|
||
|
* @method bool processInputFile(WireInputData $input, Pagefile $pagefile, $n)
|
||
|
* @method bool processItemInputfields(Pagefile $pagefile, InputfieldWrapper $inputfields, $id, WireInputData $input)
|
||
|
*
|
||
|
*/
|
||
|
class InputfieldFile extends Inputfield implements InputfieldItemList, InputfieldHasSortableValue {
|
||
|
|
||
|
public static function getModuleInfo() {
|
||
|
return array(
|
||
|
'title' => __('Files', __FILE__), // Module Title
|
||
|
'summary' => __('One or more file uploads (sortable)', __FILE__), // Module Summary
|
||
|
'version' => 126,
|
||
|
'permanent' => true,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Cache of responses we'll be sending on ajax requests
|
||
|
*
|
||
|
*/
|
||
|
protected $ajaxResponses = array();
|
||
|
|
||
|
/**
|
||
|
* Was a file replaced?
|
||
|
*
|
||
|
*/
|
||
|
protected $singleFileReplacement = false;
|
||
|
|
||
|
/**
|
||
|
* Saved instanceof WireUpload in case API retrieval is needed (see getWireUpload() method)
|
||
|
*
|
||
|
*/
|
||
|
protected $wireUpload = null;
|
||
|
|
||
|
/**
|
||
|
* Set to the current Pagefile item when doing iteration
|
||
|
*
|
||
|
* @var Pagefile|null
|
||
|
*
|
||
|
*/
|
||
|
protected $currentItem = null;
|
||
|
|
||
|
/**
|
||
|
* True when field should behave in an upload only mode
|
||
|
*
|
||
|
* @var bool|int
|
||
|
*
|
||
|
*/
|
||
|
protected $uploadOnlyMode = 0;
|
||
|
|
||
|
/**
|
||
|
* This is true when we are only rendering the value rather than the inputs
|
||
|
*
|
||
|
* @var bool
|
||
|
*
|
||
|
*/
|
||
|
protected $renderValueMode = false;
|
||
|
|
||
|
/**
|
||
|
* True when in ajax mode
|
||
|
*
|
||
|
* @var bool
|
||
|
*
|
||
|
*/
|
||
|
protected $isAjax = false;
|
||
|
|
||
|
/**
|
||
|
* Admin theme specific settings
|
||
|
*
|
||
|
* @var array
|
||
|
*
|
||
|
*/
|
||
|
protected $themeSettings = array();
|
||
|
|
||
|
/**
|
||
|
* Commonly used text labels, translated, indexed by label name
|
||
|
*
|
||
|
* @var array
|
||
|
*
|
||
|
*/
|
||
|
protected $labels = array();
|
||
|
|
||
|
/**
|
||
|
* Cached value of Fieldgroup used for Pagefile custom fields, as used by getItemInputfields() method
|
||
|
*
|
||
|
* @var Fieldgroup|null|bool Null when not yet known, false when known not applicable, Fieldgroup when known and in use
|
||
|
*
|
||
|
*/
|
||
|
protected $itemFieldgroup = null;
|
||
|
|
||
|
/**
|
||
|
* Cached result from FieldtypeFile::getValidFileExtension()
|
||
|
*
|
||
|
* @var array
|
||
|
*
|
||
|
*/
|
||
|
protected $extensionsInfo = array();
|
||
|
|
||
|
/**
|
||
|
* Initialize the InputfieldFile
|
||
|
*
|
||
|
*/
|
||
|
public function init() {
|
||
|
parent::init();
|
||
|
|
||
|
// note: these two fields originate from FieldtypeFile.
|
||
|
// Initializing them here ensures this Inputfield has the values set automatically.
|
||
|
$this->set('extensions', '');
|
||
|
$this->set('okExtensions', array()); // manually whitelisted problematic extensions
|
||
|
$this->set('maxFiles', 0);
|
||
|
$this->set('maxFilesize', 0);
|
||
|
$this->set('useTags', 0);
|
||
|
$this->set('tagsList', '');
|
||
|
|
||
|
// native to this Inputfield
|
||
|
$this->set('unzip', 0);
|
||
|
$this->set('overwrite', 0);
|
||
|
$this->set('descriptionRows', 1);
|
||
|
$this->set('destinationPath', '');
|
||
|
$this->set('itemClass', 'InputfieldFileItem ui-widget ui-widget-content');
|
||
|
$this->set('noUpload', 0); // set to 1 to disable uploading to this field
|
||
|
$this->set('noLang', 0);
|
||
|
$this->set('noAjax', 0); // disable ajax uploading
|
||
|
$this->set('noCollapseItem', 0);
|
||
|
$this->set('noShortName', 0);
|
||
|
$this->set('noCustomButton', false);
|
||
|
$this->attr('type', 'file');
|
||
|
|
||
|
$this->labels = array(
|
||
|
'description' => $this->_('Description'),
|
||
|
'tags' => $this->_('Tags'),
|
||
|
'drag-drop' => $this->_('drag and drop files in here'),
|
||
|
'delete' => $this->_('Delete'),
|
||
|
'choose-file' => $this->_('Choose File'),
|
||
|
'choose-files' => $this->_('Choose Files'),
|
||
|
);
|
||
|
|
||
|
$this->isAjax = $this->wire('input')->get('InputfieldFileAjax')
|
||
|
|| $this->wire('input')->get('reloadInputfieldAjax')
|
||
|
|| $this->wire('input')->get('renderInputfieldAjax');
|
||
|
|
||
|
$this->setMaxFilesize(trim(ini_get('post_max_size')));
|
||
|
$this->uploadOnlyMode = (int) $this->wire('input')->get('uploadOnlyMode');
|
||
|
$this->addClass('InputfieldItemList', 'wrapClass');
|
||
|
$this->addClass('InputfieldHasFileList', 'wrapClass');
|
||
|
|
||
|
$themeDefaults = array(
|
||
|
'error' => "<span class='ui-state-error-text'>{out}</span>",
|
||
|
);
|
||
|
$themeSettings = $this->wire('config')->InputfieldFile;
|
||
|
$this->themeSettings = is_array($themeSettings) ? array_merge($themeDefaults, $themeSettings) : $themeDefaults;
|
||
|
}
|
||
|
|
||
|
public function get($key) {
|
||
|
if($key === 'renderValueMode') return $this->renderValueMode;
|
||
|
if($key === 'singleFileReplacement') return $this->singleFileReplacement;
|
||
|
if($key === 'descriptionFieldLabel') return $this->labels['description'];
|
||
|
if($key === 'tagsFieldLabel') return $this->labels['tags'];
|
||
|
if($key === 'deleteLabel') return $this->labels['delete'];
|
||
|
if($key === 'themeSettings') return $this->themeSettings;
|
||
|
return parent::get($key);
|
||
|
}
|
||
|
|
||
|
public function set($key, $value) {
|
||
|
if($key == 'maxFilesize') return $this->setMaxFilesize($value);
|
||
|
return parent::set($key, $value);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the max file size in bytes or use string like "30m", "2g" "500k"
|
||
|
*
|
||
|
* @param int|string $filesize
|
||
|
* @return $this
|
||
|
*
|
||
|
*/
|
||
|
public function setMaxFilesize($filesize) {
|
||
|
$max = $this->strToBytes($filesize);
|
||
|
$phpMax = $this->strToBytes(ini_get('upload_max_filesize'));
|
||
|
if($phpMax < $max) $max = $phpMax;
|
||
|
$this->maxFilesize = $max;
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convert string like "32M" to bytes (integer)
|
||
|
*
|
||
|
* @param string|int $filesize
|
||
|
* @return int
|
||
|
*
|
||
|
*/
|
||
|
protected function strToBytes($filesize) {
|
||
|
if(ctype_digit("$filesize")) {
|
||
|
$bytes = (int) $filesize;
|
||
|
} else {
|
||
|
$filesize = rtrim($filesize, 'bB'); // convert mb=>m, gb=>g, kb=>k
|
||
|
$last = strtolower(substr($filesize, -1));
|
||
|
if(ctype_alpha($last)) $filesize = rtrim($filesize, $last);
|
||
|
$filesize = (int) $filesize;
|
||
|
if($last == 'g') {
|
||
|
$bytes = (($filesize * 1024) * 1024) * 1024;
|
||
|
} else if($last == 'm') {
|
||
|
$bytes = ($filesize * 1024) * 1024;
|
||
|
} else if($last == 'k') {
|
||
|
$bytes = $filesize * 1024;
|
||
|
} else if($filesize > 0) {
|
||
|
$bytes = $filesize;
|
||
|
} else {
|
||
|
$bytes = (5 * 1024) * 1024;
|
||
|
}
|
||
|
}
|
||
|
return $bytes;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Per Inputfield interface, returns true when this field is empty
|
||
|
*
|
||
|
*/
|
||
|
public function isEmpty() {
|
||
|
return !wireCount($this->value);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set an attribute
|
||
|
*
|
||
|
* @param array|string $key
|
||
|
* @param array|int|string $value
|
||
|
* @return Inputfield|InputfieldFile
|
||
|
*
|
||
|
*/
|
||
|
public function setAttribute($key, $value) {
|
||
|
if($key == 'value') {
|
||
|
if($value instanceof Pagefile) {
|
||
|
// if given a Pagefile rather than a Pagefiles, use the Pagefiles instead
|
||
|
$value = $value->pagefiles;
|
||
|
}
|
||
|
if($value instanceof Pagefiles) {
|
||
|
$page = $value->page;
|
||
|
if($page && $page->template->noLang) $this->noLang = true;
|
||
|
}
|
||
|
}
|
||
|
return parent::setAttribute($key, $value);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check to ensure that the containing form as an 'enctype' attr needed for uploading files
|
||
|
*
|
||
|
*/
|
||
|
protected function checkFormEnctype() {
|
||
|
$parent = $this->parent;
|
||
|
while($parent) {
|
||
|
if($parent->attr('method') == 'post') {
|
||
|
if(!$parent->attr('enctype')) $parent->attr('enctype', 'multipart/form-data');
|
||
|
break;
|
||
|
}
|
||
|
$parent = $parent->parent;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the parent of this Inputfield
|
||
|
*
|
||
|
* @param InputfieldWrapper $parent
|
||
|
* @return $this
|
||
|
*
|
||
|
*/
|
||
|
public function setParent(InputfieldWrapper $parent) {
|
||
|
parent::setParent($parent);
|
||
|
$this->checkFormEnctype();
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the unique 'id' attribute for the given Pagefile
|
||
|
*
|
||
|
* @param Pagefile $pagefile
|
||
|
* @param string $context Optional context string (like for repeaters) 3.0.178+
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
protected function pagefileId(Pagefile $pagefile, $context = '') {
|
||
|
return $this->name . "_" . $context . $pagefile->hash;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render a description input for the given Pagefile
|
||
|
*
|
||
|
* @param Pagefile $pagefile
|
||
|
* @param string $id
|
||
|
* @param int $n
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
protected function renderItemDescriptionField(Pagefile $pagefile, $id, $n) {
|
||
|
|
||
|
if($n) {}
|
||
|
$out = '';
|
||
|
$tabs = '';
|
||
|
static $hasLangTabs = null;
|
||
|
static $langTabSettings = array();
|
||
|
|
||
|
if($this->renderValueMode) {
|
||
|
if($this->wire('languages')) {
|
||
|
$description = $pagefile->description($this->wire('user')->language);
|
||
|
} else {
|
||
|
$description = $pagefile->description;
|
||
|
}
|
||
|
if(strlen($description)) $description =
|
||
|
"<div class='InputfieldFileDescription detail'>" . $this->wire('sanitizer')->entities1($description) . "</div>";
|
||
|
return $description;
|
||
|
}
|
||
|
|
||
|
if($this->descriptionRows > 0) {
|
||
|
|
||
|
$userLanguage = $this->wire('user')->language;
|
||
|
$languages = $this->noLang ? null : $this->wire('languages');
|
||
|
$defaultDescriptionFieldLabel = $this->wire('sanitizer')->entities1($this->labels['description']);
|
||
|
|
||
|
if(!$userLanguage || !$languages || $languages->count() < 2) {
|
||
|
$numLanguages = 0;
|
||
|
$languages = array(null);
|
||
|
} else {
|
||
|
$numLanguages = $languages->count();
|
||
|
if(is_null($hasLangTabs)) {
|
||
|
$hasLangTabs = $this->wire('modules')->isInstalled('LanguageTabs');
|
||
|
if($hasLangTabs) {
|
||
|
/** @var LanguageTabs $languageTabs */
|
||
|
$languageTabs = $this->wire('modules')->getModule('LanguageTabs');
|
||
|
$langTabSettings = $languageTabs->getSettings();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
foreach($languages as $language) {
|
||
|
|
||
|
$descriptionFieldName = "description_$id";
|
||
|
$descriptionFieldLabel = $defaultDescriptionFieldLabel;
|
||
|
$labelClass = "detail";
|
||
|
$attrStr = '';
|
||
|
|
||
|
if($language) {
|
||
|
$tabField = empty($langTabSettings['tabField']) ? 'title' : $langTabSettings['tabField'];
|
||
|
$descriptionFieldLabel = (string) $language->getUnformatted($tabField);
|
||
|
if(empty($descriptionFieldLabel)) $descriptionFieldLabel = $language->get('name');
|
||
|
$descriptionFieldLabel = $this->wire('sanitizer')->entities($descriptionFieldLabel);
|
||
|
if(!$language->isDefault()) $descriptionFieldName = "description{$language->id}_$id";
|
||
|
$labelClass .= ' LanguageSupportLabel';
|
||
|
if(!$languages->editable($language)) {
|
||
|
$labelClass .= ' LanguageNotEditable';
|
||
|
$descriptionFieldLabel = "<s>$descriptionFieldLabel</s>";
|
||
|
}
|
||
|
$tabID = "langTab_{$id}__$language";
|
||
|
$aClass = "langTab$language";
|
||
|
if(!empty($langTabSettings['aClass'])) $aClass .= " " . $langTabSettings['aClass'];
|
||
|
$tabs .= "<li><a data-lang='$language' class='$aClass' href='#$tabID'>$descriptionFieldLabel</a></li>";
|
||
|
$out .= "<div class='InputfieldFileDescription LanguageSupport' data-language='$language' id='$tabID'>"; // open wrapper
|
||
|
} else {
|
||
|
$out .= "<div class='InputfieldFileDescription'>"; // open wrapper
|
||
|
$attrStr = "placeholder='$descriptionFieldLabel…'";
|
||
|
$labelClass = 'detail pw-hidden';
|
||
|
}
|
||
|
|
||
|
$attrStr = "name='$descriptionFieldName' id='$descriptionFieldName' $attrStr";
|
||
|
|
||
|
$out .= "<label for='$descriptionFieldName' class='$labelClass'>$descriptionFieldLabel</label>";
|
||
|
|
||
|
$description = $this->wire('sanitizer')->entities($pagefile->description($language));
|
||
|
|
||
|
if($this->descriptionRows > 1) {
|
||
|
$out .= "<textarea $attrStr rows='$this->descriptionRows'>$description</textarea>";
|
||
|
} else {
|
||
|
$out .= "<input type='text' $attrStr value='$description' />";
|
||
|
}
|
||
|
|
||
|
$out .= "</div>"; // close wrapper
|
||
|
}
|
||
|
|
||
|
if($numLanguages && $hasLangTabs) {
|
||
|
$ulClass = empty($langTabSettings['ulClass']) ? '' : " class='$langTabSettings[ulClass]'";
|
||
|
$ulAttr = empty($langTabSettings['ulAttrs']) ? '' : " $langTabSettings[ulAttrs]";
|
||
|
$out = "<div class='hasLangTabs langTabsContainer'><div class='langTabs'><ul $ulAttr$ulClass>$tabs</ul>$out</div></div>";
|
||
|
if($this->isAjax) $out .= "<script>setupLanguageTabs($('#wrap_" . $this->attr('id') . "'));</script>";
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
if($this->useTags) $out .= $this->renderItemTagsField($pagefile, $id, $n);
|
||
|
|
||
|
return $out;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render the tags input for the given Pagefile
|
||
|
*
|
||
|
* @param Pagefile $pagefile
|
||
|
* @param string $id
|
||
|
* @param int $n
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
protected function renderItemTagsField(Pagefile $pagefile, $id, $n) {
|
||
|
|
||
|
if($n) {}
|
||
|
$tagsLabel = $this->wire('sanitizer')->entities($this->labels['tags']) . '…';
|
||
|
$tagsStr = $this->wire('sanitizer')->entities($pagefile->tags);
|
||
|
$tagsAttr = '';
|
||
|
|
||
|
if($this->useTags >= FieldtypeFile::useTagsPredefined) {
|
||
|
// select predefined
|
||
|
$tagsClass = 'InputfieldFileTagsSelect';
|
||
|
$tagsAttr = "data-cfgname='InputfieldFileTags_{$this->hasField->name}' ";
|
||
|
|
||
|
} else {
|
||
|
// text input
|
||
|
$tagsClass = 'InputfieldFileTagsInput';
|
||
|
}
|
||
|
|
||
|
$out =
|
||
|
"<div class='InputfieldFileTags'>" .
|
||
|
"<label for='tags_$id' class='detail pw-hidden'>$tagsLabel</label>" .
|
||
|
"<input type='text' name='tags_$id' id='tags_$id' value='$tagsStr' " .
|
||
|
"placeholder='$tagsLabel' class='$tagsClass' $tagsAttr/>" .
|
||
|
"</div>";
|
||
|
|
||
|
return $out;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get a basename for the file, possibly shortened, suitable for display in InputfieldFileList
|
||
|
*
|
||
|
* @param Pagefile $pagefile
|
||
|
* @param int $maxLength
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
public function getDisplayBasename(Pagefile $pagefile, $maxLength = 25) {
|
||
|
$displayName = $pagefile->basename;
|
||
|
if($this->noShortName) return $displayName;
|
||
|
if(strlen($displayName) > $maxLength) {
|
||
|
$ext = ".$pagefile->ext";
|
||
|
$maxLength -= (strlen($ext) + 1);
|
||
|
$displayName = basename($displayName, $ext);
|
||
|
$displayName = substr($displayName, 0, $maxLength);
|
||
|
$displayName .= "…" . ltrim($ext, '.');
|
||
|
}
|
||
|
return $displayName;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render markup for a file item
|
||
|
*
|
||
|
* @param Pagefile $pagefile
|
||
|
* @param string $id
|
||
|
* @param int $n
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
protected function ___renderItem($pagefile, $id, $n) {
|
||
|
|
||
|
$displayName = $this->getDisplayBasename($pagefile);
|
||
|
$deleteLabel = $this->labels['delete'];
|
||
|
|
||
|
$out =
|
||
|
"<p class='InputfieldFileInfo InputfieldItemHeader ui-state-default ui-widget-header'>" .
|
||
|
wireIconMarkupFile($pagefile->basename, "fa-fw HideIfEmpty") . ' ' .
|
||
|
"<a class='InputfieldFileName' title='$pagefile->basename' target='_blank' href='{$pagefile->url}'>$displayName</a> " .
|
||
|
"<span class='InputfieldFileStats'>" . str_replace(' ', ' ', $pagefile->filesizeStr) . "</span> ";
|
||
|
|
||
|
if(!$this->renderValueMode) $out .=
|
||
|
"<label class='InputfieldFileDelete'>" .
|
||
|
"<input type='checkbox' name='delete_$id' value='1' title='$deleteLabel' />" .
|
||
|
"<i class='fa fa-fw fa-trash'></i></label>";
|
||
|
|
||
|
$description = $this->renderItemDescriptionField($pagefile, $id, $n);
|
||
|
$class = 'InputfieldFileData ';
|
||
|
$class .= $description ? 'description ui-widget-content' : 'InputfieldFileFields';
|
||
|
|
||
|
$out .= "</p><div class='$class'>" . $description;
|
||
|
|
||
|
$inputfields = $this->getItemInputfields($pagefile);
|
||
|
if($inputfields) $out .= $inputfields->render();
|
||
|
|
||
|
if(!$this->renderValueMode) {
|
||
|
$out .= "<input class='InputfieldFileSort' type='text' name='sort_$id' value='$n' />";
|
||
|
}
|
||
|
|
||
|
$out .= "</div>";
|
||
|
|
||
|
return $out;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Wrap output of files list item
|
||
|
*
|
||
|
* @param string $out
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
protected function renderItemWrap($out) {
|
||
|
// note: using currentItem rather than a new argument since there are now a few modules extending
|
||
|
// this one and if they implement their own calls to this method or version of this method then
|
||
|
// they will get strict notices from php if we add a new argument here.
|
||
|
$item = $this->currentItem;
|
||
|
$id = $item && !$this->renderValueMode ? " id='file_$item->hash'" : "";
|
||
|
return "<li$id class='{$this->itemClass}'>$out</li>";
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render files list ready
|
||
|
*
|
||
|
* @param Pagefiles|null $value
|
||
|
* @throws WireException
|
||
|
* @throws WirePermissionException
|
||
|
*
|
||
|
*/
|
||
|
protected function renderListReady($value) {
|
||
|
if(!$this->renderValueMode) {
|
||
|
// if just rendering the files list (as opposed to saving it), delete any temp files that may have accumulated
|
||
|
if(!$this->overwrite && !count($_POST) && !$this->isAjax && !$this->uploadOnlyMode) {
|
||
|
// don't delete files when in render single field or fields mode
|
||
|
if(!$this->wire('input')->get('field') && !$this->wire('input')->get('fields')) {
|
||
|
if($value instanceof Pagefiles) $value->deleteAllTemp();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render files list
|
||
|
*
|
||
|
* @param Pagefiles|null $value
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
protected function ___renderList($value) {
|
||
|
|
||
|
if(!$value) return '';
|
||
|
$out = '';
|
||
|
$n = 0;
|
||
|
|
||
|
$this->renderListReady($value);
|
||
|
|
||
|
if(!$this->uploadOnlyMode && WireArray::iterable($value)) {
|
||
|
foreach($value as $k => $pagefile) {
|
||
|
$id = $this->pagefileId($pagefile);
|
||
|
$this->currentItem = $pagefile;
|
||
|
$out .= $this->renderItemWrap($this->renderItem($pagefile, $id, $n++));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$class = 'InputfieldFileList ui-helper-clearfix';
|
||
|
if($this->overwrite && !$this->renderValueMode) $class .= " InputfieldFileOverwrite";
|
||
|
if($out) $out = "<ul class='$class'>$out</ul>";
|
||
|
|
||
|
return $out;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render upload area
|
||
|
*
|
||
|
* @param Pagefiles|null $value
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
protected function ___renderUpload($value) {
|
||
|
if($value) {}
|
||
|
if($this->noUpload || $this->renderValueMode) return '';
|
||
|
|
||
|
// enables user to choose more than one file
|
||
|
if($this->maxFiles != 1) $this->setAttribute('multiple', 'multiple');
|
||
|
|
||
|
$attrs = $this->getAttributes();
|
||
|
unset($attrs['value']);
|
||
|
if(substr($attrs['name'], -1) != ']') $attrs['name'] .= '[]';
|
||
|
|
||
|
$extensions = $this->getAllowedExtensions();
|
||
|
$formatExtensions = $this->formatExtensions();
|
||
|
$chooseLabel = $this->labels['choose-file'];
|
||
|
$dragDropLabel = $this->labels['drag-drop'];
|
||
|
$attrStr = $this->getAttributesString($attrs);
|
||
|
|
||
|
$out =
|
||
|
"<div " .
|
||
|
"data-maxfilesize='$this->maxFilesize' " .
|
||
|
"data-extensions='$extensions' " .
|
||
|
"data-fieldname='$attrs[name]' " .
|
||
|
"class='InputfieldFileUpload'>
|
||
|
";
|
||
|
|
||
|
if($this->getSetting('noCustomButton')) {
|
||
|
$out .= "<input $attrStr>";
|
||
|
|
||
|
} else {
|
||
|
$out .= "
|
||
|
<div class='InputMask ui-button ui-state-default'>
|
||
|
<span class='ui-button-text'>
|
||
|
<i class='fa fa-fw fa-folder-open-o'></i>$chooseLabel
|
||
|
</span>
|
||
|
<input $attrStr>
|
||
|
</div>
|
||
|
";
|
||
|
}
|
||
|
|
||
|
$out .= "
|
||
|
<span class='InputfieldFileValidExtensions detail'>$formatExtensions</span>
|
||
|
<input type='hidden' class='InputfieldFileMaxFiles' value='$this->maxFiles' />
|
||
|
";
|
||
|
|
||
|
if(!$this->noAjax) $out .= "
|
||
|
<span class='AjaxUploadDropHere description'>
|
||
|
<span>
|
||
|
<i class='fa fa-cloud-upload'></i> $dragDropLabel
|
||
|
</span>
|
||
|
</span>
|
||
|
";
|
||
|
|
||
|
$out .= "</div>"; // .InputfieldFileUpload
|
||
|
|
||
|
return $out;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render ready
|
||
|
*
|
||
|
* @param Inputfield|null $parent
|
||
|
* @param bool $renderValueMode
|
||
|
* @return bool
|
||
|
*
|
||
|
*/
|
||
|
public function renderReady(Inputfield $parent = null, $renderValueMode = false) {
|
||
|
|
||
|
/** @var Config $config */
|
||
|
$config = $this->wire('config');
|
||
|
|
||
|
$this->addClass('InputfieldNoFocus', 'wrapClass');
|
||
|
if(!$renderValueMode) $this->addClass('InputfieldHasUpload', 'wrapClass');
|
||
|
|
||
|
if($this->useTags) {
|
||
|
$this->wire('modules')->get('JqueryUI')->use('selectize');
|
||
|
$this->addClass('InputfieldFileHasTags', 'wrapClass');
|
||
|
if($this->useTags >= FieldtypeFile::useTagsPredefined && $this->hasField) {
|
||
|
// predefined tags
|
||
|
$fieldName = $this->hasField->name;
|
||
|
$jsName = "InputfieldFileTags_$fieldName";
|
||
|
$allowUserTags = $this->useTags & FieldtypeFile::useTagsNormal;
|
||
|
$data = $config->js($jsName);
|
||
|
if(!is_array($data)) $data = array();
|
||
|
if(empty($data['tags'])) {
|
||
|
$tags = array();
|
||
|
foreach(explode(' ', (string) $this->get('tagsList')) as $tag) {
|
||
|
$tag = trim($tag);
|
||
|
if(!strlen($tag)) continue;
|
||
|
$tags[strtolower($tag)] = $tag;
|
||
|
}
|
||
|
if($allowUserTags) {
|
||
|
$pagefiles = $this->val();
|
||
|
if($pagefiles instanceof Pagefiles) {
|
||
|
$_tags = $pagefiles->tags(true);
|
||
|
if(count($_tags)) $tags = array_merge($tags, $_tags);
|
||
|
}
|
||
|
}
|
||
|
$data['tags'] = array_values($tags);
|
||
|
$data['allowUserTags'] = $allowUserTags;
|
||
|
$config->js($jsName, $data);
|
||
|
}
|
||
|
$this->wrapAttr('data-configName', $jsName);
|
||
|
} else {
|
||
|
// regular tags text input
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$data = $config->js('InputfieldFile');
|
||
|
if(!is_array($data)) $data = array();
|
||
|
if(empty($data['labels'])) $data['labels'] = array();
|
||
|
if(empty($data['labels']['bad-ext'])) {
|
||
|
$data['labels']['bad-ext'] = $this->_('Unsupported file extension, please use only: EXTENSIONS');
|
||
|
$data['labels']['too-big'] = $this->_('File is too big - maximum allowed size is MAX_KB kb');
|
||
|
$config->js('InputfieldFile', $data);
|
||
|
}
|
||
|
|
||
|
$this->getItemInputfields(); // custom fields ready
|
||
|
|
||
|
return parent::renderReady($parent, $renderValueMode);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render Inputfield input
|
||
|
*
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
public function ___render() {
|
||
|
if(!$this->extensions) $this->error($this->_('No file extensions are defined for this field.'));
|
||
|
$numItems = wireCount($this->value);
|
||
|
if($this->allowCollapsedItems()) $this->addClass('InputfieldItemListCollapse', 'wrapClass');
|
||
|
if($numItems == 0) {
|
||
|
$this->addClass('InputfieldFileEmpty', 'wrapClass');
|
||
|
} else if($numItems == 1) {
|
||
|
$this->addClass('InputfieldFileSingle', 'wrapClass');
|
||
|
} else {
|
||
|
$this->addClass('InputfieldFileMultiple', 'wrapClass');
|
||
|
}
|
||
|
return $this->renderList($this->value) . $this->renderUpload($this->value);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render Inputfield value
|
||
|
*
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
public function ___renderValue() {
|
||
|
$this->renderValueMode = true;
|
||
|
$out = $this->render();
|
||
|
$this->renderValueMode = false;
|
||
|
return $out;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* File added hook
|
||
|
*
|
||
|
* @param Pagefile $pagefile
|
||
|
* @throws WireException
|
||
|
*
|
||
|
*/
|
||
|
protected function ___fileAdded(Pagefile $pagefile) {
|
||
|
if($this->noUpload) return;
|
||
|
$sanitizer = $this->wire()->sanitizer;
|
||
|
|
||
|
$isValid = $sanitizer->validateFile($pagefile->filename(), array(
|
||
|
'pagefile' => $pagefile
|
||
|
));
|
||
|
|
||
|
if($isValid === false) {
|
||
|
$errors = $sanitizer->errors('clear array');
|
||
|
throw new WireException(
|
||
|
"$pagefile->basename - " . $this->_('File failed validation') .
|
||
|
(count($errors) ? ": " . implode(', ', $errors) : "")
|
||
|
);
|
||
|
} else if($isValid === null) {
|
||
|
// there was no validator available for this file type
|
||
|
}
|
||
|
|
||
|
$message = $this->_('Added file:') . " {$pagefile->basename}"; // Label that precedes an added filename
|
||
|
|
||
|
if($this->isAjax && !$this->noAjax) {
|
||
|
$n = count($this->value);
|
||
|
if($n) $n--; // for sorting
|
||
|
$this->currentItem = $pagefile;
|
||
|
$markup = $this->fileAddedGetMarkup($pagefile, $n);
|
||
|
$this->ajaxResponse(false, $message, $pagefile->url, $pagefile->filesize(), $markup);
|
||
|
} else {
|
||
|
$this->message($message);
|
||
|
}
|
||
|
|
||
|
$pagefile->createdUser = $this->wire('user');
|
||
|
$pagefile->modifiedUser = $this->wire('user');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get markup for added file
|
||
|
*
|
||
|
* @param Pagefile $pagefile
|
||
|
* @param int $n
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
protected function fileAddedGetMarkup(Pagefile $pagefile, $n) {
|
||
|
return $this->renderItemWrap($this->renderItem($pagefile, $this->pagefileId($pagefile), $n));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Given a Pagefile return array of meta data pulled from it
|
||
|
*
|
||
|
* @param Pagefile $pagefile
|
||
|
* @param array $metadata Existing metadata, if applicable
|
||
|
* @return array Associative array of meta data (i.e. description and tags)
|
||
|
*
|
||
|
*/
|
||
|
protected function ___extractMetadata(Pagefile $pagefile, array $metadata = array()) {
|
||
|
|
||
|
$metadata['description'] = $pagefile->description;
|
||
|
|
||
|
/** @var Languages $languages */
|
||
|
$languages = $this->wire('languages');
|
||
|
if($languages && !$this->noLang) {
|
||
|
foreach($languages as $language) {
|
||
|
if($language->isDefault()) continue;
|
||
|
$metadata["description$language->id"] = $pagefile->description($language);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$metadata['tags'] = $pagefile->tags;
|
||
|
$filedata = $pagefile->filedata();
|
||
|
if(count($filedata)) $metadata['filedata'] = $filedata;
|
||
|
|
||
|
return $metadata;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Process input to add a file
|
||
|
*
|
||
|
* @param string $filename
|
||
|
* @throws WireException
|
||
|
*
|
||
|
*/
|
||
|
protected function ___processInputAddFile($filename) {
|
||
|
|
||
|
$total = count($this->value);
|
||
|
$metadata = array();
|
||
|
$rm = null;
|
||
|
|
||
|
if($this->maxFiles > 1 && $total >= $this->maxFiles) return;
|
||
|
|
||
|
// allow replacement of file if maxFiles is 1
|
||
|
if($this->maxFiles == 1 && $total) {
|
||
|
$pagefile = $this->value->first();
|
||
|
$metadata = $this->extractMetadata($pagefile, $metadata);
|
||
|
$rm = true;
|
||
|
if($filename == $pagefile->basename) {
|
||
|
// use overwrite mode rather than replace mode when single file and same filename
|
||
|
if($this->overwrite) $rm = false;
|
||
|
}
|
||
|
if($rm) {
|
||
|
if($this->overwrite) $this->processInputDeleteFile($pagefile);
|
||
|
$this->singleFileReplacement = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if($this->overwrite) {
|
||
|
$pagefile = $this->value->get($filename);
|
||
|
clearstatcache();
|
||
|
if($pagefile) {
|
||
|
// already have a file of the same name
|
||
|
if($pagefile instanceof Pageimage) $pagefile->removeVariations();
|
||
|
$metadata = $this->extractMetadata($pagefile, $metadata);
|
||
|
} else {
|
||
|
// we don't have a file with the same name as the one that was uploaded
|
||
|
// file must be in another files field on the same page, that could be problematic
|
||
|
$ul = $this->getWireUpload();
|
||
|
// see if any files were overwritten that weren't part of our field
|
||
|
// if so, we need to restore them and issue an error
|
||
|
$err = false;
|
||
|
foreach($ul->getOverwrittenFiles() as $bakFile => $newFile) {
|
||
|
if(basename($newFile) != $filename) continue;
|
||
|
$this->wire('files')->unlink($newFile);
|
||
|
$this->wire('files')->rename($bakFile, $newFile); // restore
|
||
|
$ul->error(sprintf($this->_('Refused file %s because it is already on the file system and owned by a different field.'), $filename));
|
||
|
$err = true;
|
||
|
}
|
||
|
if($err) return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$this->value->add($filename);
|
||
|
/** @var Pagefile $item */
|
||
|
$item = $this->value->last();
|
||
|
|
||
|
try {
|
||
|
foreach($metadata as $key => $val) {
|
||
|
if($val) $item->$key = $val;
|
||
|
}
|
||
|
// items saved in ajax or uploadOnly mode are temporary till saved in non-ajax/non-uploadOnly
|
||
|
if($this->isAjax && !$this->overwrite) {
|
||
|
if($this->wire('input')->get('InputfieldFileAjax') !== 'noTemp') {
|
||
|
$item->isTemp(true);
|
||
|
}
|
||
|
}
|
||
|
$this->fileAdded($item);
|
||
|
} catch(\Exception $e) {
|
||
|
$item->unlink();
|
||
|
$this->value->remove($item);
|
||
|
throw new WireException($e->getMessage());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Process input to delete a Pagefile item
|
||
|
*
|
||
|
* @param Pagefile $pagefile
|
||
|
*
|
||
|
*/
|
||
|
protected function ___processInputDeleteFile(Pagefile $pagefile) {
|
||
|
$fileLabel = $this->wire('config')->debug ? $pagefile->url() : $pagefile->name;
|
||
|
$this->message($this->_("Deleted file:") . " $fileLabel"); // Label that precedes a deleted filename
|
||
|
$this->value->delete($pagefile);
|
||
|
$this->trackChange('value');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Process input for one Pagefile
|
||
|
*
|
||
|
* @param WireInputData $input
|
||
|
* @param Pagefile $pagefile
|
||
|
* @param int $n
|
||
|
* @return bool
|
||
|
*
|
||
|
*/
|
||
|
protected function ___processInputFile(WireInputData $input, Pagefile $pagefile, $n) {
|
||
|
|
||
|
$saveFields = false; // allow custom Inputfields to be saved?
|
||
|
$changed = false; // are there any changes to this file?
|
||
|
$id = $this->name . '_' . $pagefile->hash;
|
||
|
|
||
|
if($this->uploadOnlyMode) {
|
||
|
// skip files that aren't present as just uploaded
|
||
|
$key = "sort_$id";
|
||
|
if($input->$key === null) return false;
|
||
|
}
|
||
|
|
||
|
// replace (currently only used by InputfieldImage)
|
||
|
$key = "replace_$id";
|
||
|
$replace = $input->$key;
|
||
|
if($replace) {
|
||
|
if(strpos($replace, '?') !== false) {
|
||
|
list($replace, $unused) = explode('?', $replace);
|
||
|
if($unused) {}
|
||
|
}
|
||
|
$replaceFile = $this->value->getFile($replace);
|
||
|
if($replaceFile && $replaceFile instanceof Pagefile) {
|
||
|
$this->processInputDeleteFile($replaceFile);
|
||
|
if(strtolower($pagefile->ext()) == strtolower($replaceFile->ext())) {
|
||
|
$this->value->rename($pagefile, $replaceFile->name);
|
||
|
}
|
||
|
$changed = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// rename (currently only used by InputfieldImage)
|
||
|
$key = "rename_$id";
|
||
|
$rename = $input->$key;
|
||
|
if(strlen($rename) && $rename != $pagefile->basename(false)) {
|
||
|
$name = $pagefile->basename();
|
||
|
$rename .= "." . $pagefile->ext();
|
||
|
// cleanBasename($basename, $originalize = false, $allowDots = true, $translate = false)
|
||
|
$rename = $pagefile->pagefiles->cleanBasename($rename, true, true, true);
|
||
|
$message = sprintf($this->_('Renamed file "%1$s" to "%2$s"'), $name, $rename);
|
||
|
if($pagefile->rename($rename) !== false) {
|
||
|
$this->message($message);
|
||
|
$changed = true;
|
||
|
} else {
|
||
|
$this->warning($this->_('Failed') . " - $message");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// description and tags
|
||
|
$languages = $this->noLang ? null : $this->wire('languages');
|
||
|
$keys = $languages ? array('tags') : array('description', 'tags');
|
||
|
|
||
|
foreach($keys as $key) {
|
||
|
if(isset($input[$key . '_' . $id])) {
|
||
|
$value = $input[$key . '_' . $id];
|
||
|
if(is_array($value)) $value = implode(' ', $value);
|
||
|
$value = trim($value);
|
||
|
if($value != $pagefile->$key) {
|
||
|
$pagefile->$key = $value;
|
||
|
$changed = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// multi-language descriptions
|
||
|
if($languages) foreach($languages as $language) {
|
||
|
if(!$languages->editable($language)) continue;
|
||
|
$key = $language->isDefault() ? "description_$id" : "description{$language->id}_$id";
|
||
|
if(!isset($input[$key])) continue;
|
||
|
$value = trim($input[$key]);
|
||
|
if($value != $pagefile->description($language)) {
|
||
|
$pagefile->description($language, $value);
|
||
|
$changed = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if($this->uploadOnlyMode) {
|
||
|
if($this->uploadOnlyMode === 2) {
|
||
|
$sort = 0; // ensures an isTemp(false) call occurs below
|
||
|
} else {
|
||
|
$sort = null;
|
||
|
}
|
||
|
$changed = true;
|
||
|
} else {
|
||
|
$key = "sort_$id";
|
||
|
$sort = $input->$key;
|
||
|
if($sort !== null) {
|
||
|
$sort = (int) $sort;
|
||
|
$pagefile->set('sort', $sort);
|
||
|
if($n !== $sort) $changed = true;
|
||
|
$saveFields = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if($saveFields) {
|
||
|
// save custom Inputfields
|
||
|
$inputfields = $this->getItemInputfields($pagefile);
|
||
|
if($inputfields && $this->processItemInputfields($pagefile, $inputfields, $id, $input)) $changed = true;
|
||
|
}
|
||
|
|
||
|
if(isset($input['delete_' . $id])) {
|
||
|
$this->processInputDeleteFile($pagefile);
|
||
|
$changed = true;
|
||
|
|
||
|
} else if(!$this->isAjax && !$this->overwrite && $pagefile->isTemp() && $sort !== null) {
|
||
|
// if page saved with temporary items when not ajax, those temporary items become non-temp
|
||
|
$pagefile->isTemp(false);
|
||
|
// @todo should the next statement instead be this below?
|
||
|
// if($this->maxFiles > 0) while(count($this->value) > $this->>maxFiles) { ... } ?
|
||
|
if($this->maxFiles == 1) while(count($this->value) > 1) {
|
||
|
$item = $this->value->first();
|
||
|
$this->value->remove($item);
|
||
|
}
|
||
|
$changed = true;
|
||
|
}
|
||
|
|
||
|
return $changed;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Process custom Inputfields for Pagefile item
|
||
|
*
|
||
|
* @param Pagefile $pagefile
|
||
|
* @param InputfieldWrapper $inputfields
|
||
|
* @param string $id Pagefile ID string
|
||
|
* @param WireInputData $input
|
||
|
* @return bool True if changes detected, false if not
|
||
|
* @since 3.0.142
|
||
|
*
|
||
|
*/
|
||
|
protected function ___processItemInputfields(Pagefile $pagefile, InputfieldWrapper $inputfields, $id, WireInputData $input) {
|
||
|
|
||
|
$changed = false;
|
||
|
$inputfields->resetTrackChanges(true);
|
||
|
$inputfields->processInput($input);
|
||
|
|
||
|
foreach($inputfields->getAll() as $f) {
|
||
|
/** @var Inputfield $f */
|
||
|
foreach($f->getErrors(true) as $error) {
|
||
|
$msg = "$this->label ($pagefile->name): $error";
|
||
|
$this->error($msg);
|
||
|
$f->error($msg);
|
||
|
}
|
||
|
if(!$f->isChanged() && !$pagefile->isTemp()) {
|
||
|
continue;
|
||
|
}
|
||
|
$name = str_replace("_$id", '', $f->attr('name'));
|
||
|
if($f->getSetting('useLanguages')) {
|
||
|
$value = $pagefile->getFieldValue($name);
|
||
|
if(is_object($value)) $value->setFromInputfield($f);
|
||
|
} else {
|
||
|
$value = $f->val();
|
||
|
}
|
||
|
$pagefile->setFieldValue($name, $value, true);
|
||
|
$changed = true;
|
||
|
}
|
||
|
|
||
|
return $changed;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Process input
|
||
|
*
|
||
|
* @param WireInputData $input
|
||
|
* @return self
|
||
|
*
|
||
|
*/
|
||
|
public function ___processInput(WireInputData $input) {
|
||
|
|
||
|
if(is_null($this->value)) $this->value = $this->wire(new Pagefiles($this->wire('page')));
|
||
|
if(!$this->destinationPath) $this->destinationPath = $this->value->path();
|
||
|
|
||
|
if(!$this->destinationPath || !is_dir($this->destinationPath)) {
|
||
|
return $this->error($this->_("destinationPath is empty or does not exist"));
|
||
|
}
|
||
|
if(!is_writable($this->destinationPath)) {
|
||
|
return $this->error($this->_("destinationPath is not writable"));
|
||
|
}
|
||
|
|
||
|
$changed = false;
|
||
|
$total = count($this->value);
|
||
|
|
||
|
if(!$this->noUpload) {
|
||
|
|
||
|
if($this->maxFiles <= 1 || $total < $this->maxFiles) {
|
||
|
|
||
|
$ul = $this->getWireUpload();
|
||
|
$ul->setName($this->attr('name'));
|
||
|
$ul->setDestinationPath($this->destinationPath);
|
||
|
$ul->setOverwrite($this->overwrite);
|
||
|
$ul->setAllowAjax($this->noAjax ? false : true);
|
||
|
if($this->maxFilesize) $ul->setMaxFileSize($this->maxFilesize);
|
||
|
|
||
|
if($this->maxFiles == 1) {
|
||
|
$ul->setMaxFiles(1);
|
||
|
|
||
|
} else if($this->maxFiles) {
|
||
|
$maxFiles = $this->maxFiles - $total;
|
||
|
$ul->setMaxFiles($maxFiles);
|
||
|
|
||
|
} else if($this->unzip) {
|
||
|
$ul->setExtractArchives(true);
|
||
|
}
|
||
|
|
||
|
$ul->setValidExtensions($this->getAllowedExtensions(true));
|
||
|
|
||
|
foreach($ul->execute() as $filename) {
|
||
|
$this->processInputAddFile($filename);
|
||
|
$changed = true;
|
||
|
}
|
||
|
|
||
|
if($this->isAjax && !$this->noAjax) foreach($ul->getErrors() as $error) {
|
||
|
$this->ajaxResponse(true, $error);
|
||
|
}
|
||
|
|
||
|
} else if($this->maxFiles) {
|
||
|
// over the limit
|
||
|
$this->ajaxResponse(true, $this->_("Max file upload limit reached"));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$n = 0;
|
||
|
|
||
|
foreach($this->value as $pagefile) {
|
||
|
if($this->processInputFile($input, $pagefile, $n)) $changed = true;
|
||
|
$n++;
|
||
|
}
|
||
|
|
||
|
if($changed) {
|
||
|
$this->value->sort('sort');
|
||
|
$this->trackChange('value');
|
||
|
}
|
||
|
|
||
|
if(count($this->ajaxResponses) && $this->isAjax) {
|
||
|
echo $this->renderAjaxResponse();
|
||
|
}
|
||
|
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render JSON response to AJAX request
|
||
|
*
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
protected function renderAjaxResponse() {
|
||
|
if($this->wire('input')->get('ckeupload')) {
|
||
|
// https://docs.ckeditor.com/ckeditor4/docs/#!/guide/dev_file_upload
|
||
|
$a = $this->ajaxResponses[0];
|
||
|
$response = array(
|
||
|
'uploaded' => $a['error'] ? 0 : 1,
|
||
|
'fileName' => basename($a['file']),
|
||
|
'url' => $a['file'],
|
||
|
'ajaxResponse' => $a, // for InputfieldImage.js
|
||
|
);
|
||
|
if($a['error']) {
|
||
|
$response['error'] = array(
|
||
|
'message' => $a['message']
|
||
|
);
|
||
|
}
|
||
|
return json_encode($response);
|
||
|
} else {
|
||
|
return json_encode($this->ajaxResponses);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Send an ajax response
|
||
|
*
|
||
|
* @param bool $error Whether it was successful
|
||
|
* @param string $message Message you want to return
|
||
|
* @param string $file Full path and filename or blank if not applicable
|
||
|
* @param string $size
|
||
|
* @param string $markup
|
||
|
*
|
||
|
*/
|
||
|
protected function ajaxResponse($error, $message, $file = '', $size = '', $markup = '') {
|
||
|
$response = array(
|
||
|
'error' => $error,
|
||
|
'message' => $message,
|
||
|
'file' => $file,
|
||
|
'size' => $size,
|
||
|
'markup' => $markup,
|
||
|
'replace' => $this->singleFileReplacement,
|
||
|
'overwrite' => $this->overwrite
|
||
|
);
|
||
|
|
||
|
$this->ajaxResponses[] = $response;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the current WireUpload instance or create a new one if not yet created
|
||
|
*
|
||
|
* @return WireUpload
|
||
|
*
|
||
|
*/
|
||
|
public function getWireUpload() {
|
||
|
if(is_null($this->wireUpload)) $this->wireUpload = $this->wire(new WireUpload($this->attr('name')));
|
||
|
return $this->wireUpload;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Template method: allow items to be collapsed?
|
||
|
*
|
||
|
* @return bool
|
||
|
*
|
||
|
*/
|
||
|
protected function allowCollapsedItems() {
|
||
|
$allow = $this->descriptionRows == 0 && !$this->useTags && !$this->noCollapseItem;
|
||
|
if($allow && $this->hasField) {
|
||
|
/** @var FieldtypeFile $fieldtype */
|
||
|
$fieldtype = $this->hasField->type;
|
||
|
if($fieldtype->getFieldsTemplate($this->hasField)) $allow = false;
|
||
|
}
|
||
|
return $allow;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Format list of file extensions for output with upload field
|
||
|
*
|
||
|
* @param array|string $extensions
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
protected function formatExtensions($extensions = '') {
|
||
|
$sanitizer = $this->wire()->sanitizer;
|
||
|
$badExtensions = array();
|
||
|
if(empty($extensions)) {
|
||
|
$info = $this->getExtensionsInfo();
|
||
|
$extensions = $info['valid'];
|
||
|
$badExtensions = $info['invalid'];
|
||
|
} else if(is_string($extensions)) {
|
||
|
while(strpos($extensions, ' ') !== false) $extensions = str_replace(' ', ' ', $extensions);
|
||
|
$extensions = explode(' ', trim($extensions));
|
||
|
}
|
||
|
$out = $sanitizer->entities(implode(', ', $extensions));
|
||
|
if(count($badExtensions)) {
|
||
|
if($out) $out .= ', ';
|
||
|
$out .= '<s>' . $sanitizer->entities(implode(', ', $badExtensions)) . '</s>';
|
||
|
}
|
||
|
return $out;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get allowed file extensions
|
||
|
*
|
||
|
* @param bool $getArray
|
||
|
* @return array|string
|
||
|
* @since 3.0.167
|
||
|
*
|
||
|
*/
|
||
|
protected function getAllowedExtensions($getArray = false) {
|
||
|
$info = $this->getExtensionsInfo();
|
||
|
$extensions = $info['valid'];
|
||
|
if($this->unzip && !$this->maxFiles) if(!in_array('zip', $extensions)) $extensions[] = 'zip';
|
||
|
return $getArray ? $extensions : implode(' ', $extensions);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get extensions info (see FieldtypeFile::getValidFileExtensions)
|
||
|
*
|
||
|
* @return array
|
||
|
* @since 3.0.167
|
||
|
*
|
||
|
*/
|
||
|
protected function getExtensionsInfo() {
|
||
|
if(empty($this->extensionsInfo)) {
|
||
|
$this->extensionsInfo = $this->wire()->fieldtypes->FieldtypeFile->getValidFileExtensions($this);
|
||
|
}
|
||
|
return $this->extensionsInfo;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get custom Inputfields for editing given Pagefile
|
||
|
*
|
||
|
* @param Pagefile|null $item Specify Pagefile item, or omit to prepare for render ready
|
||
|
* @return bool|InputfieldWrapper
|
||
|
* @since 3.0.142
|
||
|
*
|
||
|
*/
|
||
|
public function getItemInputfields(Pagefile $item = null) {
|
||
|
|
||
|
/** @var Pagefiles $pagefiles */
|
||
|
$value = $this->val();
|
||
|
$pagefiles = $value instanceof Pagefile ? $value->pagefiles : $value;
|
||
|
|
||
|
if(!$pagefiles instanceof Pagefiles) {
|
||
|
if($this->hasPage && $this->hasField) {
|
||
|
$value = $this->hasPage->get($this->hasField->name);
|
||
|
$pagefiles = $value instanceof Pagefile ? $value->pagefiles : $value;
|
||
|
}
|
||
|
if(!$pagefiles instanceof Pagefiles) {
|
||
|
// no value present on this Inputfield
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if($this->itemFieldgroup === false) {
|
||
|
// item fieldgroup already determined not in use
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if($this->itemFieldgroup === null) {
|
||
|
// item fieldgroup not yet determined
|
||
|
$this->itemFieldgroup = false;
|
||
|
$template = $pagefiles->getFieldsTemplate();
|
||
|
if(!$template) return false;
|
||
|
$this->itemFieldgroup = $template->fieldgroup;
|
||
|
}
|
||
|
|
||
|
$context = '';
|
||
|
$process = $this->wire()->process;
|
||
|
if($item && $process instanceof WirePageEditor) {
|
||
|
$contextPage = $process->getPage();
|
||
|
if(wireInstanceOf($contextPage, 'RepeaterPage')) {
|
||
|
$context = "repeater{$contextPage->id}_";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** @var Page $page */
|
||
|
$page = $pagefiles->getFieldsPage();
|
||
|
$id = $item ? ('_' . $this->pagefileId($item, $context)) : '';
|
||
|
$inputfields = $this->itemFieldgroup->getPageInputfields($page, $id, '', false);
|
||
|
if(!$inputfields) return false;
|
||
|
|
||
|
/** @var Languages|null $languages */
|
||
|
$languages = $this->wire('languages');
|
||
|
|
||
|
foreach($inputfields->getAll() as $f) {
|
||
|
|
||
|
if(!$item) {
|
||
|
// prepare inputfields for render rather than populating them
|
||
|
$f->renderReady();
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/** @var Inputfield $f */
|
||
|
$name = str_replace($id, '', $f->name);
|
||
|
$value = $item ? $item->getFieldValue($name) : null;
|
||
|
if($value === null) continue;
|
||
|
|
||
|
if($languages && $f->getSetting('useLanguages') && $value instanceof LanguagesValueInterface) {
|
||
|
foreach($languages as $language) {
|
||
|
$v = $value->getLanguageValue($language->id);
|
||
|
if($language->isDefault()) $f->val($v);
|
||
|
$f->set("value$language->id", $v);
|
||
|
}
|
||
|
} else if($f instanceof InputfieldCheckbox) {
|
||
|
if($value) $f->attr('checked', 'checked');
|
||
|
} else {
|
||
|
$f->val($value);
|
||
|
}
|
||
|
|
||
|
if($f->className() === 'InputfieldCKEditor') {
|
||
|
// CKE does not like being placed in file/image fields.
|
||
|
// I'm sure it's possible, but needs more work and debugging, so it's disabled for now.
|
||
|
$allow = false;
|
||
|
} else {
|
||
|
$allow = true;
|
||
|
}
|
||
|
|
||
|
if(!$allow) {
|
||
|
$inputfields->remove($f);
|
||
|
$this->prependMarkup =
|
||
|
"<p class='ui-state-error-text'>" .
|
||
|
sprintf($this->_('Field “%1$s” type “%2$s” is not supported in field “%3$s”'), $f->label, $f->className(), $this->label) .
|
||
|
'</p>';
|
||
|
$f->getParent()->remove($f);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $inputfields;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Configuration settings for InputfieldFile
|
||
|
*
|
||
|
* @return InputfieldWrapper
|
||
|
*
|
||
|
*/
|
||
|
public function ___getConfigInputfields() {
|
||
|
$inputfields = parent::___getConfigInputfields();
|
||
|
require_once(__DIR__ . '/config.php');
|
||
|
$configuration = new InputfieldFileConfiguration();
|
||
|
$this->wire($configuration);
|
||
|
$configuration->getConfigInputfields($this, $inputfields);
|
||
|
return $inputfields;
|
||
|
}
|
||
|
}
|