Index: branches/5.3.x/core/kernel/utility/formatters/upload_formatter.php =================================================================== diff -u -N -r15677 -r15902 --- branches/5.3.x/core/kernel/utility/formatters/upload_formatter.php (.../upload_formatter.php) (revision 15677) +++ branches/5.3.x/core/kernel/utility/formatters/upload_formatter.php (.../upload_formatter.php) (revision 15902) @@ -1,6 +1,6 @@ Application->isAdmin ) { + // this allows to revert kUtil::escape() call for each field submitted on front-end + $value = is_array($value) ? array_map('htmlspecialchars_decode', $value) : htmlspecialchars_decode($value); + } + + $options = $object->GetFieldOptions($field_name); + if ( getArrayValue($options, 'upload_dir') ) { $this->DestinationPath = $options['upload_dir']; $this->FullPath = FULL_PATH . $this->DestinationPath; } + + if ( is_array($value) && isset($value['tmp_ids']) ) { + // SWF Uploader + return $this->_processFlashUploader($value, $field_name, $object); + } + + return $this->_processRegularUploader($value, $field_name, $object); } /** - * Processes file uploads from form + * Handles uploaded files, provided by Flash uploader * - * @param mixed $value + * @param Array|string $value * @param string $field_name * @param kDBItem $object - * @return mixed - * @access public + * @return string + * @access protected */ - public function Parse($value, $field_name, &$object) + protected function _processFlashUploader($value, $field_name, $object) { - if ( !$this->Application->isAdmin ) { - // this allows to revert htmlspecialchars call for each field submitted on front-end - $value = is_array($value) ? array_map('htmlspecialchars_decode', $value) : htmlspecialchars_decode($value); + $options = $object->GetFieldOptions($field_name); + $this->sorting = isset($value['order']) ? explode('|', $value['order']) : Array (); + + if ( $value['tmp_deleted'] ) { + $n_upload = Array (); + $deleted = explode('|', $value['tmp_deleted']); + $upload = explode('|', $value['upload']); + + foreach ($upload as $name) { + if ( in_array($name, $deleted) ) { + continue; + } + + $n_upload[] = $name; + } + + $value['upload'] = implode('|', $n_upload); } - $ret = !is_array($value) ? $value : ''; - $options = $object->GetFieldOptions($field_name); + if ( !$value['tmp_ids'] ) { + // no pending files -> return already uploaded files + return $this->_sortFiles($value['upload']); + } - $this->_initUploadFolder($options); + $swf_uploaded_ids = explode('|', $value['tmp_ids']); + $swf_uploaded_names = explode('|', $value['tmp_names']); + $existing = $value['upload'] ? explode('|', $value['upload']) : Array (); - // SWF Uploader: BEGIN - if ( is_array($value) && isset($value['json']) ) { - $files_info = $this->_decodeJSON($value['json'], $options); - $this->Application->StoreVar($object->getFileInfoVariableName($field_name), serialize($files_info)); + $fret = Array (); + $max_files = $this->_getMaxFiles($options); + $pending_actions = $object->getPendingActions(); + $files_to_delete = $this->_getFilesToDelete($object); - return getArrayValue($value, 'upload'); + for ($i = 0; $i < min($max_files, count($swf_uploaded_ids)); $i++) { + // don't delete uploaded file, when it's name matches delete file name + $real_name = $this->_getRealFilename($swf_uploaded_names[$i], $options, $object, $files_to_delete); + $file_name = $this->FullPath . $real_name; + + $tmp_file = WRITEABLE . '/tmp/' . $swf_uploaded_ids[$i] . '_' . $swf_uploaded_names[$i]; + rename($tmp_file, $file_name); + + @chmod($file_name, 0666); + $fret[] = getArrayValue($options, 'upload_dir') ? $real_name : $this->DestinationPath . $real_name; + + $pending_actions[] = Array ( + 'action' => 'make_live', 'id' => $object->GetID(), 'field' => $field_name, 'file' => $file_name + ); + + $this->_renameFileInSorting($swf_uploaded_names[$i], $real_name); } - // SWF Uploader: END + $object->setPendingActions($pending_actions); + + return $this->_sortFiles(array_merge($existing, $fret)); + } + + /** + * Returns files, scheduled for deleting + * + * @param kDBItem $object + * @return Array + * @access protected + */ + protected function _getFilesToDelete($object) + { + $ret = Array (); + + foreach ($object->getPendingActions() as $data) { + if ( $data['action'] == 'delete' ) { + $ret[] = $data['file']; + } + } + + return $ret; + } + + /** + * Handles regular file upload + * + * @param string|Array $value + * @param string $field_name + * @param kDBItem $object + * @return string + * @access protected + */ + protected function _processRegularUploader($value, $field_name, $object) + { + $ret = !is_array($value) ? $value : ''; + $options = $object->GetFieldOptions($field_name); + if ( getArrayValue($value, 'upload') && getArrayValue($value, 'error') == UPLOAD_ERR_NO_FILE ) { // file was not uploaded this time, but was uploaded before, then use previously uploaded file (from db) return getArrayValue($value, 'upload'); } if ( is_array($value) && count($value) > 1 && $value['size'] ) { if ( is_array($value) && (int)$value['error'] === UPLOAD_ERR_OK ) { - $max_filesize = isset($options['max_size']) ? $options['max_size'] : MAX_UPLOAD_SIZE; + $max_file_size = isset($options['max_size']) ? $options['max_size'] : MAX_UPLOAD_SIZE; - // we can get mime type based on file content and no use one, provided by the client + // we can get mime type based on file content and don't use one, provided by the client // $value['type'] = kUtil::mimeContentType($value['tmp_name']); if ( getArrayValue($options, 'file_types') && !$this->extensionMatch($value['name'], $options['file_types']) ) { @@ -117,7 +213,7 @@ $object->SetError($field_name, 'bad_file_format', 'la_error_InvalidFileFormat', $error_params); } - elseif ( $value['size'] > $max_filesize ) { + elseif ( $value['size'] > $max_file_size ) { $object->SetError($field_name, 'bad_file_size', 'la_error_FileTooLarge'); } elseif ( !is_writable($this->FullPath) ) { @@ -164,8 +260,8 @@ // delete previous file, when new file is uploaded under same field /*$previous_file = isset($value['upload']) ? $value['upload'] : false; - if ($previous_file && file_exists($this->FullPath.$previous_file)) { - unlink($this->FullPath.$previous_file); + if ( $previous_file && file_exists($this->FullPath . $previous_file) ) { + unlink($this->FullPath . $previous_file); }*/ } } @@ -193,7 +289,7 @@ protected function extensionMatch($filename, $file_types) { if ( preg_match_all('/\*\.(.*?)(;|$)/', $file_types, $regs) ) { - $file_extension = mb_strtolower( pathinfo($filename, PATHINFO_EXTENSION) ); + $file_extension = mb_strtolower(pathinfo($filename, PATHINFO_EXTENSION)); $file_extensions = array_map('mb_strtolower', $regs[1]); return in_array($file_extension, $file_extensions); @@ -203,61 +299,25 @@ } /** - * Decodes JSON information about uploaded files + * Resorts uploaded files according to given file order * - * @param string $json - * @param Array $options - * @return Array + * @param Array|string $files + * @return string * @access protected */ - protected function _decodeJSON($json, $options) + protected function _sortFiles($files) { - if ( !$json ) { - return Array (); + if ( !is_array($files) ) { + $files = explode('|', $files); } - $ret = Array (); - $files_info = explode('|', $json); - $max_files = $this->_getMaxFiles($options); + $sorted_files = array_intersect($this->sorting, $files); // removes deleted files from sorting + $new_files = array_diff($files, $sorted_files); // files, that weren't sorted - add to the end - foreach ($files_info as $file_info) { - $file_info = (array)json_decode($file_info); - - if ( $file_info['deleted'] ) { - $ret[$file_info['name']] = $file_info; - } - elseif ( $max_files ) { - $ret[$file_info['name']] = $file_info; - $max_files--; - } - } - - uasort($ret, Array ($this, '_sortFiles')); - - return $ret; + return implode('|', array_merge($sorted_files, $new_files)); } /** - * Resorts uploaded files according to given file order - * - * @param $file_a - * @param $file_b - * @return int - * @access protected - */ - protected function _sortFiles($file_a, $file_b) - { - $file_a_order = isset($file_a['order']) ? (int)$file_a['order'] : 0; - $file_b_order = isset($file_b['order']) ? (int)$file_b['order'] : 0; - - if ( $file_a_order == $file_b_order ) { - return 0; - } - - return ($file_a_order < $file_b_order) ? -1 : 1; - } - - /** * Returns maximal allowed file count per field * * @param Array $options @@ -274,66 +334,6 @@ } /** - * Processes uploaded files - * - * @param kDBItem $object - * @param string $field_name - * @param int $id - * @return Array - */ - public function processFlashUpload($object, $field_name, $id = null) - { - $value = $object->GetDBField($field_name); - $options = $object->GetFieldOptions($field_name); - - $this->_initUploadFolder($options); - $files_info = $this->Application->RecallVar($object->getFileInfoVariableName($field_name, $id)); - - if ( !$files_info ) { - $this->Application->RemoveVar($object->getFileInfoVariableName($field_name, $id)); - - return Array (); - } - - $files_info = unserialize($files_info); - $live_files = $value ? explode('|', $value) : Array (); - - // don't rename file into file, that will be deleted - $files_to_delete = $this->_getFilesToDelete($object); - - foreach ($files_info as $file_name => $file_info) { - if ( $file_info['deleted'] ) { - // user deleted live file - $live_files = array_diff($live_files, Array ($file_name)); - } - elseif ( $file_info['temp'] == 1 ) { - // user uploaded new file to temp folder - - // 1. get unique filename for live folder - $real_name = $this->_getRealFilename($file_name, $options, $object, $files_to_delete); - $file_name = $this->FullPath . $real_name; - - // 2. move file from temp folder to live folder - $tmp_file = WRITEABLE . '/tmp/' . $file_info['id'] . '_' . $file_info['name']; - rename($tmp_file, $file_name); - - // 3. add to resulting file list - @chmod($file_name, 0666); - $live_files[] = getArrayValue($options, 'upload_dir') ? $real_name : $this->DestinationPath . $real_name; - } - } - - $this->Application->RemoveVar($object->getFileInfoVariableName($field_name, $id)); - $object->SetDBField($field_name, implode('|', $live_files)); - - if ( $object->GetOriginalField($field_name, true) != $object->GetField($field_name) ) { - return Array ($field_name); - } - - return Array (); - } - - /** * Returns final filename after applying storage-engine specific naming * * @param string $file_name @@ -352,48 +352,31 @@ } /** - * Returns list of files, that user marked for deletion + * Renames file in sorting list * - * @param kDBItem $object - * @return Array + * @param string $old_name + * @param string $new_name + * @return void * @access protected */ - protected function _getFilesToDelete($object) + protected function _renameFileInSorting($old_name, $new_name) { - $var_name = $object->getPendingActionVariableName(); - $schedule = $this->Application->RecallVar($var_name); + $index = array_search($old_name, $this->sorting); - if ( !$schedule ) { - return Array (); + if ( $index !== false ) { + $this->sorting[$index] = $new_name; } - - $ret = Array (); - $schedule = unserialize($schedule); - - foreach ($schedule as $data) { - if ( $data['action'] == 'delete' ) { - $ret[] = $data['file']; - } - } - - return $ret; } - /** - * Allows to determine single-file format based on multi-file format - * - * @param string $format - * @return string - * @access protected - */ - protected function getSingleFormat($format) + function getSingleFormat($format) { $single_mapping = Array ( + 'file_raw_urls' => 'raw_url', + 'file_display_names' => 'display_name', 'file_urls' => 'full_url', 'file_paths' => 'full_path', 'file_sizes' => 'file_size', 'files_resized' => 'resize', - 'files_json' => 'file_json', 'img_sizes' => 'img_size', 'wms' => 'wm', ); @@ -417,15 +400,12 @@ } $options = $object->GetFieldOptions($field_name); + if ( !isset($format) ) { $format = isset($options['format']) ? $options['format'] : false; } - if ( $format && preg_match('/(file_urls|file_paths|file_names|file_sizes|img_sizes|files_resized|files_json|wms)(.*)/', $format, $regs) ) { - if ( $format == 'files_json' ) { - $value = $this->_mergeFilesFromSession($value, $field_name, $object); - } - + if ( $format && preg_match('/(file_raw_urls|file_display_names|file_urls|file_paths|file_names|file_sizes|img_sizes|files_resized|wms)(.*)/', $format, $regs) ) { if ( !$value || $format == 'file_names' ) { // storage format matches display format OR no value return $value; @@ -443,122 +423,92 @@ } $tc_value = $this->TypeCast($value, $options); + if ( ($tc_value === false) || ($tc_value != $value) ) { // for leaving badly formatted date on the form return $value; } - // force direct links for case, when non-swf uploader is used - return $this->GetFormatted($tc_value, $field_name, $object, $format, true); + return $this->GetFormatted($tc_value, $field_name, $object, $format); } /** - * Merges filenames from session into database filenames list - * - * @param string $value - * @param string $field_name - * @param kDBItem $object - * @return string - */ - protected function _mergeFilesFromSession($value, $field_name, $object) - { - $files_info = $this->Application->RecallVar($object->getFileInfoVariableName($field_name)); - - if ( $files_info ) { - $temp_files = array_keys(unserialize($files_info)); - $live_files = $value ? explode('|', $value) : Array (); - - $value = implode('|', array_merge($live_files, $temp_files)); - } - - return $value; - } - - /** * Return formatted file url,path or size * * @param string $value * @param string $field_name * @param kDBItem $object * @param string $format - * @param bool $force_direct_links * @return string */ - function GetFormatted($value, $field_name, &$object, $format = NULL, $force_direct_links = NULL) + function GetFormatted($value, $field_name, &$object, $format = NULL) { - if ( !$format || !$value ) { + if ( !$format ) { return $value; } $options = $object->GetFieldOptions($field_name); $upload_dir = isset($options['include_path']) && $options['include_path'] ? '' : $this->getUploadDir($options); + $file_path = strlen($value) ? FULL_PATH . str_replace('/', DIRECTORY_SEPARATOR, $upload_dir) . $value : ''; if ( preg_match('/resize:([\d]*)x([\d]*)/', $format, $regs) ) { $image_helper = $this->Application->recallObject('ImageHelper'); /* @var $image_helper ImageHelper */ - return $image_helper->ResizeImage($value ? FULL_PATH . str_replace('/', DIRECTORY_SEPARATOR, $upload_dir) . $value : '', $format); + try { + return $image_helper->ResizeImage($file_path, $format); + } + catch ( \RuntimeException $e ) { + // error, during image resize -> return empty string + return ''; + } } + elseif ( !strlen($file_path) || !file_exists($file_path) ) { + // file doesn't exist OR not uploaded + return ''; + } switch ($format) { + case 'display_name': + return kUtil::removeTempExtension($value); + break; + + case 'raw_url': + return $this->fileHelper->pathToUrl($file_path); + break; + case 'full_url': - if ( isset($force_direct_links) ) { - $direct_links = $force_direct_links; - } - else { - $direct_links = isset($options['direct_links']) ? $options['direct_links'] : false; - } + $direct_links = isset($options['direct_links']) ? $options['direct_links'] : true; if ( $direct_links ) { - return $this->fileHelper->pathToUrl(FULL_PATH . $upload_dir . $value); + return $this->fileHelper->pathToUrl($file_path); } else { $url_params = Array ( 'no_amp' => 1, 'pass' => 'm,'.$object->Prefix, $object->Prefix . '_event' => 'OnViewFile', - 'file' => rawurlencode($value), 'field' => $field_name + 'file' => kUtil::escape($value, kUtil::ESCAPE_URL), 'field' => $field_name ); return $this->Application->HREF('', '', $url_params); } break; case 'full_path': - return FULL_PATH . str_replace('/', DIRECTORY_SEPARATOR, $upload_dir) . $value; + return $file_path; break; case 'file_size': - return filesize(FULL_PATH . str_replace('/', DIRECTORY_SEPARATOR, $upload_dir) . $value); + return filesize($file_path); break; case 'img_size': $image_helper = $this->Application->recallObject('ImageHelper'); /* @var $image_helper ImageHelper */ - $image_info = $image_helper->getImageInfo(FULL_PATH . str_replace('/', DIRECTORY_SEPARATOR, $upload_dir) . $value); + $image_info = $image_helper->getImageInfo($file_path); return $image_info ? $image_info[3] : ''; break; - - case 'file_json': - // get info about 1 file as JSON-encoded object - $files_info = $this->Application->RecallVar($object->getFileInfoVariableName($field_name)); - $files_info = $files_info ? unserialize($files_info) : Array (); - - if ( isset($files_info[$value]) ) { - // file that was uploaded, but not saved to database - return json_encode($files_info[$value]); - } - - $file_info = Array ( - 'id' => 'uploaded_' . crc32($value), - 'name' => $value, - 'size' => $this->GetFormatted($value, $field_name, $object, 'file_size'), - 'deleted' => 0, - 'temp' => 0, - ); - - return json_encode($file_info); - break; } return sprintf($format, $value); @@ -571,6 +521,7 @@ * @param array $options * @return string * @access protected + * @throws Exception */ protected function getStorageEngineFolder($file_name, $options) { @@ -645,6 +596,7 @@ } } + class kPictureFormatter extends kUploadFormatter { public function __construct() @@ -662,10 +614,9 @@ * @param string $field_name * @param kDBItem $object * @param string $format - * @param bool $force_direct_links * @return string */ - function GetFormatted($value, $field_name, &$object, $format = NULL, $force_direct_links = NULL) + function GetFormatted($value, $field_name, &$object, $format = NULL) { if ( $format == 'img_size' ) { $options = $object->GetFieldOptions($field_name); @@ -675,6 +626,6 @@ return ' ' . $image_info[3]; } - return parent::GetFormatted($value, $field_name, $object, $format, $force_direct_links); + return parent::GetFormatted($value, $field_name, $object, $format); } } \ No newline at end of file