__('Files', __FILE__), // Module Title 'summary' => __('One or more file uploads (sortable)', __FILE__), // Module Summary 'version' => 114, '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; /** * 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('maxFiles', 0); $this->set('maxFilesize', 0); $this->set('useTags', 0); // 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('descriptionFieldLabel', $this->_('Description')); // Description field label $this->set('tagsFieldLabel', $this->_('Tags')); // Tags field label $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->attr('type', 'file'); // get the max filesize $filesize = trim(ini_get('post_max_size')); $last = strtolower(substr($filesize, -1)); if($last == 'g') $this->maxFilesize = (($filesize*1024)*1024)*1024; else if($last == 'm') $this->maxFilesize = ($filesize*1024)*1024; else if($last == 'k') $this->maxFilesize = $filesize*1024; else $this->maxFilesize = (5*1024)*1024; } /** * Per Inputfield interface, returns true when this field is empty * */ public function isEmpty() { return !count($this->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; } protected function pagefileId(Pagefile $pagefile) { return $this->name . "_" . $pagefile->hash; } protected function renderItemDescriptionField(Pagefile $pagefile, $id, $n) { $out = ''; $tabs = ''; static $hasLangTabs = null; if($this->descriptionRows > 0) { $userLanguage = $this->wire('user')->language; $languages = $this->noLang ? null : $this->wire('languages'); 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'); } foreach($languages as $language) { $descriptionFieldName = "description_$id"; $descriptionFieldLabel = $this->descriptionFieldLabel; $labelClass = "detail"; if($language) { $descriptionFieldLabel = (string) $language->getUnformatted('title'); if(empty($descriptionFieldLabel)) $descriptionFieldLabel = $language->get('name'); $descriptionFieldLabel = $this->wire('sanitizer')->entities($descriptionFieldLabel); if(!$language->isDefault()) $descriptionFieldName = "description{$language->id}_$id"; $tabID = "langTab_{$id}__$language"; $tabs .= "
  • " . $language->get('title|name') . "
  • "; $out .= "
    "; // open wrapper $labelClass .= ' LanguageSupportLabel'; } else { $out .= "
    "; // open wrapper } $out .= ""; $description = $this->wire('sanitizer')->entities($pagefile->description($language)); if($this->descriptionRows > 1) { $out .= ""; } else { $out .= ""; } $out .= "
    "; // close wrapper } if($numLanguages && $hasLangTabs) { $ajax = $this->wire('config')->ajax; $out = "
      $tabs
    $out
    "; if($ajax) $out .= ""; } } if($this->useTags) { $tags = $this->wire('sanitizer')->entities($pagefile->tags); $out .= "" . "" . "" . ""; } return $out; } protected function ___renderItem($pagefile, $id, $n) { $out = "\n\t\t

    " . "\n\t\t\t" . "\n\t\t\t" . "\n\t\t\t{$pagefile->basename} " . "\n\t\t\t{$pagefile->filesizeStr} " . "\n\t\t\t" . "\n\t\t

    " . "\n\t\t
    " . "\n\t\t\t" . $this->renderItemDescriptionField($pagefile, $id, $n) . "\n\t\t\t" . "\n\t\t
    "; return $out; } protected function renderItemWrap($out) { return "\n\t
  • $out\n\t
  • "; } protected function ___renderList($value) { if(!$value) return ''; $out = ''; $n = 0; foreach($value as $k => $pagefile) { $id = $this->pagefileId($pagefile); $out .= $this->renderItemWrap($this->renderItem($pagefile, $id, $n++)); } $class = 'InputfieldFileList ui-helper-clearfix'; if($this->overwrite) $class .= " InputfieldFileOverwrite"; if($out) $out = "\n"; return $out; } protected function ___renderUpload($value) { if($this->noUpload) 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->extensions; if($this->unzip && !$this->maxFiles) $extensions .= ' zip'; $out = "\n

    " . "\n\t" . "\n\tgetAttributesString($attrs) . " />" . "\n\t" . htmlspecialchars(str_replace(' ', ', ', trim($extensions))) . ""; if(!$this->noAjax) $out .= "\n\t  " . $this->_('drag and drop files in here') . ""; // Ajax upload instruction $out .= "\n

    "; return $out; } public function ___render() { if(!$this->extensions) $this->error($this->_('No file extensions are defined for this field.')); return $this->renderList($this->value) . $this->renderUpload($this->value); } protected function ___fileAdded(Pagefile $pagefile) { if($this->noUpload) return; $message = $this->_('Added file:') . " {$pagefile->basename}"; // Label that precedes an added filename if($this->config->ajax && !$this->noAjax) { $n = count($this->value); if($n) $n--; // for sorting $markup = $this->renderItemWrap($this->renderItem($pagefile, $this->pagefileId($pagefile), $n)); $this->ajaxResponse(false, $message, $pagefile->url, $pagefile->filesize(), $markup); } else { $this->message($message); } } protected function ___processInputAddFile($filename) { $total = count($this->value); $description = ''; $tags = ''; 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(); $description = $pagefile->description; $tags = $pagefile->tags; $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) { $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(); $description = $pagefile->description; $tags = $pagefile->tags; } 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; unlink($newFile); 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); $item = $this->value->last(); try { if($description) $item->description = $description; if($tags) $item->tags = $tags; $this->fileAdded($item); } catch(Exception $e) { $item->unlink(); $this->value->remove($item); throw new WireException($e->getMessage()); } } protected function ___processInputDeleteFile(Pagefile $pagefile) { $this->message($this->_("Deleted file:") . " $pagefile"); // Label that precedes a deleted filename $this->value->delete($pagefile); } protected function ___processInputFile(WireInputData $input, Pagefile $pagefile, $n) { $id = $this->name . '_' . $pagefile->hash; $changed = false; $languages = $this->noLang ? null : $this->wire('languages'); $keys = $languages ? array('tags') : array('description', 'tags'); foreach($keys as $key) { if(isset($input[$key . '_' . $id])) { $value = trim($input[$key . '_' . $id]); if($value != $pagefile->$key) { $pagefile->$key = $value; $changed = true; } } } // multi-language descriptions if($languages) foreach($languages as $language) { $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(isset($input['delete_' . $id])) { $this->processInputDeleteFile($pagefile); $changed = true; } $key = "sort_$id"; $val = (int) $input->$key; if($val !== NULL) { $pagefile->sort = $val; if($n !== $val) $changed = true; } return $changed; } public function ___processInput(WireInputData $input) { if(is_null($this->value)) $this->value = new Pagefiles($this->fuel('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(explode(' ', trim($this->extensions))); foreach($ul->execute() as $filename) { $this->processInputAddFile($filename); $changed = true; } if($this->config->ajax && !$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->config->ajax) { echo json_encode($this->ajaxResponses); } return $this; } /** * Send an ajax response * * $error bool Whether it was successful * $message string Message you want to return * $file string Full path and filename or blank if not applicable * */ 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 = new WireUpload($this->attr('name')); return $this->wireUpload; } public function ___getConfigInputfields() { $inputfields = parent::___getConfigInputfields(); $f = $this->modules->get("InputfieldCheckbox"); $f->attr('name', 'unzip'); $f->attr('value', 1); $f->setAttribute('checked', $this->unzip ? 'checked' : ''); $f->label = $this->_('Decompress ZIP files?'); $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 $f->columnWidth = 50; $inputfields->append($f); $f = $this->modules->get("InputfieldCheckbox"); $f->attr('name', 'overwrite'); $f->label = $this->_('Overwrite existing files?'); $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 if($this->overwrite) $f->attr('checked', 'checked'); $f->columnWidth = 50; $inputfields->append($f); $f = $this->modules->get("InputfieldInteger"); $f->attr('name', 'descriptionRows'); $f->attr('value', $this->descriptionRows !== null ? (int) $this->descriptionRows : 1); //$f->minValue = 0; //$f->maxValue = 30; $f->label = $this->_('Number of rows for description field?'); $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 $inputfields->append($f); if($this->wire('languages') && $this->descriptionRows >= 1) { $f = $this->modules->get("InputfieldCheckbox"); $f->attr('name', 'noLang'); $f->attr('value', 1); $f->setAttribute('checked', $this->noLang ? 'checked' : ''); $f->label = $this->_('Disable multi-language descriptions?'); $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 $inputfields->append($f); } return $inputfields; } }