fileHelper = $this->Application->recallObject('FileHelper'); 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 * @param string $field_name * @param kDBItem $object * @return mixed * @access public */ public function Parse($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); } $ret = !is_array($value) ? $value : ''; $options = $object->GetFieldOptions($field_name); $this->_initUploadFolder($options); // 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)); return getArrayValue($value, 'upload'); } // SWF Uploader: END 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; // we can get mime type based on file content and no 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']) ) { // match by file extensions $error_params = Array ( 'file_name' => $value['name'], 'file_types' => $options['file_types'], ); $object->SetError($field_name, 'bad_file_format', 'la_error_InvalidFileFormat', $error_params); } elseif ( getArrayValue($options, 'allowed_types') && !in_array($value['type'], $options['allowed_types']) ) { // match by mime type provided by web-browser $error_params = Array ( 'file_type' => $value['type'], 'allowed_types' => $options['allowed_types'], ); $object->SetError($field_name, 'bad_file_format', 'la_error_InvalidFileFormat', $error_params); } elseif ( $value['size'] > $max_filesize ) { $object->SetError($field_name, 'bad_file_size', 'la_error_FileTooLarge'); } elseif ( !is_writable($this->FullPath) ) { $object->SetError($field_name, 'cant_save_file', 'la_error_cant_save_file'); } else { $real_name = $this->_getRealFilename($value['name'], $options, $object); $file_name = $this->FullPath . $real_name; $storage_format = isset($options['storage_format']) ? $options['storage_format'] : false; if ( $storage_format ) { $image_helper = $this->Application->recallObject('ImageHelper'); /* @var $image_helper ImageHelper */ move_uploaded_file($value['tmp_name'], $value['tmp_name'] . '.jpg'); // add extension, so ResizeImage can work $url = $image_helper->ResizeImage($value['tmp_name'] . '.jpg', $storage_format); $tmp_name = preg_replace('/^' . preg_quote($this->Application->BaseURL(), '/') . '/', '/', $url); $moved = rename($tmp_name, $file_name); } else { $moved = move_uploaded_file($value['tmp_name'], $file_name); } if ( !$moved ) { $object->SetError($field_name, 'cant_save_file', 'la_error_cant_save_file'); } else { @chmod($file_name, 0666); if ( getArrayValue($options, 'size_field') ) { $object->SetDBField($options['size_field'], $value['size']); } if ( getArrayValue($options, 'orig_name_field') ) { $object->SetDBField($options['orig_name_field'], $value['name']); } if ( getArrayValue($options, 'content_type_field') ) { $object->SetDBField($options['content_type_field'], $value['type']); } $ret = getArrayValue($options, 'upload_dir') ? $real_name : $this->DestinationPath . $real_name; // 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); }*/ } } } else { $object->SetError($field_name, 'cant_save_file', 'la_error_cant_save_file'); } } if ( (count($value) > 1) && $value['error'] && ($value['error'] != UPLOAD_ERR_NO_FILE) ) { $object->SetError($field_name, 'cant_save_file', 'la_error_cant_save_file', $value); } return $ret; } /** * Checks, that given file name has on of provided file extensions * * @param string $filename * @param string $file_types * @return bool * @access protected */ protected function extensionMatch($filename, $file_types) { 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); } return true; } /** * 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; } /** * 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 * @return int * @access protected */ protected function _getMaxFiles($options) { if ( !isset($options['multiple']) ) { return 1; } return $options['multiple'] == false ? 1 : $options['multiple']; } /** * 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 * @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', ); return $single_mapping[$format]; } /** * Return formatted file url,path or size (or same for multiple files) * * @param string $value * @param string $field_name * @param kDBItem|kDBList $object * @param string $format * @return string */ function Format($value, $field_name, &$object, $format = NULL) { if ( is_null($value) ) { return ''; } $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 ( !$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]; foreach ($files as $a_file) { $ret[] = $this->GetFormatted($a_file, $field_name, $object, $format); } return implode('|', $ret); } $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); } /** * 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) { 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) ) { $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); } switch ($format) { case 'full_url': if ( isset($force_direct_links) ) { $direct_links = $force_direct_links; } else { $direct_links = isset($options['direct_links']) ? $options['direct_links'] : false; } if ( $direct_links ) { return $this->fileHelper->pathToUrl(FULL_PATH . $upload_dir . $value); } else { $url_params = Array ( 'no_amp' => 1, 'pass' => 'm,'.$object->Prefix, $object->Prefix . '_event' => 'OnViewFile', 'file' => rawurlencode($value), 'field' => $field_name ); return $this->Application->HREF('', '', $url_params); } break; case 'full_path': return FULL_PATH . str_replace('/', DIRECTORY_SEPARATOR, $upload_dir) . $value; break; case 'file_size': return filesize(FULL_PATH . str_replace('/', DIRECTORY_SEPARATOR, $upload_dir) . $value); 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); 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); } /** * Creates & returns folder, based on storage engine specified in field options * * @param string $file_name * @param array $options * @return string * @access protected */ protected function getStorageEngineFolder($file_name, $options) { $storage_engine = (string)getArrayValue($options, 'storage_engine'); if ( !$storage_engine ) { return ''; } switch ($storage_engine) { case StorageEngine::HASH: $folder_path = kUtil::getHashPathForLevel($file_name); break; case StorageEngine::TIMESTAMP: $folder_path = adodb_date('Y-m/d/'); break; default: throw new Exception('Unknown storage engine "' . $storage_engine . '".'); break; } return $folder_path; } /** * Applies prefix & suffix to uploaded filename, based on storage engine in field options * * @param string $name * @param array $options * @param string $unit_prefix * @return string * @access protected */ protected function getStorageEngineFile($name, $options, $unit_prefix) { $prefix = $this->getStorageEngineFilePart(getArrayValue($options, 'filename_prefix'), $unit_prefix); $suffix = $this->getStorageEngineFilePart(getArrayValue($options, 'filename_suffix'), $unit_prefix); $parts = pathinfo($name); return ($prefix ? $prefix . '_' : '') . $parts['filename'] . ($suffix ? '_' . $suffix : '') . '.' . $parts['extension']; } /** * Creates prefix/suffix to join with uploaded file * * Added "u" before user_id to keep this value after FileHelper::ensureUniqueFilename method call * * @param string $option * @param string $unit_prefix * @return string * @access protected */ protected function getStorageEngineFilePart($option, $unit_prefix) { $replace_from = Array ( StorageEngine::PS_DATE_TIME, StorageEngine::PS_PREFIX, StorageEngine::PS_USER ); $replace_to = Array ( adodb_date('Ymd-His'), $unit_prefix, 'u' . $this->Application->RecallVar('user_id') ); return str_replace($replace_from, $replace_to, $option); } public function getUploadDir($options) { return isset($options['upload_dir']) ? $options['upload_dir'] : $this->DestinationPath; } } class kPictureFormatter extends kUploadFormatter { public function __construct() { $this->NakeLookupPath = IMAGES_PATH; // used ? $this->DestinationPath = kUtil::constOn('ADMIN') ? IMAGES_PENDING_PATH : IMAGES_PATH; parent::__construct(); } /** * 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) { if ( $format == 'img_size' ) { $options = $object->GetFieldOptions($field_name); $img_path = FULL_PATH . '/' . $this->getUploadDir($options) . $value; $image_info = getimagesize($img_path); return ' ' . $image_info[3]; } return parent::GetFormatted($value, $field_name, $object, $format, $force_direct_links); } }