source: trunk/i.php @ 13644

Last change on this file since 13644 was 13527, checked in by plg, 13 years ago

feature 414 (yes, a 6 years old request): ability to define the list of
permitted characters in file/directory names for synchronization.

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