Index: branches/5.2.x/core/kernel/utility/formatters/upload_formatter.php =================================================================== diff -u -N -r15347 -r15446 --- branches/5.2.x/core/kernel/utility/formatters/upload_formatter.php (.../upload_formatter.php) (revision 15347) +++ branches/5.2.x/core/kernel/utility/formatters/upload_formatter.php (.../upload_formatter.php) (revision 15446) @@ -1,6 +1,6 @@ fileHelper = $this->Application->recallObject('FileHelper'); - if ($this->DestinationPath) { - $this->FullPath = FULL_PATH.$this->DestinationPath; + if ( $this->DestinationPath ) { + $this->FullPath = FULL_PATH . $this->DestinationPath; } } /** + * Initializes upload folder + * + * @param Array $options + * @return void + * @access protected + */ + protected function _initUploadFolder($options) + { + if ( getArrayValue($options, 'upload_dir') ) { + $this->DestinationPath = $options['upload_dir']; + $this->FullPath = FULL_PATH . $this->DestinationPath; + } + } + + /** * Processes file uploads from form * * @param mixed $value @@ -64,87 +76,24 @@ $ret = !is_array($value) ? $value : ''; $options = $object->GetFieldOptions($field_name); - if (getArrayValue($options, 'upload_dir')) { - $this->DestinationPath = $options['upload_dir']; - $this->FullPath = FULL_PATH.$this->DestinationPath; - } + $this->_initUploadFolder($options); - // SWF Uploader - if (is_array($value) && isset($value['tmp_ids'])) { - $this->sorting = isset($value['order']) ? explode('|', $value['order']) : 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)); - if ($value['tmp_deleted']) { - $deleted = explode('|', $value['tmp_deleted']); - $upload = explode('|', $value['upload']); - $n_upload = array(); -// $n_ids = array(); - foreach ($upload as $name) { - if (in_array($name, $deleted)) continue; - $n_upload[] = $name; -// $n_ids[] = $name; - } - $value['upload'] = implode('|', $n_upload); -// $value['tmp_ids'] = implode('|', $n_ids); - } - - if (!$value['tmp_ids']) { - // no pending files -> return already uploaded files - return $this->_sortFiles($value['upload']); - } - - $swf_uploaded_ids = explode('|', $value['tmp_ids']); - $swf_uploaded_names = explode('|', $value['tmp_names']); - $existing = $value['upload'] ? explode('|', $value['upload']) : array(); - - if (isset($options['multiple'])) { - $max_files = $options['multiple'] == false ? 1 : $options['multiple']; - } - else { - $max_files = 1; - } - - $fret = array(); - - // don't delete uploaded file, when it's name matches delete file name - $var_name = $object->getPrefixSpecial().'_file_pending_actions'.$this->Application->GetVar('m_wid'); - $schedule = $this->Application->RecallVar($var_name); - $schedule = $schedule ? unserialize($schedule) : Array(); - $files2delete = Array(); - - foreach ($schedule as $data) { - if ($data['action'] == 'delete') { - $files2delete[] = $data['file']; - } - } - - for ($i = 0; $i < min($max_files, count($swf_uploaded_ids)); $i++) { - $real_name = $this->getStorageEngineFile($swf_uploaded_names[$i], $options, $object->Prefix); - $real_name = $this->getStorageEngineFolder($real_name, $options) . $real_name; - - $real_name = $this->fileHelper->ensureUniqueFilename($this->FullPath, $real_name, $files2delete); - $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; - - $this->_renameFileInSorting($swf_uploaded_names[$i], $real_name); - } - - return $this->_sortFiles(array_merge($existing, $fret)); + return getArrayValue($value, 'upload'); } + // SWF Uploader: END - // SWF Uploader END - - if (getArrayValue($value, 'upload') && getArrayValue($value, 'error') == UPLOAD_ERR_NO_FILE) { + 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) && $value['error'] === UPLOAD_ERR_OK) { + if ( is_array($value) && count($value) > 1 && $value['size'] ) { + if ( is_array($value) && $value['error'] === UPLOAD_ERR_OK ) { $max_filesize = 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 @@ -175,10 +124,7 @@ $object->SetError($field_name, 'cant_save_file', 'la_error_cant_save_file'); } else { - $real_name = $this->getStorageEngineFile($value['name'], $options, $object->Prefix); - $real_name = $this->getStorageEngineFolder($real_name, $options) . $real_name; - - $real_name = $this->fileHelper->ensureUniqueFilename($this->FullPath, $real_name); + $real_name = $this->_getRealFilename($value['name'], $options, $object); $file_name = $this->FullPath . $real_name; $storage_format = isset($options['storage_format']) ? $options['storage_format'] : false; @@ -237,68 +183,212 @@ } /** - * Resorts uploaded files according to given file order + * Checks, that given file name has on of provided file extensions * - * @param Array|string $new_files - * @return string + * @param string $filename + * @param string $file_types + * @return bool * @access protected */ - protected function _sortFiles($files) + protected function extensionMatch($filename, $file_types) { - if ( !is_array($files) ) { - $files = explode('|', $files); + if ( preg_match_all('/\*\.(.*?)(;|$)/', $file_types, $regs) ) { + $file_extension = mb_strtolower( pathinfo($filename, PATHINFO_EXTENSION) ); + $file_extensions = array_map('mb_strtolower', $regs[1]); + + return in_array($file_extension, $file_extensions); } - $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 + return true; + } - return implode('|', array_merge($sorted_files, $new_files)); + /** + * Decodes JSON information about uploaded files + * + * @param string $json + * @param Array $options + * @return Array + * @access protected + */ + protected function _decodeJSON($json, $options) + { + if ( !$json ) { + return Array (); + } + + $ret = Array (); + $files_info = explode('|', $json); + $max_files = $this->_getMaxFiles($options); + + 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; } /** - * Renames file in sorting list + * Resorts uploaded files according to given file order * - * @param string $old_name - * @param string $new_name - * @return void + * @param $file_a + * @param $file_b + * @return int * @access protected */ - protected function _renameFileInSorting($old_name, $new_name) + protected function _sortFiles($file_a, $file_b) { - $index = array_search($old_name, $this->sorting); + $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 ( $index !== false ) { - $this->sorting[$index] = $new_name; + if ( $file_a_order == $file_b_order ) { + return 0; } + + return ($file_a_order < $file_b_order) ? -1 : 1; } /** - * Checks, that given file name has on of provided file extensions + * Returns maximal allowed file count per field * - * @param string $filename - * @param string $file_types - * @return bool + * @param Array $options + * @return int * @access protected */ - protected function extensionMatch($filename, $file_types) + protected function _getMaxFiles($options) { - if ( preg_match_all('/\*\.(.*?)(;|$)/', $file_types, $regs) ) { - $file_extension = mb_strtolower( pathinfo($filename, PATHINFO_EXTENSION) ); - $file_extensions = array_map('mb_strtolower', $regs[1]); + if ( !isset($options['multiple']) ) { + return 1; + } - return in_array($file_extension, $file_extensions); + return $options['multiple'] == false ? 1 : $options['multiple']; + } + + /** + * Processes uploaded files + * + * @param kDBItem $object + * @param string $field_name + * @param int $id + * @return string + */ + 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 $value; } - return true; + $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)); + + return implode('|', $live_files); } - function getSingleFormat($format) + /** + * Returns final filename after applying storage-engine specific naming + * + * @param string $file_name + * @param Array $options + * @param kDBItem $object + * @param Array $files_to_delete + * @return string + * @access protected + */ + protected function _getRealFilename($file_name, $options, $object, $files_to_delete = Array ()) { + $real_name = $this->getStorageEngineFile($file_name, $options, $object->Prefix); + $real_name = $this->getStorageEngineFolder($real_name, $options) . $real_name; + + return $this->fileHelper->ensureUniqueFilename($this->FullPath, $real_name, $files_to_delete); + } + + /** + * Returns list of files, that user marked for deletion + * + * @param kDBItem $object + * @return Array + * @access protected + */ + protected function _getFilesToDelete($object) + { + $var_name = $object->getPendingActionVariableName(); + $schedule = $this->Application->RecallVar($var_name); + + if ( !$schedule ) { + return Array (); + } + + $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) + { $single_mapping = Array ( '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', ); @@ -317,24 +407,28 @@ */ function Format($value, $field_name, &$object, $format = NULL) { - if (is_null($value)) { + if ( is_null($value) ) { return ''; } $options = $object->GetFieldOptions($field_name); - if (!isset($format)) { + 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|wms)(.*)/', $format, $regs)) { - if (!$value || $format == 'file_names') { + 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 ( !$value || $format == 'file_names' ) { // storage format matches display format OR no value return $value; } $ret = Array (); $files = explode('|', $value); - $format = $this->getSingleFormat($regs[1]).$regs[2]; + $format = $this->getSingleFormat($regs[1]) . $regs[2]; foreach ($files as $a_file) { $ret[] = $this->GetFormatted($a_file, $field_name, $object, $format); @@ -344,13 +438,38 @@ } $tc_value = $this->TypeCast($value, $options); - if( ($tc_value === false) || ($tc_value != $value) ) return $value; // for leaving badly formatted date on the form + 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); } /** + * 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 @@ -362,14 +481,14 @@ */ function GetFormatted($value, $field_name, &$object, $format = NULL, $force_direct_links = NULL) { - if (!$format) { + if ( !$format ) { return $value; } $options = $object->GetFieldOptions($field_name); $upload_dir = isset($options['include_path']) && $options['include_path'] ? '' : $this->getUploadDir($options); - if (preg_match('/resize:([\d]*)x([\d]*)/', $format, $regs)) { + if ( preg_match('/resize:([\d]*)x([\d]*)/', $format, $regs) ) { $image_helper = $this->Application->recallObject('ImageHelper'); /* @var $image_helper ImageHelper */ @@ -414,6 +533,27 @@ $image_info = $image_helper->getImageInfo(FULL_PATH . str_replace('/', DIRECTORY_SEPARATOR, $upload_dir) . $value); 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);