1<?php 2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project 3// 4// All Rights Reserved. See copyright.txt for details and a complete list of authors. 5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details. 6// $Id$ 7 8/** 9 * Handler class for Image 10 * 11 * Letter key: ~i~ 12 * 13 */ 14class Tracker_field_Image extends Tracker_Field_File 15{ 16 private $imgMimeTypes; 17 private $imgMaxSize; 18 19 public static function getTypes() 20 { 21 return [ 22 'i' => [ 23 'name' => tr('Image'), 24 'description' => tr('Deprecated in favor of the Files field.'), 25 'help' => 'Image Tracker Field', 26 'prefs' => ['trackerfield_image'], 27 'tags' => ['deprecated'], 28 'warning' => tra('Deprecated in favor of the Files field.'), 29 'default' => 'n', 30 'params' => [ 31 'xListSize' => [ 32 'name' => tr('List image width'), 33 'description' => tr('Display size in pixels'), 34 'filter' => 'int', 35 'default' => 30, 36 'legacy_index' => 0, 37 ], 38 'yListSize' => [ 39 'name' => tr('List image height'), 40 'description' => tr('Display size in pixels'), 41 'filter' => 'int', 42 'default' => 30, 43 'legacy_index' => 1, 44 ], 45 'xDetailSize' => [ 46 'name' => tr('Detail image width'), 47 'description' => tr('Display size in pixels'), 48 'filter' => 'int', 49 'default' => 300, 50 'legacy_index' => 2, 51 ], 52 'yDetailSize' => [ 53 'name' => tr('Detail image height'), 54 'description' => tr('Display size in pixels'), 55 'filter' => 'int', 56 'default' => 300, 57 'legacy_index' => 3, 58 ], 59 'uploadLimitScale' => [ 60 'name' => tr('Maximum image size'), 61 'description' => tr('Maximum image width or height in pixels.'), 62 'filter' => 'int', 63 'default' => '1000', 64 'legacy_index' => 4, 65 ], 66 'shadowbox' => [ 67 'name' => tr('Shadowbox'), 68 'description' => tr('Shadowbox usage on this field'), 69 'filter' => 'alpha', 70 'options' => [ 71 '' => tr('Do not use'), 72 'individual' => tr('One box per item'), 73 'group' => tr('Use the same box for all images'), 74 ], 75 'legacy_index' => 5, 76 ], 77 'imageMissingIcon' => [ 78 'name' => tr('Missing Icon'), 79 'description' => tr('Icon to use when no images have been uploaded.'), 80 'filter' => 'url', 81 'legacy_index' => 6, 82 ], 83 ], 84 ], 85 ]; 86 } 87 88 function __construct($fieldInfo, $itemData, $trackerDefinition) 89 { 90 parent::__construct($fieldInfo, $itemData, $trackerDefinition); 91 $this->imgMimeTypes = ['image/jpeg', 'image/gif', 'image/png', 'image/pjpeg', 'image/bmp']; 92 $this->imgMaxSize = (1048576 * 4); // 4Mo 93 } 94 95 function getFieldData(array $requestData = []) 96 { 97 global $prefs; 98 $smarty = TikiLib::lib('smarty'); 99 $ins_id = $this->getInsertId(); 100 101 if (! empty($prefs['fgal_match_regex']) && ! empty($_FILES[$ins_id]['name'])) { 102 if (! preg_match('/' . $prefs['fgal_match_regex'] . '/', $_FILES[$ins_id]['name'], $reqs)) { 103 $smarty->assign('msg', tra('Invalid imagename (using filters for filenames)')); 104 $smarty->display("error.tpl"); 105 die; 106 } 107 } 108 if (! empty($prefs['fgal_nmatch_regex']) && ! empty($_FILES[$ins_id]['name'])) { 109 if (preg_match('/' . $prefs['fgal_nmatch_regex'] . '/', $_FILES[$ins_id]['name'], $reqs)) { 110 $smarty->assign('msg', tra('Invalid imagename (using filters for filenames)')); 111 $smarty->display("error.tpl"); 112 die; 113 } 114 } 115 116 // "Blank" means remove image 117 if (! empty($requestData[$ins_id]) && $requestData[$ins_id] == 'blank') { 118 return [ 'value' => 'blank' ]; 119 } 120 121 if (! empty($requestData)) { 122 return parent::getFieldData($requestData); 123 } else { 124 return [ 'value' => $this->getValue() ]; 125 } 126 } 127 128 function renderInnerOutput($context = []) 129 { 130 global $prefs; 131 $smarty = TikiLib::lib('smarty'); 132 133 $val = $this->getConfiguration('value'); 134 $list_mode = ! empty($context['list_mode']) ? $context['list_mode'] : 'n'; 135 if ($list_mode == 'csv') { 136 return $val; // return the filename 137 } 138 $pre = ''; 139 if (! empty($val) && file_exists($val)) { 140 $params['file'] = $val; 141 $shadowtype = $this->getOption('shadowbox'); 142 if ($prefs['feature_shadowbox'] == 'y' && ! empty($shadowtype)) { 143 switch ($shadowtype) { 144 case 'item': 145 $rel = '[' . $this->getItemId() . ']'; 146 break; 147 case 'individual': 148 $rel = ''; 149 break; 150 default: 151 $rel = '[' . $this->getConfiguration('fieldId') . ']'; 152 break; 153 } 154 $pre = "<a href=\"$val\" data-box=\"shadowbox$rel;type=img\">"; 155 } 156 if ($this->getOption('xListSize') || $this->getOption('yListSize') || $this->getOption('xDetailSize') || $this->getOption('yDetailSize')) { 157 $image_size_info = getimagesize($val); 158 } 159 if ($list_mode != 'n') { 160 if ($this->getOption('xListSize') || $this->getOption('yListSize')) { 161 list( $params['width'], $params['height']) = $this->get_resize_dimensions( 162 $image_size_info[0], 163 $image_size_info[1], 164 $this->getOption('xListSize'), 165 $this->getOption('yListSize') 166 ); 167 } 168 } else { 169 if ($this->getOption('xDetailSize') || $this->getOption('yDetailSize')) { 170 list( $params['width'], $params['height']) = $this->get_resize_dimensions( 171 $image_size_info[0], 172 $image_size_info[1], 173 $this->getOption('xDetailSize'), 174 $this->getOption('yDetailSize') 175 ); 176 } 177 } 178 } else { 179 if ($this->getOption('imageMissingIcon')) { 180 $params['file'] = $this->getOption('imageMissingIcon'); 181 $params['alt'] = 'n/a'; 182 } else { 183 return ''; 184 } 185 } 186 $smarty->loadPlugin('smarty_function_html_image'); 187 $ret = smarty_function_html_image($params, $smarty->getEmptyInternalTemplate()); 188 if (! empty($pre)) { 189 $ret = $pre . $ret . '</a>'; 190 } 191 return $ret; 192 } 193 194 function renderInput($context = []) 195 { 196 return $this->renderTemplate( 197 'trackerinput/image.tpl', 198 $context, 199 [ 200 'image_tag' => $this->renderInnerOutput($context), 201 ] 202 ); 203 } 204 205 function handleSave($value, $oldValue) 206 { 207 if (! empty($value)) { 208 $old_file = $oldValue; 209 210 if ($value == 'blank') { 211 if (file_exists($old_file)) { 212 unlink($old_file); 213 } 214 215 return [ 216 'value' => '', 217 ]; 218 } 219 220 $type = $this->getConfiguration('file_type'); 221 222 if ($this->isImageType($type)) { 223 if ($maxSize = $this->getOption('uploadLimitScale')) { 224 $imagegallib = TikiLib::lib('imagegal'); // TODO: refactor to use Image class directly and remove dependency on imagegals 225 $imagegallib->image = $value; 226 $imagegallib->readimagefromstring(); 227 $imagegallib->getimageinfo(); 228 if ($imagegallib->xsize > $maxSize || $imagegallib->ysize > $maxSize) { 229 $imagegallib->rescaleImage($maxSize, $maxSize); 230 $value = $imagegallib->image; 231 } 232 } 233 $filesize = $this->getConfiguration('file_size'); 234 if ($filesize <= $this->imgMaxSize) { 235 $itemId = $this->getItemId(); 236 $file_name = $this->getImageFilename($this->getConfiguration('file_name'), $itemId, $this->getConfiguration('fieldId')); 237 238 file_put_contents($file_name, $value); 239 chmod($file_name, 0644); // seems necessary on some system (see move_uploaded_file doc on php.net 240 241 if (file_exists($old_file) && $old_file != $file_name) { 242 unlink($old_file); 243 } 244 245 return [ 246 'value' => $file_name, 247 ]; 248 } 249 } 250 } 251 252 return [ 253 'value' => false, 254 ]; 255 } 256 257 /** 258 * Calculate the size of a resized image 259 * 260 * TODO move to a lib (Images depends on Imagick or GD which this doesn't need) 261 * 262 * @param int $image_width (existing image width) 263 * @param int $image_height (existing image height) 264 * @param int $max_width (max width to scale to) 265 * @param int $max_height (optional max height) 266 * @param bool $upscale (whether to make images larger - default = false) 267 * 268 * @return array(int $resized_width, int $resized_height) 269 */ 270 private function get_resize_dimensions($image_width, $image_height, $max_width = null, $max_height = null, $upscale = false) 271 { 272 if (! $upscale && $image_width <= $max_width && $image_height <= $max_height) { 273 return [$image_width, $image_height]; 274 } 275 if (! $max_height || ($max_width && $image_width > $image_height && $image_height < $max_height)) { 276 $ratio = $max_width / $image_width; 277 } else { 278 $ratio = $max_height / $image_height; 279 if ($max_width && round($image_width * $ratio) > $max_width) { 280 $ratio = $max_width / $image_width; 281 } 282 } 283 return [round($image_width * $ratio), round($image_height * $ratio)]; 284 } 285 286 function getImageFilename($name, $itemId, $fieldId) 287 { 288 $ext = pathinfo($name, PATHINFO_EXTENSION); 289 if (! in_array($ext, ['png', 'gif', 'jpg', 'jpeg'])) { 290 $ext = 'jpg'; 291 } 292 293 do { 294 $name = md5(uniqid("$name.$itemId.$fieldId")); 295 $name .= '.' . $ext; 296 } while (file_exists("img/trackers/$name")); 297 298 return "img/trackers/$name"; 299 } 300 301 function isImageType($mimeType) 302 { 303 return in_array($mimeType, $this->imgMimeTypes); 304 } 305 306 function getDocumentPart(Search_Type_Factory_Interface $typeFactory) 307 { 308 $value = $this->getValue(); 309 $baseKey = $this->getBaseKey(); 310 311 return [ 312 $baseKey => $typeFactory->plaintext($value), 313 ]; 314 } 315} 316