579054d6ebd70a18caba3abd46612723c6e5474f
[phpfspot.git] / phpfspot.class.php
1 <?php
2
3 require_once "phpfspot_cfg.php";
4 require_once "phpfspot_db.php";
5 require_once "phpfspot_tmpl.php";
6
7 class PHPFSPOT {
8
9    var $cfg;
10    var $db;
11    var $cfg_db;
12    var $tmpl;
13    var $tags;
14    var $avail_tags;
15
16    public function __construct()
17    {
18       /* Check necessary requirements */
19       if(!$this->checkRequirements()) {
20          exit(1);
21       }
22
23       $this->cfg = new PHPFSPOT_CFG;
24
25       $this->db  = new PHPFSPOT_DB(&$this, $this->cfg->fspot_db);
26       
27       if(!is_writeable(dirname($this->cfg->phpfspot_db))) {
28          print dirname($this->cfg->phpfspot_db) .": directory is not writeable!";
29          exit(1);
30       }
31          
32       $this->cfg_db = new PHPFSPOT_DB(&$this, $this->cfg->phpfspot_db);
33       $this->check_config_table();
34
35       $this->tmpl = new PHPFSPOT_TMPL($this);
36
37       $this->get_tags();
38
39       session_start();
40
41       if(!isset($_SESSION['tag_condition']))
42          $_SESSION['tag_condition'] = 'or';
43
44       if(!isset($_SESSION['searchfor']))
45          $_SESSION['searchfor'] = '';
46
47       // if begin_with is still set but rows_per_page is now 0, unset it
48       if(isset($_SESSION['begin_with']) && $this->cfg->rows_per_page == 0)
49          unset($_SESSION['begin_with']);
50
51    } // __construct()
52
53    public function __destruct()
54    {
55
56    } // __destruct()
57
58    public function show()
59    {
60       $this->tmpl->assign('searchfor', $_SESSION['searchfor']);
61       $this->tmpl->assign('page_title', $this->cfg->page_title);
62       $this->tmpl->assign('current_condition', $_SESSION['tag_condition']);
63
64       $_SESSION['start_action'] = $_GET['mode'];
65
66       switch($_GET['mode']) {
67          case 'showpi':
68             if(isset($_GET['tags'])) {
69                $_SESSION['selected_tags'] = split(',', $_GET['tags']);
70             }
71             if(isset($_GET['from_date'])) {
72                $_SESSION['from_date'] = $_GET['from_date'];
73             }
74             if(isset($_GET['to_date'])) {
75                $_SESSION['to_date'] = $_GET['to_date'];
76             }
77             break;
78          case 'showp':
79             if(isset($_GET['tags'])) {
80                $_SESSION['selected_tags'] = split(',', $_GET['tags']);
81                $_SESSION['start_action'] = 'showp';
82             }
83             if(isset($_GET['id'])) {
84                $_SESSION['current_photo'] = $_GET['id'];
85                $_SESSION['start_action'] = 'showp';
86             }
87             if(isset($_GET['from_date'])) {
88                $_SESSION['from_date'] = $_GET['from_date'];
89             }
90             if(isset($_GET['to_date'])) {
91                $_SESSION['to_date'] = $_GET['to_date'];
92             }
93             break;
94          case 'export':
95             $this->tmpl->show("export.tpl");
96             return;
97             break;
98
99       }
100
101       $this->tmpl->assign('from_date', $this->get_calendar('from'));
102       $this->tmpl->assign('to_date', $this->get_calendar('to'));
103       $this->tmpl->assign('content_page', 'welcome.tpl');
104       $this->tmpl->show("index.tpl");
105
106
107    } // show()
108
109    private function get_tags()
110    {
111    
112       $this->avail_tags = Array();
113       $count = 0;
114    
115       $result = $this->db->db_query("
116          SELECT id,name
117          FROM tags
118          ORDER BY sort_priority ASC
119       ");
120       
121       while($row = $this->db->db_fetch_object($result)) {
122
123          $tag_id = $row['id'];
124          $tag_name = $row['name'];
125
126          /* check if config requests to ignore this tag */
127          if(in_array($row['name'], $this->cfg->hide_tags))
128             continue;
129
130          $this->tags[$tag_id] = $tag_name; 
131          $this->avail_tags[$count] = $tag_id;
132
133          $count++;
134
135       }
136
137    } // get_tags()
138
139    public function get_photo_details($idx)
140    {
141       $result = $this->db->db_query("
142          SELECT *
143          FROM photos
144          WHERE id='". $idx ."'
145       ");
146       
147       return $this->db->db_fetch_object($result);
148
149    } // get_photo_details
150
151    public function getPhotoName($idx, $limit = 0)
152    {
153       if($details = $this->get_photo_details($idx)) {
154          $name = $details['name'];
155          if($limit != 0 && strlen($name) > $limit) {
156             $name = substr($name, 0, $limit-5) ."...". substr($name, -($limit-5));
157          }
158          return $name;
159       }
160
161    } // getPhotoName()
162
163    public function translate_path($path, $width = 0)
164    {  
165       return str_replace($this->cfg->path_replace_from, $this->cfg->path_replace_to, $path);
166
167    } // translate_path
168
169    public function showPhoto($photo)
170    {
171       $all_photos = $this->getPhotoSelection();
172       $count = count($all_photos);
173
174       for($i = 0; $i < $count; $i++) {
175          
176          if($get_next) {
177             $next_img = $all_photos[$i];
178             break;
179          }
180
181          if($all_photos[$i] == $photo) {
182             $get_next = 1;
183          }
184          else {
185             $previous_img = $all_photos[$i];
186          }
187
188          if($photo == $all_photos[$i]) {
189                $current = $i;
190          }
191       }
192
193       $details = $this->get_photo_details($photo);
194       $orig_path = $this->translate_path($details['directory_path']) ."/". $details['name'];
195       $thumb_path = $this->cfg->base_path ."/thumbs/". $this->cfg->photo_width ."_". $this->getMD5($photo);
196
197       if(!file_exists($orig_path)) {
198          $this->_warning("Photo ". $orig_path ." does not exist!<br />\n");
199       }
200
201       if(!is_readable($orig_path)) {
202          $this->_warning("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
203       }
204
205       /* If the thumbnail doesn't exist yet, try to create it */
206       if(!file_exists($thumb_path)) {
207          $this->gen_thumb($photo, true);
208       }
209
210       $meta = $this->get_meta_informations($orig_path);
211
212       /* If EXIF data are available, use them */
213       if(isset($meta['ExifImageWidth'])) {
214          $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
215       } else {
216          $info = getimagesize($orig_path);
217          $meta_res = $info[0] ."x". $info[1]; 
218       }
219
220       $meta_date = isset($meta['FileDateTime']) ? strftime("%a %x %X", $meta['FileDateTime']) : "n/a";
221       $meta_make = isset($meta['Make']) ? $meta['Make'] ." ". $meta['Model'] : "n/a";
222       $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
223
224       $current_tags = $this->getCurrentTags();
225       $extern_link = "index.php?mode=showp&id=". $photo;
226       if($current_tags != "") {
227          $extern_link.= "&tags=". $current_tags;
228       }
229       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
230          $extern_link.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
231       }
232
233       $this->tmpl->assign('extern_link', $extern_link);
234
235       if(file_exists($thumb_path)) {
236
237          $info = getimagesize($thumb_path);
238
239          $this->tmpl->assign('description', $details['description']);
240          $this->tmpl->assign('image_name', $details['name']);
241
242          $this->tmpl->assign('width', $info[0]);
243          $this->tmpl->assign('height', $info[1]);
244          $this->tmpl->assign('ExifMadeOn', $meta_date);
245          $this->tmpl->assign('ExifMadeWith', $meta_make);
246          $this->tmpl->assign('ExifOrigResolution', $meta_res);
247          $this->tmpl->assign('ExifFileSize', $meta_size);
248     
249          $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&amp;width=". $this->cfg->photo_width);
250          $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
251
252          $this->tmpl->assign('tags', $this->get_photo_tags($photo));
253          $this->tmpl->assign('current', $current);
254       }
255       else {
256          $this->_warning("Can't open file ". $thumb_path ."\n");
257       }
258
259       if($previous_img) {
260          $this->tmpl->assign('previous_url', "javascript:showImage(". $previous_img .");");
261          $this->tmpl->assign('prev_img', $previous_img);
262       }
263
264       if($next_img) {
265          $this->tmpl->assign('next_url', "javascript:showImage(". $next_img .");");
266          $this->tmpl->assign('next_img', $next_img);
267       }
268       $this->tmpl->assign('mini_width', $this->cfg->mini_width);
269
270       $this->tmpl->show("single_photo.tpl");
271
272    } // showPhoto()
273
274    public function getAvailableTags()
275    {
276       $result = $this->db->db_query("
277          SELECT tag_id as id, count(tag_id) as quantity
278          FROM photo_tags
279          INNER JOIN tags t
280             ON t.id = tag_id
281          GROUP BY tag_id
282          ORDER BY t.name ASC
283       ");
284
285       $tags = Array();
286
287       while($row = $this->db->db_fetch_object($result)) {
288          $tags[$row['id']] = $row['quantity'];
289       }
290
291       // change these font sizes if you will
292       $max_size = 125; // max font size in %
293       $min_size = 75; // min font size in %
294
295       // get the largest and smallest array values
296       $max_qty = max(array_values($tags));
297       $min_qty = min(array_values($tags));
298
299       // find the range of values
300       $spread = $max_qty - $min_qty;
301       if (0 == $spread) { // we don't want to divide by zero
302          $spread = 1;
303       }
304
305       // determine the font-size increment
306       // this is the increase per tag quantity (times used)
307       $step = ($max_size - $min_size)/($spread);
308
309       // loop through our tag array
310       foreach ($tags as $key => $value) {
311
312          if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
313             continue;
314
315           // calculate CSS font-size
316           // find the $value in excess of $min_qty
317           // multiply by the font-size increment ($size)
318           // and add the $min_size set above
319          $size = $min_size + (($value - $min_qty) * $step);
320           // uncomment if you want sizes in whole %:
321          $size = ceil($size);
322
323          print "<a href=\"javascript:Tags('add', ". $key .");\" class=\"tag\" style=\"font-size: ". $size ."%;\">". $this->tags[$key] ."</a>, ";
324
325       }
326
327    } // getAvailableTags()
328
329    public function getSelectedTags()
330    {
331       $output = "";
332       foreach($this->avail_tags as $tag)
333       {
334          // return all selected tags
335          if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
336             $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
337          }
338       }
339
340       $output = substr($output, 0, strlen($output)-2);
341       print $output;
342
343    } // getSelectedTags()
344
345    public function addTag($tag)
346    {
347       // if the result of a date search are displayed, reset them
348       $this->resetDateSearch();
349
350       if(!isset($_SESSION['selected_tags']))
351          $_SESSION['selected_tags'] = Array();
352
353       if(!in_array($tag, $_SESSION['selected_tags']))
354          array_push($_SESSION['selected_tags'], $tag);
355    
356    } // addTag()
357
358    public function delTag($tag)
359    {
360       if(isset($_SESSION['selected_tags'])) {
361          $key = array_search($tag, $_SESSION['selected_tags']);
362          unset($_SESSION['selected_tags'][$key]);
363          sort($_SESSION['selected_tags']);
364       }
365
366    } // delTag()
367
368    public function resetTags()
369    {
370       if(isset($_SESSION['selected_tags']))
371          unset($_SESSION['selected_tags']);
372
373    } // resetTags()
374
375    public function resetPhotoView()
376    {
377       if(isset($_SESSION['current_photo']))
378          unset($_SESSION['current_photo']);
379
380    } // resetPhotoView();
381
382    public function resetTagSearch()
383    {
384       if(isset($_SESSION['searchfor']))
385          unset($_SESSION['searchfor']);
386
387    } // resetTagSearch()
388
389    public function resetDateSearch()
390    {
391       if(isset($_SESSION['from_date']))
392          unset($_SESSION['from_date']);
393       if(isset($_SESSION['to_date']))
394          unset($_SESSION['to_date']);
395
396    } // resetDateSearch();
397
398    public function getPhotoSelection()
399    {  
400       $matched_photos = Array();
401
402       /* return a search result */
403       if(isset($_SESSION['searchfor']) && $_SESSION['searchfor'] != '') {
404          $result = $this->db->db_query("
405             SELECT DISTINCT photo_id
406                FROM photo_tags pt
407             INNER JOIN photos p
408                ON p.id=pt.photo_id
409             INNER JOIN tags t
410                ON pt.tag_id=t.id
411             WHERE t.name LIKE '%". $_SESSION['searchfor'] ."%'
412                ORDER BY p.time ASC
413          ");
414          while($row = $this->db->db_fetch_object($result)) {
415             array_push($matched_photos, $row['photo_id']);
416          }
417          return $matched_photos;
418       }
419
420       /* return according the selected tags */
421       if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
422          $selected = "";
423          foreach($_SESSION['selected_tags'] as $tag)
424             $selected.= $tag .",";
425          $selected = substr($selected, 0, strlen($selected)-1);
426
427          if($_SESSION['tag_condition'] == 'or') {
428             $result = $this->db->db_query("
429                SELECT DISTINCT photo_id
430                   FROM photo_tags pt
431                INNER JOIN photos p
432                   ON p.id=pt.photo_id
433                WHERE pt.tag_id IN (". $selected .")
434                ORDER BY p.time ASC
435             ");
436          }
437          elseif($_SESSION['tag_condition'] == 'and') {
438
439             if(count($_SESSION['selected_tags']) >= 32) {
440                print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
441                print "evaluate your tag selection. Please remove some tags from your selection.\n";
442                return Array();
443             } 
444
445             /* Join together a table looking like
446
447                pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
448
449                so the query can quickly return all images matching the
450                selected tags in an AND condition
451
452             */
453
454             $query_str = "
455                SELECT DISTINCT pt1.photo_id
456                   FROM photo_tags pt1
457             ";
458
459             for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
460                $query_str.= "
461                   INNER JOIN photo_tags pt". ($i+2) ."
462                      ON pt1.photo_id=pt". ($i+2) .".photo_id
463                ";
464             }
465             $query_str.= "WHERE pt1.tag_id=". $_SESSION['selected_tags'][0];
466             for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
467                $query_str.= "
468                   AND pt". ($i+1) .".tag_id=". $_SESSION['selected_tags'][$i] ."
469                "; 
470             }
471             $result = $this->db->db_query($query_str);
472          }
473
474          while($row = $this->db->db_fetch_object($result)) {
475             array_push($matched_photos, $row['photo_id']);
476          }
477          return $matched_photos;
478       }
479
480       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
481          $from_date = strtotime($_SESSION['from_date']);
482          $to_date = strtotime($_SESSION['to_date']);
483          $result = $this->db->db_query("
484             SELECT DISTINCT photo_id
485                FROM photo_tags pt
486             INNER JOIN photos p
487                ON p.id=pt.photo_id
488             WHERE 
489                time>='". $from_date ."'
490             AND
491                time<='". $to_date ."'
492             ORDER BY p.time ASC
493          ");
494          while($row = $this->db->db_fetch_object($result)) {
495             array_push($matched_photos, $row['photo_id']);
496          }
497          return $matched_photos;
498       } 
499
500       /* return all available photos */
501       $result = $this->db->db_query("
502          SELECT DISTINCT photo_id
503             FROM photo_tags pt
504          INNER JOIN photos p
505             ON p.id=pt.photo_id
506          ORDER BY p.time ASC
507       ");
508       while($row = $this->db->db_fetch_object($result)) {
509          array_push($matched_photos, $row['photo_id']);
510       }
511       return $matched_photos;
512
513    } // getPhotoSelection()
514
515    public function showPhotoIndex()
516    {
517       $photos = $this->getPhotoSelection();
518
519       $count = count($photos);
520
521       if(isset($_SESSION['begin_with']) && $_SESSION['begin_with'] != "")
522          $anchor = $_SESSION['begin_with'];
523
524       if(!isset($this->cfg->rows_per_page) || $this->cfg->rows_per_page == 0) {
525
526          $begin_with = 0;
527          $end_with = $count;
528
529       }
530       elseif($this->cfg->rows_per_page > 0) {
531
532          if(!$_SESSION['begin_with'] || $_SESSION['begin_with'] == 0)
533             $begin_with = 0;
534          else {
535
536             $begin_with = $_SESSION['begin_with'];
537
538             // verify $begin_with - perhaps the thumbs-per-rows or
539             // rows-per-page variables have changed or the jump back
540             // from a photo wasn't exact - so calculate the real new
541             // starting point
542             $multiplicator = $this->cfg->rows_per_page * $this->cfg->thumbs_per_row;
543             for($i = 0; $i <= $count; $i+=$multiplicator) {
544                if($begin_with >= $i && $begin_with < $i+$multiplicator) {
545                   $begin_with = $i;
546                   break;
547                }
548             }
549          }
550
551          $end_with = $begin_with + ($this->cfg->rows_per_page * $this->cfg->thumbs_per_row);
552       }
553
554    
555       $rows = 0;
556       $cols = 0;
557       $images[$rows] = Array();
558       $img_height[$rows] = Array();
559       $img_width[$rows] = Array();
560       $img_id[$rows] = Array();
561       $img_name[$rows] = Array();
562       $img_title = Array();
563
564       for($i = $begin_with; $i < $end_with; $i++) {
565
566          $images[$rows][$cols] = $photos[$i];
567          $img_id[$rows][$cols] = $i;
568          $img_name[$rows][$cols] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
569          $img_title[$rows][$cols] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
570
571          $thumb_path = $this->cfg->base_path ."/thumbs/". $this->cfg->thumb_width ."_". $this->getMD5($photos[$i]);
572
573          if(file_exists($thumb_path)) {
574             $info = getimagesize($thumb_path); 
575             $img_width[$rows][$cols] = $info[0];
576             $img_height[$rows][$cols] = $info[1];
577          }
578
579          if($cols == $this->cfg->thumbs_per_row-1) {
580             $cols = 0;
581             $rows++;
582             $images[$rows] = Array();
583             $img_width[$rows] = Array();
584             $img_height[$rows] = Array();
585          }
586          else {
587             $cols++;
588          }
589       } 
590
591       // +1 for for smarty's selection iteration
592       $rows++;
593
594       if(isset($_SESSION['searchfor']) && $_SESSION['searchfor'] != '')
595          $this->tmpl->assign('searchfor', $_SESSION['searchfor']);
596
597       /* do we have to display the page selector ? */
598       if($this->cfg->rows_per_page != 0) {
599       
600          /* calculate the page switchers */
601          $previous_start = $begin_with - ($this->cfg->rows_per_page * $this->cfg->thumbs_per_row);
602          $next_start = $begin_with + ($this->cfg->rows_per_page * $this->cfg->thumbs_per_row);
603
604          if($begin_with != 0) 
605             $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");"); 
606          if($end_with < $count)
607             $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");"); 
608
609          $photo_per_page  = $this->cfg->rows_per_page * $this->cfg->thumbs_per_row;
610          $last_page = ceil($count / $photo_per_page);
611
612          /* get the current selected page */
613          if($begin_with == 0) {
614             $current_page = 1;
615          } else {
616             $current_page = 0;
617             for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
618                $current_page++;
619             }
620          } 
621
622          for($i = 1; $i <= $last_page; $i++) {
623
624             if($current_page == $i)
625                $style = "style=\"font-size: 125%;\"";
626             elseif($current_page-1 == $i || $current_page+1 == $i)
627                $style = "style=\"font-size: 105%;\"";
628             elseif(($current_page-5 >= $i) && ($i != 1) ||
629                ($current_page+5 <= $i) && ($i != $last_page))
630                $style = "style=\"font-size: 75%;\"";
631             else
632                $style = "";
633
634             $select = "<a href=\"javascript:showPhotoIndex(". (($i*$photo_per_page)-$photo_per_page) .");\"";
635                if($style != "")
636                   $select.= $style;
637             $select.= ">". $i ."</a>&nbsp;";
638
639             // until 9 pages we show the selector from 1-9
640             if($last_page <= 9) {
641                $page_select.= $select;
642                continue;
643             } else {
644                if($i == 1 /* first page */ || 
645                   $i == $last_page /* last page */ ||
646                   $i == $current_page /* current page */ ||
647                   $i == ceil($last_page * 0.25) /* first quater */ ||
648                   $i == ceil($last_page * 0.5) /* half */ ||
649                   $i == ceil($last_page * 0.75) /* third quater */ ||
650                   (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
651                   (in_array($i, array($last_page, $last_page-1, $last_page-2, $last_page-3, $last_page-4, $last_page-5)) && $current_page >= $last_page-4) /* the last 6 */ ||
652                   $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
653                   $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
654
655                   $page_select.= $select;
656                   continue;
657
658                }
659             }
660
661             $page_select.= ".";
662          }
663
664          /* only show the page selector if we have more then one page */
665          if($last_page > 1)
666             $this->tmpl->assign('page_selector', $page_select);
667       }
668
669       
670       $current_tags = $this->getCurrentTags();
671       $extern_link = "index.php?mode=showpi";
672       if($current_tags != "") {
673          $extern_link.= "&tags=". $current_tags;
674       }
675       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
676          $extern_link.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
677       }
678
679       $export_link = "index.php?mode=export";
680
681       $this->tmpl->assign('extern_link', $extern_link);
682       $this->tmpl->assign('export_link', $export_link);
683       $this->tmpl->assign('count', $count);
684       $this->tmpl->assign('width', $this->cfg->thumb_width);
685       $this->tmpl->assign('images', $images);
686       $this->tmpl->assign('img_width', $img_width);
687       $this->tmpl->assign('img_height', $img_height);
688       $this->tmpl->assign('img_id', $img_id);
689       $this->tmpl->assign('img_name', $img_name);
690       $this->tmpl->assign('img_title', $img_title);
691       $this->tmpl->assign('rows', $rows);
692       $this->tmpl->assign('columns', $this->cfg->thumbs_per_row);
693
694       $this->tmpl->show("photo_index.tpl");
695
696       if(isset($anchor))
697          print "<script language=\"JavaScript\">self.location.hash = '#image". $anchor ."';</script>\n";
698
699    } // showPhotoIndex()
700
701    public function showCredits()
702    {
703       $this->tmpl->assign('version', $this->cfg->version);
704       $this->tmpl->assign('product', $this->cfg->product);
705       $this->tmpl->show("credits.tpl");
706
707    } // showCredits()
708
709    public function create_thumbnail($orig_image, $thumb_image, $width)
710    {  
711       if(!file_exists($orig_image))
712          return false;
713
714       $details = getimagesize($orig_image);
715       
716       /* check if original photo is a support image type */
717       if(!$this->checkifImageSupported($details['mime']))
718          return false;
719
720       $meta = $this->get_meta_informations($orig_image);
721
722       $rotate = 0;
723       $flip = false;
724
725       switch($meta['Orientation']) {
726
727          case 1: /* top, left */
728             $rotate = 0; $flip = false; break;
729          case 2: /* top, right */
730             $rotate = 0; $flip = true; break;
731          case 3: /* bottom, left */
732             $rotate = 180; $flip = false; break;
733          case 4: /* bottom, right */
734             $rotate = 180; $flip = true; break;
735          case 5: /* left side, top */
736             $rotate = 90; $flip = true; break;
737          case 6: /* right side, top */
738             $rotate = 90; $flip = false; break;
739          case 7: /* left side, bottom */
740             $rotate = 270; $flip = true; break;
741          case 8: /* right side, bottom */
742             $rotate = 270; $flip = false; break;
743       }
744
745       $src_img = @imagecreatefromjpeg($orig_image);
746
747       if(!$src_img) {
748          print "Can't load image from ". $orig_image ."\n";
749          return false;
750       }
751
752       /* grabs the height and width */
753       $cur_width = imagesx($src_img);
754       $cur_height = imagesy($src_img);
755
756       // If requested width is more then the actual image width,
757       // do not generate a thumbnail
758
759       if($width >= $cur_width) {
760          imagedestroy($src_img);
761          return true;
762       }
763
764       // If the image will be rotate because EXIF orientation said so
765       // 'virtually rotate' the image for further calculations
766       if($rotate == 90 || $rotate == 270) {
767          $tmp = $cur_width;
768          $cur_width = $cur_height;
769          $cur_height = $tmp;
770       }
771
772       /* calculates aspect ratio */
773       $aspect_ratio = $cur_height / $cur_width;
774
775       /* sets new size */
776       if($aspect_ratio < 1) {
777          $new_w = $width;
778          $new_h = abs($new_w * $aspect_ratio);
779       } else {
780          /* 'virtually' rotate the image and calculate it's ratio */
781          $tmp_w = $cur_height;
782          $tmp_h = $cur_width;
783          /* now get the ratio from the 'rotated' image */
784          $tmp_ratio = $tmp_h/$tmp_w;
785          /* now calculate the new dimensions */
786          $tmp_w = $width;
787          $tmp_h = abs($tmp_w * $tmp_ratio);
788
789          // now that we know, how high they photo should be, if it
790          // gets rotated, use this high to scale the image
791          $new_h = $tmp_h;
792          $new_w = abs($new_h / $aspect_ratio);
793
794          // If the image will be rotate because EXIF orientation said so
795          // now 'virtually rotate' back the image for the image manipulation
796          if($rotate == 90 || $rotate == 270) {
797             $tmp = $new_w;
798             $new_w = $new_h;
799             $new_h = $tmp;
800          }
801       }
802
803       /* creates new image of that size */
804       $dst_img = imagecreatetruecolor($new_w, $new_h);
805
806       imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
807
808       /* copies resized portion of original image into new image */
809       imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
810
811       /* needs the image to be flipped horizontal? */
812       if($flip) {
813          print "(FLIP)";
814          $image = $dst_img;
815          for($x = 0; $x < $new_w; $x++) {
816             imagecopy($dst_img, $image, $x, 0, $w - $x - 1, 0, 1, $h);
817          }
818       }
819
820       if($rotate) {
821          $this->_debug("(ROTATE)");
822          $dst_img = $this->rotateImage($dst_img, $rotate);
823       }
824
825       /* write down new generated file */
826       $result = imagejpeg($dst_img, $thumb_image, 75);
827
828       /* free your mind */
829       imagedestroy($dst_img);
830       imagedestroy($src_img);
831
832       if($result === false) {
833          print "Can't write thumbnail ". $thumb_image ."\n";
834          return false;
835       }
836
837       return true;
838
839    } // create_thumbnail()
840
841    public function get_meta_informations($file)
842    {
843       return exif_read_data($file);
844
845    } // get_meta_informations()
846
847    public function check_config_table()
848    {
849       // if the config table doesn't exist yet, create it
850       if(!$this->cfg_db->db_check_table_exists("images")) {
851          $this->cfg_db->db_exec("
852             CREATE TABLE images (
853                img_idx int primary key,
854                img_md5 varchar(32)
855             )
856             ");
857       }
858
859    } // check_config_table
860
861    /**
862     * Generates a thumbnail from photo idx
863     *
864     * This function will generate JPEG thumbnails from provided F-Spot photo
865     * indizes.
866     *
867     * 1. Check if all thumbnail generations (width) are already in place and
868     *    readable
869     * 2. Check if the md5sum of the original file has changed
870     * 3. Generate the thumbnails if needed
871     */
872    public function gen_thumb($idx = 0, $force = 0)
873    {
874       $error = 0;
875
876       $resolutions = Array(
877          $this->cfg->thumb_width,
878          $this->cfg->photo_width,
879          $this->cfg->mini_width,
880       );
881
882       /* get details from F-Spot's database */
883       $details = $this->get_photo_details($idx);
884
885       /* calculate file MD5 sum */
886       $full_path = $this->translate_path($details['directory_path'])  ."/". $details['name'];
887
888       if(!file_exists($full_path)) {
889          $this->_warning("File ". $full_path ." does not exist\n");
890          return;
891       }
892
893       if(!is_readable($full_path)) {
894          $this->_warning("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
895          return;
896       }
897
898       $file_md5 = md5_file($full_path);
899
900       $this->_debug("Image [". $idx ."] ". $details['name'] ." Thumbnails:");
901
902       foreach($resolutions as $resolution) {
903
904          $thumb_path = $this->cfg->base_path ."/thumbs/". $resolution ."_". $file_md5;
905
906          /* if the thumbnail file doesn't exist, create it */
907          if(!file_exists($thumb_path)) {
908
909             $this->_debug(" ". $resolution ."px");
910             if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
911                $error = 1;
912          }
913
914          /* if the file hasn't changed there is no need to regen the thumb */
915          elseif($file_md5 != $this->getMD5($idx) || $force) {
916
917             $this->_debug(" ". $resolution ."px");
918             if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
919                $error = 1;
920
921          }
922       }
923
924       /* set the new/changed MD5 sum for the current photo */
925       if(!$error) {
926          $this->setMD5($idx, $file_md5);
927       }
928
929       $this->_debug("\n");
930
931    } // gen_thumb()
932
933    public function getMD5($idx)
934    {
935       $result = $this->cfg_db->db_query("
936          SELECT img_md5 
937          FROM images
938          WHERE img_idx='". $idx ."'
939       ");
940
941       if(!$result)
942          return 0;
943
944       $img = $this->cfg_db->db_fetch_object($result);
945       return $img['img_md5'];
946       
947    } // getMD5()
948
949    private function setMD5($idx, $md5)
950    {
951       $result = $this->cfg_db->db_exec("
952          REPLACE INTO images (img_idx, img_md5)
953          VALUES ('". $idx ."', '". $md5 ."')
954       ");
955
956    } // setMD5()
957
958    public function setTagCondition($mode)
959    {
960       $_SESSION['tag_condition'] = $mode;
961
962    } // setTagCondition()
963
964    public function startTagSearch($searchfor)
965    {
966       $_SESSION['searchfor'] = $searchfor;
967       $_SESSION['selected_tags'] = Array();
968
969       foreach($this->avail_tags as $tag) {
970          if(preg_match('/'. $searchfor .'/i', $this->tags[$tag]))
971             array_push($_SESSION['selected_tags'], $tag);
972       }
973
974       $this->resetDateSearch();
975
976    } // startTagSearch()
977
978    public function startDateSearch($from, $to)
979    {
980       $_SESSION['from_date'] = $from;
981       $_SESSION['to_date'] = $to;
982    }
983
984    private function rotateImage($img, $degrees)
985    {
986       if(function_exists("imagerotate"))
987          $img = imagerotate($img, $degrees, 0);
988       else
989       {
990          function imagerotate($src_img, $angle)
991          {
992             $src_x = imagesx($src_img);
993             $src_y = imagesy($src_img);
994             if ($angle == 180) {
995                $dest_x = $src_x;
996                $dest_y = $src_y;
997             }
998             elseif ($src_x <= $src_y) {
999                $dest_x = $src_y;
1000                $dest_y = $src_x;
1001             }
1002             elseif ($src_x >= $src_y) {
1003                $dest_x = $src_y;
1004                $dest_y = $src_x;
1005             }
1006                
1007             $rotate=imagecreatetruecolor($dest_x,$dest_y);
1008             imagealphablending($rotate, false);
1009                
1010             switch ($angle) {
1011             
1012                case 90:
1013                   for ($y = 0; $y < ($src_y); $y++) {
1014                      for ($x = 0; $x < ($src_x); $x++) {
1015                         $color = imagecolorat($src_img, $x, $y);
1016                         imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
1017                      }
1018                   }
1019                   break;
1020
1021                case 270:
1022                   for ($y = 0; $y < ($src_y); $y++) {
1023                      for ($x = 0; $x < ($src_x); $x++) {
1024                         $color = imagecolorat($src_img, $x, $y);
1025                         imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
1026                      }
1027                   }
1028                   break;
1029
1030                case 180:
1031                   for ($y = 0; $y < ($src_y); $y++) {
1032                      for ($x = 0; $x < ($src_x); $x++) {
1033                         $color = imagecolorat($src_img, $x, $y);
1034                         imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
1035                      }
1036                   }
1037                   break;
1038
1039                default: $rotate = $src_img;
1040             };
1041
1042             return $rotate;
1043
1044          }
1045
1046          $img = imagerotate($img, $degrees);
1047
1048       }
1049
1050       return $img;
1051
1052    } // rotateImage()
1053
1054    private function get_photo_tags($idx)
1055    {
1056       $result = $this->db->db_query("
1057          SELECT t.id, t.name
1058          FROM tags t
1059          INNER JOIN photo_tags pt
1060             ON t.id=pt.tag_id
1061          WHERE pt.photo_id='". $idx ."'
1062       ");
1063
1064       $tags = Array();
1065
1066       while($row = $this->db->db_fetch_object($result))
1067          $tags[$row['id']] = $row['name'];
1068
1069       return $tags;
1070
1071    } // get_photo_tags()
1072
1073    public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
1074    {
1075       if (strlen($color) != 6) 
1076          $color = 000000;
1077
1078       $int = hexdec($color);
1079       $h = imagefontheight($font);
1080       $fw = imagefontwidth($font);
1081       $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
1082       $lines = count($txt);
1083       $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
1084       $bg = imagecolorallocate($im, 255, 255, 255);
1085       $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
1086       $y = 0;
1087
1088       foreach ($txt as $text) {
1089          $x = (($w - ($fw * strlen($text))) / 2);
1090          imagestring($im, $font, $x, $y, $text, $color);
1091          $y += ($h + $space);
1092       }
1093
1094       Header("Content-type: image/png");
1095       ImagePng($im);
1096
1097    } // showTextImage()
1098
1099    private function checkRequirements()
1100    {
1101       if(!function_exists("imagecreatefromjpeg")) {
1102          print "PHP GD library extension is missing<br />\n";
1103          $missing = true;
1104       }
1105
1106       if(!function_exists("sqlite3_open")) {
1107          print "PHP SQLite3 library extension is missing<br />\n";
1108          $missing = true;
1109       }
1110
1111       /* Check for HTML_AJAX PEAR package, lent from Horde project */
1112       ini_set('track_errors', 1);
1113       @include_once 'HTML/AJAX/Server.php';
1114       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1115          print "PEAR HTML_AJAX package is missing<br />\n";
1116          $missing = true;
1117       }
1118       @include_once 'Calendar/Calendar.php';
1119       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1120          print "PEAR Calendar package is missing<br />\n";
1121          $missing = true;
1122       }
1123       ini_restore('track_errors');
1124
1125       if(isset($missing))
1126          return false;
1127
1128       return true;
1129
1130    } // checkRequirements()
1131
1132    private function _debug($text)
1133    {
1134       if($this->fromcmd) {
1135          print $text;
1136       }
1137
1138    } // _debug()
1139
1140    public function checkifImageSupported($mime)
1141    {
1142       if(in_array($mime, Array("image/jpeg")))
1143          return true;
1144
1145       return false;
1146
1147    } // checkifImageSupported()
1148
1149    public function _warning($text)
1150    {
1151       print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
1152       print $text;
1153
1154    } // _warning()
1155
1156    private function get_calendar($mode)
1157    {
1158       $year = $_SESSION[$mode .'_date'] ? date("Y", strtotime($_SESSION[$mode .'_date'])) : date("Y");
1159       $month = $_SESSION[$mode .'_date'] ? date("m", strtotime($_SESSION[$mode .'_date'])) : date("m");
1160       $day = $_SESSION[$mode .'_date'] ? date("d", strtotime($_SESSION[$mode .'_date'])) : date("d");
1161
1162       $output = "<input type=\"text\" size=\"3\" id=\"". $mode ."year\" value=\"". $year ."\" />\n";
1163       $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."month\" value=\"". $month ."\" />\n";
1164       $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."day\" value=\"". $day ."\" />\n";
1165       return $output;
1166
1167    } // get_calendar()
1168
1169    public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
1170    {
1171       if (!isset($year)) $year = date('Y');
1172       if (!isset($month)) $month = date('m');
1173       if (!isset($day)) $day = date('d');
1174       $rows = 1;
1175       $cols = 1;
1176       $matrix = Array();
1177
1178       require_once CALENDAR_ROOT.'Month/Weekdays.php';
1179       require_once CALENDAR_ROOT.'Day.php';
1180
1181
1182       // Build the month
1183       $month = new Calendar_Month_Weekdays($year,$month);
1184
1185       // Create links
1186       $prevStamp = $month->prevMonth(true);
1187       $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
1188       $nextStamp = $month->nextMonth(true);
1189       $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
1190
1191       $selectedDays = array (
1192          new Calendar_Day($year,$month,$day),
1193          new Calendar_Day($year,12,25),
1194       );
1195
1196       // Build the days in the month
1197       $month->build($selectedDays);
1198
1199       $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
1200       $this->tmpl->assign('prev_month', $prev);
1201       $this->tmpl->assign('next_month', $next);
1202
1203       while ( $day = $month->fetch() ) {
1204    
1205          if(!isset($matrix[$rows]))
1206             $matrix[$rows] = Array();
1207
1208          $string = "";
1209
1210          $dayStamp = $day->thisDay(true);
1211          $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
1212
1213          // isFirst() to find start of week
1214          if ( $day->isFirst() )
1215             $string.= "<tr>\n";
1216
1217          if ( $day->isSelected() ) {
1218             $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
1219          } else if ( $day->isEmpty() ) {
1220             $string.= "<td>&nbsp;</td>\n";
1221          } else {
1222             $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
1223          }
1224
1225          // isLast() to find end of week
1226          if ( $day->isLast() )
1227             $string.= "</tr>\n";
1228
1229          $matrix[$rows][$cols] = $string;
1230
1231          $cols++;
1232
1233          if($cols > 7) {
1234             $cols = 1;
1235             $rows++;
1236          }
1237       }
1238
1239       $this->tmpl->assign('matrix', $matrix);
1240       $this->tmpl->assign('rows', $rows);
1241       $this->tmpl->show("calendar.tpl");
1242
1243    } // get_calendar_matrix()
1244
1245    public function getExport($mode)
1246    {
1247       $pictures = $this->getPhotoSelection();
1248       $current_tags = $this->getCurrentTags();  
1249
1250       if(!isset($_SERVER['HTTPS'])) $protocol = "http";
1251       else $protocol = "https";
1252
1253       $server_name = $_SERVER['SERVER_NAME'];
1254
1255       foreach($pictures as $picture) {
1256
1257          $orig_url = $protocol ."://". $server_name . $this->cfg->web_path ."index.php?mode=showp&id=". $picture;
1258          if($current_tags != "") {
1259             $orig_url.= "&tags=". $current_tags;
1260          } 
1261          if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1262             $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
1263          }
1264
1265          $thumb_url = $protocol ."://". $server_name . $this->cfg->web_path ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
1266
1267          switch($mode) {
1268
1269             case 'HTML':
1270                // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
1271                print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
1272                break;
1273                
1274             case 'MoinMoin':
1275                // [%pictureurl% %thumbnailurl%]
1276                print htmlspecialchars(" * [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
1277                break;
1278          }
1279
1280       }
1281
1282    } // getExport()
1283
1284    private function getCurrentTags()
1285    {
1286       $current_tags = "";
1287       if($_SESSION['selected_tags'] != "") {
1288          foreach($_SESSION['selected_tags'] as $tag)
1289             $current_tags.= $tag .",";
1290          $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
1291       }
1292       return $current_tags;
1293
1294    } // getCurrentTags()
1295
1296    public function getCurrentPhoto()
1297    {
1298       if(isset($_SESSION['current_photo'])) {
1299          print $_SESSION['current_photo'];
1300       }
1301    } // getCurrentPhoto()
1302
1303    public function whatToDo()
1304    {
1305       if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1306          return "showpi_tags";
1307       }
1308       elseif(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1309          return "showpi_date";
1310       }
1311       elseif(isset($_SESSION['current_photo'])) {
1312          return "show_photo";
1313       }
1314       elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
1315          return "showpi";
1316       }
1317
1318       return "nothing special";
1319
1320    } // whatToDo()
1321
1322    private function getuid()
1323    {
1324       if($uid = posix_getuid()) {
1325          if($user = posix_getpwuid($uid)) {
1326             return $user['name'];
1327          }
1328       }
1329    
1330       return 'n/a';
1331    
1332    } // getuid()
1333
1334 }
1335
1336 ?>