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

Last change on this file since 29393 was 29124, checked in by plg, 10 years ago

feature 3067: upload any file type with the new HTML5 upload form.

$file_types = confupload_form_all_types ? $conffile_ext : $confpicture_ext;

By default, confupload_form_all_types = false;

File size: 17.2 KB
Line 
1<?php
2// +-----------------------------------------------------------------------+
3// | Piwigo - a PHP based photo gallery                                    |
4// +-----------------------------------------------------------------------+
5// | Copyright(C) 2008-2014 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');
25include_once(PHPWG_ROOT_PATH.'admin/include/image.class.php');
26
27// add default event handler for image and thumbnail resize
28add_event_handler('upload_image_resize', 'pwg_image_resize');
29add_event_handler('upload_thumbnail_resize', 'pwg_image_resize');
30
31function get_upload_form_config()
32{
33  // default configuration for upload
34  $upload_form_config = array(
35    'original_resize' => array(
36      'default' => false,
37      'can_be_null' => false,
38      ),
39
40    'original_resize_maxwidth' => array(
41      'default' => 2000,
42      'min' => 500,
43      'max' => 20000,
44      'pattern' => '/^\d+$/',
45      'can_be_null' => false,
46      'error_message' => l10n('The original maximum width must be a number between %d and %d'),
47      ),
48
49    'original_resize_maxheight' => array(
50      'default' => 2000,
51      'min' => 300,
52      'max' => 20000,
53      'pattern' => '/^\d+$/',
54      'can_be_null' => false,
55      'error_message' => l10n('The original maximum height must be a number between %d and %d'),
56      ),
57
58    'original_resize_quality' => array(
59      'default' => 95,
60      'min' => 50,
61      'max' => 98,
62      'pattern' => '/^\d+$/',
63      'can_be_null' => false,
64      'error_message' => l10n('The original image quality must be a number between %d and %d'),
65      ),
66    );
67
68  return $upload_form_config;
69}
70
71function save_upload_form_config($data, &$errors=array(), &$form_errors=array())
72{
73  if (!is_array($data) or empty($data))
74  {
75    return false;
76  }
77
78  $upload_form_config = get_upload_form_config();
79  $updates = array();
80
81  foreach ($data as $field => $value)
82  {
83    if (!isset($upload_form_config[$field]))
84    {
85      continue;
86    }
87    if (is_bool($upload_form_config[$field]['default']))
88    {
89      if (isset($value))
90      {
91        $value = true;
92      }
93      else
94      {
95        $value = false;
96      }
97
98      $updates[] = array(
99        'param' => $field,
100        'value' => boolean_to_string($value)
101        );
102    }
103    elseif ($upload_form_config[$field]['can_be_null'] and empty($value))
104    {
105      $updates[] = array(
106        'param' => $field,
107        'value' => 'false'
108        );
109    }
110    else
111    {
112      $min = $upload_form_config[$field]['min'];
113      $max = $upload_form_config[$field]['max'];
114      $pattern = $upload_form_config[$field]['pattern'];
115
116      if (preg_match($pattern, $value) and $value >= $min and $value <= $max)
117      {
118         $updates[] = array(
119          'param' => $field,
120          'value' => $value
121          );
122      }
123      else
124      {
125        $errors[] = sprintf(
126          $upload_form_config[$field]['error_message'],
127          $min, $max
128          );
129
130        $form_errors[$field] = '['.$min.' .. '.$max.']';
131      }
132    }
133  }
134
135  if (count($errors) == 0)
136  {
137    mass_updates(
138      CONFIG_TABLE,
139      array(
140        'primary' => array('param'),
141        'update' => array('value')
142        ),
143      $updates
144      );
145    return true;
146  }
147
148  return false;
149}
150
151function add_uploaded_file($source_filepath, $original_filename=null, $categories=null, $level=null, $image_id=null, $original_md5sum=null)
152{
153  // 1) move uploaded file to upload/2010/01/22/20100122003814-449ada00.jpg
154  //
155  // 2) keep/resize original
156  //
157  // 3) register in database
158
159  // TODO
160  // * check md5sum (already exists?)
161
162  global $conf, $user;
163
164  if (isset($original_md5sum))
165  {
166    $md5sum = $original_md5sum;
167  }
168  else
169  {
170    $md5sum = md5_file($source_filepath);
171  }
172
173  $file_path = null;
174  $is_tiff = false;
175
176  if (isset($image_id))
177  {
178    // this photo already exists, we update it
179    $query = '
180SELECT
181    path
182  FROM '.IMAGES_TABLE.'
183  WHERE id = '.$image_id.'
184;';
185    $result = pwg_query($query);
186    while ($row = pwg_db_fetch_assoc($result))
187    {
188      $file_path = $row['path'];
189    }
190
191    if (!isset($file_path))
192    {
193      die('['.__FUNCTION__.'] this photo does not exist in the database');
194    }
195
196    // delete all physical files related to the photo (thumbnail, web site, HD)
197    delete_element_files(array($image_id));
198  }
199  else
200  {
201    // this photo is new
202
203    // current date
204    list($dbnow) = pwg_db_fetch_row(pwg_query('SELECT NOW();'));
205    list($year, $month, $day) = preg_split('/[^\d]/', $dbnow, 4);
206
207    // upload directory hierarchy
208    $upload_dir = sprintf(
209      PHPWG_ROOT_PATH.$conf['upload_dir'].'/%s/%s/%s',
210      $year,
211      $month,
212      $day
213      );
214
215    // compute file path
216    $date_string = preg_replace('/[^\d]/', '', $dbnow);
217    $random_string = substr($md5sum, 0, 8);
218    $filename_wo_ext = $date_string.'-'.$random_string;
219    $file_path = $upload_dir.'/'.$filename_wo_ext.'.';
220
221    list($width, $height, $type) = getimagesize($source_filepath);
222   
223    if (IMAGETYPE_PNG == $type)
224    {
225      $file_path.= 'png';
226    }
227    elseif (IMAGETYPE_GIF == $type)
228    {
229      $file_path.= 'gif';
230    }
231    elseif (IMAGETYPE_TIFF_MM == $type or IMAGETYPE_TIFF_II == $type)
232    {
233      $is_tiff = true;
234      $file_path.= 'tif';
235    }
236    elseif (IMAGETYPE_JPEG == $type)
237    {
238      $file_path.= 'jpg';
239    }
240    elseif (isset($conf['upload_form_all_types']) and $conf['upload_form_all_types'])
241    {
242      $original_extension = strtolower(get_extension($original_filename));
243
244      if (in_array($original_extension, $conf['file_ext']))
245      {
246        $file_path.= $original_extension;
247      }
248      else
249      {
250        die('unexpected file type');
251      }
252    }
253    else
254    {
255      die('forbidden file type');
256    }
257
258    prepare_directory($upload_dir);
259  }
260
261  if (is_uploaded_file($source_filepath))
262  {
263    move_uploaded_file($source_filepath, $file_path);
264  }
265  else
266  {
267    rename($source_filepath, $file_path);
268  }
269  @chmod($file_path, 0644);
270
271  if ($is_tiff and pwg_image::get_library() == 'ext_imagick')
272  {
273    // move the uploaded file to pwg_representative sub-directory
274    $representative_file_path = dirname($file_path).'/pwg_representative/';
275    $representative_file_path.= get_filename_wo_extension(basename($file_path)).'.';
276
277    $representative_ext = $conf['tiff_representative_ext'];
278    $representative_file_path.= $representative_ext;
279
280    prepare_directory(dirname($representative_file_path));
281
282    $exec = $conf['ext_imagick_dir'].'convert';
283
284    if ('jpg' == $conf['tiff_representative_ext'])
285    {
286      $exec .= ' -quality 98';
287    }
288
289    $exec .= ' "'.realpath($file_path).'"';
290
291    $dest = pathinfo($representative_file_path);
292    $exec .= ' "'.realpath($dest['dirname']).'/'.$dest['basename'].'"';
293
294    $exec .= ' 2>&1';
295    @exec($exec, $returnarray);
296
297    // sometimes ImageMagick creates file-0.jpg (full size) + file-1.jpg
298    // (thumbnail). I don't know how to avoid it.
299    $representative_file_abspath = realpath($dest['dirname']).'/'.$dest['basename'];
300    if (!file_exists($representative_file_abspath))
301    {
302      $first_file_abspath = preg_replace(
303        '/\.'.$representative_ext.'$/',
304        '-0.'.$representative_ext,
305        $representative_file_abspath
306        );
307
308      if (file_exists($first_file_abspath))
309      {
310        rename($first_file_abspath, $representative_file_abspath);
311      }
312    }
313  }
314
315  //
316  // generate pwg_representative in case of video
317  //
318  $ffmpeg_video_exts = array( // extensions tested with FFmpeg
319    'wmv','mov','mkv','mp4','mpg','flv','asf','xvid','divx','mpeg',
320    'avi','rm',
321    );
322 
323  if (isset($original_extension) and in_array($original_extension, $ffmpeg_video_exts))
324  {
325    $representative_file_path = dirname($file_path).'/pwg_representative/';
326    $representative_file_path.= get_filename_wo_extension(basename($file_path)).'.';
327   
328    $representative_ext = 'jpg';
329    $representative_file_path.= $representative_ext;
330   
331    prepare_directory(dirname($representative_file_path));
332   
333    $second = 1;
334   
335    $ffmpeg = $conf['ffmpeg_dir'].'ffmpeg';
336    $ffmpeg.= ' -i "'.$file_path.'"';
337    $ffmpeg.= ' -an -ss '.$second;
338    $ffmpeg.= ' -t 1 -r 1 -y -vcodec mjpeg -f mjpeg';
339    $ffmpeg.= ' "'.$representative_file_path.'"';
340   
341    // file_put_contents('/tmp/ffmpeg.log', "\n==== ".date('c')."\n".__FUNCTION__.' : '.$ffmpeg."\n", FILE_APPEND);
342   
343    @exec($ffmpeg);
344
345    if (!file_exists($representative_file_path))
346    {
347      $representative_ext = null;
348    }
349  }
350
351  if (isset($original_extension) and 'pdf' == $original_extension and pwg_image::get_library() == 'ext_imagick')
352  {
353    $representative_file_path = dirname($file_path).'/pwg_representative/';
354    $representative_file_path.= get_filename_wo_extension(basename($file_path)).'.';
355   
356    $representative_ext = 'jpg';
357    $representative_file_path.= $representative_ext;
358
359    prepare_directory(dirname($representative_file_path));
360   
361    $exec = $conf['ext_imagick_dir'].'convert';
362    $exec.= ' -quality 98';
363    $exec.= ' "'.realpath($file_path).'"[0]';
364
365    $dest = pathinfo($representative_file_path);
366    $exec.= ' "'.realpath($dest['dirname']).'/'.$dest['basename'].'"';
367    $exec.= ' 2>&1';
368    @exec($exec, $returnarray);
369  }
370 
371  if (pwg_image::get_library() != 'gd')
372  {
373    if ($conf['original_resize'])
374    {
375      $need_resize = need_resize($file_path, $conf['original_resize_maxwidth'], $conf['original_resize_maxheight']);
376
377      if ($need_resize)
378      {
379        $img = new pwg_image($file_path);
380
381        $img->pwg_resize(
382          $file_path,
383          $conf['original_resize_maxwidth'],
384          $conf['original_resize_maxheight'],
385          $conf['original_resize_quality'],
386          $conf['upload_form_automatic_rotation'],
387          false
388          );
389
390        $img->destroy();
391      }
392    }
393  }
394
395  // we need to save the rotation angle in the database to compute
396  // width/height of "multisizes"
397  $rotation_angle = pwg_image::get_rotation_angle($file_path);
398  $rotation = pwg_image::get_rotation_code_from_angle($rotation_angle);
399
400  $file_infos = pwg_image_infos($file_path);
401
402  if (isset($image_id))
403  {
404    $update = array(
405      'file' => pwg_db_real_escape_string(isset($original_filename) ? $original_filename : basename($file_path)),
406      'filesize' => $file_infos['filesize'],
407      'width' => $file_infos['width'],
408      'height' => $file_infos['height'],
409      'md5sum' => $md5sum,
410      'added_by' => $user['id'],
411      'rotation' => $rotation,
412      );
413
414    if (isset($level))
415    {
416      $update['level'] = $level;
417    }
418
419    single_update(
420      IMAGES_TABLE,
421      $update,
422      array('id' => $image_id)
423      );
424  }
425  else
426  {
427    // database registration
428    $file = pwg_db_real_escape_string(isset($original_filename) ? $original_filename : basename($file_path));
429    $insert = array(
430      'file' => $file,
431      'name' => get_name_from_file($file),
432      'date_available' => $dbnow,
433      'path' => preg_replace('#^'.preg_quote(PHPWG_ROOT_PATH).'#', '', $file_path),
434      'filesize' => $file_infos['filesize'],
435      'width' => $file_infos['width'],
436      'height' => $file_infos['height'],
437      'md5sum' => $md5sum,
438      'added_by' => $user['id'],
439      'rotation' => $rotation,
440      );
441
442    if (isset($level))
443    {
444      $insert['level'] = $level;
445    }
446
447    if (isset($representative_ext))
448    {
449      $insert['representative_ext'] = $representative_ext;
450    }
451
452    single_insert(IMAGES_TABLE, $insert);
453
454    $image_id = pwg_db_insert_id(IMAGES_TABLE);
455  }
456
457  if (isset($categories) and count($categories) > 0)
458  {
459    associate_images_to_categories(
460      array($image_id),
461      $categories
462      );
463  }
464
465  // update metadata from the uploaded file (exif/iptc)
466  if ($conf['use_exif'] and !function_exists('read_exif_data'))
467  {
468    $conf['use_exif'] = false;
469  }
470  sync_metadata(array($image_id));
471
472  invalidate_user_cache();
473
474  // cache thumbnail
475  $query = '
476SELECT
477    id,
478    path
479  FROM '.IMAGES_TABLE.'
480  WHERE id = '.$image_id.'
481;';
482  $image_infos = pwg_db_fetch_assoc(pwg_query($query));
483
484  set_make_full_url();
485  // in case we are on uploadify.php, we have to replace the false path
486  $thumb_url = preg_replace('#admin/include/i#', 'i', DerivativeImage::thumb_url($image_infos));
487  unset_make_full_url();
488
489  fetchRemote($thumb_url, $dest);
490
491
492  return $image_id;
493}
494
495function prepare_directory($directory)
496{
497  if (!is_dir($directory)) {
498    if (substr(PHP_OS, 0, 3) == 'WIN')
499    {
500      $directory = str_replace('/', DIRECTORY_SEPARATOR, $directory);
501    }
502    umask(0000);
503    $recursive = true;
504    if (!@mkdir($directory, 0777, $recursive))
505    {
506      die('[prepare_directory] cannot create directory "'.$directory.'"');
507    }
508  }
509
510  if (!is_writable($directory))
511  {
512    // last chance to make the directory writable
513    @chmod($directory, 0777);
514
515    if (!is_writable($directory))
516    {
517      die('[prepare_directory] directory "'.$directory.'" has no write access');
518    }
519  }
520
521  secure_directory($directory);
522}
523
524function need_resize($image_filepath, $max_width, $max_height)
525{
526  // TODO : the resize check should take the orientation into account. If a
527  // rotation must be applied to the resized photo, then we should test
528  // invert width and height.
529  list($width, $height) = getimagesize($image_filepath);
530
531  if ($width > $max_width or $height > $max_height)
532  {
533    return true;
534  }
535
536  return false;
537}
538
539function pwg_image_infos($path)
540{
541  list($width, $height) = getimagesize($path);
542  $filesize = floor(filesize($path)/1024);
543
544  return array(
545    'width'  => $width,
546    'height' => $height,
547    'filesize' => $filesize,
548    );
549}
550
551function is_valid_image_extension($extension)
552{
553  global $conf;
554 
555  if (isset($conf['upload_form_all_types']) and $conf['upload_form_all_types'])
556  {
557    $extensions = $conf['file_ext'];
558  }
559  else
560  {
561    $extensions = $conf['picture_ext'];
562  }
563
564  return array_unique(array_map('strtolower', $extensions));
565}
566
567function file_upload_error_message($error_code)
568{
569  switch ($error_code) {
570    case UPLOAD_ERR_INI_SIZE:
571      return sprintf(
572        l10n('The uploaded file exceeds the upload_max_filesize directive in php.ini: %sB'),
573        get_ini_size('upload_max_filesize', false)
574        );
575    case UPLOAD_ERR_FORM_SIZE:
576      return l10n('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form');
577    case UPLOAD_ERR_PARTIAL:
578      return l10n('The uploaded file was only partially uploaded');
579    case UPLOAD_ERR_NO_FILE:
580      return l10n('No file was uploaded');
581    case UPLOAD_ERR_NO_TMP_DIR:
582      return l10n('Missing a temporary folder');
583    case UPLOAD_ERR_CANT_WRITE:
584      return l10n('Failed to write file to disk');
585    case UPLOAD_ERR_EXTENSION:
586      return l10n('File upload stopped by extension');
587    default:
588      return l10n('Unknown upload error');
589  }
590}
591
592function get_ini_size($ini_key, $in_bytes=true)
593{
594  $size = ini_get($ini_key);
595
596  if ($in_bytes)
597  {
598    $size = convert_shorthand_notation_to_bytes($size);
599  }
600
601  return $size;
602}
603
604function convert_shorthand_notation_to_bytes($value)
605{
606  $suffix = substr($value, -1);
607  $multiply_by = null;
608
609  if ('K' == $suffix)
610  {
611    $multiply_by = 1024;
612  }
613  else if ('M' == $suffix)
614  {
615    $multiply_by = 1024*1024;
616  }
617  else if ('G' == $suffix)
618  {
619    $multiply_by = 1024*1024*1024;
620  }
621
622  if (isset($multiply_by))
623  {
624    $value = substr($value, 0, -1);
625    $value*= $multiply_by;
626  }
627
628  return $value;
629}
630
631function add_upload_error($upload_id, $error_message)
632{
633  $_SESSION['uploads_error'][$upload_id][] = $error_message;
634}
635
636function ready_for_upload_message()
637{
638  global $conf;
639
640  $relative_dir = preg_replace('#^'.PHPWG_ROOT_PATH.'#', '', $conf['upload_dir']);
641
642  if (!is_dir($conf['upload_dir']))
643  {
644    if (!is_writable(dirname($conf['upload_dir'])))
645    {
646      return sprintf(
647        l10n('Create the "%s" directory at the root of your Piwigo installation'),
648        $relative_dir
649        );
650    }
651  }
652  else
653  {
654    if (!is_writable($conf['upload_dir']))
655    {
656      @chmod($conf['upload_dir'], 0777);
657
658      if (!is_writable($conf['upload_dir']))
659      {
660        return sprintf(
661          l10n('Give write access (chmod 777) to "%s" directory at the root of your Piwigo installation'),
662          $relative_dir
663          );
664      }
665    }
666  }
667
668  return null;
669}
670?>
Note: See TracBrowser for help on using the repository browser.