Page MenuHomeIn-Portal Phabricator

in-portal
No OneTemporary

File Metadata

Created
Sat, Feb 22, 12:06 AM

in-portal

Index: branches/5.2.x/core/kernel/utility/formatters/upload_formatter.php
===================================================================
--- branches/5.2.x/core/kernel/utility/formatters/upload_formatter.php (revision 15787)
+++ branches/5.2.x/core/kernel/utility/formatters/upload_formatter.php (revision 15788)
@@ -1,620 +1,631 @@
<?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;
}
}
/**
* 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);
}
$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);
}
/**
* 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 ) {
$max_file_size = isset($options['max_size']) ? $options['max_size'] : MAX_UPLOAD_SIZE;
// 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']) ) {
// 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_file_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 {
$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;
}
/**
* 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 || !$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(FULL_PATH . $upload_dir . $value);
+ 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(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
);
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;
}
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);
}
}
\ No newline at end of file
Index: branches/5.2.x/core/units/helpers/image_helper.php
===================================================================
--- branches/5.2.x/core/units/helpers/image_helper.php (revision 15787)
+++ branches/5.2.x/core/units/helpers/image_helper.php (revision 15788)
@@ -1,708 +1,714 @@
<?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 ImageHelper extends kHelper {
/**
* File helper reference
*
* @var FileHelper
*/
var $fileHelper = null;
public function __construct()
{
parent::__construct();
ini_set('gd.jpeg_ignore_warning', 1);
$this->fileHelper = $this->Application->recallObject('FileHelper');
}
/**
* Parses format string into array
*
* @param string $format sample format: "resize:300x500;wm:inc/wm.png|c|-20"
* @return Array sample result: Array('max_width' => 300, 'max_height' => 500, 'wm_filename' => 'inc/wm.png', 'h_margin' => 'c', 'v_margin' => -20)
*/
function parseFormat($format)
{
$res = Array ();
$format_parts = explode(';', $format);
foreach ($format_parts as $format_part) {
if (preg_match('/^resize:(\d*)x(\d*)$/', $format_part, $regs)) {
$res['max_width'] = $regs[1];
$res['max_height'] = $regs[2];
}
elseif (preg_match('/^wm:([^\|]*)\|([^\|]*)\|([^\|]*)$/', $format_part, $regs)) {
$res['wm_filename'] = FULL_PATH.THEMES_PATH.'/'.$regs[1];
$res['h_margin'] = strtolower($regs[2]);
$res['v_margin'] = strtolower($regs[3]);
}
elseif (preg_match('/^crop:([^\|]*)\|([^\|]*)$/', $format_part, $regs)) {
$res['crop_x'] = strtolower($regs[1]);
$res['crop_y'] = strtolower($regs[2]);
}
elseif ($format_part == 'img_size' || $format_part == 'img_sizes') {
$res['image_size'] = true;
}
elseif (preg_match('/^fill:(.*)$/', $format_part, $regs)) {
$res['fill'] = $regs[1];
} elseif (preg_match('/^default:(.*)$/', $format_part, $regs)) {
$res['default'] = FULL_PATH.THEMES_PATH.'/'.$regs[1];
}
}
return $res;
}
/**
* Resized given image to required dimensions & saves resized image to "resized" subfolder in source image folder
*
* @param string $src_image full path to image (on server)
* @param mixed $max_width maximal allowed resized image width or false if no limit
* @param mixed $max_height maximal allowed resized image height or false if no limit
+ *
* @return string direct url to resized image
+ * @throws \RuntimeException When image doesn't exist.
*/
function ResizeImage($src_image, $max_width, $max_height = false)
{
$image_size = false;
if (is_numeric($max_width)) {
$params['max_width'] = $max_width;
$params['max_height'] = $max_height;
}
else {
$params = $this->parseFormat($max_width);
if (array_key_exists('image_size', $params)) {
// image_size param shouldn't affect resized file name (crc part)
$image_size = $params['image_size'];
unset($params['image_size']);
}
}
if ((!$src_image || !file_exists($src_image)) && array_key_exists('default', $params) && !(defined('DBG_IMAGE_RECOVERY') && DBG_IMAGE_RECOVERY)) {
$src_image = $params['default'];
}
+ if ( !strlen($src_image) || !file_exists($src_image) ) {
+ throw new \RuntimeException(sprintf('Image "%s" doesn\'t exist', $src_image));
+ }
+
if ($params['max_width'] > 0 || $params['max_height'] > 0) {
list ($params['target_width'], $params['target_height'], $needs_resize) = $this->GetImageDimensions($src_image, $params['max_width'], $params['max_height'], $params);
if (!is_numeric($params['max_width'])) {
$params['max_width'] = $params['target_width'];
}
if (!is_numeric($params['max_height'])) {
$params['max_height'] = $params['target_height'];
}
$src_path = dirname($src_image);
$transform_keys = Array ('crop_x', 'crop_y', 'fill', 'wm_filename');
if ($needs_resize || array_intersect(array_keys($params), $transform_keys)) {
// resize required OR watermarking required -> change resulting image name !
$src_path_escaped = preg_replace('/(\\\[\d]+)/', '\\\\\1', $src_path); // escape replacement patterns, like "\<number>"
$dst_image = preg_replace('/^'.preg_quote($src_path, '/').'(.*)\.(.*)$/', $src_path_escaped . DIRECTORY_SEPARATOR . 'resized\\1_' . crc32(serialize($params)) . '.\\2', $src_image);
$this->fileHelper->CheckFolder( dirname($dst_image) );
if (!file_exists($dst_image) || filemtime($src_image) > filemtime($dst_image)) {
// resized image not available OR should be recreated due source image change
$params['dst_image'] = $dst_image;
$image_resized = $this->ScaleImage($src_image, $params);
if (!$image_resized) {
// resize failed, because of server error
$dst_image = $src_image;
}
}
// resize/watermarking ok
$src_image = $dst_image;
}
}
if ($image_size) {
// return only image size (resized or not)
$image_info = $this->getImageInfo($src_image);
return $image_info ? $image_info[3] : '';
}
return $this->fileHelper->pathToUrl($src_image);
}
/**
* Proportionally resizes given image to destination dimensions
*
* @param string $src_image full path to source image (already existing)
* @param Array $params
* @return bool
*/
function ScaleImage($src_image, $params)
{
$image_info = $this->getImageInfo($src_image);
if (!$image_info) {
return false;
}
/*list ($params['max_width'], $params['max_height'], $resized) = $this->GetImageDimensions($src_image, $params['max_width'], $params['max_height'], $params);
if (!$resized) {
// image dimensions are smaller or equals to required dimensions
return false;
}*/
if (!$this->Application->ConfigValue('ForceImageMagickResize') && function_exists('imagecreatefromjpeg')) {
// try to resize using GD
$resize_map = Array (
'image/jpeg' => 'imagecreatefromjpeg:imagejpeg:jpg',
'image/gif' => 'imagecreatefromgif:imagegif:gif',
'image/png' => 'imagecreatefrompng:imagepng:png',
'image/bmp' => 'imagecreatefrombmp:imagejpeg:bmp',
'image/x-ms-bmp' => 'imagecreatefrombmp:imagejpeg:bmp',
);
$mime_type = $image_info['mime'];
if (!isset($resize_map[$mime_type])) {
return false;
}
list ($read_function, $write_function, $file_extension) = explode(':', $resize_map[$mime_type]);
// when source image has large dimensions (over 1MB filesize), then 16M is not enough
kUtil::setResourceLimit();
$src_image_rs = @$read_function($src_image);
if ($src_image_rs) {
$dst_image_rs = imagecreatetruecolor($params['target_width'], $params['target_height']); // resize target size
$preserve_transparency = ($file_extension == 'gif') || ($file_extension == 'png');
if ($preserve_transparency) {
// preserve transparency of PNG and GIF images
$dst_image_rs = $this->_preserveTransparency($src_image_rs, $dst_image_rs, $image_info[2]);
}
// 1. resize
imagecopyresampled($dst_image_rs, $src_image_rs, 0, 0, 0, 0, $params['target_width'], $params['target_height'], $image_info[0], $image_info[1]);
$watermark_size = 'target';
if (array_key_exists('crop_x', $params) || array_key_exists('crop_y', $params)) {
// 2.1. crop image to given size
$dst_image_rs =& $this->_cropImage($dst_image_rs, $params, $preserve_transparency ? $image_info[2] : false);
$watermark_size = 'max';
} elseif (array_key_exists('fill', $params)) {
// 2.2. fill image margins from resize with given color
$dst_image_rs =& $this->_applyFill($dst_image_rs, $params, $preserve_transparency ? $image_info[2] : false);
$watermark_size = 'max';
}
// 3. apply watermark
$dst_image_rs =& $this->_applyWatermark($dst_image_rs, $params[$watermark_size . '_width'], $params[$watermark_size . '_height'], $params);
if ($write_function == 'imagegif') {
return @$write_function($dst_image_rs, $params['dst_image']);
}
return @$write_function($dst_image_rs, $params['dst_image'], $write_function == 'imagepng' ? 0 : 100);
}
}
else {
// try to resize using ImageMagick
// TODO: implement crop and watermarking using imagemagick
exec('/usr/bin/convert '.$src_image.' -resize '.$params['target_width'].'x'.$params['target_height'].' '.$params['dst_image'], $shell_output, $exec_status);
return $exec_status == 0;
}
return false;
}
/**
* Preserve transparency for GIF and PNG images
*
* @param resource $src_image_rs
* @param resource $dst_image_rs
* @param int $image_type
* @return resource
*/
function _preserveTransparency($src_image_rs, $dst_image_rs, $image_type)
{
$transparent_index = imagecolortransparent($src_image_rs);
// if we have a specific transparent color
if ( $transparent_index >= 0 && $transparent_index < imagecolorstotal($src_image_rs) ) {
// get the original image's transparent color's RGB values
$transparent_color = imagecolorsforindex($src_image_rs, $transparent_index);
// allocate the same color in the new image resource
$transparent_index = imagecolorallocate($dst_image_rs, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
// completely fill the background of the new image with allocated color
imagefill($dst_image_rs, 0, 0, $transparent_index);
// set the background color for new image to transparent
imagecolortransparent($dst_image_rs, $transparent_index);
return $dst_image_rs;
}
// always make a transparent background color for PNGs that don't have one allocated already
if ( $image_type == IMAGETYPE_PNG ) {
// turn off transparency blending (temporarily)
imagealphablending($dst_image_rs, false);
// create a new transparent color for image
$transparent_color = imagecolorallocatealpha($dst_image_rs, 0, 0, 0, 127);
// completely fill the background of the new image with allocated color
imagefill($dst_image_rs, 0, 0, $transparent_color);
// restore transparency blending
imagesavealpha($dst_image_rs, true);
}
return $dst_image_rs;
}
/**
* Fills margins (if any) of resized are with given color
*
* @param resource $src_image_rs resized image resource
* @param Array $params crop parameters
* @param int|bool $image_type
* @return resource
*/
function &_applyFill(&$src_image_rs, $params, $image_type = false)
{
$x_position = round(($params['max_width'] - $params['target_width']) / 2); // center
$y_position = round(($params['max_height'] - $params['target_height']) / 2); // center
// crop resized image
$fill_image_rs = imagecreatetruecolor($params['max_width'], $params['max_height']);
if ($image_type !== false) {
$fill_image_rs = $this->_preserveTransparency($src_image_rs, $fill_image_rs, $image_type);
}
$fill = $params['fill'];
if (substr($fill, 0, 1) == '#') {
// hexdecimal color
$color = imagecolorallocate($fill_image_rs, hexdec( substr($fill, 1, 2) ), hexdec( substr($fill, 3, 2) ), hexdec( substr($fill, 5, 2) ));
}
else {
// for now we don't support color names, but we will in future
return $src_image_rs;
}
imagefill($fill_image_rs, 0, 0, $color);
imagecopy($fill_image_rs, $src_image_rs, $x_position, $y_position, 0, 0, $params['target_width'], $params['target_height']);
return $fill_image_rs;
}
/**
* Crop given image resource using given params and return resulting image resource
*
* @param resource $src_image_rs resized image resource
* @param Array $params crop parameters
* @param int|bool $image_type
* @return resource
*/
function &_cropImage(&$src_image_rs, $params, $image_type = false)
{
if ($params['crop_x'] == 'c') {
$x_position = round(($params['max_width'] - $params['target_width']) / 2); // center
}
elseif ($params['crop_x'] >= 0) {
$x_position = $params['crop_x']; // margin from left
}
else {
$x_position = $params['target_width'] - ($params['max_width'] - $params['crop_x']); // margin from right
}
if ($params['crop_y'] == 'c') {
$y_position = round(($params['max_height'] - $params['target_height']) / 2); // center
}
elseif ($params['crop_y'] >= 0) {
$y_position = $params['crop_y']; // margin from top
}
else {
$y_position = $params['target_height'] - ($params['max_height'] - $params['crop_y']); // margin from bottom
}
// crop resized image
$crop_image_rs = imagecreatetruecolor($params['max_width'], $params['max_height']);
if ($image_type !== false) {
$crop_image_rs = $this->_preserveTransparency($src_image_rs, $crop_image_rs, $image_type);
}
if (array_key_exists('fill', $params)) {
// fill image margins from resize with given color
$crop_image_rs =& $this->_applyFill($crop_image_rs, $params, $image_type);
}
imagecopy($crop_image_rs, $src_image_rs, $x_position, $y_position, 0, 0, $params['target_width'], $params['target_height']);
return $crop_image_rs;
}
/**
* Apply watermark (transparent PNG image) to given resized image resource
*
* @param resource $src_image_rs
* @param int $max_width
* @param int $max_height
* @param Array $params
* @return resource
*/
function &_applyWatermark(&$src_image_rs, $max_width, $max_height, $params)
{
$watermark_file = array_key_exists('wm_filename', $params) ? $params['wm_filename'] : false;
if (!$watermark_file || !file_exists($watermark_file)) {
// no watermark required, or provided watermark image is missing
return $src_image_rs;
}
$watermark_img_rs = imagecreatefrompng($watermark_file);
list ($watermark_width, $watermark_height) = $this->getImageInfo($watermark_file);
imagealphablending($src_image_rs, true);
if ($params['h_margin'] == 'c') {
$x_position = round($max_width / 2 - $watermark_width / 2); // center
}
elseif ($params['h_margin'] >= 0) {
$x_position = $params['h_margin']; // margin from left
}
else {
$x_position = $max_width - ($watermark_width - $params['h_margin']); // margin from right
}
if ($params['v_margin'] == 'c') {
$y_position = round($max_height / 2 - $watermark_height / 2); // center
}
elseif ($params['v_margin'] >= 0) {
$y_position = $params['v_margin']; // margin from top
}
else {
$y_position = $max_height - ($watermark_height - $params['v_margin']); // margin from bottom
}
imagecopy($src_image_rs, $watermark_img_rs, $x_position, $y_position, 0, 0, $watermark_width, $watermark_height);
return $src_image_rs;
}
/**
* Returns destination image size without actual resizing (useful for <img .../> HTML tag)
*
* @param string $src_image full path to source image (already existing)
* @param int $dst_width destination image width (in pixels)
* @param int $dst_height destination image height (in pixels)
* @param Array $params
* @return Array resized image dimensions (0 - width, 1 - height)
*/
function GetImageDimensions($src_image, $dst_width, $dst_height, $params)
{
$image_info = $this->getImageInfo($src_image);
if (!$image_info) {
return false;
}
$orig_width = $image_info[0];
$orig_height = $image_info[1];
$too_large = is_numeric($dst_width) ? ($orig_width > $dst_width) : false;
$too_large = $too_large || (is_numeric($dst_height) ? ($orig_height > $dst_height) : false);
if ($too_large) {
$width_ratio = $dst_width ? $dst_width / $orig_width : 1;
$height_ratio = $dst_height ? $dst_height / $orig_height : 1;
if (array_key_exists('crop_x', $params) || array_key_exists('crop_y', $params)) {
// resize by smallest inverted radio
$resize_by = $this->_getCropImageMinRatio($image_info, $dst_width, $dst_height);
if ($resize_by === false) {
return Array ($orig_width, $orig_height, false);
}
$ratio = $resize_by == 'width' ? $width_ratio : $height_ratio;
}
else {
$ratio = min($width_ratio, $height_ratio);
}
$width = ceil($orig_width * $ratio);
$height = ceil($orig_height * $ratio);
}
else {
$width = $orig_width;
$height = $orig_height;
}
return Array ($width, $height, $too_large);
}
/**
* Returns ratio type with smaller relation of original size to target size
*
* @param Array $image_info image information from "ImageHelper::getImageInfo"
* @param int $dst_width destination image width (in pixels)
* @param int $dst_height destination image height (in pixels)
* @return Array
*/
function _getCropImageMinRatio($image_info, $dst_width, $dst_height)
{
$width_ratio = $dst_width ? $image_info[0] / $dst_width : 1;
$height_ratio = $dst_height ? $image_info[1] / $dst_height : 1;
$minimal_ratio = min($width_ratio, $height_ratio);
if ($minimal_ratio < 1) {
// ratio is less then 1, image will be enlarged -> don't allow that
return false;
}
return $width_ratio < $height_ratio ? 'width' : 'height';
}
/**
* Returns image dimensions + checks if given file is existing image
*
* @param string $src_image full path to source image (already existing)
* @return mixed
*/
function getImageInfo($src_image)
{
if (!file_exists($src_image)) {
return false;
}
$image_info = @getimagesize($src_image);
if (!$image_info) {
trigger_error('Image <b>'.$src_image.'</b> <span class="debug_error">missing or invalid</span>', E_USER_WARNING);
return false;
}
return $image_info;
}
/**
* Returns maximal image size (width & height) among fields specified
*
* @param kDBItem $object
* @param string $fields
* @param string $format any format, that returns full url (e.g. files_resized:WxH, resize:WxH, full_url, full_urls)
* @return string
*/
function MaxImageSize(&$object, $fields, $format = null)
{
static $cached_sizes = Array ();
$cache_key = $object->getPrefixSpecial().'_'.$object->GetID();
if (!isset($cached_sizes[$cache_key])) {
$images = Array ();
$fields = explode(',', $fields);
foreach ($fields as $field) {
$image_data = $object->GetField($field, $format);
if (!$image_data) {
continue;
}
$images = array_merge($images, explode('|', $image_data));
}
$max_width = 0;
$max_height = 0;
$base_url = rtrim($this->Application->BaseURL(), '/');
foreach ($images as $image_url) {
$image_path = preg_replace('/^'.preg_quote($base_url, '/').'(.*)/', FULL_PATH.'\\1', $image_url);
$image_info = $this->getImageInfo($image_path);
$max_width = max($max_width, $image_info[0]);
$max_height = max($max_height, $image_info[1]);
}
$cached_sizes[$cache_key] = Array ($max_width, $max_height);
}
return $cached_sizes[$cache_key];
}
/**
* Puts existing item images (from sub-item) to virtual fields (in main item)
*
* @param kCatDBItem|kDBItem $object
*/
function LoadItemImages(&$object)
{
if (!$this->_canUseImages($object)) {
return ;
}
$max_image_count = $this->Application->ConfigValue($object->Prefix.'_MaxImageCount');
$sql = 'SELECT *
FROM '.TABLE_PREFIX.'CatalogImages
WHERE ResourceId = '.$object->GetDBField('ResourceId').'
ORDER BY Priority DESC
LIMIT 0, ' . (int)$max_image_count;
$item_images = $this->Conn->Query($sql);
$image_counter = 1;
foreach ($item_images as $item_image) {
$image_path = $item_image['ThumbPath'];
if ($item_image['DefaultImg'] == 1 || $item_image['Name'] == 'main') {
// process primary image separately
if ( $object->isField('PrimaryImage') ) {
$object->SetDBField('PrimaryImage', $image_path);
$object->SetOriginalField('PrimaryImage', $image_path);
$object->SetFieldOption('PrimaryImage', 'original_field', $item_image['Name']);
$this->_loadCustomFields($object, $item_image, 0);
}
continue;
}
if (abs($item_image['Priority'])) {
// use Priority as image counter, when specified
$image_counter = abs($item_image['Priority']);
}
if ( $object->isField('Image'.$image_counter) ) {
$object->SetDBField('Image'.$image_counter, $image_path);
$object->SetOriginalField('Image'.$image_counter, $image_path);
$object->SetFieldOption('Image'.$image_counter, 'original_field', $item_image['Name']);
$this->_loadCustomFields($object, $item_image, $image_counter);
}
$image_counter++;
}
}
/**
* Saves newly uploaded images to external image table
*
* @param kCatDBItem|kDBItem $object
*/
function SaveItemImages(&$object)
{
if (!$this->_canUseImages($object)) {
return ;
}
$table_name = $this->Application->getUnitOption('img', 'TableName');
$max_image_count = $this->Application->getUnitOption($object->Prefix, 'ImageCount'); // $this->Application->ConfigValue($object->Prefix.'_MaxImageCount');
$i = 0;
while ($i < $max_image_count) {
$field = $i ? 'Image'.$i : 'PrimaryImage';
$field_options = $object->GetFieldOptions($field);
$image_src = $object->GetDBField($field);
if ($image_src) {
if (isset($field_options['original_field'])) {
$key_clause = 'Name = '.$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 ThumbPath
FROM '.$table_name.'
WHERE '.$key_clause;
$image_src = $this->Conn->GetOne($sql);
if (@unlink(FULL_PATH.$image_src)) {
$sql = 'DELETE FROM '.$table_name.'
WHERE '.$key_clause;
$this->Conn->Query($sql);
}
}
else {
// image record found -> update
$fields_hash = Array (
'ThumbPath' => $image_src,
);
$this->_saveCustomFields($object, $fields_hash, $i);
$this->Conn->doUpdate($fields_hash, $table_name, $key_clause);
}
}
else {
// image record not found -> create
$fields_hash = Array (
'ResourceId' => $object->GetDBField('ResourceId'),
'Name' => $field,
'AltName' => $field,
'Enabled' => STATUS_ACTIVE,
'DefaultImg' => $i ? 0 : 1, // first image is primary, others not primary
'ThumbPath' => $image_src,
'Priority' => ($i == 0)? 0 : $i * (-1),
);
$this->_saveCustomFields($object, $fields_hash, $i);
$this->Conn->doInsert($fields_hash, $table_name);
$field_options['original_field'] = $field;
$object->SetFieldOptions($field, $field_options);
}
}
$i++;
}
}
/**
* Adds ability to load custom fields along with main image field
*
* @param kCatDBItem|kDBItem $object
* @param Array $fields_hash
* @param int $counter 0 - primary image, other number - additional image number
*/
function _loadCustomFields(&$object, $fields_hash, $counter)
{
$field_name = $counter ? 'Image' . $counter . 'Alt' : 'PrimaryImageAlt';
$object->SetDBField($field_name, (string)$fields_hash['AltName']);
}
/**
* Adds ability to save custom field along with main image save
*
* @param kCatDBItem|kDBItem $object
* @param Array $fields_hash
* @param int $counter 0 - primary image, other number - additional image number
*/
function _saveCustomFields(&$object, &$fields_hash, $counter)
{
$field_name = $counter ? 'Image' . $counter . 'Alt' : 'PrimaryImageAlt';
$fields_hash['AltName'] = (string)$object->GetDBField($field_name);
}
/**
* Checks, that item can use image upload capabilities
*
* @param kCatDBItem|kDBItem $object
* @return bool
*/
function _canUseImages(&$object)
{
$prefix = $object->Prefix == 'p' ? 'img' : $object->Prefix . '-img';
return $this->Application->prefixRegistred($prefix);
}
}
\ No newline at end of file

Event Timeline