source: trunk/admin/include/functions_upload.inc.php @ 9022

Revision 9022, 20.2 KB checked in by plg, 9 years ago (diff)

no need to check the availability of a GD function in the ImageMagick resize algorithm

Line 
1<?php
2// +-----------------------------------------------------------------------+
3// | Piwigo - a PHP based photo gallery                                    |
4// +-----------------------------------------------------------------------+
5// | Copyright(C) 2008-2011 Piwigo Team                  http://piwigo.org |
6// | Copyright(C) 2003-2008 PhpWebGallery Team    http://phpwebgallery.net |
7// | Copyright(C) 2002-2003 Pierrick LE GALL   http://le-gall.net/pierrick |
8// +-----------------------------------------------------------------------+
9// | This program is free software; you can redistribute it and/or modify  |
10// | it under the terms of the GNU General Public License as published by  |
11// | the Free Software Foundation                                          |
12// |                                                                       |
13// | This program is distributed in the hope that it will be useful, but   |
14// | WITHOUT ANY WARRANTY; without even the implied warranty of            |
15// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
16// | General Public License for more details.                              |
17// |                                                                       |
18// | You should have received a copy of the GNU General Public License     |
19// | along with this program; if not, write to the Free Software           |
20// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
21// | USA.                                                                  |
22// +-----------------------------------------------------------------------+
23
24include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
25
26// add default event handler for image and thumbnail resize
27add_event_handler('upload_image_resize', 'pwg_image_resize', EVENT_HANDLER_PRIORITY_NEUTRAL, 7);
28add_event_handler('upload_thumbnail_resize', 'pwg_image_resize', EVENT_HANDLER_PRIORITY_NEUTRAL, 7);
29
30function get_upload_form_config()
31{
32  // default configuration for upload
33  $upload_form_config = array(
34    'websize_resize' => array(
35      'default' => true,
36      'can_be_null' => false,
37      ),
38   
39    'websize_maxwidth' => array(
40      'default' => 800,
41      'min' => 100,
42      'max' => 1600,
43      'pattern' => '/^\d+$/',
44      'can_be_null' => true,
45      'error_message' => l10n('The websize maximum width must be a number between %d and %d'),
46      ),
47 
48    'websize_maxheight' => array(
49      'default' => 600,
50      'min' => 100,
51      'max' => 1200,
52      'pattern' => '/^\d+$/',
53      'can_be_null' => true,
54      'error_message' => l10n('The websize maximum height must be a number between %d and %d'),
55      ),
56 
57    'websize_quality' => array(
58      'default' => 95,
59      'min' => 50,
60      'max' => 100,
61      'pattern' => '/^\d+$/',
62      'can_be_null' => false,
63      'error_message' => l10n('The websize image quality must be a number between %d and %d'),
64      ),
65 
66    'thumb_maxwidth' => array(
67      'default' => 128,
68      'min' => 50,
69      'max' => 300,
70      'pattern' => '/^\d+$/',
71      'can_be_null' => false,
72      'error_message' => l10n('The thumbnail maximum width must be a number between %d and %d'),
73      ),
74 
75    'thumb_maxheight' => array(
76      'default' => 96,
77      'min' => 50,
78      'max' => 300,
79      'pattern' => '/^\d+$/',
80      'can_be_null' => false,
81      'error_message' => l10n('The thumbnail maximum height must be a number between %d and %d'),
82      ),
83 
84    'thumb_quality' => array(
85      'default' => 95,
86      'min' => 50,
87      'max' => 100,
88      'pattern' => '/^\d+$/',
89      'can_be_null' => false,
90      'error_message' => l10n('The thumbnail image quality must be a number between %d and %d'),
91      ),
92 
93    'hd_keep' => array(
94      'default' => true,
95      'can_be_null' => false,
96      ),
97 
98    'hd_resize' => array(
99      'default' => false,
100      'can_be_null' => false,
101      ),
102 
103    'hd_maxwidth' => array(
104      'default' => 2000,
105      'min' => 500,
106      'max' => 20000,
107      'pattern' => '/^\d+$/',
108      'can_be_null' => false,
109      'error_message' => l10n('The high definition maximum width must be a number between %d and %d'),
110      ),
111 
112    'hd_maxheight' => array(
113      'default' => 2000,
114      'min' => 500,
115      'max' => 20000,
116      'pattern' => '/^\d+$/',
117      'can_be_null' => false,
118      'error_message' => l10n('The high definition maximum height must be a number between %d and %d'),
119      ),
120 
121    'hd_quality' => array(
122      'default' => 95,
123      'min' => 50,
124      'max' => 100,
125      'pattern' => '/^\d+$/',
126      'can_be_null' => false,
127      'error_message' => l10n('The high definition image quality must be a number between %d and %d'),
128      ),
129    );
130
131  return $upload_form_config;
132}
133
134/*
135 * automatic fill of configuration parameters
136 */
137function prepare_upload_configuration()
138{
139  global $conf;
140
141  $inserts = array();
142 
143  foreach (get_upload_form_config() as $param_shortname => $param)
144  {
145    $param_name = 'upload_form_'.$param_shortname;
146 
147    if (!isset($conf[$param_name]))
148    {
149      $conf[$param_name] = $param['default'];
150     
151      array_push(
152        $inserts,
153        array(
154          'param' => $param_name,
155          'value' => boolean_to_string($param['default']),
156          )
157        );
158    }
159  }
160 
161  if (count($inserts) > 0)
162  {
163    mass_inserts(
164      CONFIG_TABLE,
165      array_keys($inserts[0]),
166      $inserts
167      );
168  }
169}
170
171function add_uploaded_file($source_filepath, $original_filename=null, $categories=null, $level=null)
172{
173  // Here is the plan
174  //
175  // 1) move uploaded file to upload/2010/01/22/20100122003814-449ada00.jpg
176  //
177  // 2) if taller than max_height or wider than max_width, move to pwg_high
178  //    + web sized creation
179  //
180  // 3) thumbnail creation from web sized
181  //
182  // 4) register in database
183 
184  // TODO
185  // * check md5sum (already exists?)
186 
187  global $conf, $user;
188 
189  // current date
190  list($dbnow) = pwg_db_fetch_row(pwg_query('SELECT NOW();'));
191  list($year, $month, $day) = preg_split('/[^\d]/', $dbnow, 4);
192 
193  // upload directory hierarchy
194  $upload_dir = sprintf(
195    PHPWG_ROOT_PATH.$conf['upload_dir'].'/%s/%s/%s',
196    $year,
197    $month,
198    $day
199    );
200
201  // compute file path
202  $md5sum = md5_file($source_filepath);
203  $date_string = preg_replace('/[^\d]/', '', $dbnow);
204  $random_string = substr($md5sum, 0, 8);
205  $filename_wo_ext = $date_string.'-'.$random_string;
206  $file_path = $upload_dir.'/'.$filename_wo_ext.'.';
207
208  list($width, $height, $type) = getimagesize($source_filepath);
209  if (IMAGETYPE_PNG == $type)
210  {
211    $file_path.= 'png';
212  }
213  else
214  {
215    $file_path.= 'jpg';
216  }
217
218  prepare_directory($upload_dir);
219  if (is_uploaded_file($source_filepath))
220  {
221    move_uploaded_file($source_filepath, $file_path);
222  }
223  else
224  {
225    copy($source_filepath, $file_path);
226  }
227
228  if ($conf['upload_form_websize_resize']
229      and need_resize($file_path, $conf['upload_form_websize_maxwidth'], $conf['upload_form_websize_maxheight']))
230  {
231    $high_path = file_path_for_type($file_path, 'high');
232    $high_dir = dirname($high_path);
233    prepare_directory($high_dir);
234   
235    rename($file_path, $high_path);
236    $high_infos = pwg_image_infos($high_path);
237   
238    trigger_event(
239      'upload_image_resize',
240      false,
241      $high_path,
242      $file_path,
243      $conf['upload_form_websize_maxwidth'],
244      $conf['upload_form_websize_maxheight'],
245      $conf['upload_form_websize_quality'],
246      false
247      );
248
249    if (is_imagick())
250    {
251      if ($conf['upload_form_hd_keep'])
252      {
253        if ($conf['upload_form_hd_resize'])
254        {
255          $need_resize = need_resize($high_path, $conf['upload_form_hd_maxwidth'], $conf['upload_form_hd_maxheight']);
256       
257          if ($need_resize)
258          {
259            pwg_image_resize(
260              false,
261              $high_path,
262              $high_path,
263              $conf['upload_form_hd_maxwidth'],
264              $conf['upload_form_hd_maxheight'],
265              $conf['upload_form_hd_quality'],
266              false
267              );
268            $high_infos = pwg_image_infos($high_path);
269          }
270        }
271      }
272      else
273      {
274        unlink($high_path);
275        $high_infos = null;
276      }
277    }
278  }
279
280  $file_infos = pwg_image_infos($file_path);
281 
282  $thumb_path = file_path_for_type($file_path, 'thumb');
283  $thumb_dir = dirname($thumb_path);
284  prepare_directory($thumb_dir);
285 
286  trigger_event(
287    'upload_thumbnail_resize',
288    false,
289    $file_path,
290    $thumb_path,
291    $conf['upload_form_thumb_maxwidth'],
292    $conf['upload_form_thumb_maxheight'],
293    $conf['upload_form_thumb_quality'],
294    true
295    );
296 
297  $thumb_infos = pwg_image_infos($thumb_path);
298
299  // database registration
300  $insert = array(
301    'file' => pwg_db_real_escape_string(isset($original_filename) ? $original_filename : basename($file_path)),
302    'date_available' => $dbnow,
303    'tn_ext' => 'jpg',
304    'path' => preg_replace('#^'.preg_quote(PHPWG_ROOT_PATH).'#', '', $file_path),
305    'filesize' => $file_infos['filesize'],
306    'width' => $file_infos['width'],
307    'height' => $file_infos['height'],
308    'md5sum' => $md5sum,
309    'added_by' => $user['id'],
310    );
311
312  if (isset($high_infos))
313  {
314    $insert['has_high'] = 'true';
315    $insert['high_filesize'] = $high_infos['filesize'];
316  }
317
318  if (isset($level))
319  {
320    $insert['level'] = $level;
321  }
322 
323  mass_inserts(
324    IMAGES_TABLE,
325    array_keys($insert),
326    array($insert)
327    );
328 
329  $image_id = pwg_db_insert_id(IMAGES_TABLE);
330
331  if (isset($categories) and count($categories) > 0)
332  {
333    associate_images_to_categories(
334      array($image_id),
335      $categories
336      );
337  }
338 
339  // update metadata from the uploaded file (exif/iptc)
340  if ($conf['use_exif'] and !function_exists('read_exif_data'))
341  {
342    $conf['use_exif'] = false;
343  }
344  update_metadata(array($image_id=>$file_path));
345 
346  invalidate_user_cache();
347
348  return $image_id;
349}
350
351function prepare_directory($directory)
352{
353  if (!is_dir($directory)) {
354    if (substr(PHP_OS, 0, 3) == 'WIN')
355    {
356      $directory = str_replace('/', DIRECTORY_SEPARATOR, $directory);
357    }
358    umask(0000);
359    $recursive = true;
360    if (!@mkdir($directory, 0777, $recursive))
361    {
362      die('[prepare_directory] cannot create directory "'.$directory.'"');
363    }
364  }
365
366  if (!is_writable($directory))
367  {
368    // last chance to make the directory writable
369    @chmod($directory, 0777);
370
371    if (!is_writable($directory))
372    {
373      die('[prepare_directory] directory "'.$directory.'" has no write access');
374    }
375  }
376
377  secure_directory($directory);
378}
379
380function need_resize($image_filepath, $max_width, $max_height)
381{
382  // TODO : the resize check should take the orientation into account. If a
383  // rotation must be applied to the resized photo, then we should test
384  // invert width and height.
385  list($width, $height) = getimagesize($image_filepath);
386 
387  if ($width > $max_width or $height > $max_height)
388  {
389    return true;
390  }
391
392  return false;
393}
394
395function get_resize_dimensions($width, $height, $max_width, $max_height, $rotation=null)
396{
397  $rotate_for_dimensions = false;
398  if (isset($rotation) and in_array(abs($rotation), array(90, 270)))
399  {
400    $rotate_for_dimensions = true;
401  }
402
403  if ($rotate_for_dimensions)
404  {
405    list($width, $height) = array($height, $width);
406  }
407 
408  $ratio_width  = $width / $max_width;
409  $ratio_height = $height / $max_height;
410 
411  // maximal size exceeded ?
412  if ($ratio_width > 1 or $ratio_height > 1)
413  {
414    if ($ratio_width < $ratio_height)
415    { 
416      $destination_width = ceil($width / $ratio_height);
417      $destination_height = $max_height;
418    }
419    else
420    { 
421      $destination_width = $max_width; 
422      $destination_height = ceil($height / $ratio_width);
423    }
424  }
425
426  if ($rotate_for_dimensions)
427  {
428    list($destination_width, $destination_height) = array($destination_height, $destination_width);
429  }
430 
431  return array(
432    'width' => $destination_width,
433    'height'=> $destination_height,
434    );
435}
436
437function pwg_image_resize($result, $source_filepath, $destination_filepath, $max_width, $max_height, $quality, $strip_metadata=false)
438{
439  if ($result !== false)
440  {
441    //someone hooked us - so we skip
442    return $result;
443  }
444
445  if (is_imagick())
446  {
447    return pwg_image_resize_im($source_filepath, $destination_filepath, $max_width, $max_height, $quality, $strip_metadata);
448  }
449  else
450  {
451    return pwg_image_resize_gd($source_filepath, $destination_filepath, $max_width, $max_height, $quality);
452  }
453}
454
455function pwg_image_resize_gd($source_filepath, $destination_filepath, $max_width, $max_height, $quality)
456{
457  if (!function_exists('gd_info'))
458  {
459    return false;
460  }
461
462  // extension of the picture filename
463  $extension = strtolower(get_extension($source_filepath));
464
465  $source_image = null;
466  if (in_array($extension, array('jpg', 'jpeg')))
467  {
468    $source_image = imagecreatefromjpeg($source_filepath);
469  }
470  else if ($extension == 'png')
471  {
472    $source_image = imagecreatefrompng($source_filepath);
473  }
474  else
475  {
476    die('unsupported file extension');
477  }
478
479  $rotation = null;
480  if (function_exists('imagerotate'))
481  {
482    $rotation = get_rotation_angle($source_filepath);
483  }
484 
485  // width/height
486  $source_width  = imagesx($source_image); 
487  $source_height = imagesy($source_image);
488 
489  $resize_dimensions = get_resize_dimensions($source_width, $source_height, $max_width, $max_height, $rotation);
490
491  // testing on height is useless in theory: if width is unchanged, there
492  // should be no resize, because width/height ratio is not modified.
493  if ($resize_dimensions['width'] == $source_width and $resize_dimensions['height'] == $source_height)
494  {
495    // the image doesn't need any resize! We just copy it to the destination
496    copy($source_filepath, $destination_filepath);
497    return true;
498  }
499 
500  $destination_image = imagecreatetruecolor($resize_dimensions['width'], $resize_dimensions['height']);
501 
502  imagecopyresampled(
503    $destination_image,
504    $source_image,
505    0,
506    0,
507    0,
508    0,
509    $resize_dimensions['width'],
510    $resize_dimensions['height'],
511    $source_width,
512    $source_height
513    );
514
515  // rotation occurs only on resized photo to avoid useless memory use
516  if (isset($rotation))
517  {
518    $destination_image = imagerotate($destination_image, $rotation, 0);
519  }
520 
521  $extension = strtolower(get_extension($destination_filepath));
522  if ($extension == 'png')
523  {
524    imagepng($destination_image, $destination_filepath);
525  }
526  else
527  {
528    imagejpeg($destination_image, $destination_filepath, $quality);
529  }
530  // freeing memory ressources
531  imagedestroy($source_image);
532  imagedestroy($destination_image);
533
534  // everything should be OK if we are here!
535  return true;
536}
537
538function pwg_image_resize_im($source_filepath, $destination_filepath, $max_width, $max_height, $quality, $strip_metadata=false)
539{
540  // extension of the picture filename
541  $extension = strtolower(get_extension($source_filepath));
542  if (!in_array($extension, array('jpg', 'jpeg', 'png')))
543  {
544    die('[Imagick] unsupported file extension');
545  }
546
547  $image = new Imagick($source_filepath);
548
549  $rotation = get_rotation_angle($source_filepath);
550 
551  // width/height
552  $source_width  = $image->getImageWidth();
553  $source_height = $image->getImageHeight();
554 
555  $resize_dimensions = get_resize_dimensions($source_width, $source_height, $max_width, $max_height, $rotation);
556
557  // testing on height is useless in theory: if width is unchanged, there
558  // should be no resize, because width/height ratio is not modified.
559  if ($resize_dimensions['width'] == $source_width and $resize_dimensions['height'] == $source_height)
560  {
561    // the image doesn't need any resize! We just copy it to the destination
562    copy($source_filepath, $destination_filepath);
563    return true;
564  }
565
566  $image->setImageCompressionQuality($quality);
567  $image->setInterlaceScheme(Imagick::INTERLACE_LINE);
568 
569  if ($strip_metadata)
570  {
571    // we save a few kilobytes. For example a thumbnail with metadata
572    // weights 25KB, without metadata 7KB.
573    $image->stripImage();
574  }
575 
576  $image->resizeImage($resize_dimensions['width'], $resize_dimensions['height'], Imagick::FILTER_LANCZOS, 0.9);
577
578  if (isset($rotation))
579  {
580    $image->rotateImage(new ImagickPixel(), -$rotation);
581    $image->setImageOrientation(Imagick::ORIENTATION_TOPLEFT);
582  }
583
584  $image->writeImage($destination_filepath);
585  $image->destroy();
586
587  // everything should be OK if we are here!
588  return true;
589}
590
591function get_rotation_angle($source_filepath)
592{
593  global $conf;
594
595  if (!$conf['upload_form_automatic_rotation'])
596  {
597    return null;
598  }
599 
600  if (!function_exists('exif_read_data'))
601  {
602    return null;
603  }
604
605  $rotation = null;
606 
607  $exif = exif_read_data($source_filepath);
608 
609  if (isset($exif['Orientation']) and preg_match('/^\s*(\d)/', $exif['Orientation'], $matches))
610  {
611    $orientation = $matches[1];
612    if (in_array($orientation, array(3, 4)))
613    {
614      $rotation = 180;
615    }
616    elseif (in_array($orientation, array(5, 6)))
617    {
618      $rotation = 270;
619    }
620    elseif (in_array($orientation, array(7, 8)))
621    {
622      $rotation = 90;
623    }
624  }
625
626  return $rotation;
627}
628
629function pwg_image_infos($path)
630{
631  list($width, $height) = getimagesize($path);
632  $filesize = floor(filesize($path)/1024);
633 
634  return array(
635    'width'  => $width,
636    'height' => $height,
637    'filesize' => $filesize,
638    );
639}
640
641function is_valid_image_extension($extension)
642{
643  return in_array(strtolower($extension), array('jpg', 'jpeg', 'png'));
644}
645
646function file_upload_error_message($error_code)
647{
648  switch ($error_code) {
649    case UPLOAD_ERR_INI_SIZE:
650      return sprintf(
651        l10n('The uploaded file exceeds the upload_max_filesize directive in php.ini: %sB'),
652        get_ini_size('upload_max_filesize', false)
653        );
654    case UPLOAD_ERR_FORM_SIZE:
655      return l10n('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form');
656    case UPLOAD_ERR_PARTIAL:
657      return l10n('The uploaded file was only partially uploaded');
658    case UPLOAD_ERR_NO_FILE:
659      return l10n('No file was uploaded');
660    case UPLOAD_ERR_NO_TMP_DIR:
661      return l10n('Missing a temporary folder');
662    case UPLOAD_ERR_CANT_WRITE:
663      return l10n('Failed to write file to disk');
664    case UPLOAD_ERR_EXTENSION:
665      return l10n('File upload stopped by extension');
666    default:
667      return l10n('Unknown upload error');
668  }
669}
670
671function get_ini_size($ini_key, $in_bytes=true)
672{
673  $size = ini_get($ini_key);
674
675  if ($in_bytes)
676  {
677    $size = convert_shortand_notation_to_bytes($size);
678  }
679 
680  return $size;
681}
682
683function convert_shortand_notation_to_bytes($value)
684{
685  $suffix = substr($value, -1);
686  $multiply_by = null;
687 
688  if ('K' == $suffix)
689  {
690    $multiply_by = 1024;
691  }
692  else if ('M' == $suffix)
693  {
694    $multiply_by = 1024*1024;
695  }
696  else if ('G' == $suffix)
697  {
698    $multiply_by = 1024*1024*1024;
699  }
700 
701  if (isset($multiply_by))
702  {
703    $value = substr($value, 0, -1);
704    $value*= $multiply_by;
705  }
706
707  return $value;
708}
709
710function add_upload_error($upload_id, $error_message)
711{
712  if (!isset($_SESSION['uploads_error']))
713  {
714    $_SESSION['uploads_error'] = array();
715  }
716  if (!isset($_SESSION['uploads_error'][$upload_id]))
717  {
718    $_SESSION['uploads_error'][$upload_id] = array();
719  }
720
721  array_push($_SESSION['uploads_error'][$upload_id], $error_message);
722}
723
724function is_imagick()
725{
726  if (extension_loaded('imagick'))
727  {
728    return true;
729  }
730
731  return false;
732}
733
734function ready_for_upload_message()
735{
736  global $conf;
737
738  $relative_dir = preg_replace('#^'.PHPWG_ROOT_PATH.'#', '', $conf['upload_dir']);
739
740  if (!is_dir($conf['upload_dir']))
741  {
742    if (!is_writable(dirname($conf['upload_dir'])))
743    {
744      return sprintf(
745        l10n('Create the "%s" directory at the root of your Piwigo installation'),
746        $relative_dir
747        );
748    }
749  }
750  else
751  {
752    if (!is_writable($conf['upload_dir']))
753    {
754      @chmod($conf['upload_dir'], 0777);
755     
756      if (!is_writable($conf['upload_dir']))
757      {
758        return sprintf(
759          l10n('Give write access (chmod 777) to "%s" directory at the root of your Piwigo installation'),
760          $relative_dir
761          );
762      }
763    }
764  }
765
766  return null;
767}
768
769function file_path_for_type($file_path, $type='thumb')
770{
771  // resolve the $file_path depending on the $type
772  if ('thumb' == $type) {
773    $file_path = get_thumbnail_location(
774      array(
775        'path' => $file_path,
776        'tn_ext' => 'jpg',
777        )
778      );
779  }
780
781  if ('high' == $type) {
782    @include_once(PHPWG_ROOT_PATH.'include/functions_picture.inc.php');
783    $file_path = get_high_location(
784      array(
785        'path' => $file_path,
786        'has_high' => 'true'
787        )
788      );
789  }
790
791  return $file_path;
792}
793?>
Note: See TracBrowser for help on using the repository browser.