source: trunk/i.php @ 17980

Last change on this file since 17980 was 17681, checked in by rvelices, 12 years ago

bug 2686: multi size with diacritical file names not generated

File size: 17.0 KB
Line 
1<?php
2// +-----------------------------------------------------------------------+
3// | Piwigo - a PHP based photo gallery                                    |
4// +-----------------------------------------------------------------------+
5// | Copyright(C) 2008-2012 Piwigo Team                  http://piwigo.org |
6// +-----------------------------------------------------------------------+
7// | This program is free software; you can redistribute it and/or modify  |
8// | it under the terms of the GNU General Public License as published by  |
9// | the Free Software Foundation                                          |
10// |                                                                       |
11// | This program is distributed in the hope that it will be useful, but   |
12// | WITHOUT ANY WARRANTY; without even the implied warranty of            |
13// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
14// | General Public License for more details.                              |
15// |                                                                       |
16// | You should have received a copy of the GNU General Public License     |
17// | along with this program; if not, write to the Free Software           |
18// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
19// | USA.                                                                  |
20// +-----------------------------------------------------------------------+
21
22define('PHPWG_ROOT_PATH','./');
23
24// fast bootstrap - no db connection
25include(PHPWG_ROOT_PATH . 'include/config_default.inc.php');
26@include(PHPWG_ROOT_PATH. 'local/config/config.inc.php');
27
28defined('PWG_LOCAL_DIR') or define('PWG_LOCAL_DIR', 'local/');
29defined('PWG_DERIVATIVE_DIR') or define('PWG_DERIVATIVE_DIR', $conf['data_location'].'i/');
30
31@include(PHPWG_ROOT_PATH.PWG_LOCAL_DIR .'config/database.inc.php');
32
33
34function trigger_action() {}
35function get_extension( $filename )
36{
37  return substr( strrchr( $filename, '.' ), 1, strlen ( $filename ) );
38}
39
40function mkgetdir($dir)
41{
42  if ( !is_dir($dir) )
43  {
44    global $conf;
45    if (substr(PHP_OS, 0, 3) == 'WIN')
46    {
47      $dir = str_replace('/', DIRECTORY_SEPARATOR, $dir);
48    }
49    $umask = umask(0);
50    $mkd = @mkdir($dir, $conf['chmod_value'], true);
51    umask($umask);
52    if ($mkd==false)
53    {
54      return false;
55    }
56
57    $file = $dir.'/index.htm';
58    file_exists($file) or @file_put_contents( $file, 'Not allowed!' );
59  }
60  if ( !is_writable($dir) )
61  {
62    return false;
63  }
64  return true;
65}
66
67// end fast bootstrap
68
69function ilog()
70{
71  global $conf;
72  if (!$conf['enable_i_log']) return;
73
74  $line = date("c");
75  foreach( func_get_args() as $arg)
76  {
77    $line .= ' ';
78    if (is_array($arg))
79    {
80      $line .= implode(' ', $arg);
81    }
82    else
83    {
84      $line .= $arg;
85    }
86  }
87        $file=PHPWG_ROOT_PATH.$conf['data_location'].'tmp/i.log';
88  if (false == file_put_contents($file, $line."\n", FILE_APPEND))
89        {
90                mkgetdir(dirname($file));
91        }
92}
93
94function ierror($msg, $code)
95{
96  if ($code==301 || $code==302)
97  {
98    if (ob_get_length () !== FALSE)
99    {
100      ob_clean();
101    }
102    // default url is on html format
103    $url = html_entity_decode($msg);
104    header('Request-URI: '.$url);
105    header('Content-Location: '.$url);
106    header('Location: '.$url);
107    ilog('WARN', $code, $url, $_SERVER['REQUEST_URI']);
108    exit;
109  }
110  if ($code>=400)
111  {
112    $protocol = $_SERVER["SERVER_PROTOCOL"];
113    if ( ('HTTP/1.1' != $protocol) && ('HTTP/1.0' != $protocol) )
114      $protocol = 'HTTP/1.0';
115
116    header( "$protocol $code $msg", true, $code );
117  }
118  //todo improve
119  echo $msg;
120  ilog('ERROR', $code, $msg, $_SERVER['REQUEST_URI']);
121  exit;
122}
123
124function time_step( &$step )
125{
126  $tmp = $step;
127  $step = microtime(true);
128  return intval(1000*($step - $tmp));
129}
130
131function url_to_size($s)
132{
133  $pos = strpos($s, 'x');
134  if ($pos===false)
135  {
136    return array((int)$s, (int)$s);
137  }
138  return array((int)substr($s,0,$pos), (int)substr($s,$pos+1));
139}
140
141function parse_custom_params($tokens)
142{
143  if (count($tokens)<1)
144    ierror('Empty array while parsing Sizing', 400);
145
146  $crop = 0;
147  $min_size = null;
148
149  $token = array_shift($tokens);
150  if ($token[0]=='s')
151  {
152    $size = url_to_size( substr($token,1) );
153  }
154  elseif ($token[0]=='e')
155  {
156    $crop = 1;
157    $size = $min_size = url_to_size( substr($token,1) );
158  }
159  else
160  {
161    $size = url_to_size( $token );
162    if (count($tokens)<2)
163      ierror('Sizing arr', 400);
164
165    $token = array_shift($tokens);
166    $crop = char_to_fraction($token);
167
168    $token = array_shift($tokens);
169    $min_size = url_to_size( $token );
170  }
171  return new DerivativeParams( new SizingParams($size, $crop, $min_size) );
172}
173
174function parse_request()
175{
176  global $conf, $page;
177
178  if ( $conf['question_mark_in_urls']==false and
179       isset($_SERVER["PATH_INFO"]) and !empty($_SERVER["PATH_INFO"]) )
180  {
181    $req = $_SERVER["PATH_INFO"];
182    $req = str_replace('//', '/', $req);
183    $path_count = count( explode('/', $req) );
184    $page['root_path'] = PHPWG_ROOT_PATH.str_repeat('../', $path_count-1);
185  }
186  else
187  {
188    $req = $_SERVER["QUERY_STRING"];
189    if ($pos=strpos($req, '&'))
190    {
191      $req = substr($req, 0, $pos);
192    }
193    $req = rawurldecode($req);
194    /*foreach (array_keys($_GET) as $keynum => $key)
195    {
196      $req = $key;
197      break;
198    }*/
199    $page['root_path'] = PHPWG_ROOT_PATH;
200  }
201
202  $req = ltrim($req, '/');
203
204  foreach (preg_split('#/+#', $req) as $token)
205  {
206    preg_match($conf['sync_chars_regex'], $token) or ierror('Invalid chars in request', 400);
207  }
208
209  $page['derivative_path'] = PHPWG_ROOT_PATH.PWG_DERIVATIVE_DIR.$req;
210
211  $pos = strrpos($req, '.');
212  $pos!== false || ierror('Missing .', 400);
213  $ext = substr($req, $pos);
214  $page['derivative_ext'] = $ext;
215  $req = substr($req, 0, $pos);
216
217  $pos = strrpos($req, '-');
218  $pos!== false || ierror('Missing -', 400);
219  $deriv = substr($req, $pos+1);
220  $req = substr($req, 0, $pos);
221
222  $deriv = explode('_', $deriv);
223  foreach (ImageStdParams::get_defined_type_map() as $type => $params)
224  {
225    if ( derivative_to_url($type) == $deriv[0])
226    {
227      $page['derivative_type'] = $type;
228      $page['derivative_params'] = $params;
229      break;
230    }
231  }
232
233  if (!isset($page['derivative_type']))
234  {
235    if (derivative_to_url(IMG_CUSTOM) == $deriv[0])
236    {
237      $page['derivative_type'] = IMG_CUSTOM;
238    }
239    else
240    {
241      ierror('Unknown parsing type', 400);
242    }
243  }
244  array_shift($deriv);
245
246  if ($page['derivative_type'] == IMG_CUSTOM)
247  {
248    $params = $page['derivative_params'] = parse_custom_params($deriv);
249    ImageStdParams::apply_global($params);
250
251    if ($params->sizing->ideal_size[0] < 20 or $params->sizing->ideal_size[1] < 20)
252    {
253      ierror('Invalid size', 400);
254    }
255    if ($params->sizing->max_crop < 0 or $params->sizing->max_crop > 1)
256    {
257      ierror('Invalid crop', 400);
258    }
259    $greatest = ImageStdParams::get_by_type(IMG_XXLARGE);
260
261    $key = array();
262    $params->add_url_tokens($key);
263    $key = implode('_', $key);
264    if (!isset(ImageStdParams::$custom[$key]))
265    {
266      ierror('Size not allowed', 403);
267    }
268  }
269
270  if (is_file(PHPWG_ROOT_PATH.$req.$ext))
271  {
272    $req = './'.$req; // will be used to match #iamges.path
273  }
274  elseif (is_file(PHPWG_ROOT_PATH.'../'.$req.$ext))
275  {
276    $req = '../'.$req;
277  }
278
279  $page['src_location'] = $req.$ext;
280  $page['src_path'] = PHPWG_ROOT_PATH.$page['src_location'];
281  $page['src_url'] = $page['root_path'].$page['src_location'];
282}
283
284function try_switch_source(DerivativeParams $params, $original_mtime)
285{
286  global $page;
287  $original_size = null;
288  if (isset($page['original_size']))
289  {
290    $original_size = $page['original_size'];
291    if ($page['rotation_angle']==90 || $page['rotation_angle']==270)
292    {
293      $tmp = $original_size[0];
294      $original_size[0] = $original_size[1];
295      $original_size[1] = $tmp;
296    }
297  }
298
299  $use_watermark = $params->use_watermark;
300  if ($use_watermark)
301  {
302    if (!isset($original_size))
303      return false; // cannot really know if a watermark is required
304    $dsize = $params->compute_final_size($original_size);
305    $use_watermark = $params->will_watermark($dsize);
306    ilog($use_watermark, $dsize);
307  }
308
309  $candidates = array();
310  foreach(ImageStdParams::get_defined_type_map() as $candidate)
311  {
312    if ($candidate->type == $params->type)
313      continue;
314    if ($candidate->use_watermark != $use_watermark)
315      continue;
316    if ($candidate->max_width() < $params->max_width() || $candidate->max_height() < $params->max_height())
317      continue;
318    if ($params->sizing->max_crop==0)
319    {
320      if ($candidate->sizing->max_crop!=0)
321        continue;
322    }
323    else
324    {
325      if ($candidate->sizing->max_crop!=0)
326        continue; // this could be optimized
327      if (!isset($original_size))
328        continue;
329      $candidate_size = $candidate->compute_final_size($original_size);
330      if ($candidate_size[0] < $params->sizing->min_size[0] || $candidate_size[1] < $params->sizing->min_size[1] )
331        continue;
332    }
333    $candidates[] = $candidate;
334  }
335
336  foreach( array_reverse($candidates) as $candidate)
337  {
338    $candidate_path = $page['derivative_path'];
339    $candidate_path = str_replace( '-'.derivative_to_url($params->type), '-'.derivative_to_url($candidate->type), $candidate_path);
340    $candidate_mtime = @filemtime($candidate_path);
341    if ($candidate_mtime === false
342      || $candidate_mtime < $original_mtime
343      || $candidate_mtime < $candidate->last_mod_time)
344      continue;
345    $params->use_watermark = false;
346    $params->sharpen = min(1, $params->sharpen);
347    $page['src_path'] = $candidate_path;
348    $page['src_url'] = $page['root_path'] . substr($candidate_path, strlen(PHPWG_ROOT_PATH));
349    $page['rotation_angle'] = 0;
350                return true;
351  }
352        return false;
353}
354
355function send_derivative($expires)
356{
357  global $page;
358
359  if (isset($_GET['ajaxload']) and $_GET['ajaxload'] == 'true')
360  {
361    include_once(PHPWG_ROOT_PATH.'include/functions_cookie.inc.php');
362    include_once(PHPWG_ROOT_PATH.'include/functions_url.inc.php');
363
364    $response = new json_response();
365    $response->url = embellish_url(get_absolute_root_url().$page['derivative_path']);
366    echo json_encode($response);
367    return;
368  }
369  $fp = fopen($page['derivative_path'], 'rb');
370
371  $fstat = fstat($fp);
372  header('Last-Modified: '.gmdate('D, d M Y H:i:s', $fstat['mtime']).' GMT');
373  if ($expires!==false)
374  {
375    header('Expires: '.gmdate('D, d M Y H:i:s', $expires).' GMT');
376  }
377  header('Content-length: '.$fstat['size']);
378  header('Connection: close');
379
380  $ctype="application/octet-stream";
381  switch (strtolower($page['derivative_ext']))
382  {
383    case ".jpe": case ".jpeg": case ".jpg": $ctype="image/jpeg"; break;
384    case ".png": $ctype="image/png"; break;
385    case ".gif": $ctype="image/gif"; break;
386  }
387  header("Content-Type: $ctype");
388
389  fpassthru($fp);
390  fclose($fp);
391}
392
393class json_response
394{
395  var $url;
396}
397
398$page=array();
399$begin = $step = microtime(true);
400$timing=array();
401foreach( explode(',','load,rotate,crop,scale,sharpen,watermark,save,send') as $k )
402{
403  $timing[$k] = '';
404}
405
406include_once(PHPWG_ROOT_PATH .'include/dblayer/functions_'.$conf['dblayer'].'.inc.php');
407include_once( PHPWG_ROOT_PATH .'/include/derivative_params.inc.php');
408include_once( PHPWG_ROOT_PATH .'/include/derivative_std_params.inc.php');
409
410try
411{
412  $pwg_db_link = pwg_db_connect($conf['db_host'], $conf['db_user'],
413                                $conf['db_password'], $conf['db_base']);
414}
415catch (Exception $e)
416{
417  ilog("db error", $e->getMessage());
418}
419pwg_db_check_charset();
420
421list($conf['derivatives']) = pwg_db_fetch_row(pwg_query('SELECT value FROM '.$prefixeTable.'config WHERE param=\'derivatives\''));
422ImageStdParams::load_from_db();
423
424
425parse_request();
426//var_export($page);
427
428$params = $page['derivative_params'];
429
430$src_mtime = @filemtime($page['src_path']);
431if ($src_mtime === false)
432{
433  ierror('Source not found', 404);
434}
435
436$need_generate = false;
437$derivative_mtime = @filemtime($page['derivative_path']);
438if ($derivative_mtime === false or
439    $derivative_mtime < $src_mtime or
440    $derivative_mtime < $params->last_mod_time)
441{
442  $need_generate = true;
443}
444
445$expires=false;
446$now = time();
447if ( isset($_GET['b']) )
448{
449  $expires = $now + 100;
450  header("Cache-control: no-store, max-age=100");
451}
452elseif ( $now > (max($src_mtime, $params->last_mod_time) + 24*3600) )
453{// somehow arbitrary - if derivative params or src didn't change for the last 24 hours, we send an expire header for several days
454  $expires = $now + 10*24*3600;
455}
456
457if (!$need_generate)
458{
459  if ( isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] )
460    and strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $derivative_mtime)
461  {// send the last mod time of the file back
462    header('Last-Modified: '.gmdate('D, d M Y H:i:s', $derivative_mtime).' GMT', true, 304);
463    header('Expires: '.gmdate('D, d M Y H:i:s', time()+10*24*3600).' GMT', true, 304);
464    exit;
465  }
466  send_derivative($expires);
467  exit;
468}
469
470include_once(PHPWG_ROOT_PATH . 'admin/include/image.class.php');
471$page['coi'] = null;
472if (strpos($page['src_location'], '/pwg_representative/')===false
473    && strpos($page['src_location'], 'themes/')===false
474    && strpos($page['src_location'], 'plugins/')===false)
475{
476  try
477  {
478    $query = '
479SELECT *
480  FROM '.$prefixeTable.'images
481  WHERE path=\''.$page['src_location'].'\'
482;';
483
484    if ( ($row=pwg_db_fetch_assoc(pwg_query($query))) )
485    {
486      if (isset($row['width']))
487      {
488        $page['original_size'] = array($row['width'],$row['height']);
489      }
490      $page['coi'] = $row['coi'];
491
492      if (!isset($row['rotation']))
493      {
494        $page['rotation_angle'] = pwg_image::get_rotation_angle($page['src_path']);
495
496        single_update(
497          $prefixeTable.'images',
498          array('rotation' => pwg_image::get_rotation_code_from_angle($page['rotation_angle'])),
499          array('id' => $row['id'])
500          );
501      }
502      else
503      {
504        $page['rotation_angle'] = pwg_image::get_rotation_angle_from_code($row['rotation']);
505      }
506    }
507    if (!$row)
508    {
509      ierror('Db file path not found', 404);
510    }
511  }
512  catch (Exception $e)
513  {
514    ilog("db error", $e->getMessage());
515  }
516}
517else
518{
519  $page['rotation_angle'] = 0;
520}
521mysql_close($pwg_db_link);
522
523if (!try_switch_source($params, $src_mtime) && $params->type==IMG_CUSTOM)
524{
525        $sharpen = 0;
526        foreach (ImageStdParams::get_defined_type_map() as $std_params)
527        {
528                $sharpen += $std_params->sharpen;
529        }
530        $params->sharpen = round($sharpen / count(ImageStdParams::get_defined_type_map()) );
531}
532
533if (!mkgetdir(dirname($page['derivative_path'])))
534{
535  ierror("dir create error", 500);
536}
537
538ignore_user_abort(true);
539@set_time_limit(0);
540
541$image = new pwg_image($page['src_path']);
542$timing['load'] = time_step($step);
543
544$changes = 0;
545
546// rotate
547if (0 != $page['rotation_angle'])
548{
549  $image->rotate($page['rotation_angle']);
550  $changes++;
551  $timing['rotate'] = time_step($step);
552}
553
554// Crop & scale
555$o_size = $d_size = array($image->get_width(),$image->get_height());
556$params->sizing->compute($o_size , $page['coi'], $crop_rect, $scaled_size );
557if ($crop_rect)
558{
559  $changes++;
560  $image->crop( $crop_rect->width(), $crop_rect->height(), $crop_rect->l, $crop_rect->t);
561  $timing['crop'] = time_step($step);
562}
563
564if ($scaled_size)
565{
566  $changes++;
567  $image->resize( $scaled_size[0], $scaled_size[1] );
568  $d_size = $scaled_size;
569  $timing['scale'] = time_step($step);
570}
571
572if ($params->sharpen)
573{
574  $changes += $image->sharpen( $params->sharpen );
575  $timing['sharpen'] = time_step($step);
576}
577
578if ($params->will_watermark($d_size))
579{
580  $wm = ImageStdParams::get_watermark();
581  $wm_image = new pwg_image(PHPWG_ROOT_PATH.$wm->file);
582  $wm_size = array($wm_image->get_width(),$wm_image->get_height());
583  if ($d_size[0]<$wm_size[0] or $d_size[1]<$wm_size[1])
584  {
585    $wm_scaling_params = SizingParams::classic($d_size[0], $d_size[1]);
586    $wm_scaling_params->compute($wm_size, null, $tmp, $wm_scaled_size);
587    $wm_size = $wm_scaled_size;
588    $wm_image->resize( $wm_scaled_size[0], $wm_scaled_size[1] );
589  }
590  $x = round( ($wm->xpos/100)*($d_size[0]-$wm_size[0]) );
591  $y = round( ($wm->ypos/100)*($d_size[1]-$wm_size[1]) );
592  if ($image->compose($wm_image, $x, $y, $wm->opacity))
593  {
594    $changes++;
595    if ($wm->xrepeat)
596    {
597      // todo
598      $pad = $wm_size[0] + max(30, round($wm_size[0]/4));
599      for($i=-$wm->xrepeat; $i<=$wm->xrepeat; $i++)
600      {
601        if (!$i) continue;
602        $x2 = $x + $i * $pad;
603        if ($x2>=0 && $x2+$wm_size[0]<$d_size[0])
604          if (!$image->compose($wm_image, $x2, $y, $wm->opacity))
605            break;
606      }
607    }
608  }
609  $wm_image->destroy();
610  $timing['watermark'] = time_step($step);
611}
612
613// no change required - redirect to source
614if (!$changes)
615{
616  header("X-i: No change");
617  ierror( $page['src_url'], 301);
618}
619
620if ($d_size[0]*$d_size[1] < 256000)
621{// strip metadata for small images
622  $image->strip();
623}
624
625$image->set_compression_quality( ImageStdParams::$quality );
626$image->write( $page['derivative_path'] );
627$image->destroy();
628@chmod($page['derivative_path'], 0644);
629$timing['save'] = time_step($step);
630
631send_derivative($expires);
632$timing['send'] = time_step($step);
633
634ilog('perf',
635  basename($page['src_path']), $o_size, $o_size[0]*$o_size[1],
636  basename($page['derivative_path']), $d_size, $d_size[0]*$d_size[1],
637  function_exists('memory_get_peak_usage') ? round( memory_get_peak_usage()/(1024*1024), 1) : '',
638  time_step($begin),
639  '|', $timing);
640?>
Note: See TracBrowser for help on using the repository browser.