source: trunk/i.php @ 31103

Last change on this file since 31103 was 31103, checked in by mistic100, 9 years ago

feature 3221 Lazy log file open, clean code

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