d67c7e39dc4f1b1543296ce209ed7e0071a4beaa
[phpfspot.git] / phpfspot.class.php
1 <?php
2
3 /***************************************************************************
4  *
5  * Copyright (c) by Andreas Unterkircher, unki@netshadow.at
6  * All rights reserved
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program; if not, write to the Free Software
20  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21  *
22  ***************************************************************************/
23
24 require_once "phpfspot_cfg.php";
25 require_once "phpfspot_db.php";
26
27 class PHPFSPOT {
28
29    var $cfg;
30    var $db;
31    var $cfg_db;
32    var $tmpl;
33    var $tags;
34    var $avail_tags;
35
36    /**
37     * class constructor
38     *
39     * this function will be called on class construct
40     * and will check requirements, loads configuration,
41     * open databases and start the user session
42     */
43    public function __construct()
44    {
45       $this->cfg = new PHPFSPOT_CFG;
46
47       /* set application name and version information */
48       $this->cfg->product = "phpfspot";
49       $this->cfg->version = "1.2";
50
51       /* Check necessary requirements */
52       if(!$this->checkRequirements()) {
53          exit(1);
54       }
55
56       $this->db  = new PHPFSPOT_DB($this, $this->cfg->fspot_db);
57       
58       if(!is_writeable(dirname($this->cfg->phpfspot_db))) {
59          print dirname($this->cfg->phpfspot_db) .": directory is not writeable!";
60          exit(1);
61       }
62          
63       $this->cfg_db = new PHPFSPOT_DB($this, $this->cfg->phpfspot_db);
64       if(!is_writeable($this->cfg->phpfspot_db)) {
65          print $this->cfg->phpfspot_db ." is not writeable for user ". $this->getuid() ."\n";
66          exit(1);
67       }
68       $this->check_config_table();
69
70       /* include Smarty template engine */
71       if(!$this->check_readable($this->cfg->smarty_path .'/libs/Smarty.class.php')) {
72          exit(1);
73       }
74       require $this->cfg->smarty_path .'/libs/Smarty.class.php';
75       /* overload Smarty class if our own template handler */
76       require_once "phpfspot_tmpl.php";
77       $this->tmpl = new PHPFSPOT_TMPL($this);
78
79       $this->get_tags();
80
81       session_start();
82
83       if(!isset($_SESSION['tag_condition']))
84          $_SESSION['tag_condition'] = 'or';
85
86       if(!isset($_SESSION['sort_order']))
87          $_SESSION['sort_order'] = 'date_asc';
88
89       if(!isset($_SESSION['searchfor']))
90          $_SESSION['searchfor'] = '';
91
92       // if begin_with is still set but rows_per_page is now 0, unset it
93       if(isset($_SESSION['begin_with']) && $this->cfg->rows_per_page == 0)
94          unset($_SESSION['begin_with']);
95
96    } // __construct()
97
98    public function __destruct()
99    {
100
101    } // __destruct()
102
103    /**
104     * show - generate html output
105     *
106     * this function can be called after the constructor has
107     * prepared everyhing. it will load the index.tpl smarty
108     * template. if necessary it will registere pre-selects
109     * (photo index, photo, tag search, date search) into
110     * users session.
111     */
112    public function show()
113    {
114       $this->tmpl->assign('searchfor', $_SESSION['searchfor']);
115       $this->tmpl->assign('page_title', $this->cfg->page_title);
116       $this->tmpl->assign('current_condition', $_SESSION['tag_condition']);
117       $this->tmpl->assign('template_path', 'themes/'. $this->cfg->theme_name);
118
119       $_SESSION['start_action'] = $_GET['mode'];
120
121       switch($_GET['mode']) {
122          case 'showpi':
123             if(isset($_GET['tags'])) {
124                $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
125             }
126             if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
127                $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
128             }
129             if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
130                $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
131             }
132             break;
133          case 'showp':
134             if(isset($_GET['tags'])) {
135                $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
136                $_SESSION['start_action'] = 'showp';
137             }
138             if(isset($_GET['id']) && is_numeric($_GET['id'])) {
139                $_SESSION['current_photo'] = $_GET['id'];
140                $_SESSION['start_action'] = 'showp';
141             }
142             if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
143                $_SESSION['from_date'] = strtotime($_GET['from_date']);
144             }
145             if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
146                $_SESSION['to_date'] = strtotime($_GET['to_date']);
147             }
148             break;
149          case 'export':
150             $this->tmpl->show("export.tpl");
151             return;
152             break;
153          case 'slideshow':
154             $this->tmpl->show("slideshow.tpl");
155             return;
156             break;
157          case 'rss':
158             if(isset($_GET['tags'])) {
159                $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
160             }
161             if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
162                $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
163             }
164             if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
165                $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
166             }
167             $this->getRSSFeed();
168             return;
169             break;
170       }
171
172       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date']))
173          $this->tmpl->assign('date_search_enabled', true);
174
175       $this->tmpl->assign('from_date', $this->get_calendar('from'));
176       $this->tmpl->assign('to_date', $this->get_calendar('to'));
177       $this->tmpl->assign('sort_field', $this->get_sort_field());
178       $this->tmpl->assign('content_page', 'welcome.tpl');
179       $this->tmpl->show("index.tpl");
180
181    } // show()
182
183    /**
184     * get_tags - grab all tags of f-spot's database
185     *
186     * this function will get all available tags from
187     * the f-spot database and store them within two
188     * arrays within this class for later usage. in
189     * fact, if the user requests (hide_tags) it will
190     * opt-out some of them.
191     *
192     * this function is getting called once by show()
193     */
194    private function get_tags()
195    {
196       $this->avail_tags = Array();
197       $count = 0;
198    
199       $result = $this->db->db_query("
200          SELECT id,name
201          FROM tags
202          ORDER BY sort_priority ASC
203       ");
204       
205       while($row = $this->db->db_fetch_object($result)) {
206
207          $tag_id = $row['id'];
208          $tag_name = $row['name'];
209
210          /* if the user has specified to ignore this tag in phpfspot's
211             configuration, ignore it here so it does not get added to
212             the tag list.
213          */
214          if(in_array($row['name'], $this->cfg->hide_tags))
215             continue;
216
217          /* if the user has specified to only show certain tags which
218             are specified in phpfspot's configuration, ignore all others
219             so they will not be added to the tag list.
220          */
221          if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags) &&
222             !in_array($row['name'], $this->cfg->show_tags))
223             continue;
224
225          $this->tags[$tag_id] = $tag_name; 
226          $this->avail_tags[$count] = $tag_id;
227          $count++;
228
229       }
230
231    } // get_tags()
232
233    /** 
234     * extract all photo details
235     * 
236     * retrieve all available details from f-spot's
237     * database and return them as object
238     */
239    public function get_photo_details($idx)
240    {
241       $query_str = "
242          SELECT p.id, p.name, p.time, p.directory_path, p.description
243          FROM photos p
244       ";
245
246       /* if show_tags is set, only return details for photos which
247          are specified to be shown
248       */
249       if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
250          $query_str.= "
251             INNER JOIN photo_tags pt
252                ON p.id=pt.photo_id
253             INNER JOIN tags t
254                ON pt.tag_id=t.id
255             WHERE p.id='". $idx ."'
256             AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
257       }
258       else {
259          $query_str.= "
260             WHERE p.id='". $idx ."'
261          ";
262       }
263
264       $result = $this->db->db_query($query_str);
265       return $this->db->db_fetch_object($result);
266
267    } // get_photo_details
268
269    /**
270     * returns aligned photo names 
271     *
272     * this function returns aligned (length) names for
273     * an specific photo. If the length of the name exceeds
274     * $limit the name will be shrinked (...)
275     */
276    public function getPhotoName($idx, $limit = 0)
277    {
278       if($details = $this->get_photo_details($idx)) {
279          $name = $this->shrink_text($details['name'], $limit);
280          return $name;
281       }
282
283    } // getPhotoName()
284
285    /**
286     * shrink text according provided limit
287     *
288     * If the length of the name exceeds $limit the
289     * text will be shortend and some content in between
290     * will be replaced with "..." 
291     */
292    private function shrink_text($text, $limit)
293    {
294       if($limit != 0 && strlen($text) > $limit) {
295          $text = substr($text, 0, $limit-5) ."...". substr($text, -($limit-5));
296       }
297
298       return $text;
299
300    } // shrink_text();
301
302    /**
303     * translate f-spoth photo path
304     * 
305     * as the full-qualified path recorded in the f-spot database
306     * is usally not the same as on the webserver, this function
307     * will replace the path with that one specified in the cfg
308     */
309    public function translate_path($path, $width = 0)
310    {  
311       return str_replace($this->cfg->path_replace_from, $this->cfg->path_replace_to, $path);
312
313    } // translate_path
314
315    /**
316     * control HTML ouput for a single photo
317     *
318     * this function provides all the necessary information
319     * for the single photo template.
320     */
321    public function showPhoto($photo)
322    {
323       /* get all photos from the current photo selection */
324       $all_photos = $this->getPhotoSelection();
325       $count = count($all_photos);
326
327       for($i = 0; $i < $count; $i++) {
328          
329          // $get_next will be set, when the photo which has to
330          // be displayed has been found - this means that the
331          // next available is in fact the NEXT image (for the
332          // navigation icons) 
333          if(isset($get_next)) {
334             $next_img = $all_photos[$i];
335             break;
336          }
337
338          /* the next photo is our NEXT photo */
339          if($all_photos[$i] == $photo) {
340             $get_next = 1;
341          }
342          else {
343             $previous_img = $all_photos[$i];
344          }
345
346          if($photo == $all_photos[$i]) {
347                $current = $i;
348          }
349       }
350
351       $details = $this->get_photo_details($photo);
352
353       if(!$details) {
354          print "error";
355          return;
356       }
357
358       $orig_path = $this->translate_path($details['directory_path']) ."/". $details['name'];
359       $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
360
361       if(!file_exists($orig_path)) {
362          $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
363       }
364
365       if(!is_readable($orig_path)) {
366          $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
367       }
368
369       /* If the thumbnail doesn't exist yet, try to create it */
370       if(!file_exists($thumb_path)) {
371          $this->gen_thumb($photo, true);
372          $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
373       }
374
375       /* get f-spot database meta information */
376       $meta = $this->get_meta_informations($orig_path);
377
378       /* If EXIF data are available, use them */
379       if(isset($meta['ExifImageWidth'])) {
380          $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
381       } else {
382          $info = getimagesize($orig_path);
383          $meta_res = $info[0] ."x". $info[1]; 
384       }
385
386       $meta_date = isset($meta['FileDateTime']) ? strftime("%a %x %X", $meta['FileDateTime']) : "n/a";
387       $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
388       $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
389
390       $extern_link = "index.php?mode=showp&id=". $photo;
391       $current_tags = $this->getCurrentTags();
392       if($current_tags != "") {
393          $extern_link.= "&tags=". $current_tags;
394       }
395       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
396          $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
397       }
398
399       $this->tmpl->assign('extern_link', $extern_link);
400
401       if(file_exists($thumb_path)) {
402
403          $info = getimagesize($thumb_path);
404
405          $this->tmpl->assign('description', $details['description']);
406          $this->tmpl->assign('image_name', $details['name']);
407
408          $this->tmpl->assign('width', $info[0]);
409          $this->tmpl->assign('height', $info[1]);
410          $this->tmpl->assign('ExifMadeOn', $meta_date);
411          $this->tmpl->assign('ExifMadeWith', $meta_make);
412          $this->tmpl->assign('ExifOrigResolution', $meta_res);
413          $this->tmpl->assign('ExifFileSize', $meta_size);
414     
415          $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&amp;width=". $this->cfg->photo_width);
416          $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
417
418          $this->tmpl->assign('tags', $this->get_photo_tags($photo));
419          $this->tmpl->assign('current', $current);
420       }
421       else {
422          $this->_error("Can't open file ". $thumb_path ."\n");
423          return;
424       }
425
426       if($previous_img) {
427          $this->tmpl->assign('previous_url', "javascript:showImage(". $previous_img .");");
428          $this->tmpl->assign('prev_img', $previous_img);
429       }
430
431       if($next_img) {
432          $this->tmpl->assign('next_url', "javascript:showImage(". $next_img .");");
433          $this->tmpl->assign('next_img', $next_img);
434       }
435       $this->tmpl->assign('mini_width', $this->cfg->mini_width);
436       $this->tmpl->assign('photo_number', $i);
437       $this->tmpl->assign('photo_count', count($all_photos));
438
439       $this->tmpl->show("single_photo.tpl");
440
441    } // showPhoto()
442
443    /**
444     * all available tags and tag cloud
445     *
446     * this function outputs all available tags (time ordered)
447     * and in addition output them as tag cloud (tags which have
448     * many photos will appears more then others)
449     */
450    public function getAvailableTags()
451    {
452       $output = "";
453
454       $result = $this->db->db_query("
455          SELECT tag_id as id, count(tag_id) as quantity
456          FROM photo_tags
457          INNER JOIN tags t
458             ON t.id = tag_id
459          GROUP BY tag_id
460          ORDER BY t.name ASC
461       ");
462
463       $tags = Array();
464
465       while($row = $this->db->db_fetch_object($result)) {
466          $tags[$row['id']] = $row['quantity'];
467       }
468
469       // change these font sizes if you will
470       $max_size = 125; // max font size in %
471       $min_size = 75; // min font size in %
472
473       // get the largest and smallest array values
474       $max_qty = max(array_values($tags));
475       $min_qty = min(array_values($tags));
476
477       // find the range of values
478       $spread = $max_qty - $min_qty;
479       if (0 == $spread) { // we don't want to divide by zero
480          $spread = 1;
481       }
482
483       // determine the font-size increment
484       // this is the increase per tag quantity (times used)
485       $step = ($max_size - $min_size)/($spread);
486
487       // loop through our tag array
488       foreach ($tags as $key => $value) {
489
490          if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
491             continue;
492
493           // calculate CSS font-size
494           // find the $value in excess of $min_qty
495           // multiply by the font-size increment ($size)
496           // and add the $min_size set above
497          $size = $min_size + (($value - $min_qty) * $step);
498           // uncomment if you want sizes in whole %:
499          $size = ceil($size);
500
501          if(isset($this->tags[$key])) {
502             $output.= "<a href=\"javascript:Tags('add', ". $key .");\" class=\"tag\" style=\"font-size: ". $size ."%;\">". $this->tags[$key] ."</a>, ";
503          }
504
505       }
506
507       $output = substr($output, 0, strlen($output)-2);
508       print $output;
509
510    } // getAvailableTags()
511
512    /**
513     * output all selected tags
514     *
515     * this function output all tags which have been selected
516     * by the user. the selected tags are stored in the 
517     * session-variable $_SESSION['selected_tags']
518     */
519    public function getSelectedTags()
520    {
521       $output = "";
522       foreach($this->avail_tags as $tag)
523       {
524          // return all selected tags
525          if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
526             $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
527          }
528       }
529
530       $output = substr($output, 0, strlen($output)-2);
531       print $output;
532
533    } // getSelectedTags()
534
535    /**
536     * add tag to users session variable
537     *
538     * this function will add the specified to users current
539     * tag selection. if a date search has been made before
540     * it will be now cleared
541     */
542    public function addTag($tag)
543    {
544       if(!isset($_SESSION['selected_tags']))
545          $_SESSION['selected_tags'] = Array();
546
547       if(!in_array($tag, $_SESSION['selected_tags']))
548          array_push($_SESSION['selected_tags'], $tag);
549    
550    } // addTag()
551
552    /**
553     * remove tag to users session variable
554     *
555     * this function removes the specified tag from
556     * users current tag selection
557     */
558    public function delTag($tag)
559    {
560       if(isset($_SESSION['selected_tags'])) {
561          $key = array_search($tag, $_SESSION['selected_tags']);
562          unset($_SESSION['selected_tags'][$key]);
563          sort($_SESSION['selected_tags']);
564       }
565
566    } // delTag()
567
568    /**
569     * reset tag selection
570     *
571     * if there is any tag selection, it will be
572     * deleted now
573     */
574    public function resetTags()
575    {
576       if(isset($_SESSION['selected_tags']))
577          unset($_SESSION['selected_tags']);
578
579    } // resetTags()
580
581    /**
582     * reset single photo
583     *
584     * if a specific photo was requested (external link)
585     * unset the session variable now
586     */
587    public function resetPhotoView()
588    {
589       if(isset($_SESSION['current_photo']))
590          unset($_SESSION['current_photo']);
591
592    } // resetPhotoView();
593
594    /**
595     * reset tag search
596     *
597     * if any tag search has taken place, reset
598     * it now
599     */
600    public function resetTagSearch()
601    {
602       if(isset($_SESSION['searchfor']))
603          unset($_SESSION['searchfor']);
604
605    } // resetTagSearch()
606
607     /**
608     * reset date search
609     *
610     * if any date search has taken place, reset
611     * it now
612     */
613    public function resetDateSearch()
614    {
615       if(isset($_SESSION['from_date']))
616          unset($_SESSION['from_date']);
617       if(isset($_SESSION['to_date']))
618          unset($_SESSION['to_date']);
619
620    } // resetDateSearch();
621
622    /**
623     * return all photo according selection
624     *
625     * this function returns all photos based on
626     * the tag-selection, tag- or date-search.
627     * the tag-search also has to take care of AND
628     * and OR conjunctions
629     */
630    public function getPhotoSelection()
631    {  
632       $matched_photos = Array();
633
634       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
635          $from_date = $_SESSION['from_date'];
636          $to_date = $_SESSION['to_date'];
637          $additional_where_cond = "
638                p.time>='". $from_date ."'
639             AND
640                p.time<='". $to_date ."'
641          ";
642       } 
643
644       if(isset($_SESSION['sort_order'])) {
645          $order_str = $this->get_sort_order();
646       }
647
648       /* return a search result */
649       if(isset($_SESSION['searchfor']) && $_SESSION['searchfor'] != '') {
650          $query_str = "
651             SELECT DISTINCT photo_id
652                FROM photo_tags pt
653             INNER JOIN photos p
654                ON p.id=pt.photo_id
655             INNER JOIN tags t
656                ON pt.tag_id=t.id
657             WHERE t.name LIKE '%". $_SESSION['searchfor'] ."%'";
658
659          if(isset($additional_where_cond))
660             $query_str.= "AND ". $additional_where_cond ." ";
661
662          if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
663             $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
664          }
665          
666          if(isset($order_str))
667             $query_str.= $order_str;
668
669          $result = $this->db->db_query($query_str);
670          while($row = $this->db->db_fetch_object($result)) {
671             array_push($matched_photos, $row['photo_id']);
672          }
673          return $matched_photos;
674       }
675
676       /* return according the selected tags */
677       if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
678          $selected = "";
679          foreach($_SESSION['selected_tags'] as $tag)
680             $selected.= $tag .",";
681          $selected = substr($selected, 0, strlen($selected)-1);
682
683          if($_SESSION['tag_condition'] == 'or') {
684             $query_str = "
685                SELECT DISTINCT pt.photo_id
686                   FROM photo_tags pt
687                INNER JOIN photos p
688                   ON p.id=pt.photo_id
689                INNER JOIN tags t
690                   ON pt.tag_id=t.id
691                WHERE pt.tag_id IN (". $selected .")
692             ";
693             if(isset($additional_where_cond)) 
694                $query_str.= "AND ". $additional_where_cond ." ";
695
696             if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
697                $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
698             }
699
700             if(isset($order_str))
701                $query_str.= $order_str;
702          }
703          elseif($_SESSION['tag_condition'] == 'and') {
704
705             if(count($_SESSION['selected_tags']) >= 32) {
706                print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
707                print "evaluate your tag selection. Please remove some tags from your selection.\n";
708                return Array();
709             } 
710
711             /* Join together a table looking like
712
713                pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
714
715                so the query can quickly return all images matching the
716                selected tags in an AND condition
717
718             */
719
720             $query_str = "
721                SELECT DISTINCT pt1.photo_id
722                   FROM photo_tags pt1
723             ";
724
725             if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
726                $query_str.= "
727                   INNER JOIN tags t
728                      ON pt1.tag_id=t.id
729                ";
730             }
731
732             for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
733                $query_str.= "
734                   INNER JOIN photo_tags pt". ($i+2) ."
735                      ON pt1.photo_id=pt". ($i+2) .".photo_id
736                ";
737             }
738             $query_str.= "
739                INNER JOIN photos p
740                   ON pt1.photo_id=p.id
741             ";
742             $query_str.= "WHERE pt1.tag_id=". $_SESSION['selected_tags'][0];
743             for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
744                $query_str.= "
745                   AND pt". ($i+1) .".tag_id=". $_SESSION['selected_tags'][$i] ."
746                "; 
747             }
748             if(isset($additional_where_cond)) 
749                $query_str.= "AND ". $additional_where_cond;
750
751             if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
752                $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
753             }
754
755             if(isset($order_str))
756                $query_str.= $order_str;
757
758          }
759
760          $result = $this->db->db_query($query_str);
761          while($row = $this->db->db_fetch_object($result)) {
762             array_push($matched_photos, $row['photo_id']);
763          }
764          return $matched_photos;
765       }
766
767       /* return all available photos */
768       $query_str = "
769          SELECT DISTINCT photo_id
770             FROM photo_tags pt
771          INNER JOIN photos p
772             ON p.id=pt.photo_id
773          INNER JOIN tags t
774             ON pt.tag_id=t.id
775       ";
776       if(isset($additional_where_cond)) 
777          $query_str.= "WHERE ". $additional_where_cond ." ";
778
779       if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
780          $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
781       }
782  
783       if(isset($order_str))
784          $query_str.= $order_str;
785
786       $result = $this->db->db_query($query_str);
787       while($row = $this->db->db_fetch_object($result)) {
788          array_push($matched_photos, $row['photo_id']);
789       }
790       return $matched_photos;
791
792    } // getPhotoSelection()
793
794     /**
795     * control HTML ouput for photo index
796     *
797     * this function provides all the necessary information
798     * for the photo index template.
799     */
800    public function showPhotoIndex()
801    {
802       $photos = $this->getPhotoSelection();
803
804       $count = count($photos);
805
806       if(isset($_SESSION['begin_with']) && $_SESSION['begin_with'] != "")
807          $anchor = $_SESSION['begin_with'];
808
809       if(!isset($this->cfg->rows_per_page) || $this->cfg->rows_per_page == 0) {
810
811          $begin_with = 0;
812          $end_with = $count;
813
814       }
815       elseif($this->cfg->rows_per_page > 0) {
816
817          if(!$_SESSION['begin_with'] || $_SESSION['begin_with'] == 0)
818             $begin_with = 0;
819          else {
820
821             $begin_with = $_SESSION['begin_with'];
822
823             // verify $begin_with - perhaps the thumbs-per-rows or
824             // rows-per-page variables have changed or the jump back
825             // from a photo wasn't exact - so calculate the real new
826             // starting point
827             $multiplicator = $this->cfg->rows_per_page * $this->cfg->thumbs_per_row;
828             for($i = 0; $i <= $count; $i+=$multiplicator) {
829                if($begin_with >= $i && $begin_with < $i+$multiplicator) {
830                   $begin_with = $i;
831                   break;
832                }
833             }
834          }
835
836          $end_with = $begin_with + ($this->cfg->rows_per_page * $this->cfg->thumbs_per_row);
837       }
838
839    
840       $rows = 0;
841       $cols = 0;
842       $images[$rows] = Array();
843       $img_height[$rows] = Array();
844       $img_width[$rows] = Array();
845       $img_id[$rows] = Array();
846       $img_name[$rows] = Array();
847       $img_title = Array();
848
849       for($i = $begin_with; $i < $end_with; $i++) {
850
851          $images[$rows][$cols] = $photos[$i];
852          $img_id[$rows][$cols] = $i;
853          $img_name[$rows][$cols] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
854          $img_title[$rows][$cols] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
855
856          $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i]);
857
858          if(file_exists($thumb_path)) {
859             $info = getimagesize($thumb_path); 
860             $img_width[$rows][$cols] = $info[0];
861             $img_height[$rows][$cols] = $info[1];
862          }
863
864          if($cols == $this->cfg->thumbs_per_row-1) {
865             $cols = 0;
866             $rows++;
867             $images[$rows] = Array();
868             $img_width[$rows] = Array();
869             $img_height[$rows] = Array();
870          }
871          else {
872             $cols++;
873          }
874       } 
875
876       // +1 for for smarty's selection iteration
877       $rows++;
878
879       if(isset($_SESSION['searchfor']) && $_SESSION['searchfor'] != '')
880          $this->tmpl->assign('searchfor', $_SESSION['searchfor']);
881
882       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
883          $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
884          $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
885       }
886
887       if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
888          $this->tmpl->assign('tag_result', 1);
889       }
890
891       /* do we have to display the page selector ? */
892       if($this->cfg->rows_per_page != 0) {
893       
894          /* calculate the page switchers */
895          $previous_start = $begin_with - ($this->cfg->rows_per_page * $this->cfg->thumbs_per_row);
896          $next_start = $begin_with + ($this->cfg->rows_per_page * $this->cfg->thumbs_per_row);
897
898          if($begin_with != 0) 
899             $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");"); 
900          if($end_with < $count)
901             $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");"); 
902
903          $photo_per_page  = $this->cfg->rows_per_page * $this->cfg->thumbs_per_row;
904          $last_page = ceil($count / $photo_per_page);
905
906          /* get the current selected page */
907          if($begin_with == 0) {
908             $current_page = 1;
909          } else {
910             $current_page = 0;
911             for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
912                $current_page++;
913             }
914          } 
915
916          $dotdot_made = 0;
917
918          for($i = 1; $i <= $last_page; $i++) {
919
920             if($current_page == $i)
921                $style = "style=\"font-size: 125%; text-decoration: underline;\"";
922             elseif($current_page-1 == $i || $current_page+1 == $i)
923                $style = "style=\"font-size: 105%;\"";
924             elseif(($current_page-5 >= $i) && ($i != 1) ||
925                ($current_page+5 <= $i) && ($i != $last_page))
926                $style = "style=\"font-size: 75%;\"";
927             else
928                $style = "";
929
930             $select = "<a href=\"javascript:showPhotoIndex(". (($i*$photo_per_page)-$photo_per_page) .");\"";
931                if($style != "")
932                   $select.= $style;
933             $select.= ">". $i ."</a>&nbsp;";
934
935             // until 9 pages we show the selector from 1-9
936             if($last_page <= 9) {
937                $page_select.= $select;
938                continue;
939             } else {
940                if($i == 1 /* first page */ || 
941                   $i == $last_page /* last page */ ||
942                   $i == $current_page /* current page */ ||
943                   $i == ceil($last_page * 0.25) /* first quater */ ||
944                   $i == ceil($last_page * 0.5) /* half */ ||
945                   $i == ceil($last_page * 0.75) /* third quater */ ||
946                   (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
947                   (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 */ ||
948                   $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
949                   $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
950
951                   $page_select.= $select;
952                   $dotdot_made = 0;
953                   continue;
954
955                }
956             }
957
958             if(!$dotdot_made) {
959                $page_select.= ".........&nbsp;";
960                $dotdot_made = 1;
961             }
962          }
963
964          /* only show the page selector if we have more then one page */
965          if($last_page > 1)
966             $this->tmpl->assign('page_selector', $page_select);
967       }
968
969       
970       $current_tags = $this->getCurrentTags();
971       $extern_link = "index.php?mode=showpi";
972       $rss_link = "index.php?mode=rss";
973       if($current_tags != "") {
974          $extern_link.= "&tags=". $current_tags;
975          $rss_link.= "&tags=". $current_tags;
976       }
977       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
978          $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
979          $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
980       }
981
982       $export_link = "index.php?mode=export";
983       $slideshow_link = "index.php?mode=slideshow";
984
985       $this->tmpl->assign('extern_link', $extern_link);
986       $this->tmpl->assign('slideshow_link', $slideshow_link);
987       $this->tmpl->assign('export_link', $export_link);
988       $this->tmpl->assign('rss_link', $rss_link);
989       $this->tmpl->assign('count', $count);
990       $this->tmpl->assign('width', $this->cfg->thumb_width);
991       $this->tmpl->assign('images', $images);
992       $this->tmpl->assign('img_width', $img_width);
993       $this->tmpl->assign('img_height', $img_height);
994       $this->tmpl->assign('img_id', $img_id);
995       $this->tmpl->assign('img_name', $img_name);
996       $this->tmpl->assign('img_title', $img_title);
997       $this->tmpl->assign('rows', $rows);
998       $this->tmpl->assign('columns', $this->cfg->thumbs_per_row);
999
1000       $this->tmpl->show("photo_index.tpl");
1001
1002       if(isset($anchor))
1003          print "<script language=\"JavaScript\">self.location.hash = '#image". $anchor ."';</script>\n";
1004
1005    } // showPhotoIndex()
1006
1007    /**
1008     * show credit template
1009     */
1010    public function showCredits()
1011    {
1012       $this->tmpl->assign('version', $this->cfg->version);
1013       $this->tmpl->assign('product', $this->cfg->product);
1014       $this->tmpl->show("credits.tpl");
1015
1016    } // showCredits()
1017
1018    /**
1019     * create_thumbnails for the requested width
1020     *
1021     * this function creates image thumbnails of $orig_image
1022     * stored as $thumb_image. It will check if the image is
1023     * in a supported format, if necessary rotate the image
1024     * (based on EXIF orientation meta headers) and re-sizing.
1025     */
1026    public function create_thumbnail($orig_image, $thumb_image, $width)
1027    {  
1028       if(!file_exists($orig_image)) {
1029          return false;
1030       }
1031
1032       $details = getimagesize($orig_image);
1033       
1034       /* check if original photo is a support image type */
1035       if(!$this->checkifImageSupported($details['mime']))
1036          return false;
1037
1038       $meta = $this->get_meta_informations($orig_image);
1039
1040       $rotate = 0;
1041       $flip = false;
1042
1043       switch($meta['Orientation']) {
1044
1045          case 1: /* top, left */
1046             $rotate = 0; $flip = false; break;
1047          case 2: /* top, right */
1048             $rotate = 0; $flip = true; break;
1049          case 3: /* bottom, left */
1050             $rotate = 180; $flip = false; break;
1051          case 4: /* bottom, right */
1052             $rotate = 180; $flip = true; break;
1053          case 5: /* left side, top */
1054             $rotate = 90; $flip = true; break;
1055          case 6: /* right side, top */
1056             $rotate = 90; $flip = false; break;
1057          case 7: /* left side, bottom */
1058             $rotate = 270; $flip = true; break;
1059          case 8: /* right side, bottom */
1060             $rotate = 270; $flip = false; break;
1061       }
1062
1063       $src_img = @imagecreatefromjpeg($orig_image);
1064
1065       if(!$src_img) {
1066          print "Can't load image from ". $orig_image ."\n";
1067          return false;
1068       }
1069
1070       /* grabs the height and width */
1071       $cur_width = imagesx($src_img);
1072       $cur_height = imagesy($src_img);
1073
1074       // If requested width is more then the actual image width,
1075       // do not generate a thumbnail, instead safe the original
1076       // as thumbnail but with lower quality
1077
1078       if($width >= $cur_width) {
1079          $result = imagejpeg($src_img, $thumb_image, 75);
1080          imagedestroy($src_img);
1081          return true;
1082       }
1083
1084       // If the image will be rotate because EXIF orientation said so
1085       // 'virtually rotate' the image for further calculations
1086       if($rotate == 90 || $rotate == 270) {
1087          $tmp = $cur_width;
1088          $cur_width = $cur_height;
1089          $cur_height = $tmp;
1090       }
1091
1092       /* calculates aspect ratio */
1093       $aspect_ratio = $cur_height / $cur_width;
1094
1095       /* sets new size */
1096       if($aspect_ratio < 1) {
1097          $new_w = $width;
1098          $new_h = abs($new_w * $aspect_ratio);
1099       } else {
1100          /* 'virtually' rotate the image and calculate it's ratio */
1101          $tmp_w = $cur_height;
1102          $tmp_h = $cur_width;
1103          /* now get the ratio from the 'rotated' image */
1104          $tmp_ratio = $tmp_h/$tmp_w;
1105          /* now calculate the new dimensions */
1106          $tmp_w = $width;
1107          $tmp_h = abs($tmp_w * $tmp_ratio);
1108
1109          // now that we know, how high they photo should be, if it
1110          // gets rotated, use this high to scale the image
1111          $new_h = $tmp_h;
1112          $new_w = abs($new_h / $aspect_ratio);
1113
1114          // If the image will be rotate because EXIF orientation said so
1115          // now 'virtually rotate' back the image for the image manipulation
1116          if($rotate == 90 || $rotate == 270) {
1117             $tmp = $new_w;
1118             $new_w = $new_h;
1119             $new_h = $tmp;
1120          }
1121       }
1122
1123       /* creates new image of that size */
1124       $dst_img = imagecreatetruecolor($new_w, $new_h);
1125
1126       imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1127
1128       /* copies resized portion of original image into new image */
1129       imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1130
1131       /* needs the image to be flipped horizontal? */
1132       if($flip) {
1133          print "(FLIP)";
1134          $image = $dst_img;
1135          for($x = 0; $x < $new_w; $x++) {
1136             imagecopy($dst_img, $image, $x, 0, $w - $x - 1, 0, 1, $h);
1137          }
1138       }
1139
1140       if($rotate) {
1141          $this->_debug("(ROTATE)");
1142          $dst_img = $this->rotateImage($dst_img, $rotate);
1143       }
1144
1145       /* write down new generated file */
1146       $result = imagejpeg($dst_img, $thumb_image, 75);
1147
1148       /* free your mind */
1149       imagedestroy($dst_img);
1150       imagedestroy($src_img);
1151
1152       if($result === false) {
1153          print "Can't write thumbnail ". $thumb_image ."\n";
1154          return false;
1155       }
1156
1157       return true;
1158
1159    } // create_thumbnail()
1160
1161    /**
1162     * return all exif meta data from the file
1163     */
1164    public function get_meta_informations($file)
1165    {
1166       return exif_read_data($file);
1167
1168    } // get_meta_informations()
1169
1170    /**
1171     * create phpfspot own sqlite database
1172     *
1173     * this function creates phpfspots own sqlite database
1174     * if it does not exist yet. this own is used to store
1175     * some necessary informations (md5 sum's, ...).
1176     */
1177    public function check_config_table()
1178    {
1179       // if the config table doesn't exist yet, create it
1180       if(!$this->cfg_db->db_check_table_exists("images")) {
1181          $this->cfg_db->db_exec("
1182             CREATE TABLE images (
1183                img_idx int primary key,
1184                img_md5 varchar(32)
1185             )
1186             ");
1187       }
1188
1189    } // check_config_table
1190
1191    /**
1192     * Generates a thumbnail from photo idx
1193     *
1194     * This function will generate JPEG thumbnails from provided F-Spot photo
1195     * indizes.
1196     *
1197     * 1. Check if all thumbnail generations (width) are already in place and
1198     *    readable
1199     * 2. Check if the md5sum of the original file has changed
1200     * 3. Generate the thumbnails if needed
1201     */
1202    public function gen_thumb($idx = 0, $force = 0)
1203    {
1204       $error = 0;
1205
1206       $resolutions = Array(
1207          $this->cfg->thumb_width,
1208          $this->cfg->photo_width,
1209          $this->cfg->mini_width,
1210       );
1211
1212       /* get details from F-Spot's database */
1213       $details = $this->get_photo_details($idx);
1214
1215       /* calculate file MD5 sum */
1216       $full_path = $this->translate_path($details['directory_path'])  ."/". $details['name'];
1217
1218       if(!file_exists($full_path)) {
1219          $this->_error("File ". $full_path ." does not exist\n");
1220          return;
1221       }
1222
1223       if(!is_readable($full_path)) {
1224          $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
1225          return;
1226       }
1227
1228       $file_md5 = md5_file($full_path);
1229
1230       $this->_debug("Image [". $idx ."] ". $this->shrink_text($details['name'], 20) ." Thumbnails:");
1231
1232       foreach($resolutions as $resolution) {
1233
1234          $thumb_sub_path = substr($file_md5, 0, 2);
1235          $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
1236
1237          if(!file_exists(dirname($thumb_path))) {
1238             mkdir(dirname($thumb_path), 0755);
1239          }
1240
1241          /* if the thumbnail file doesn't exist, create it */
1242          if(!file_exists($thumb_path)) {
1243
1244             $this->_debug(" ". $resolution ."px");
1245             if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1246                $error = 1;
1247          }
1248          /* if the file hasn't changed there is no need to regen the thumb */
1249          elseif($file_md5 != $this->getMD5($idx) || $force) {
1250
1251             $this->_debug(" ". $resolution ."px");
1252             if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1253                $error = 1;
1254
1255          }
1256       }
1257
1258       /* set the new/changed MD5 sum for the current photo */
1259       if(!$error) {
1260          $this->setMD5($idx, $file_md5);
1261       }
1262
1263       $this->_debug("\n");
1264
1265    } // gen_thumb()
1266
1267    /**
1268     * returns stored md5 sum for a specific photo
1269     *
1270     * this function queries the phpfspot database for a
1271     * stored MD5 checksum of the specified photo
1272     */
1273    public function getMD5($idx)
1274    {
1275       $result = $this->cfg_db->db_query("
1276          SELECT img_md5 
1277          FROM images
1278          WHERE img_idx='". $idx ."'
1279       ");
1280
1281       if(!$result)
1282          return 0;
1283
1284       $img = $this->cfg_db->db_fetch_object($result);
1285       return $img['img_md5'];
1286       
1287    } // getMD5()
1288
1289    /**
1290     * set MD5 sum for the specific photo
1291     */
1292    private function setMD5($idx, $md5)
1293    {
1294       $result = $this->cfg_db->db_exec("
1295          REPLACE INTO images (img_idx, img_md5)
1296          VALUES ('". $idx ."', '". $md5 ."')
1297       ");
1298
1299    } // setMD5()
1300
1301    /**
1302     * store current tag condition
1303     *
1304     * this function stores the current tag condition
1305     * (AND or OR) in the users session variables
1306     */
1307    public function setTagCondition($mode)
1308    {
1309       $_SESSION['tag_condition'] = $mode;
1310
1311    } // setTagCondition()
1312
1313    /** 
1314     * invoke tag & date search 
1315     *
1316     * this function will return all matching tags and store
1317     * them in the session variable selected_tags. furthermore
1318     * it also handles the date search.
1319     * getPhotoSelection() will then only return the matching
1320     * photos.
1321     */
1322    public function startSearch($searchfor, $sort_order, $from = 0, $to = 0)
1323    {
1324       $_SESSION['searchfor'] = $searchfor;
1325       $_SESSION['sort_order'] = $sort_order;
1326       if($from != 0)
1327          $_SESSION['from_date'] = strtotime($from);
1328       else
1329          unset($_SESSION['from_date']);
1330       if($to != 0)
1331          $_SESSION['to_date'] = strtotime($to);
1332       else
1333          unset($_SESSION['to_date']);
1334
1335       if($searchfor != "") {
1336          /* new search, reset the current selected tags */
1337          $_SESSION['selected_tags'] = Array();
1338          foreach($this->avail_tags as $tag) {
1339             if(preg_match('/'. $searchfor .'/i', $this->tags[$tag]))
1340                array_push($_SESSION['selected_tags'], $tag);
1341          }
1342       }
1343
1344    } // startSearch()
1345
1346    /**
1347     * rotate image
1348     *
1349     * this function rotates the image according the
1350     * specified angel.
1351     */
1352    private function rotateImage($img, $degrees)
1353    {
1354       if(function_exists("imagerotate")) {
1355          $img = imagerotate($img, $degrees, 0);
1356       } else {
1357          function imagerotate($src_img, $angle)
1358          {
1359             $src_x = imagesx($src_img);
1360             $src_y = imagesy($src_img);
1361             if ($angle == 180) {
1362                $dest_x = $src_x;
1363                $dest_y = $src_y;
1364             }
1365             elseif ($src_x <= $src_y) {
1366                $dest_x = $src_y;
1367                $dest_y = $src_x;
1368             }
1369             elseif ($src_x >= $src_y) {
1370                $dest_x = $src_y;
1371                $dest_y = $src_x;
1372             }
1373                
1374             $rotate=imagecreatetruecolor($dest_x,$dest_y);
1375             imagealphablending($rotate, false);
1376                
1377             switch ($angle) {
1378             
1379                case 90:
1380                   for ($y = 0; $y < ($src_y); $y++) {
1381                      for ($x = 0; $x < ($src_x); $x++) {
1382                         $color = imagecolorat($src_img, $x, $y);
1383                         imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
1384                      }
1385                   }
1386                   break;
1387
1388                case 270:
1389                   for ($y = 0; $y < ($src_y); $y++) {
1390                      for ($x = 0; $x < ($src_x); $x++) {
1391                         $color = imagecolorat($src_img, $x, $y);
1392                         imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
1393                      }
1394                   }
1395                   break;
1396
1397                case 180:
1398                   for ($y = 0; $y < ($src_y); $y++) {
1399                      for ($x = 0; $x < ($src_x); $x++) {
1400                         $color = imagecolorat($src_img, $x, $y);
1401                         imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
1402                      }
1403                   }
1404                   break;
1405
1406                default:
1407                   $rotate = $src_img;
1408                   break;
1409             };
1410
1411             return $rotate;
1412
1413          }
1414
1415          $img = imagerotate($img, $degrees);
1416
1417       }
1418
1419       return $img;
1420
1421    } // rotateImage()
1422
1423    /**
1424     * return all assigned tags for the specified photo
1425     */
1426    private function get_photo_tags($idx)
1427    {
1428       $result = $this->db->db_query("
1429          SELECT t.id, t.name
1430          FROM tags t
1431          INNER JOIN photo_tags pt
1432             ON t.id=pt.tag_id
1433          WHERE pt.photo_id='". $idx ."'
1434       ");
1435
1436       $tags = Array();
1437
1438       while($row = $this->db->db_fetch_object($result))
1439          $tags[$row['id']] = $row['name'];
1440
1441       return $tags;
1442
1443    } // get_photo_tags()
1444
1445    /**
1446     * create on-the-fly images with text within
1447     */
1448    public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
1449    {
1450       if (strlen($color) != 6) 
1451          $color = 000000;
1452
1453       $int = hexdec($color);
1454       $h = imagefontheight($font);
1455       $fw = imagefontwidth($font);
1456       $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
1457       $lines = count($txt);
1458       $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
1459       $bg = imagecolorallocate($im, 255, 255, 255);
1460       $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
1461       $y = 0;
1462
1463       foreach ($txt as $text) {
1464          $x = (($w - ($fw * strlen($text))) / 2);
1465          imagestring($im, $font, $x, $y, $text, $color);
1466          $y += ($h + $space);
1467       }
1468
1469       Header("Content-type: image/png");
1470       ImagePng($im);
1471
1472    } // showTextImage()
1473
1474    /**
1475     * check if all requirements are met
1476     */
1477    private function checkRequirements()
1478    {
1479       if(!function_exists("imagecreatefromjpeg")) {
1480          print "PHP GD library extension is missing<br />\n";
1481          $missing = true;
1482       }
1483
1484       if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
1485          print "PHP SQLite3 library extension is missing<br />\n";
1486          $missing = true;
1487       }
1488
1489       /* Check for HTML_AJAX PEAR package, lent from Horde project */
1490       ini_set('track_errors', 1);
1491       @include_once 'HTML/AJAX/Server.php';
1492       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1493          print "PEAR HTML_AJAX package is missing<br />\n";
1494          $missing = true;
1495       }
1496       @include_once 'Calendar/Calendar.php';
1497       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1498          print "PEAR Calendar package is missing<br />\n";
1499          $missing = true;
1500       }
1501       ini_restore('track_errors');
1502
1503       if(isset($missing))
1504          return false;
1505
1506       return true;
1507
1508    } // checkRequirements()
1509
1510    private function _debug($text)
1511    {
1512       if($this->fromcmd) {
1513          print $text;
1514       }
1515
1516    } // _debug()
1517
1518    /**
1519     * check if specified MIME type is supported
1520     */
1521    public function checkifImageSupported($mime)
1522    {
1523       if(in_array($mime, Array("image/jpeg")))
1524          return true;
1525
1526       return false;
1527
1528    } // checkifImageSupported()
1529
1530    public function _error($text)
1531    {
1532       switch($this->cfg->logging) {
1533          case 'display':
1534             print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
1535             print $text;
1536             break;
1537          case 'errorlog':  
1538             error_log($text);
1539             break;
1540          case 'logfile':
1541             error_log($text, 3, $his->cfg->log_file);
1542             break;
1543       }
1544
1545    } // _error()
1546
1547    /**
1548     * output calendard input fields
1549     */
1550    private function get_calendar($mode)
1551    {
1552       $year = $_SESSION[$mode .'_date'] ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
1553       $month = $_SESSION[$mode .'_date'] ? date("m", $_SESSION[$mode .'_date']) : date("m");
1554       $day = $_SESSION[$mode .'_date'] ? date("d", $_SESSION[$mode .'_date']) : date("d");
1555
1556       $output = "<input type=\"text\" size=\"3\" id=\"". $mode ."year\" value=\"". $year ."\"";
1557       if(!isset($_SESSION[$mode .'_date'])) $output.= " disabled=\"disabled\"";
1558       $output.= " />\n";
1559       $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."month\" value=\"". $month ."\"";
1560       if(!isset($_SESSION[$mode .'_date'])) $output.= " disabled=\"disabled\"";
1561       $output.= " />\n";
1562       $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."day\" value=\"". $day ."\"";
1563       if(!isset($_SESSION[$mode .'_date'])) $output.= " disabled=\"disabled\"";
1564       $output.= " />\n";
1565       return $output;
1566
1567    } // get_calendar()
1568
1569    /**
1570     * output calendar matrix
1571     */
1572    public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
1573    {
1574       if (!isset($year)) $year = date('Y');
1575       if (!isset($month)) $month = date('m');
1576       if (!isset($day)) $day = date('d');
1577       $rows = 1;
1578       $cols = 1;
1579       $matrix = Array();
1580
1581       require_once CALENDAR_ROOT.'Month/Weekdays.php';
1582       require_once CALENDAR_ROOT.'Day.php';
1583
1584       // Build the month
1585       $month = new Calendar_Month_Weekdays($year,$month);
1586
1587       // Create links
1588       $prevStamp = $month->prevMonth(true);
1589       $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
1590       $nextStamp = $month->nextMonth(true);
1591       $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
1592
1593       $selectedDays = array (
1594          new Calendar_Day($year,$month,$day),
1595          new Calendar_Day($year,12,25),
1596       );
1597
1598       // Build the days in the month
1599       $month->build($selectedDays);
1600
1601       $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
1602       $this->tmpl->assign('prev_month', $prev);
1603       $this->tmpl->assign('next_month', $next);
1604
1605       while ( $day = $month->fetch() ) {
1606    
1607          if(!isset($matrix[$rows]))
1608             $matrix[$rows] = Array();
1609
1610          $string = "";
1611
1612          $dayStamp = $day->thisDay(true);
1613          $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
1614
1615          // isFirst() to find start of week
1616          if ( $day->isFirst() )
1617             $string.= "<tr>\n";
1618
1619          if ( $day->isSelected() ) {
1620             $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
1621          } else if ( $day->isEmpty() ) {
1622             $string.= "<td>&nbsp;</td>\n";
1623          } else {
1624             $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
1625          }
1626
1627          // isLast() to find end of week
1628          if ( $day->isLast() )
1629             $string.= "</tr>\n";
1630
1631          $matrix[$rows][$cols] = $string;
1632
1633          $cols++;
1634
1635          if($cols > 7) {
1636             $cols = 1;
1637             $rows++;
1638          }
1639       }
1640
1641       $this->tmpl->assign('matrix', $matrix);
1642       $this->tmpl->assign('rows', $rows);
1643       $this->tmpl->show("calendar.tpl");
1644
1645    } // get_calendar_matrix()
1646
1647    /**
1648     * output export page
1649     */
1650    public function getExport($mode)
1651    {
1652       $pictures = $this->getPhotoSelection();
1653       $current_tags = $this->getCurrentTags();  
1654
1655       foreach($pictures as $picture) {
1656
1657          $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
1658          if($current_tags != "") {
1659             $orig_url.= "&tags=". $current_tags;
1660          } 
1661          if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1662             $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
1663          }
1664
1665          $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
1666
1667          switch($mode) {
1668
1669             case 'HTML':
1670                // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
1671                print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
1672                break;
1673                
1674             case 'MoinMoin':
1675                // "[%pictureurl% %thumbnailurl%]"
1676                print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
1677                break;
1678
1679             case 'MoinMoinList':
1680                // " * [%pictureurl% %thumbnailurl%]"
1681                print "&nbsp;" . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
1682                break;
1683          }
1684
1685       }
1686
1687    } // getExport()
1688
1689    /**
1690     * output RSS feed
1691     */
1692    public function getRSSFeed()
1693    {
1694       Header("Content-type: text/xml; charset=utf-8");
1695       print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1696 ?>
1697 <rss version="2.0"
1698    xmlns:media="http://search.yahoo.com/mrss/"
1699    xmlns:dc="http://purl.org/dc/elements/1.1/"
1700  >
1701  <channel>
1702   <title>phpfspot</title>
1703   <description>phpfspot RSS feed</description>
1704   <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
1705   <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
1706   <generator>phpfspot</generator>
1707 <?php
1708
1709       $pictures = $this->getPhotoSelection();
1710       $current_tags = $this->getCurrentTags();  
1711
1712       foreach($pictures as $picture) {
1713
1714          $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
1715          if($current_tags != "") {
1716             $orig_url.= "&tags=". $current_tags;
1717          } 
1718          if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1719             $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
1720          }
1721
1722          $details = $this->get_photo_details($picture);
1723
1724          $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
1725          $thumb_html = htmlspecialchars("
1726 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
1727 <br>
1728 ". $details['description']);
1729
1730          $orig_path = $this->translate_path($details['directory_path']) ."/". $details['name'];
1731          $meta = $this->get_meta_informations($orig_path);
1732          $meta_date = isset($meta['FileDateTime']) ? $meta['FileDateTime'] : filemtime($orig_path);
1733
1734 ?>
1735   <item>
1736    <title><?php print htmlspecialchars($details['name']); ?></title>
1737    <link><?php print htmlspecialchars($orig_url); ?></link>
1738    <guid><?php print htmlspecialchars($orig_url); ?></guid>
1739    <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $meta_date); ?></dc:date.Taken>
1740    <description>
1741     <?php print $thumb_html; ?> 
1742    </description>
1743    <pubDate><?php print strftime("%a, %d %b %Y %T %z", $meta_date); ?></pubDate>
1744   </item>
1745 <?php
1746
1747       }
1748 ?>
1749  </channel>
1750 </rss>
1751 <?php
1752
1753
1754    } // getExport()
1755
1756  
1757    /**
1758     * return all selected tags as one string
1759     */
1760    private function getCurrentTags()
1761    {
1762       $current_tags = "";
1763       if($_SESSION['selected_tags'] != "") {
1764          foreach($_SESSION['selected_tags'] as $tag)
1765             $current_tags.= $tag .",";
1766          $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
1767       }
1768       return $current_tags;
1769
1770    } // getCurrentTags()
1771
1772    /**
1773     * return the current photo
1774     */
1775    public function getCurrentPhoto()
1776    {
1777       if(isset($_SESSION['current_photo'])) {
1778          print $_SESSION['current_photo'];
1779       }
1780    } // getCurrentPhoto()
1781
1782    /**
1783     * tells the client browser what to do
1784     *
1785     * this function is getting called via AJAX by the
1786     * client browsers. it will tell them what they have
1787     * to do next. This is necessary for directly jumping
1788     * into photo index or single photo view when the are
1789     * requested with specific URLs
1790     */
1791    public function whatToDo()
1792    {
1793       if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
1794          return "show_photo";
1795       }
1796       elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1797          return "showpi_tags";
1798       }
1799       elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
1800          return "showpi";
1801       }
1802
1803       return "nothing special";
1804
1805    } // whatToDo()
1806
1807    /**
1808     * return the current process-user
1809     */
1810    private function getuid()
1811    {
1812       if($uid = posix_getuid()) {
1813          if($user = posix_getpwuid($uid)) {
1814             return $user['name'];
1815          }
1816       }
1817    
1818       return 'n/a';
1819    
1820    } // getuid()
1821
1822    /**
1823     * returns a select-dropdown box to select photo index sort parameters
1824     */
1825    private function get_sort_field()
1826    {
1827       $output = "<select name=\"sort_order\">";
1828       foreach(array('date_asc', 'date_desc', 'name_asc', 'name_desc') as $sort_order) {
1829          $output.= "<option value=\"". $sort_order ."\"";
1830          if($sort_order == $_SESSION['sort_order']) {
1831             $output.= " selected=\"selected\"";
1832          }
1833          $output.= ">". $sort_order ."</option>";
1834       }
1835       $output.= "</select>";
1836       return $output;
1837
1838    } // get_sort_field()
1839
1840    /**
1841     * returns the currently selected sort order
1842     */ 
1843    private function get_sort_order()
1844    {
1845       switch($_SESSION['sort_order']) {
1846          case 'date_asc':
1847             return " ORDER BY p.time ASC";
1848             break;
1849          case 'date_desc':
1850             return " ORDER BY p.time DESC";
1851             break;
1852          case 'name_asc':
1853             return " ORDER BY p.name ASC";
1854             break;
1855          case 'name_desc':
1856             return " ORDER BY p.name DESC";
1857             break;
1858       }
1859
1860    } // get_sort_order()
1861
1862    /***
1863      * return the next to be shown slide show image
1864      *
1865      * this function returns the URL of the next image
1866      * in the slideshow sequence.
1867      */
1868    public function getNextSlideShowImage()
1869    {
1870       $all_photos = $this->getPhotoSelection();
1871
1872       if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1) 
1873          $_SESSION['slideshow_img'] = 0;
1874       else
1875          $_SESSION['slideshow_img']++;
1876
1877       return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
1878
1879    } // getNextSlideShowImage()
1880
1881    /***
1882      * return the previous to be shown slide show image
1883      *
1884      * this function returns the URL of the previous image
1885      * in the slideshow sequence.
1886      */
1887    public function getPrevSlideShowImage()
1888    {
1889       $all_photos = $this->getPhotoSelection();
1890
1891       if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
1892          $_SESSION['slideshow_img'] = 0;
1893       else
1894          $_SESSION['slideshow_img']--;
1895
1896       return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
1897
1898    } // getPrevSlideShowImage()
1899
1900    public function resetSlideShow()
1901    {
1902       if(isset($_SESSION['slideshow_img']))
1903          unset($_SESSION['slideshow_img']);
1904    } // resetSlideShow()
1905    
1906    /***
1907      * get random photo
1908      *
1909      * this function will get all photos from the fspot
1910      * database and randomly return ONE entry
1911      *
1912      * saddly there is yet no sqlite3 function which returns
1913      * the bulk result in array, so we have to fill up our
1914      * own here.
1915      */ 
1916    public function get_random_photo()
1917    {
1918       $all = Array();
1919
1920       $result = $this->db->db_query("
1921          SELECT id
1922          FROM photos
1923       ");
1924       
1925       while($row = $this->db->db_fetch_object($result)) {
1926          array_push($all, $row['id']);
1927       }
1928
1929       return $all[array_rand($all)];
1930
1931    } // get_random_photo()
1932
1933    /**
1934     * validates provided date
1935     *
1936     * this function validates if the provided date
1937     * contains a valid date and will return true 
1938     * if it is.
1939     */
1940    public function isValidDate($date_str)
1941    {
1942       $timestamp = strtotime($date_str);
1943    
1944       if(is_numeric($timestamp))
1945          return true;
1946       
1947       return false;
1948
1949    } // isValidDate()
1950
1951    /**
1952     * timestamp to string conversion
1953     */
1954    private function ts2str($timestamp)
1955    {
1956       return strftime("%Y-%m-%d", $timestamp);
1957    } // ts2str()
1958
1959    private function extractTags($tags_str)
1960    {
1961       $not_validated = split(',', $_GET['tags']);
1962       $validated = array();
1963
1964       foreach($not_validated as $tag) {
1965          if(is_numeric($tag))
1966             array_push($validated, $tag);
1967       }
1968    
1969       return $validated;
1970    
1971    } // extractTags()
1972
1973    /**
1974     * returns the full path to a thumbnail
1975     */
1976    public function get_thumb_path($width, $photo)
1977    {
1978       $md5 = $this->getMD5($photo);
1979       $sub_path = substr($md5, 0, 2);
1980       return $this->cfg->thumb_path
1981          . "/"
1982          . $sub_path
1983          . "/"
1984          . $width
1985          . "_"
1986          . $md5;
1987
1988    } // get_thumb_path()
1989
1990    /**
1991     * returns server's virtual host name
1992     */
1993    private function get_server_name()
1994    {
1995       return $_SERVER['SERVER_NAME'];
1996    } // get_server_name()
1997
1998    /**
1999     * returns type of webprotocol which is
2000     * currently used
2001     */
2002    private function get_web_protocol()
2003    {
2004       if(!isset($_SERVER['HTTPS']))
2005          return "http";
2006       else
2007          return "https";
2008    } // get_web_protocol()
2009
2010    /**
2011     * return url to this phpfspot installation
2012     */
2013    private function get_phpfspot_url()
2014    {
2015       return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
2016    } // get_phpfspot_url()
2017    
2018    /**
2019     * check file exists and is readable
2020     *
2021     * returns true, if everything is ok, otherwise false
2022     * if $silent is not set, this function will output and
2023     * error message
2024     */
2025    private function check_readable($file, $silent = null)
2026    {
2027       if(!file_exists($file)) {
2028          if(!isset($silent))
2029             print "File \"". $file ."\" does not exist.\n";
2030          return false;
2031       }
2032
2033       if(!is_readable($file)) {
2034          if(!isset($silent))
2035             print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
2036          return false;
2037       }
2038
2039       return true;
2040
2041    } // check_readable()
2042
2043 }
2044
2045 ?>