Page Menu
Home
In-Portal Phabricator
Search
Configure Global Search
Log In
Files
F800023
in-portal
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Fri, Feb 21, 11:59 PM
Size
47 KB
Mime Type
text/x-diff
Expires
Sun, Feb 23, 11:59 PM (20 h, 21 m)
Engine
blob
Format
Raw Data
Handle
573290
Attached To
rINP In-Portal
in-portal
View Options
Index: branches/5.2.x/core/kernel/utility/formatters/upload_formatter.php
===================================================================
--- branches/5.2.x/core/kernel/utility/formatters/upload_formatter.php (revision 16686)
+++ branches/5.2.x/core/kernel/utility/formatters/upload_formatter.php (revision 16687)
@@ -1,658 +1,657 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
class kUploadFormatter extends kFormatter
{
var $DestinationPath;
var $FullPath;
/**
* File helper reference
*
* @var FileHelper
*/
var $fileHelper = NULL;
/**
* Uploaded files, that are ordered as required (both live & temp images in one list)
*
* @var Array
* @access protected
*/
protected $sorting = Array ();
/**
* Creates formatter instance
*
* @access public
*/
public function __construct()
{
parent::__construct();
$this->fileHelper = $this->Application->recallObject('FileHelper');
if ( $this->DestinationPath ) {
$this->FullPath = FULL_PATH . $this->DestinationPath;
}
}
/**
* Sets field option defaults.
*
* @param string $field_name Field nae.
* @param array $field_options Field options.
* @param kDBBase $object Object.
*
* @return void
*/
public function PrepareOptions($field_name, &$field_options, &$object)
{
if ( !$this->DestinationPath && !isset($field_options['upload_dir']) ) {
$base_path = $this->Application->getUnitOption($object->Prefix, 'BasePath');
$field_options['upload_dir'] = WRITEBALE_BASE . '/' . basename($base_path) . '/';
}
if ( !isset($field_options['max_size']) ) {
$field_options['max_size'] = MAX_UPLOAD_SIZE;
}
}
/**
* 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)
{
$value = $this->Application->HttpQuery->unescapeRequestVariable($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']) ) {
$ret = $this->_processFlashUploader($value, $field_name, $object);
}
else {
$ret = $this->_processRegularUploader($value, $field_name, $object);
}
if ( getArrayValue($options, 'upload_dir') ) {
$this->DestinationPath = null;
$this->FullPath = null;
}
return $ret;
}
/**
* Handles uploaded files, provided by Flash uploader
*
* @param Array|string $value
* @param string $field_name
* @param kDBItem $object
* @return string
* @access protected
*/
protected function _processFlashUploader($value, $field_name, $object)
{
$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);
}
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 ();
$fret = Array ();
$max_files = $this->_getMaxFiles($options);
$pending_actions = $object->getPendingActions();
$files_to_delete = $this->_getFilesToDelete($object);
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);
}
$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 ) {
// 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']) ) {
+ if ( getArrayValue($options, 'file_types')
+ && !$this->fileHelper->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'] > $options['max_size'] ) {
$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 {
$tmp_path = WRITEABLE . '/tmp/';
$filename = $this->fileHelper->ensureUniqueFilename($tmp_path, $value['name'] . '.tmp');
$tmp_file_path = $tmp_path . $filename;
$moved = move_uploaded_file($value['tmp_name'], $tmp_file_path);
$storage_format = isset($options['storage_format']) ? $options['storage_format'] : false;
if ( $storage_format ) {
/** @var kUploadHelper $upload_helper */
$upload_helper = $this->Application->recallObject('kUploadHelper');
$moved = $upload_helper->resizeUploadedFile($tmp_file_path, $storage_format);
}
if ( $moved ) {
$real_name = $this->_getRealFilename(
kUtil::removeTempExtension(basename($tmp_file_path)),
$options,
$object
);
$file_name = $this->FullPath . $real_name;
$moved = rename($tmp_file_path, $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
+ * @param string $filename Filename.
+ * @param string $file_types File types.
+ *
+ * @return boolean
+ * @deprecated 5.2.2-B2
+ * @see FileHelper::extensionMatch()
*/
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);
- }
+ kUtil::deprecatedMethod(__METHOD__, '5.2.2-B2', 'FileHelper::extensionMatch');
- return true;
+ return $this->fileHelper->extensionMatch($filename, $file_types);
}
/**
* Resorts uploaded files according to given file order
*
* @param Array|string $files
* @return string
* @access protected
*/
protected function _sortFiles($files)
{
if ( !is_array($files) ) {
$files = explode('|', $files);
}
$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 implode('|', array_merge($sorted_files, $new_files));
}
/**
* 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'];
}
/**
* 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);
}
/**
* Renames file in sorting list
*
* @param string $old_name
* @param string $new_name
* @return void
* @access protected
*/
protected function _renameFileInSorting($old_name, $new_name)
{
$index = array_search($old_name, $this->sorting);
if ( $index !== false ) {
$this->sorting[$index] = $new_name;
}
}
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',
'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_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;
}
$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;
}
return $this->GetFormatted($tc_value, $field_name, $object, $format);
}
/**
* Return formatted file url,path or size
*
* @param string $value
* @param string $field_name
* @param kDBItem $object
* @param string $format
* @return string
*/
function GetFormatted($value, $field_name, &$object, $format = NULL)
{
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) ) {
/** @var ImageHelper $image_helper */
$image_helper = $this->Application->recallObject('ImageHelper');
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':
$direct_links = isset($options['direct_links']) ? $options['direct_links'] : true;
if ( $direct_links ) {
return $this->fileHelper->pathToUrl($file_path);
}
else {
$url_params = Array (
'pass' => 'm,'.$object->Prefix,
$object->Prefix . '_event' => 'OnViewFile',
'file' => $value, 'field' => $field_name
);
return $this->Application->HREF('', '', $url_params);
}
break;
case 'full_path':
return $file_path;
break;
case 'file_size':
return filesize($file_path);
break;
case 'img_size':
/** @var ImageHelper $image_helper */
$image_helper = $this->Application->recallObject('ImageHelper');
$image_info = $image_helper->getImageInfo($file_path);
return $image_info ? $image_info[3] : '';
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
* @throws Exception
*/
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 "<strong>' . $storage_engine . '</strong>".');
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
* @return string
*/
function GetFormatted($value, $field_name, &$object, $format = 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);
}
}
Index: branches/5.2.x/core/units/helpers/file_helper.php
===================================================================
--- branches/5.2.x/core/units/helpers/file_helper.php (revision 16686)
+++ branches/5.2.x/core/units/helpers/file_helper.php (revision 16687)
@@ -1,484 +1,505 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
class FileHelper extends kHelper {
/**
* Puts existing item images (from sub-item) to virtual fields (in main item)
*
* @param kCatDBItem $object
* @return void
* @access public
*/
public function LoadItemFiles(&$object)
{
$max_file_count = $this->Application->ConfigValue($object->Prefix.'_MaxImageCount'); // file count equals to image count (temporary measure)
$sql = 'SELECT *
FROM '.TABLE_PREFIX.'CatalogFiles
WHERE ResourceId = '.$object->GetDBField('ResourceId').'
ORDER BY FileId ASC
LIMIT 0, '.(int)$max_file_count;
$item_files = $this->Conn->Query($sql);
$file_counter = 1;
foreach ($item_files as $item_file) {
$file_path = $item_file['FilePath'];
$object->SetDBField('File'.$file_counter, $file_path);
$object->SetOriginalField('File'.$file_counter, $file_path);
$object->SetFieldOption('File'.$file_counter, 'original_field', $item_file['FileName']);
$file_counter++;
}
}
/**
* Saves newly uploaded images to external image table
*
* @param kCatDBItem $object
* @return void
* @access public
*/
public function SaveItemFiles(&$object)
{
$table_name = $this->Application->getUnitOption('#file', 'TableName');
$max_file_count = $this->Application->getUnitOption($object->Prefix, 'FileCount'); // $this->Application->ConfigValue($object->Prefix.'_MaxImageCount');
$this->CheckFolder(FULL_PATH . ITEM_FILES_PATH);
$i = 0;
while ($i < $max_file_count) {
$field = 'File'.($i + 1);
$field_options = $object->GetFieldOptions($field);
$file_path = $object->GetDBField($field);
if ($file_path) {
if (isset($field_options['original_field'])) {
$key_clause = 'FileName = '.$this->Conn->qstr($field_options['original_field']).' AND ResourceId = '.$object->GetDBField('ResourceId');
if ($object->GetDBField('Delete'.$field)) {
// if item was cloned, then new filename is in db (not in $image_src)
$sql = 'SELECT FilePath
FROM '.$table_name.'
WHERE '.$key_clause;
$file_path = $this->Conn->GetOne($sql);
if (@unlink(FULL_PATH.ITEM_FILES_PATH.$file_path)) {
$sql = 'DELETE FROM '.$table_name.'
WHERE '.$key_clause;
$this->Conn->Query($sql);
}
}
else {
// image record found -> update
$fields_hash = Array (
'FilePath' => $file_path,
);
$this->Conn->doUpdate($fields_hash, $table_name, $key_clause);
}
}
else {
// record not found -> create
$fields_hash = Array (
'ResourceId' => $object->GetDBField('ResourceId'),
'FileName' => $field,
'Status' => STATUS_ACTIVE,
'FilePath' => $file_path,
);
$this->Conn->doInsert($fields_hash, $table_name);
$field_options['original_field'] = $field;
$object->SetFieldOptions($field, $field_options);
}
}
$i++;
}
}
/**
* Preserves cloned item images/files to be rewritten with original item images/files
*
* @param Array $field_values
* @return void
* @access public
*/
public function PreserveItemFiles(&$field_values)
{
foreach ($field_values as $field_name => $field_value) {
if ( !is_array($field_value) ) {
continue;
}
if ( isset($field_value['upload']) && ($field_value['error'] == UPLOAD_ERR_NO_FILE) ) {
// this is upload field, but nothing was uploaded this time
unset($field_values[$field_name]);
}
}
}
/**
* Determines what image/file fields should be created (from post or just dummy fields for 1st upload)
*
* @param string $prefix
* @param bool $is_image
* @return void
* @access public
*/
public function createItemFiles($prefix, $is_image = false)
{
$items_info = $this->Application->GetVar($prefix);
if ($items_info) {
list (, $fields_values) = each($items_info);
$this->createUploadFields($prefix, $fields_values, $is_image);
}
else {
$this->createUploadFields($prefix, Array(), $is_image);
}
}
/**
* Dynamically creates virtual fields for item for each image/file field in submit
*
* @param string $prefix
* @param Array $fields_values
* @param bool $is_image
* @return void
* @access public
*/
public function createUploadFields($prefix, $fields_values, $is_image = false)
{
$field_options = Array (
'type' => 'string',
'max_len' => 240,
'default' => '',
);
if ($is_image) {
$field_options['formatter'] = 'kPictureFormatter';
$field_options['include_path'] = 1;
$field_options['allowed_types'] = Array ('image/jpeg', 'image/pjpeg', 'image/png', 'image/x-png', 'image/gif', 'image/bmp');
$field_prefix = 'Image';
}
else {
$field_options['formatter'] = 'kUploadFormatter';
$field_options['upload_dir'] = ITEM_FILES_PATH;
$field_options['allowed_types'] = Array ('application/pdf', 'application/msexcel', 'application/msword', 'application/mspowerpoint');
$field_prefix = 'File';
}
$fields = $this->Application->getUnitOption($prefix, 'Fields');
$virtual_fields = $this->Application->getUnitOption($prefix, 'VirtualFields');
$image_count = 0;
foreach ($fields_values as $field_name => $field_value) {
if (preg_match('/^('.$field_prefix.'[\d]+|Primary'.$field_prefix.')$/', $field_name)) {
$fields[$field_name] = $field_options;
$virtual_fields[$field_name] = $field_options;
$this->_createCustomFields($prefix, $field_name, $virtual_fields, $is_image);
$image_count++;
}
}
if (!$image_count) {
// no images found in POST -> create default image fields
$image_count = $this->Application->ConfigValue($prefix.'_MaxImageCount');
if ($is_image) {
$created_count = 1;
$image_names = Array ('Primary' . $field_prefix => '');
while ($created_count < $image_count) {
$image_names[$field_prefix . $created_count] = '';
$created_count++;
}
}
else {
$created_count = 0;
$image_names = Array ();
while ($created_count < $image_count) {
$image_names[$field_prefix . ($created_count + 1)] = '';
$created_count++;
}
}
if ($created_count) {
$this->createUploadFields($prefix, $image_names, $is_image);
}
return ;
}
$this->Application->setUnitOption($prefix, $field_prefix.'Count', $image_count);
$this->Application->setUnitOption($prefix, 'Fields', $fields);
$this->Application->setUnitOption($prefix, 'VirtualFields', $virtual_fields);
}
/**
* Adds ability to create more virtual fields associated with main image/file
*
* @param string $prefix
* @param string $field_name
* @param Array $virtual_fields
* @param bool $is_image
* @return void
* @access protected
*/
protected function _createCustomFields($prefix, $field_name, &$virtual_fields, $is_image = false)
{
$virtual_fields['Delete' . $field_name] = Array ('type' => 'int', 'default' => 0);
if ( $is_image ) {
$virtual_fields[$field_name . 'Alt'] = Array ('type' => 'string', 'default' => '');
}
}
/**
* Downloads file to user
*
* @param string $filename
* @return void
* @access public
*/
public function DownloadFile($filename)
{
$this->Application->setContentType(kUtil::mimeContentType($filename), false);
header('Content-Disposition: attachment; filename="' . basename($filename) . '"');
header('Content-Length: ' . filesize($filename));
readfile($filename);
flush();
}
/**
* Creates folder with given $path
*
* @param string $path
* @return bool
* @access public
*/
public function CheckFolder($path)
{
$result = true;
if (!file_exists($path) || !is_dir($path)) {
$parent_path = preg_replace('#(/|\\\)[^/\\\]+(/|\\\)?$#', '', rtrim($path , '/\\'));
$result = $this->CheckFolder($parent_path);
if ($result) {
$result = mkdir($path);
if ($result) {
chmod($path, 0777);
// don't commit any files from created folder
if (file_exists(FULL_PATH . '/CVS')) {
$cvsignore = fopen($path . '/.cvsignore', 'w');
fwrite($cvsignore, '*.*');
fclose($cvsignore);
chmod($path . '/.cvsignore', 0777);
}
}
else {
trigger_error('Cannot create directory "<strong>' . $path . '</strong>"', E_USER_WARNING);
return false;
}
}
}
return $result;
}
/**
* Copies all files and directories from $source to $destination directory. Create destination directory, when missing.
*
* @param string $source
* @param string $destination
* @return bool
* @access public
*/
public function copyFolderRecursive($source, $destination)
{
if ( substr($source, -1) == DIRECTORY_SEPARATOR ) {
$source = substr($source, 0, -1);
$destination .= DIRECTORY_SEPARATOR . basename($source);
}
$iterator = new DirectoryIterator($source);
/** @var DirectoryIterator $file_info */
$result = $this->CheckFolder($destination);
foreach ($iterator as $file_info) {
if ( $file_info->isDot() ) {
continue;
}
$file = $file_info->getFilename();
if ( $file_info->isDir() ) {
$result = $this->copyFolderRecursive($file_info->getPathname(), $destination . DIRECTORY_SEPARATOR . $file);
}
else {
$result = copy($file_info->getPathname(), $destination . DIRECTORY_SEPARATOR . $file);
}
if (!$result) {
trigger_error('Cannot create file/directory "<strong>' . $destination . DIRECTORY_SEPARATOR . $file . '</strong>"', E_USER_WARNING);
break;
}
}
return $result;
}
/**
* Copies all files from $source to $destination directory. Create destination directory, when missing.
*
* @param string $source
* @param string $destination
* @return bool
* @access public
*/
public function copyFolder($source, $destination)
{
if ( substr($source, -1) == DIRECTORY_SEPARATOR ) {
$source = substr($source, 0, -1);
$destination .= DIRECTORY_SEPARATOR . basename($source);
}
$iterator = new DirectoryIterator($source);
/** @var DirectoryIterator $file_info */
$result = $this->CheckFolder($destination);
foreach ($iterator as $file_info) {
if ( $file_info->isDot() || !$file_info->isFile() ) {
continue;
}
$file = $file_info->getFilename();
$result = copy($file_info->getPathname(), $destination . DIRECTORY_SEPARATOR . $file);
if ( !$result ) {
trigger_error('Cannot create file "<strong>' . $destination . DIRECTORY_SEPARATOR . $file . '</strong>"', E_USER_WARNING);
break;
}
}
return $result;
}
/**
* Transforms given path to file into it's url, where each each component is encoded (excluding domain and protocol)
*
* @param string $url
* @return string
* @access public
*/
public function pathToUrl($url)
{
$url = str_replace(DIRECTORY_SEPARATOR, '/', preg_replace('/^' . preg_quote(FULL_PATH, '/') . '(.*)/', '\\1', $url, 1));
// TODO: why?
$url = implode('/', array_map('rawurlencode', explode('/', $url)));
return rtrim($this->Application->BaseURL(), '/') . $url;
}
/**
* Transforms given url to path to it
*
* @param string $url
* @return string
* @access public
*/
public function urlToPath($url)
{
$base_url = rtrim($this->Application->BaseURL(), '/');
// escape replacement patterns, like "\<number>"
$full_path = preg_replace('/(\\\[\d]+)/', '\\\\\1', FULL_PATH);
$path = preg_replace('/^' . preg_quote($base_url, '/') . '(.*)/', $full_path . '\\1', $url, 1);
return str_replace('/', DIRECTORY_SEPARATOR, kUtil::unescape($path, kUtil::ESCAPE_URL));
}
/**
* Makes given paths DocumentRoot agnostic.
*
* @param array $paths List of file paths.
*
* @return array
*/
public function makeRelative(array $paths)
{
foreach ( $paths as $index => $path ) {
$replaced_count = 0;
$relative_path = preg_replace('/^' . preg_quote(FULL_PATH, '/') . '/', '', $path, 1, $replaced_count);
if ( $replaced_count === 1 ) {
$paths[$index] = $relative_path;
}
}
return $paths;
}
/**
* Ensures, that new file will not overwrite any of previously created files with same name
*
* @param string $path
* @param string $name
* @param Array $forbidden_names
* @return string
*/
public function ensureUniqueFilename($path, $name, $forbidden_names = Array ())
{
$parts = pathinfo($name);
$ext = '.' . $parts['extension'];
$filename = $parts['filename'];
$path = rtrim($path, '/');
$original_checked = false;
$new_name = $filename . $ext;
if ( $parts['dirname'] != '.' ) {
$path .= '/' . ltrim($parts['dirname'], '/');
}
// make sure target folder always exists, especially for cases,
// when storage engine folder is supplied as a part of $name
$this->CheckFolder($path);
while (file_exists($path . '/' . $new_name) || in_array($path . '/' . $new_name, $forbidden_names)) {
if ( preg_match('/(.*)_([0-9]*)(' . preg_quote($ext, '/') . ')/', $new_name, $regs) ) {
$new_name = $regs[1] . '_' . ((int)$regs[2] + 1) . $regs[3];
}
elseif ( $original_checked ) {
$new_name = $filename . '_1' . $ext;
}
$original_checked = true;
}
if ( $parts['dirname'] != '.' ) {
$new_name = $parts['dirname'] . '/' . $new_name;
}
return $new_name;
}
+
+ /**
+ * Checks, that given file name has on of provided file extensions
+ *
+ * @param string $filename Filename.
+ * @param string $file_types File types.
+ *
+ * @return boolean
+ */
+ public 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;
+ }
+
}
Index: branches/5.2.x/core/units/helpers/upload_helper.php
===================================================================
--- branches/5.2.x/core/units/helpers/upload_helper.php (revision 16686)
+++ branches/5.2.x/core/units/helpers/upload_helper.php (revision 16687)
@@ -1,404 +1,418 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2012 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
class kUploadHelper extends kHelper
{
/**
* File helper reference
*
* @var FileHelper
*/
protected $fileHelper = null;
/**
* Creates kUploadHelper instance.
*/
public function __construct()
{
parent::__construct();
$this->fileHelper = $this->Application->recallObject('FileHelper');
// 5 minutes execution time
@set_time_limit(5 * 60);
}
/**
* Handles the upload.
*
* @param kEvent $event Event.
*
* @return string
* @throws kUploaderException When upload could not be handled properly.
*/
public function handle(kEvent $event)
{
$this->disableBrowserCache();
// Uncomment this one to fake upload time
// sleep(5);
if ( !$this->Application->HttpQuery->Post ) {
// Variables {field, id, flashsid} are always submitted through POST!
// When file size is larger, then "upload_max_filesize" (in php.ini),
// then these variables also are not submitted.
throw new kUploaderException('File size exceeds allowed limit.', 413);
}
if ( !$this->checkPermissions($event) ) {
// 403 Forbidden
throw new kUploaderException('You don\'t have permissions to upload.', 403);
}
$value = $this->Application->GetVar('file');
if ( !$value || ($value['error'] != UPLOAD_ERR_OK) ) {
// 413 Request Entity Too Large (file uploads disabled OR uploaded file was
// too large for web server to accept, see "upload_max_filesize" in php.ini)
throw new kUploaderException('File size exceeds allowed limit.', 413);
}
$value = $this->Application->unescapeRequestVariable($value);
$tmp_path = WRITEABLE . '/tmp/';
$filename = $this->getUploadedFilename() . '.tmp';
$id = $this->Application->GetVar('id');
if ( $id ) {
$filename = $id . '_' . $filename;
}
if ( !is_writable($tmp_path) ) {
// 500 Internal Server Error
// check both temp and live upload directory
throw new kUploaderException('Write permissions not set on the server, please contact server administrator.', 500);
}
$filename = $this->fileHelper->ensureUniqueFilename($tmp_path, $filename);
- $storage_format = $this->getStorageFormat($this->Application->GetVar('field'), $event);
+ $field_options = $this->getFieldOptions($this->Application->GetVar('field'), $event);
+ $storage_format = isset($field_options['storage_format']) ? $field_options['storage_format'] : false;
$file_path = $tmp_path . $filename;
$actual_file_path = $this->moveUploadedFile($file_path);
if ( $storage_format && $file_path == $actual_file_path ) {
$this->resizeUploadedFile($file_path, $storage_format);
}
+ if ( getArrayValue($field_options, 'file_types')
+ && !$this->fileHelper->extensionMatch(kUtil::removeTempExtension($filename), $field_options['file_types'])
+ ) {
+ throw new kUploaderException('File is not an allowed file type.', 415);
+ }
+
+ if ( filesize($actual_file_path) > $field_options['max_size'] ) {
+ throw new kUploaderException('File size exceeds allowed limit.', 413);
+ }
+
$this->deleteTempFiles($tmp_path);
$thumbs_path = preg_replace('/^' . preg_quote(FULL_PATH, '/') . '/', '', $tmp_path, 1);
$thumbs_path = FULL_PATH . THUMBS_PATH . $thumbs_path;
if ( file_exists($thumbs_path) ) {
$this->deleteTempFiles($thumbs_path);
}
return preg_replace('/^' . preg_quote($id, '/') . '_/', '', basename($file_path));
}
/**
* Resizes uploaded file.
*
* @param string $file_path File path.
* @param string $format Format.
*
* @return boolean
*/
public function resizeUploadedFile(&$file_path, $format)
{
/** @var ImageHelper $image_helper */
$image_helper = $this->Application->recallObject('ImageHelper');
// Add extension, so that "ImageHelper::ResizeImage" can work.
$resize_file_path = tempnam(WRITEABLE . '/tmp', 'uploaded_') . '.jpg';
if ( rename($file_path, $resize_file_path) === false ) {
return false;
}
$resized_file_path = $this->fileHelper->urlToPath(
$image_helper->ResizeImage($resize_file_path, $format)
);
$file_path = $this->replaceFileExtension(
$file_path,
pathinfo($resized_file_path, PATHINFO_EXTENSION)
);
return rename($resized_file_path, $file_path);
}
/**
* Replace extension of uploaded file.
*
* @param string $file_path File path.
* @param string $new_file_extension New file extension.
*
* @return string
*/
protected function replaceFileExtension($file_path, $new_file_extension)
{
$file_path_without_temp_file_extension = kUtil::removeTempExtension($file_path);
$current_file_extension = pathinfo($file_path_without_temp_file_extension, PATHINFO_EXTENSION);
// Format of resized file wasn't changed.
if ( $current_file_extension === $new_file_extension ) {
return $file_path;
}
$ret = preg_replace(
'/\.' . preg_quote($current_file_extension, '/') . '$/',
'.' . $new_file_extension,
$file_path_without_temp_file_extension
);
// Add ".tmp" later, since it was removed.
if ( $file_path_without_temp_file_extension !== $file_path ) {
$ret .= '.tmp';
}
// After file extension change resulting filename might not be unique in that folder anymore.
$path = pathinfo($ret, PATHINFO_DIRNAME);
return $path . '/' . $this->fileHelper->ensureUniqueFilename($path, basename($ret));
}
/**
* Sends headers to ensure, that response is never cached.
*
* @return void
*/
protected function disableBrowserCache()
{
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
}
/**
* Checks, that flash uploader is allowed to perform upload
*
* @param kEvent $event
* @return bool
*/
protected function checkPermissions(kEvent $event)
{
// Flash uploader does NOT send correct cookies, so we need to make our own check
$cookie_name = 'adm_' . $this->Application->ConfigValue('SessionCookieName');
$this->Application->HttpQuery->Cookie['cookies_on'] = 1;
$this->Application->HttpQuery->Cookie[$cookie_name] = $this->Application->GetVar('flashsid');
// this prevents session from auto-expiring when KeepSessionOnBrowserClose & FireFox is used
$this->Application->HttpQuery->Cookie[$cookie_name . '_live'] = $this->Application->GetVar('flashsid');
/** @var Session $admin_session */
$admin_session = $this->Application->recallObject('Session.admin');
if ( $this->Application->permissionCheckingDisabled($admin_session->RecallVar('user_id')) ) {
return true;
}
// copy some data from given session to current session
$backup_user_id = $this->Application->RecallVar('user_id');
$this->Application->StoreVar('user_id', $admin_session->RecallVar('user_id'));
$backup_user_groups = $this->Application->RecallVar('UserGroups');
$this->Application->StoreVar('UserGroups', $admin_session->RecallVar('UserGroups'));
// check permissions using event, that have "add|edit" rule
$check_event = new kEvent($event->getPrefixSpecial() . ':OnProcessSelected');
$check_event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true));
/** @var kEventHandler $event_handler */
$event_handler = $this->Application->recallObject($event->Prefix . '_EventHandler');
$allowed_to_upload = $event_handler->CheckPermission($check_event);
// restore changed data, so nothing gets saved to database
$this->Application->StoreVar('user_id', $backup_user_id);
$this->Application->StoreVar('UserGroups', $backup_user_groups);
return $allowed_to_upload;
}
/**
* Returns uploaded filename.
*
* @return string
*/
protected function getUploadedFilename()
{
if ( isset($_REQUEST['name']) ) {
$file_name = $_REQUEST['name'];
}
elseif ( !empty($_FILES) ) {
$file_name = $_FILES['file']['name'];
}
else {
$file_name = uniqid('file_');
}
return $file_name;
}
/**
- * Gets storage format for a given field.
+ * Returns field options.
*
- * @param string $field_name
- * @param kEvent $event
- * @return bool
+ * @param string $field Field.
+ * @param kEvent $event Event.
+ *
+ * @return array
*/
- protected function getStorageFormat($field_name, kEvent $event)
+ protected function getFieldOptions($field, kEvent $event)
{
+ /** @var array $fields */
$fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
+
+ /** @var array $virtual_fields */
$virtual_fields = $this->Application->getUnitOption($event->Prefix, 'VirtualFields');
- $field_options = array_key_exists($field_name, $fields) ? $fields[$field_name] : $virtual_fields[$field_name];
- return isset($field_options['storage_format']) ? $field_options['storage_format'] : false;
+ return array_key_exists($field, $fields) ? $fields[$field] : $virtual_fields[$field];
}
/**
* Moves uploaded file to given location.
*
* @param string $file_path File path.
*
* @return string
* @throws kUploaderException When upload could not be handled properly.
*/
protected function moveUploadedFile($file_path)
{
// Chunking might be enabled.
$chunk = (int)$this->Application->GetVar('chunk', 0);
$chunks = (int)$this->Application->GetVar('chunks', 0);
$actual_file_path = $file_path . '.part';
// Open temp file.
if ( !$out = @fopen($actual_file_path, $chunks ? 'ab' : 'wb') ) {
throw new kUploaderException('Failed to open output stream.', 102);
}
if ( !empty($_FILES) ) {
if ( $_FILES['file']['error'] || !is_uploaded_file($_FILES['file']['tmp_name']) ) {
throw new kUploaderException('Failed to move uploaded file.', 103);
}
// Read binary input stream and append it to temp file.
if ( !$in = @fopen($_FILES['file']['tmp_name'], 'rb') ) {
throw new kUploaderException('Failed to open input stream.', 101);
}
}
else {
if ( !$in = @fopen('php://input', 'rb') ) {
throw new kUploaderException('Failed to open input stream.', 101);
}
}
while ( $buff = fread($in, 4096) ) {
fwrite($out, $buff);
}
@fclose($out);
@fclose($in);
// Check if file has been uploaded.
if ( !$chunks || $chunk == $chunks - 1 ) {
// Strip the temp .part suffix off.
rename($actual_file_path, $file_path);
$actual_file_path = $file_path;
}
return $actual_file_path;
}
/**
* Delete temporary files, that won't be used for sure
*
* @param string $path
* @return void
*/
protected function deleteTempFiles($path)
{
$files = glob($path . '*.*');
$max_file_date = strtotime('-1 day');
foreach ( $files as $file ) {
if ( filemtime($file) < $max_file_date ) {
unlink($file);
}
}
}
/**
* Prepares object for operations with file on given field.
*
* @param kEvent $event Event.
* @param string $field Field.
*
* @return kDBItem
*/
public function prepareUploadedFile(kEvent $event, $field)
{
/** @var kDBItem $object */
$object = $event->getObject(Array ('skip_autoload' => true));
$filename = $this->getSafeFilename();
if ( !$filename ) {
$object->SetDBField($field, '');
return $object;
}
// set current uploaded file
if ( $this->Application->GetVar('tmp') ) {
$options = $object->GetFieldOptions($field);
$options['upload_dir'] = WRITEBALE_BASE . '/tmp/';
unset($options['include_path']);
$object->SetFieldOptions($field, $options);
$filename = $this->Application->GetVar('id') . '_' . $filename;
}
$object->SetDBField($field, $filename);
return $object;
}
/**
* Returns safe version of filename specified in url
*
* @return bool|string
* @access protected
*/
protected function getSafeFilename()
{
$filename = $this->Application->GetVar('file');
$filename = $this->Application->unescapeRequestVariable($filename);
if ( (strpos($filename, '../') !== false) || (trim($filename) !== $filename) ) {
// when relative paths or special chars are found template names from url, then it's hacking attempt
return false;
}
return $filename;
}
}
class kUploaderException extends Exception
{
}
Event Timeline
Log In to Comment