Index: branches/5.2.x/core/units/helpers/file_helper.php =================================================================== --- branches/5.2.x/core/units/helpers/file_helper.php (revision 16128) +++ branches/5.2.x/core/units/helpers/file_helper.php (revision 16129) @@ -1,463 +1,484 @@ 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 "' . $path . '"', 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 $file_info DirectoryIterator */ $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 "' . $destination . DIRECTORY_SEPARATOR . $file . '"', 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 $file_info DirectoryIterator */ $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 "' . $destination . DIRECTORY_SEPARATOR . $file . '"', 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 "\" $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; } } Index: branches/5.2.x/core/units/helpers/image_helper.php =================================================================== --- branches/5.2.x/core/units/helpers/image_helper.php (revision 16128) +++ branches/5.2.x/core/units/helpers/image_helper.php (revision 16129) @@ -1,714 +1,720 @@ 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 "\" - $dst_image = preg_replace('/^'.preg_quote($src_path, '/').'(.*)\.(.*)$/', $src_path_escaped . DIRECTORY_SEPARATOR . 'resized\\1_' . crc32(serialize($params)) . '.\\2', $src_image); + // Resize required OR watermarking required -> change resulting image name ! + if ( $needs_resize || array_intersect(array_keys($params), $transform_keys) ) { + // Escape replacement patterns, like "\". + $src_path_escaped = preg_replace('/(\\\[\d]+)/', '\\\\\1', $src_path); + $params_hash = kUtil::crc32(serialize($this->fileHelper->makeRelative($params))); + $dst_image = preg_replace( + '/^' . preg_quote($src_path, '/') . '(.*)\.(.*)$/', + $src_path_escaped . DIRECTORY_SEPARATOR . 'resized\\1_' . $params_hash . '.\\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 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 '.$src_image.' missing or invalid', 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); } } Index: branches/5.2.x/core/units/helpers/minifiers/minify_helper.php =================================================================== --- branches/5.2.x/core/units/helpers/minifiers/minify_helper.php (revision 16128) +++ branches/5.2.x/core/units/helpers/minifiers/minify_helper.php (revision 16129) @@ -1,315 +1,315 @@ debugMode = $this->Application->isDebugMode(false); $this->resourceFolder = WRITEABLE . '/cache'; } /** * When used as non-block tag, then compress given files and return url to result * * @param Array $params * @return string * @access public */ public function CompressScriptTag($params) { // put to queue if ( array_key_exists('to', $params) ) { $files = $this->Application->GetVar($params['to'], ''); $this->Application->SetVar($params['to'], $files . '|' . $params['files']); return ''; } if ( array_key_exists('from', $params) ) { // get from queue $files = $this->Application->GetVar($params['from']); } else { // get from tag $files = $params['files']; } $files = $this->_getTemplatePaths( array_map('trim', explode('|', $files)) ); if ( !$files ) { trigger_error('No files specified for compression.', E_USER_NOTICE); return ''; } $extension = pathinfo($files[0], PATHINFO_EXTENSION); $save_as = isset($params['save_as']) ? $params['save_as'] : false; $dst_file = $this->resourceFolder . DIRECTORY_SEPARATOR . ($this->debugMode ? 'd' : 'c') . '_'; + /** @var FileHelper $file_helper */ + $file_helper = $this->Application->recallObject('FileHelper'); + if ( $save_as ) { $dst_file .= $save_as . ( strpos($save_as, '.') === false ? '.' . $extension : '' ); } else { - $dst_file .= $this->_getHash($files) . '.' . $extension; + $dst_file .= $this->_getHash($file_helper->makeRelative($files)) . '.' . $extension; } $was_compressed = file_exists($dst_file); if ( !$was_compressed || ($this->debugMode && filemtime($dst_file) < $this->_getMaxFileDate($files)) ) { $string = ''; $path_length = strlen(FULL_PATH) + 1; foreach ($files as $file) { if ( !file_exists($file) ) { continue; } // add filename before for easier debugging if ( $this->debugMode ) { $string .= '/* === File: ' . substr($file, $path_length) . ' === */' . "\n"; $string .= '/* ' . str_repeat('=', strlen(substr($file, $path_length)) + 14) . ' */' . "\n\n"; } // add file content $string .= file_get_contents($file) . "\n\n"; } // replace templates base if ( isset($params['templates_base']) ) { $templates_base = $params['templates_base']; } else { $templates_base = $this->Application->ProcessParsedTag('m', 'TemplatesBase', Array ()); } $templates_base = preg_replace('/^' . preg_quote($this->Application->BaseURL(), '/') . '/', BASE_PATH . '/', $templates_base); $string = str_replace('@templates_base@', rtrim($templates_base, '/'), $string); if ( !$this->debugMode ) { // don't compress merged js/css file in debug mode to allow js/css debugging $this->compressString($string, $extension); } // save compressed file file_put_contents($dst_file, $string); } - $file_helper = $this->Application->recallObject('FileHelper'); - /* @var $file_helper FileHelper */ - return $file_helper->pathToUrl($dst_file) . '?ts=' . adodb_date('Y-m-d_H:i:s', filemtime($dst_file)); } /** * Returns maximal modification date across given files * * @param Array $files * @return int * @access protected */ protected function _getMaxFileDate($files) { $ret = 0; foreach ($files as $file) { if ( file_exists($file) ) { $ret = max($ret, filemtime($file)); } } return $ret; } /** * Returns hash string based on given files * * @param Array $files * @return int * @access protected */ protected function _getHash($files) { $hash = $files; if ($this->Application->isAdmin) { array_unshift($hash, 'A:1'); } else { array_unshift($hash, 'A:0;T:' . $this->Application->GetVar('m_theme')); } - return crc32( implode('|', $hash) ); + return kUtil::crc32(implode('|', $hash)); } /** * Deletes compression info file * * @todo also delete all listed there files * @access public */ public function delete() { $iterator = new DirectoryIterator($this->resourceFolder); /* @var $file_info DirectoryIterator */ foreach ($iterator as $file_info) { if ( !$file_info->isDir() && preg_match('/^(c|d)_.*.(css|js)$/', $file_info->getFilename()) ) { unlink( $file_info->getPathname() ); } } } /** * Compress $string based on $extension * * @param string $string * @param string $extension * @return void * @access protected */ public function compressString(&$string, $extension) { $vars = kUtil::getConfigVars(); if ( !array_key_exists('CompressionEngine', $vars) ) { // compression method not specified - use none return; } switch ( $vars['CompressionEngine'] ) { case 'yui': $this->compressViaJava($string, $extension); break; case 'php': $this->compressViaPHP($string, $extension); break; } } /** * Compresses string using YUI compressor (uses Java) * * @param string $string * @param string $extension * @return void * @access protected */ protected function compressViaJava(&$string, $extension) { $tmp_file = tempnam('/tmp', 'to_compress_'); file_put_contents($tmp_file, $string); $command = 'java -jar ' . dirname(__FILE__) . DIRECTORY_SEPARATOR . 'yuicompressor-2.4.2.jar --type ' . $extension . ' --charset utf-8 ' . $tmp_file; $string = shell_exec($command); unlink($tmp_file); } /** * Compresses string using PHP compressor * * @param string $string * @param string $extension * @return void * @access protected */ protected function compressViaPHP(&$string, $extension) { $minifier = $this->Application->makeClass($extension == 'js' ? 'JsMinifyHelper' : 'CssMinifyHelper'); /* @var $minifier JsMinifyHelper */ $string = $minifier->minify($string); } /** * Get full paths on disk for each of given templates * * @param Array $templates * @return Array * @access protected */ protected function _getTemplatePaths($templates) { $ret = Array (); $reg_exp = '/^' . preg_quote($this->Application->BaseURL(), '/') . '(.*)/'; foreach ($templates as $template) { if ( !$template ) { continue; } if ( preg_match($reg_exp, $template, $regs) ) { // full url (from current domain) to a file $ret[] = FULL_PATH . '/' . $regs[1]; } elseif ( strpos($template, '{module_path}') !== false ) { $ret = array_merge($ret, $this->_moduleInclude($template)); } else { $ret[] = $this->Application->TemplatesCache->GetRealFilename($template); } } return $ret; } /** * * @param string $template * @return Array * @access protected */ protected function _moduleInclude($template) { $ret = $included = Array (); foreach ($this->Application->ModuleInfo as $module_name => $module_data) { if ( $module_name == 'In-Portal' ) { continue; } $module_prefix = $this->Application->isAdmin ? mb_strtolower($module_name) . '/' : $module_data['TemplatePath']; if ( in_array($module_prefix, $included) ) { continue; } $ret[] = $this->Application->TemplatesCache->GetRealFilename(str_replace('{module_path}', $module_prefix, $template)); $included[] = $module_prefix; } return $ret; } - } \ No newline at end of file + }