Merge branch 'master' into nefformat
[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    private $runtime_error = false;
37    private $dbver;
38
39    /**
40     * class constructor
41     *
42     * this function will be called on class construct
43     * and will check requirements, loads configuration,
44     * open databases and start the user session
45     */
46    public function __construct()
47    {
48       $this->cfg = new PHPFSPOT_CFG;
49
50       /* verify config settings */
51       if($this->check_config_options()) {
52          exit(1);
53       }
54
55       /* set application name and version information */
56       $this->cfg->product = "phpfspot";
57       $this->cfg->version = "1.4";
58
59       $this->sort_orders= array(
60          'date_asc' => 'Date &uarr;',
61          'date_desc' => 'Date &darr;',
62          'name_asc' => 'Name &uarr;',
63          'name_desc' => 'Name &darr;',
64          'tags_asc' => 'Tags &uarr;',
65          'tags_desc' => 'Tags &darr;',
66       );
67
68       /* Check necessary requirements */
69       if(!$this->check_requirements()) {
70          exit(1);
71       }
72
73       /******* Opening F-Spot's sqlite database *********/
74
75       /* Check if database file is writeable */
76       if(!is_writeable($this->cfg->fspot_db)) {
77          print $this->cfg->fspot_db ." is not writeable for user ". $this->getuid() ."\n";
78          exit(1);
79       }
80
81       /* open the database */
82       $this->db  = new PHPFSPOT_DB($this, $this->cfg->fspot_db);
83
84       /* change sqlite temp directory, if requested */
85       if(isset($this->cfg->sqlite_temp_dir)) {
86          $this->db->db_exec("
87             PRAGMA
88                temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
89          ");
90       }
91
92       $this->dbver = $this->getFspotDBVersion();
93
94       if(!is_writeable($this->cfg->base_path ."/templates_c")) {
95          print $this->cfg->base_path ."/templates_c: directory is not writeable for user ". $this->getuid() ."\n";
96          exit(1);
97       }
98
99       if(!is_writeable($this->cfg->thumb_path)) {
100          print $this->cfg->thumb_path .": directory is not writeable for user ". $this->getuid() ."\n";
101          exit(1);
102       }
103
104       /******* Opening phpfspot's sqlite database *********/
105
106       /* Check if directory where the database file is stored is writeable  */
107       if(!is_writeable(dirname($this->cfg->phpfspot_db))) {
108          print dirname($this->cfg->phpfspot_db) .": directory is not writeable for user ". $this->getuid() ."\n";
109          exit(1);
110       }
111
112       /* Check if database file is writeable */
113       if(!is_writeable($this->cfg->phpfspot_db)) {
114          print $this->cfg->phpfspot_db ." is not writeable for user ". $this->getuid() ."\n";
115          exit(1);
116       }
117
118       /* open the database */
119       $this->cfg_db = new PHPFSPOT_DB($this, $this->cfg->phpfspot_db);
120
121       /* change sqlite temp directory, if requested */
122       if(isset($this->cfg->sqlite_temp_dir)) {
123          $this->cfg_db->db_exec("
124             PRAGMA
125                temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
126          ");
127       }
128
129       /* Check if some tables need to be created */
130       $this->check_config_table();
131
132       /* overload Smarty class with our own template handler */
133       require_once "phpfspot_tmpl.php";
134       $this->tmpl = new PHPFSPOT_TMPL($this);
135
136       /* check if all necessary indices exist */
137       $this->checkDbIndices();
138
139       /* if session is not yet started, do it now */
140       if(session_id() == "")
141          session_start();
142
143       if(!isset($_SESSION['tag_condition']))
144          $_SESSION['tag_condition'] = 'or';
145
146       if(!isset($_SESSION['sort_order']))
147          $_SESSION['sort_order'] = 'date_desc';
148
149       if(!isset($_SESSION['searchfor_tag']))
150          $_SESSION['searchfor_tag'] = '';
151
152       // if begin_with is still set but thumbs_per_page is now 0, unset it
153       if(isset($_SESSION['begin_with']) && $this->cfg->thumbs_per_page == 0)
154          unset($_SESSION['begin_with']);
155
156    } // __construct()
157
158    public function __destruct()
159    {
160
161    } // __destruct()
162
163    /**
164     * show - generate html output
165     *
166     * this function can be called after the constructor has
167     * prepared everyhing. it will load the index.tpl smarty
168     * template. if necessary it will registere pre-selects
169     * (photo index, photo, tag search, date search) into
170     * users session.
171     */
172    public function show()
173    {
174       $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
175       $this->tmpl->assign('page_title', $this->cfg->page_title);
176       $this->tmpl->assign('current_condition', $_SESSION['tag_condition']);
177       $this->tmpl->assign('template_path', 'themes/'. $this->cfg->theme_name);
178
179       if(isset($_GET['mode'])) {
180
181          $_SESSION['start_action'] = $_GET['mode'];
182
183          switch($_GET['mode']) {
184             case 'showpi':
185                if(isset($_GET['tags'])) {
186                   $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
187                }
188                if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
189                   $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
190                }
191                if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
192                   $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
193                }
194                break;
195             case 'showp':
196                if(isset($_GET['tags'])) {
197                   $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
198                   $_SESSION['start_action'] = 'showp';
199                }
200                if(isset($_GET['id']) && is_numeric($_GET['id'])) {
201                   $_SESSION['current_photo'] = $_GET['id'];
202                   $_SESSION['start_action'] = 'showp';
203                }
204                if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
205                   $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
206                }
207                if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
208                   $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
209                }
210                break;
211             case 'export':
212                $this->tmpl->show("export.tpl");
213                return;
214                break;
215             case 'slideshow':
216                $this->tmpl->show("slideshow.tpl");
217                return;
218                break;
219             case 'rss':
220                if(isset($_GET['tags'])) {
221                   $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
222                }
223                if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
224                   $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
225                }
226                if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
227                   $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
228                }
229                $this->getRSSFeed();
230                return;
231                break;
232          }
233       }
234
235       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date']))
236          $this->tmpl->assign('date_search_enabled', true);
237
238       $this->tmpl->register_function("sort_select_list", array(&$this, "smarty_sort_select_list"), false);
239       $this->tmpl->assign('from_date', $this->get_calendar('from'));
240       $this->tmpl->assign('to_date', $this->get_calendar('to'));
241       $this->tmpl->assign('content_page', 'welcome.tpl');
242       $this->tmpl->show("index.tpl");
243
244    } // show()
245
246    /**
247     * get_tags - grab all tags of f-spot's database
248     *
249     * this function will get all available tags from
250     * the f-spot database and store them within two
251     * arrays within this class for later usage. in
252     * fact, if the user requests (hide_tags) it will
253     * opt-out some of them.
254     *
255     * this function is getting called once by show()
256     */
257    private function get_tags()
258    {
259       $this->avail_tags = Array();
260       $count = 0;
261    
262       if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
263          $query_str="
264             SELECT
265                DISTINCT t1.id as id, t1.name as name
266             FROM  
267                photo_tags pt1
268             INNER JOIN photo_tags
269                pt2 ON pt1.photo_id=pt2.photo_id
270             INNER JOIN tags t1
271                ON t1.id=pt1.tag_id
272             INNER JOIN tags t2
273                ON t2.id=pt2.tag_id
274             WHERE
275                t2.name IN  ('".implode("','",$this->cfg->show_tags)."')
276             ORDER BY
277                t1.sort_priority ASC";
278
279          $result = $this->db->db_query($query_str);
280       }
281       else
282       {
283          $result = $this->db->db_query("
284             SELECT id,name
285             FROM tags
286             ORDER BY sort_priority ASC
287          ");
288       }
289       
290       while($row = $this->db->db_fetch_object($result)) {
291
292          $tag_id = $row['id'];
293          $tag_name = $row['name'];
294
295          /* if the user has specified to ignore this tag in phpfspot's
296             configuration, ignore it here so it does not get added to
297             the tag list.
298          */
299          if(in_array($row['name'], $this->cfg->hide_tags))
300             continue;
301
302          /* if you include the following if-clause and the user has specified
303             to only show certain tags which are specified in phpfspot's
304             configuration, ignore all others so they will not be added to the
305             tag list.
306          if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags) &&
307             !in_array($row['name'], $this->cfg->show_tags))
308             continue;
309          */
310
311          $this->tags[$tag_id] = $tag_name; 
312          $this->avail_tags[$count] = $tag_id;
313          $count++;
314
315       }
316
317    } // get_tags()
318
319    /** 
320     * extract all photo details
321     * 
322     * retrieve all available details from f-spot's
323     * database and return them as object
324     */
325    public function get_photo_details($idx)
326    {
327       if($this->dbver < 9) {
328          $query_str = "
329             SELECT p.id, p.name, p.time, p.directory_path, p.description
330             FROM photos p
331          ";
332       }
333       else {
334          $query_str = "
335             SELECT p.id, p.uri, p.time, p.description
336             FROM photos p
337          ";
338       }
339
340       /* if show_tags is set, only return details for photos which
341          are specified to be shown
342       */
343       if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
344          $query_str.= "
345             INNER JOIN photo_tags pt
346                ON p.id=pt.photo_id
347             INNER JOIN tags t
348                ON pt.tag_id=t.id
349             WHERE p.id='". $idx ."'
350             AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
351       }
352       else {
353          $query_str.= "
354             WHERE p.id='". $idx ."'
355          ";
356       }
357
358       if($result = $this->db->db_query($query_str)) {
359
360          $row = $this->db->db_fetch_object($result);
361
362          if($this->dbver < 9) {
363             $row['uri'] = "file://". $row['directory_path'] ."/". $row['name'];
364          }
365
366          return $row;
367
368       }
369    
370       return null;
371
372    } // get_photo_details
373
374    /**
375     * returns aligned photo names 
376     *
377     * this function returns aligned (length) names for
378     * an specific photo. If the length of the name exceeds
379     * $limit the name will be shrinked (...)
380     */
381    public function getPhotoName($idx, $limit = 0)
382    {
383       if($details = $this->get_photo_details($idx)) {
384          if($long_name = $this->parse_uri($details['uri'], 'filename')) {
385             $name = $this->shrink_text($long_name, $limit);
386             return $name;
387          }
388       }
389
390       return null;
391
392    } // getPhotoName()
393
394    /**
395     * shrink text according provided limit
396     *
397     * If the length of the name exceeds $limit the
398     * text will be shortend and some content in between
399     * will be replaced with "..." 
400     */
401    private function shrink_text($text, $limit)
402    {
403       if($limit != 0 && strlen($text) > $limit) {
404          $text = substr($text, 0, $limit-5) ."...". substr($text, -($limit-5));
405       }
406
407       return $text;
408
409    } // shrink_text();
410
411    /**
412     * translate f-spoth photo path
413     * 
414     * as the full-qualified path recorded in the f-spot database
415     * is usally not the same as on the webserver, this function
416     * will replace the path with that one specified in the cfg
417     */
418    public function translate_path($path, $width = 0)
419    {  
420       return str_replace($this->cfg->path_replace_from, $this->cfg->path_replace_to, $path);
421
422    } // translate_path
423
424    /**
425     * control HTML ouput for a single photo
426     *
427     * this function provides all the necessary information
428     * for the single photo template.
429     */
430    public function showPhoto($photo)
431    {
432       /* get all photos from the current photo selection */
433       $all_photos = $this->getPhotoSelection();
434       $count = count($all_photos);
435
436       for($i = 0; $i < $count; $i++) {
437          
438          // $get_next will be set, when the photo which has to
439          // be displayed has been found - this means that the
440          // next available is in fact the NEXT image (for the
441          // navigation icons) 
442          if(isset($get_next)) {
443             $next_img = $all_photos[$i];
444             break;
445          }
446
447          /* the next photo is our NEXT photo */
448          if($all_photos[$i] == $photo) {
449             $get_next = 1;
450          }
451          else {
452             $previous_img = $all_photos[$i];
453          }
454
455          if($photo == $all_photos[$i]) {
456                $current = $i;
457          }
458       }
459
460       $details = $this->get_photo_details($photo);
461
462       if(!$details) {
463          print "error";
464          return;
465       }
466
467       $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
468       $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
469
470       if(!file_exists($orig_path)) {
471          $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
472          return;
473       }
474
475       if(!is_readable($orig_path)) {
476          $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
477          return;
478       }
479
480       /* If the thumbnail doesn't exist yet, try to create it */
481       if(!file_exists($thumb_path)) {
482          $this->gen_thumb($photo, true);
483          $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
484       }
485
486       /* get EXIF information if JPEG */
487       if($details['mime'] == "image/jpeg") {
488          $meta = $this->get_meta_informations($orig_path);
489       }
490
491       /* If EXIF data are available, use them */
492       if(isset($meta['ExifImageWidth'])) {
493          $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
494       } else {
495          $info = getimagesize($orig_path);
496          $meta_res = $info[0] ."x". $info[1]; 
497       }
498
499       $meta_date = isset($meta['FileDateTime']) ? strftime("%a %x %X", $meta['FileDateTime']) : "n/a";
500       $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
501       $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
502
503       $extern_link = "index.php?mode=showp&id=". $photo;
504       $current_tags = $this->getCurrentTags();
505       if($current_tags != "") {
506          $extern_link.= "&tags=". $current_tags;
507       }
508       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
509          $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
510       }
511
512       $this->tmpl->assign('extern_link', $extern_link);
513
514       if(!file_exists($thumb_path)) {
515          $this->_error("Can't open file ". $thumb_path ."\n");
516          return;
517       }
518
519       $info = getimagesize($thumb_path);
520
521       $this->tmpl->assign('description', $details['description']);
522       $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
523
524       $this->tmpl->assign('width', $info[0]);
525       $this->tmpl->assign('height', $info[1]);
526       $this->tmpl->assign('ExifMadeOn', $meta_date);
527       $this->tmpl->assign('ExifMadeWith', $meta_make);
528       $this->tmpl->assign('ExifOrigResolution', $meta_res);
529       $this->tmpl->assign('ExifFileSize', $meta_size);
530  
531       $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&amp;width=". $this->cfg->photo_width);
532       $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
533       $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
534
535       $this->tmpl->assign('tags', $this->get_photo_tags($photo));
536       $this->tmpl->assign('current_page', $this->getCurrentPage($current, $count));
537       $this->tmpl->assign('current_img', $photo);
538
539       if($previous_img) {
540          $this->tmpl->assign('previous_url', "javascript:showImage(". $previous_img .");");
541          $this->tmpl->assign('prev_img', $previous_img);
542       }
543
544       if($next_img) {
545          $this->tmpl->assign('next_url', "javascript:showImage(". $next_img .");");
546          $this->tmpl->assign('next_img', $next_img);
547       }
548       $this->tmpl->assign('mini_width', $this->cfg->mini_width);
549       $this->tmpl->assign('photo_width', $this->cfg->photo_width);
550       $this->tmpl->assign('photo_number', $i);
551       $this->tmpl->assign('photo_count', count($all_photos));
552
553       $this->tmpl->show("single_photo.tpl");
554
555    } // showPhoto()
556
557    /**
558     * all available tags and tag cloud
559     *
560     * this function outputs all available tags (time ordered)
561     * and in addition output them as tag cloud (tags which have
562     * many photos will appears more then others)
563     */
564    public function getAvailableTags()
565    {
566       /* retrive tags from database */
567       $this->get_tags();
568
569       $output = "";
570
571       $result = $this->db->db_query("
572          SELECT tag_id as id, count(tag_id) as quantity
573          FROM photo_tags
574          INNER JOIN tags t
575             ON t.id = tag_id
576          GROUP BY tag_id
577          ORDER BY t.name ASC
578       ");
579
580       $tags = Array();
581
582       while($row = $this->db->db_fetch_object($result)) {
583          $tags[$row['id']] = $row['quantity'];
584       }
585
586       // change these font sizes if you will
587       $max_size = 125; // max font size in %
588       $min_size = 75; // min font size in %
589
590       // color
591       $max_sat = hexdec('cc');
592       $min_sat = hexdec('44');
593
594       // get the largest and smallest array values
595       $max_qty = max(array_values($tags));
596       $min_qty = min(array_values($tags));
597
598       // find the range of values
599       $spread = $max_qty - $min_qty;
600       if (0 == $spread) { // we don't want to divide by zero
601          $spread = 1;
602       }
603
604       // determine the font-size increment
605       // this is the increase per tag quantity (times used)
606       $step = ($max_size - $min_size)/($spread);
607       $step_sat = ($max_sat - $min_sat)/($spread);
608
609       // loop through our tag array
610       foreach ($tags as $key => $value) {
611
612          if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
613             continue;
614
615          // calculate CSS font-size
616          // find the $value in excess of $min_qty
617          // multiply by the font-size increment ($size)
618          // and add the $min_size set above
619          $size = $min_size + (($value - $min_qty) * $step);
620           // uncomment if you want sizes in whole %:
621          $size = ceil($size);
622
623          $color = $min_sat + ($value - $min_qty) * $step_sat;
624
625          $r = '44';
626          $g = dechex($color);
627          $b = '88';
628
629          if(isset($this->tags[$key])) {
630             $output.= "<a href=\"javascript:Tags('add', ". $key .");\" class=\"tag\" style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\">". $this->tags[$key] ."</a>, ";
631          }
632
633       }
634
635       $output = substr($output, 0, strlen($output)-2);
636       print $output;
637
638    } // getAvailableTags()
639
640    /**
641     * output all selected tags
642     *
643     * this function output all tags which have been selected
644     * by the user. the selected tags are stored in the 
645     * session-variable $_SESSION['selected_tags']
646     */
647    public function getSelectedTags()
648    {
649       /* retrive tags from database */
650       $this->get_tags();
651
652       $output = "";
653
654       foreach($this->avail_tags as $tag)
655       {
656          // return all selected tags
657          if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
658             $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
659          }
660       }
661
662       if($output != "") {
663          $output = substr($output, 0, strlen($output)-2);
664          return $output;
665       }
666       else {
667          return "no tags selected";
668       }
669
670    } // getSelectedTags()
671
672    /**
673     * add tag to users session variable
674     *
675     * this function will add the specified to users current
676     * tag selection. if a date search has been made before
677     * it will be now cleared
678     */
679    public function addTag($tag)
680    {
681       if(!isset($_SESSION['selected_tags']))
682          $_SESSION['selected_tags'] = Array();
683
684       if(isset($_SESSION['searchfor_tag']))
685          unset($_SESSION['searchfor_tag']);
686
687       if(!in_array($tag, $_SESSION['selected_tags']))
688          array_push($_SESSION['selected_tags'], $tag);
689
690
691       return "ok";
692    
693    } // addTag()
694
695    /**
696     * remove tag to users session variable
697     *
698     * this function removes the specified tag from
699     * users current tag selection
700     */
701    public function delTag($tag)
702    {
703       if(isset($_SESSION['searchfor_tag']))
704          unset($_SESSION['searchfor_tag']);
705
706       if(isset($_SESSION['selected_tags'])) {
707          $key = array_search($tag, $_SESSION['selected_tags']);
708          unset($_SESSION['selected_tags'][$key]);
709          sort($_SESSION['selected_tags']);
710       }
711
712       return "ok";
713
714    } // delTag()
715
716    /**
717     * reset tag selection
718     *
719     * if there is any tag selection, it will be
720     * deleted now
721     */
722    public function resetTags()
723    {
724       if(isset($_SESSION['selected_tags']))
725          unset($_SESSION['selected_tags']);
726
727    } // resetTags()
728
729    /**
730     * returns the value for the autocomplet tag-search
731     */
732    public function get_xml_tag_list()
733    {
734       if(!isset($_GET['search']) || !is_string($_GET['search']))
735          $_GET['search'] = '';
736       
737       $length = 15;
738       $i = 1;
739          
740       /* retrive tags from database */
741       $this->get_tags();
742
743       $matched_tags = Array();
744
745       header("Content-Type: text/xml");
746
747       $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
748       $string.= "<results>\n";
749
750       foreach($this->avail_tags as $tag)
751       {
752          if(!empty($_GET['search']) &&
753             preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) &&
754             count($matched_tags) < $length) {
755
756             $count = $this->get_num_photos($tag);
757
758             if($count == 1) {
759                $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n";
760             }
761             else {
762                $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n";
763
764             }
765             $i++;
766          }
767
768          /* if we have collected enough items, break out */
769          if(count($matched_tags) >= $length)
770             break;
771       }
772
773       $string.= "</results>\n";
774
775       return $string;
776
777    } // get_xml_tag_list()
778
779
780    /**
781     * reset single photo
782     *
783     * if a specific photo was requested (external link)
784     * unset the session variable now
785     */
786    public function resetPhotoView()
787    {
788       if(isset($_SESSION['current_photo']))
789          unset($_SESSION['current_photo']);
790
791    } // resetPhotoView();
792
793    /**
794     * reset tag search
795     *
796     * if any tag search has taken place, reset it now
797     */
798    public function resetTagSearch()
799    {
800       if(isset($_SESSION['searchfor_tag']))
801          unset($_SESSION['searchfor_tag']);
802
803    } // resetTagSearch()
804
805    /**
806     * reset name search
807     *
808     * if any name search has taken place, reset it now
809     */
810    public function resetNameSearch()
811    {
812       if(isset($_SESSION['searchfor_name']))
813          unset($_SESSION['searchfor_name']);
814
815    } // resetNameSearch()
816
817    /**
818     * reset date search
819     *
820     * if any date search has taken place, reset
821     * it now
822     */
823    public function resetDateSearch()
824    {
825       if(isset($_SESSION['from_date']))
826          unset($_SESSION['from_date']);
827       if(isset($_SESSION['to_date']))
828          unset($_SESSION['to_date']);
829
830    } // resetDateSearch();
831
832    /**
833     * return all photo according selection
834     *
835     * this function returns all photos based on
836     * the tag-selection, tag- or date-search.
837     * the tag-search also has to take care of AND
838     * and OR conjunctions
839     */
840    public function getPhotoSelection()
841    {  
842       $matched_photos = Array();
843       $additional_where_cond = "";
844
845       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
846          $from_date = $_SESSION['from_date'];
847          $to_date = $_SESSION['to_date'];
848          $additional_where_cond.= "
849                p.time>='". $from_date ."'
850             AND
851                p.time<='". $to_date ."'
852          ";
853       } 
854
855       if(isset($_SESSION['searchfor_name'])) {
856          if($this->dbver < 9) {
857             $additional_where_cond.= "
858                   (
859                         p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
860                      OR
861                         p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
862                   )
863             ";
864          }
865          else {
866             $additional_where_cond.= "
867                   (
868                         basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
869                      OR
870                         p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
871                   )
872             ";
873          }
874       }
875
876       if(isset($_SESSION['sort_order'])) {
877          $order_str = $this->get_sort_order();
878       }
879
880       /* return a search result */
881       if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
882          $query_str = "
883             SELECT DISTINCT pt1.photo_id
884                FROM photo_tags pt1
885             INNER JOIN photo_tags pt2
886                ON pt1.photo_id=pt2.photo_id
887             INNER JOIN tags t
888                ON pt1.tag_id=t.id
889             INNER JOIN photos p
890                ON pt1.photo_id=p.id
891             INNER JOIN tags t2
892                ON pt2.tag_id=t2.id
893             WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
894
895          if(isset($additional_where_cond) && !empty($additional_where_cond))
896             $query_str.= "AND ". $additional_where_cond ." ";
897
898          if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
899             $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
900          }
901          
902          if(isset($order_str))
903             $query_str.= $order_str;
904
905          $result = $this->db->db_query($query_str);
906          while($row = $this->db->db_fetch_object($result)) {
907             array_push($matched_photos, $row['photo_id']);
908          }
909          return $matched_photos;
910       }
911
912       /* return according the selected tags */
913       if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
914          $selected = "";
915          foreach($_SESSION['selected_tags'] as $tag)
916             $selected.= $tag .",";
917          $selected = substr($selected, 0, strlen($selected)-1);
918
919          /* photo has to match at least on of the selected tags */
920          if($_SESSION['tag_condition'] == 'or') {
921             $query_str = "
922                SELECT DISTINCT pt1.photo_id
923                   FROM photo_tags pt1
924                INNER JOIN photo_tags pt2
925                   ON pt1.photo_id=pt2.photo_id
926                INNER JOIN tags t
927                   ON pt2.tag_id=t.id
928                INNER JOIN photos p
929                   ON pt1.photo_id=p.id
930                WHERE pt1.tag_id IN (". $selected .")
931             ";
932             if(isset($additional_where_cond) && !empty($additional_where_cond)) 
933                $query_str.= "AND ". $additional_where_cond ." ";
934
935             if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
936                $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
937             }
938
939             if(isset($order_str))
940                $query_str.= $order_str;
941          }
942          /* photo has to match all selected tags */
943          elseif($_SESSION['tag_condition'] == 'and') {
944
945             if(count($_SESSION['selected_tags']) >= 32) {
946                print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
947                print "evaluate your tag selection. Please remove some tags from your selection.\n";
948                return Array();
949             } 
950
951             /* Join together a table looking like
952
953                pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
954
955                so the query can quickly return all images matching the
956                selected tags in an AND condition
957
958             */
959
960             $query_str = "
961                SELECT DISTINCT pt1.photo_id
962                   FROM photo_tags pt1
963             ";
964
965             if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
966                $query_str.= "
967                   INNER JOIN tags t
968                      ON pt1.tag_id=t.id
969                ";
970             }
971
972             for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
973                $query_str.= "
974                   INNER JOIN photo_tags pt". ($i+2) ."
975                      ON pt1.photo_id=pt". ($i+2) .".photo_id
976                ";
977             }
978             $query_str.= "
979                INNER JOIN photos p
980                   ON pt1.photo_id=p.id
981             ";
982             $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
983             for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
984                $query_str.= "
985                   AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
986                "; 
987             }
988             if(isset($additional_where_cond) && !empty($additional_where_cond)) 
989                $query_str.= "AND ". $additional_where_cond;
990
991             if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
992                $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
993             }
994
995             if(isset($order_str))
996                $query_str.= $order_str;
997
998          }
999
1000          $result = $this->db->db_query($query_str);
1001          while($row = $this->db->db_fetch_object($result)) {
1002             array_push($matched_photos, $row['photo_id']);
1003          }
1004          return $matched_photos;
1005       }
1006
1007       /* return all available photos */
1008       $query_str = "
1009          SELECT DISTINCT p.id
1010          FROM photos p
1011          LEFT JOIN photo_tags pt
1012             ON p.id=pt.photo_id
1013          LEFT JOIN tags t
1014             ON pt.tag_id=t.id
1015       ";
1016
1017       if(isset($additional_where_cond) && !empty($additional_where_cond)) 
1018          $query_str.= "WHERE ". $additional_where_cond ." ";
1019
1020       if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1021          if(isset($additional_where_cond) && !empty($additional_where_cond))
1022             $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1023          else
1024             $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1025       }
1026  
1027       if(isset($order_str))
1028          $query_str.= $order_str;
1029
1030       $result = $this->db->db_query($query_str);
1031       while($row = $this->db->db_fetch_object($result)) {
1032          array_push($matched_photos, $row['id']);
1033       }
1034       return $matched_photos;
1035
1036    } // getPhotoSelection()
1037
1038     /**
1039     * control HTML ouput for photo index
1040     *
1041     * this function provides all the necessary information
1042     * for the photo index template.
1043     */
1044    public function showPhotoIndex()
1045    {
1046       $photos = $this->getPhotoSelection();
1047
1048       $count = count($photos);
1049
1050       /* if all thumbnails should be shown on one page */
1051       if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
1052          $begin_with = 0;
1053          $end_with = $count;
1054       }
1055       /* thumbnails should be splitted up in several pages */
1056       elseif($this->cfg->thumbs_per_page > 0) {
1057
1058          if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
1059             $begin_with = 0;
1060          }
1061          else {
1062             $begin_with = $_SESSION['begin_with'];
1063          }
1064
1065          $end_with = $begin_with + $this->cfg->thumbs_per_page;
1066       }
1067
1068       $thumbs = 0;
1069       $images[$thumbs] = Array();
1070       $img_height[$thumbs] = Array();
1071       $img_width[$thumbs] = Array();
1072       $img_id[$thumbs] = Array();
1073       $img_name[$thumbs] = Array();
1074       $img_fullname[$thumbs] = Array();
1075       $img_title = Array();
1076
1077       for($i = $begin_with; $i < $end_with; $i++) {
1078
1079          if(isset($photos[$i])) {
1080
1081             $images[$thumbs] = $photos[$i];
1082             $img_id[$thumbs] = $i;
1083             $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
1084             $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0));
1085             $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
1086
1087             $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i]);
1088
1089             if(file_exists($thumb_path)) {
1090                $info = getimagesize($thumb_path); 
1091                $img_width[$thumbs] = $info[0];
1092                $img_height[$thumbs] = $info[1];
1093             }
1094             $thumbs++;
1095          } 
1096       }
1097
1098       // +1 for for smarty's selection iteration
1099       $thumbs++;
1100
1101       if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1102          $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1103
1104       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1105          $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1106          $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1107       }
1108
1109       if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1110          $this->tmpl->assign('tag_result', 1);
1111       }
1112
1113       /* do we have to display the page selector ? */
1114       if($this->cfg->thumbs_per_page != 0) {
1115
1116          $page_select = "";
1117       
1118          /* calculate the page switchers */
1119          $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1120          $next_start = $begin_with + $this->cfg->thumbs_per_page;
1121
1122          if($begin_with != 0) 
1123             $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");"); 
1124          if($end_with < $count)
1125             $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");"); 
1126
1127          $photo_per_page  = $this->cfg->thumbs_per_page;
1128          $last_page = ceil($count / $photo_per_page);
1129
1130          /* get the current selected page */
1131          if($begin_with == 0) {
1132             $current_page = 1;
1133          } else {
1134             $current_page = 0;
1135             for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1136                $current_page++;
1137             }
1138          } 
1139
1140          $dotdot_made = 0;
1141
1142          for($i = 1; $i <= $last_page; $i++) {
1143
1144             if($current_page == $i)
1145                $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1146             elseif($current_page-1 == $i || $current_page+1 == $i)
1147                $style = "style=\"font-size: 105%;\"";
1148             elseif(($current_page-5 >= $i) && ($i != 1) ||
1149                ($current_page+5 <= $i) && ($i != $last_page))
1150                $style = "style=\"font-size: 75%;\"";
1151             else
1152                $style = "";
1153
1154             $select = "<a href=\"javascript:showPhotoIndex(". (($i*$photo_per_page)-$photo_per_page) .");\"";
1155                if($style != "")
1156                   $select.= $style;
1157             $select.= ">". $i ."</a>&nbsp;";
1158
1159             // until 9 pages we show the selector from 1-9
1160             if($last_page <= 9) {
1161                $page_select.= $select;
1162                continue;
1163             } else {
1164                if($i == 1 /* first page */ || 
1165                   $i == $last_page /* last page */ ||
1166                   $i == $current_page /* current page */ ||
1167                   $i == ceil($last_page * 0.25) /* first quater */ ||
1168                   $i == ceil($last_page * 0.5) /* half */ ||
1169                   $i == ceil($last_page * 0.75) /* third quater */ ||
1170                   (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1171                   (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 */ ||
1172                   $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1173                   $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1174
1175                   $page_select.= $select;
1176                   $dotdot_made = 0;
1177                   continue;
1178
1179                }
1180             }
1181
1182             if(!$dotdot_made) {
1183                $page_select.= ".........&nbsp;";
1184                $dotdot_made = 1;
1185             }
1186          }
1187
1188          /* only show the page selector if we have more then one page */
1189          if($last_page > 1)
1190             $this->tmpl->assign('page_selector', $page_select);
1191       }
1192
1193       
1194       $current_tags = $this->getCurrentTags();
1195       $extern_link = "index.php?mode=showpi";
1196       $rss_link = "index.php?mode=rss";
1197       if($current_tags != "") {
1198          $extern_link.= "&tags=". $current_tags;
1199          $rss_link.= "&tags=". $current_tags;
1200       }
1201       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1202          $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1203          $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1204       }
1205
1206       $export_link = "index.php?mode=export";
1207       $slideshow_link = "index.php?mode=slideshow";
1208
1209       $this->tmpl->assign('extern_link', $extern_link);
1210       $this->tmpl->assign('slideshow_link', $slideshow_link);
1211       $this->tmpl->assign('export_link', $export_link);
1212       $this->tmpl->assign('rss_link', $rss_link);
1213       $this->tmpl->assign('count', $count);
1214       $this->tmpl->assign('width', $this->cfg->thumb_width);
1215       $this->tmpl->assign('preview_width', $this->cfg->photo_width);
1216       $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1217       $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1218       $this->tmpl->assign('images', $images);
1219       $this->tmpl->assign('img_width', $img_width);
1220       $this->tmpl->assign('img_height', $img_height);
1221       $this->tmpl->assign('img_id', $img_id);
1222       $this->tmpl->assign('img_name', $img_name);
1223       $this->tmpl->assign('img_fullname', $img_fullname);
1224       $this->tmpl->assign('img_title', $img_title);
1225       $this->tmpl->assign('thumbs', $thumbs);
1226
1227       $this->tmpl->show("photo_index.tpl");
1228
1229       /* if we are returning to photo index from an photo-view,
1230          scroll the window to the last shown photo-thumbnail.
1231          after this, unset the last_photo session variable.
1232       */
1233       if(isset($_SESSION['last_photo'])) {
1234          print "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n";
1235          unset($_SESSION['last_photo']);
1236       }
1237
1238    } // showPhotoIndex()
1239
1240    /**
1241     * show credit template
1242     */
1243    public function showCredits()
1244    {
1245       $this->tmpl->assign('version', $this->cfg->version);
1246       $this->tmpl->assign('product', $this->cfg->product);
1247       $this->tmpl->assign('db_version', $this->dbver);
1248       $this->tmpl->show("credits.tpl");
1249
1250    } // showCredits()
1251
1252    /**
1253     * create_thumbnails for the requested width
1254     *
1255     * this function creates image thumbnails of $orig_image
1256     * stored as $thumb_image. It will check if the image is
1257     * in a supported format, if necessary rotate the image
1258     * (based on EXIF orientation meta headers) and re-sizing.
1259     */
1260    public function create_thumbnail($orig_image, $thumb_image, $width)
1261    {  
1262       if(!file_exists($orig_image)) {
1263          return false;
1264       }
1265
1266       $details = getimagesize($orig_image);
1267       
1268       /* check if original photo is a support image type */
1269       if(!$this->checkifImageSupported($details['mime']))
1270          return false;
1271
1272       switch($details['mime']) {
1273
1274          case 'image/jpeg':
1275
1276             $meta = $this->get_meta_informations($orig_image);
1277
1278             $rotate = 0;
1279             $flip_hori = false;
1280             $flip_vert = false;
1281
1282             switch($meta['Orientation']) {
1283                case 1: /* top, left */
1284                   /* nothing to do */ break;
1285                case 2: /* top, right */
1286                   $rotate = 0; $flip_hori = true; break;
1287                case 3: /* bottom, left */
1288                   $rotate = 180; break;
1289                case 4: /* bottom, right */
1290                   $flip_vert = true; break;
1291                case 5: /* left side, top */
1292                   $rotate = 90; $flip_vert = true; break;
1293                case 6: /* right side, top */
1294                   $rotate = 90; break;
1295                case 7: /* left side, bottom */
1296                   $rotate = 270; $flip_vert = true; break;
1297                case 8: /* right side, bottom */
1298                   $rotate = 270; break;
1299             }
1300
1301             $src_img = @imagecreatefromjpeg($orig_image);
1302             $handler = "gd";
1303             break;
1304
1305          case 'image/png':
1306
1307             $src_img = @imagecreatefrompng($orig_image);
1308             $handler = "gd";
1309             break;
1310
1311          case 'image/tiff':
1312
1313             $src_img = new Imagick($orig_image);
1314             print_r($src_img->queryFormats());
1315             
1316             $handler = "imagick";
1317             exit(1);
1318             break;
1319
1320       }
1321
1322
1323       switch($handler) {
1324
1325          case 'gd':
1326
1327             if(!isset($src_img) || empty($src_img)) {
1328                print "Can't load image from ". $orig_image ."\n";
1329                return false;
1330             }
1331
1332             /* grabs the height and width */
1333             $cur_width = imagesx($src_img);
1334             $cur_height = imagesy($src_img);
1335
1336             // If requested width is more then the actual image width,
1337             // do not generate a thumbnail, instead safe the original
1338             // as thumbnail but with lower quality. But if the image
1339             // is to heigh too, then we still have to resize it.
1340             if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1341                $result = imagejpeg($src_img, $thumb_image, 75);
1342                imagedestroy($src_img);
1343                return true;
1344             }
1345
1346             // If the image will be rotate because EXIF orientation said so
1347             // 'virtually rotate' the image for further calculations
1348             if($rotate == 90 || $rotate == 270) {
1349                $tmp = $cur_width;
1350                $cur_width = $cur_height;
1351                $cur_height = $tmp;
1352             }
1353
1354             /* calculates aspect ratio */
1355             $aspect_ratio = $cur_height / $cur_width;
1356
1357             /* sets new size */
1358             if($aspect_ratio < 1) {
1359                $new_w = $width;
1360                $new_h = abs($new_w * $aspect_ratio);
1361             } else {
1362                /* 'virtually' rotate the image and calculate it's ratio */
1363                $tmp_w = $cur_height;
1364                $tmp_h = $cur_width;
1365                /* now get the ratio from the 'rotated' image */
1366                $tmp_ratio = $tmp_h/$tmp_w;
1367                /* now calculate the new dimensions */
1368                $tmp_w = $width;
1369                $tmp_h = abs($tmp_w * $tmp_ratio);
1370
1371                // now that we know, how high they photo should be, if it
1372                // gets rotated, use this high to scale the image
1373                $new_h = $tmp_h;
1374                $new_w = abs($new_h / $aspect_ratio);
1375
1376                // If the image will be rotate because EXIF orientation said so
1377                // now 'virtually rotate' back the image for the image manipulation
1378                if($rotate == 90 || $rotate == 270) {
1379                   $tmp = $new_w;
1380                   $new_w = $new_h;
1381                   $new_h = $tmp;
1382                }
1383             }
1384
1385             /* creates new image of that size */
1386             $dst_img = imagecreatetruecolor($new_w, $new_h);
1387
1388             imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1389
1390             /* copies resized portion of original image into new image */
1391             imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1392
1393             /* needs the image to be flipped horizontal? */
1394             if($flip_hori) {
1395                $this->_debug("(FLIP)");
1396                $dst_img = $this->flipImage($dst_img, 'hori');
1397             }
1398             /* needs the image to be flipped vertical? */
1399             if($flip_vert) {
1400                $this->_debug("(FLIP)");
1401                $dst_img = $this->flipImage($dst_img, 'vert');
1402             }
1403
1404             if($rotate) {
1405                $this->_debug("(ROTATE)");
1406                $dst_img = $this->rotateImage($dst_img, $rotate);
1407             }
1408
1409             /* write down new generated file */
1410             $result = imagejpeg($dst_img, $thumb_image, 75);
1411
1412             /* free your mind */
1413             imagedestroy($dst_img);
1414             imagedestroy($src_img);
1415
1416             if($result === false) {
1417                print "Can't write thumbnail ". $thumb_image ."\n";
1418                return false;
1419             }
1420
1421             return true;
1422
1423             break;
1424
1425          case 'imagick':
1426
1427             break;
1428
1429       }
1430
1431    } // create_thumbnail()
1432
1433    /**
1434     * return all exif meta data from the file
1435     */
1436    public function get_meta_informations($file)
1437    {
1438       return exif_read_data($file);
1439
1440    } // get_meta_informations()
1441
1442    /**
1443     * create phpfspot own sqlite database
1444     *
1445     * this function creates phpfspots own sqlite database
1446     * if it does not exist yet. this own is used to store
1447     * some necessary informations (md5 sum's, ...).
1448     */
1449    public function check_config_table()
1450    {
1451       // if the config table doesn't exist yet, create it
1452       if(!$this->cfg_db->db_check_table_exists("images")) {
1453          $this->cfg_db->db_exec("
1454             CREATE TABLE images (
1455                img_idx int primary key,
1456                img_md5 varchar(32)
1457             )
1458             ");
1459       }
1460
1461    } // check_config_table
1462
1463    /**
1464     * Generates a thumbnail from photo idx
1465     *
1466     * This function will generate JPEG thumbnails from provided F-Spot photo
1467     * indizes.
1468     *
1469     * 1. Check if all thumbnail generations (width) are already in place and
1470     *    readable
1471     * 2. Check if the md5sum of the original file has changed
1472     * 3. Generate the thumbnails if needed
1473     */
1474    public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
1475    {
1476       $error = 0;
1477
1478       $resolutions = Array(
1479          $this->cfg->thumb_width,
1480          $this->cfg->photo_width,
1481          $this->cfg->mini_width,
1482       );
1483
1484       /* get details from F-Spot's database */
1485       $details = $this->get_photo_details($idx);
1486
1487       /* calculate file MD5 sum */
1488       $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
1489
1490       if(!file_exists($full_path)) {
1491          $this->_error("File ". $full_path ." does not exist\n");
1492          return;
1493       }
1494
1495       if(!is_readable($full_path)) {
1496          $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
1497          return;
1498       }
1499
1500       $file_md5 = md5_file($full_path);
1501
1502       $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
1503
1504       $changes = false;
1505
1506       foreach($resolutions as $resolution) {
1507    
1508          $generate_it = false;
1509
1510          $thumb_sub_path = substr($file_md5, 0, 2);
1511          $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
1512
1513          /* if thumbnail-subdirectory does not exist yet, create it */
1514          if(!file_exists(dirname($thumb_path))) {
1515             mkdir(dirname($thumb_path), 0755);
1516          }
1517
1518          /* if the thumbnail file doesn't exist, create it */
1519          if(!file_exists($thumb_path)) {
1520             $generate_it = true;
1521          }
1522          /* if the file hasn't changed there is no need to regen the thumb */
1523          elseif($file_md5 != $this->getMD5($idx) || $force) {
1524             $generate_it = true;
1525          }
1526
1527          if($generate_it || $overwrite) {
1528
1529             $this->_debug(" ". $resolution ."px");
1530             if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1531                $error = 1;
1532
1533             $changes = true;
1534          }
1535       }
1536
1537       if(!$changes) {
1538          $this->_debug(" already exist");
1539       }
1540
1541       /* set the new/changed MD5 sum for the current photo */
1542       if(!$error) {
1543          $this->setMD5($idx, $file_md5);
1544       }
1545
1546       $this->_debug("\n");
1547
1548    } // gen_thumb()
1549
1550    /**
1551     * returns stored md5 sum for a specific photo
1552     *
1553     * this function queries the phpfspot database for a
1554     * stored MD5 checksum of the specified photo
1555     */
1556    public function getMD5($idx)
1557    {
1558       $result = $this->cfg_db->db_query("
1559          SELECT img_md5 
1560          FROM images
1561          WHERE img_idx='". $idx ."'
1562       ");
1563
1564       if(!$result)
1565          return 0;
1566
1567       $img = $this->cfg_db->db_fetch_object($result);
1568       return $img['img_md5'];
1569       
1570    } // getMD5()
1571
1572    /**
1573     * set MD5 sum for the specific photo
1574     */
1575    private function setMD5($idx, $md5)
1576    {
1577       $result = $this->cfg_db->db_exec("
1578          REPLACE INTO images (img_idx, img_md5)
1579          VALUES ('". $idx ."', '". $md5 ."')
1580       ");
1581
1582    } // setMD5()
1583
1584    /**
1585     * store current tag condition
1586     *
1587     * this function stores the current tag condition
1588     * (AND or OR) in the users session variables
1589     */
1590    public function setTagCondition($mode)
1591    {
1592       $_SESSION['tag_condition'] = $mode;
1593
1594       return "ok";
1595
1596    } // setTagCondition()
1597
1598    /** 
1599     * invoke tag & date search 
1600     *
1601     * this function will return all matching tags and store
1602     * them in the session variable selected_tags. furthermore
1603     * it also handles the date search.
1604     * getPhotoSelection() will then only return the matching
1605     * photos.
1606     */
1607    public function startSearch()
1608    {
1609       if(isset($_POST['from']) && $this->isValidDate($_POST['from'])) {
1610          $from = $_POST['from'];
1611       }
1612       if(isset($_POST['to']) && $this->isValidDate($_POST['to'])) {
1613          $to = $_POST['to'];
1614       }
1615
1616       if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
1617          $searchfor_tag = $_POST['for_tag'];
1618          $_SESSION['searchfor_tag'] = $_POST['for_tag'];
1619       }
1620
1621       if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
1622          $searchfor_name = $_POST['for_name'];
1623          $_SESSION['searchfor_name'] = $_POST['for_name'];
1624       }
1625
1626       $this->get_tags();
1627
1628       if(isset($from) && !empty($from))
1629          $_SESSION['from_date'] = strtotime($from ." 00:00:00");
1630       else
1631          unset($_SESSION['from_date']);
1632
1633       if(isset($to) && !empty($to))
1634          $_SESSION['to_date'] = strtotime($to ." 23:59:59");
1635       else
1636          unset($_SESSION['to_date']);
1637
1638       if(isset($searchfor_tag) && !empty($searchfor_tag)) {
1639          /* new search, reset the current selected tags */
1640          $_SESSION['selected_tags'] = Array();
1641          foreach($this->avail_tags as $tag) {
1642             if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
1643                array_push($_SESSION['selected_tags'], $tag);
1644          }
1645       }
1646
1647       return "ok";
1648
1649    } // startSearch()
1650
1651    /**
1652     * updates sort order in session variable
1653     *
1654     * this function is invoked by RPC and will sort the requested
1655     * sort order in the session variable.
1656     */
1657    public function updateSortOrder($order)
1658    {
1659       if(isset($this->sort_orders[$order])) {
1660          $_SESSION['sort_order'] = $order;
1661          return "ok";
1662       }
1663
1664       return "unkown error";
1665
1666    } // updateSortOrder()
1667
1668    /**
1669     * rotate image
1670     *
1671     * this function rotates the image according the
1672     * specified angel.
1673     */
1674    private function rotateImage($img, $degrees)
1675    {
1676       if(function_exists("imagerotate")) {
1677          $img = imagerotate($img, $degrees, 0);
1678       } else {
1679          function imagerotate($src_img, $angle)
1680          {
1681             $src_x = imagesx($src_img);
1682             $src_y = imagesy($src_img);
1683             if ($angle == 180) {
1684                $dest_x = $src_x;
1685                $dest_y = $src_y;
1686             }
1687             elseif ($src_x <= $src_y) {
1688                $dest_x = $src_y;
1689                $dest_y = $src_x;
1690             }
1691             elseif ($src_x >= $src_y) {
1692                $dest_x = $src_y;
1693                $dest_y = $src_x;
1694             }
1695                
1696             $rotate=imagecreatetruecolor($dest_x,$dest_y);
1697             imagealphablending($rotate, false);
1698                
1699             switch ($angle) {
1700             
1701                case 90:
1702                   for ($y = 0; $y < ($src_y); $y++) {
1703                      for ($x = 0; $x < ($src_x); $x++) {
1704                         $color = imagecolorat($src_img, $x, $y);
1705                         imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
1706                      }
1707                   }
1708                   break;
1709
1710                case 270:
1711                   for ($y = 0; $y < ($src_y); $y++) {
1712                      for ($x = 0; $x < ($src_x); $x++) {
1713                         $color = imagecolorat($src_img, $x, $y);
1714                         imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
1715                      }
1716                   }
1717                   break;
1718
1719                case 180:
1720                   for ($y = 0; $y < ($src_y); $y++) {
1721                      for ($x = 0; $x < ($src_x); $x++) {
1722                         $color = imagecolorat($src_img, $x, $y);
1723                         imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
1724                      }
1725                   }
1726                   break;
1727
1728                default:
1729                   $rotate = $src_img;
1730                   break;
1731             };
1732
1733             return $rotate;
1734
1735          }
1736
1737          $img = imagerotate($img, $degrees);
1738
1739       }
1740
1741       return $img;
1742
1743    } // rotateImage()
1744
1745    /**
1746     * returns flipped image
1747     *
1748     * this function will return an either horizontal or
1749     * vertical flipped truecolor image.
1750     */
1751    private function flipImage($image, $mode)
1752    {
1753       $w = imagesx($image);
1754       $h = imagesy($image);
1755       $flipped = imagecreatetruecolor($w, $h);
1756
1757       switch($mode) {
1758          case 'vert':
1759             for ($y = 0; $y < $h; $y++) {
1760                imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
1761             }
1762             break;
1763          case 'hori':
1764             for ($x = 0; $x < $w; $x++) {
1765                imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
1766             }
1767             break;
1768       }
1769
1770       return $flipped;
1771
1772    } // flipImage()
1773
1774    /**
1775     * return all assigned tags for the specified photo
1776     */
1777    private function get_photo_tags($idx)
1778    {
1779       $result = $this->db->db_query("
1780          SELECT t.id, t.name
1781          FROM tags t
1782          INNER JOIN photo_tags pt
1783             ON t.id=pt.tag_id
1784          WHERE pt.photo_id='". $idx ."'
1785       ");
1786
1787       $tags = Array();
1788
1789       while($row = $this->db->db_fetch_object($result))
1790          $tags[$row['id']] = $row['name'];
1791
1792       return $tags;
1793
1794    } // get_photo_tags()
1795
1796    /**
1797     * create on-the-fly images with text within
1798     */
1799    public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
1800    {
1801       if (strlen($color) != 6) 
1802          $color = 000000;
1803
1804       $int = hexdec($color);
1805       $h = imagefontheight($font);
1806       $fw = imagefontwidth($font);
1807       $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
1808       $lines = count($txt);
1809       $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
1810       $bg = imagecolorallocate($im, 255, 255, 255);
1811       $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
1812       $y = 0;
1813
1814       foreach ($txt as $text) {
1815          $x = (($w - ($fw * strlen($text))) / 2);
1816          imagestring($im, $font, $x, $y, $text, $color);
1817          $y += ($h + $space);
1818       }
1819
1820       Header("Content-type: image/png");
1821       ImagePng($im);
1822
1823    } // showTextImage()
1824
1825    /**
1826     * check if all requirements are met
1827     */
1828    private function check_requirements()
1829    {
1830       if(!function_exists("imagecreatefromjpeg")) {
1831          print "PHP GD library extension is missing<br />\n";
1832          $missing = true;
1833       }
1834
1835       if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
1836          print "PHP SQLite3 library extension is missing<br />\n";
1837          $missing = true;
1838       }
1839
1840       /* Check for HTML_AJAX PEAR package, lent from Horde project */
1841       ini_set('track_errors', 1);
1842       @include_once 'HTML/AJAX/Server.php';
1843       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1844          print "PEAR HTML_AJAX package is missing<br />\n";
1845          $missing = true;
1846       }
1847       @include_once 'Calendar/Calendar.php';
1848       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1849          print "PEAR Calendar package is missing<br />\n";
1850          $missing = true;
1851       }
1852       @include_once 'Console/Getopt.php';
1853       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1854          print "PEAR Console_Getopt package is missing<br />\n";
1855          $missing = true;
1856       }
1857       @include_once $this->cfg->smarty_path .'/libs/Smarty.class.php';
1858       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1859          print "Smarty template engine can not be found in ". $this->cfg->smarty_path ."/libs/Smarty.class.php<br />\n";
1860          $missing = true;
1861       }
1862       ini_restore('track_errors');
1863
1864       if(isset($missing))
1865          return false;
1866
1867       return true;
1868
1869    } // check_requirements()
1870
1871    private function _debug($text)
1872    {
1873       if($this->fromcmd) {
1874          print $text;
1875       }
1876
1877    } // _debug()
1878
1879    /**
1880     * check if specified MIME type is supported
1881     */
1882    public function checkifImageSupported($mime)
1883    {
1884       if(in_array($mime, Array("image/jpeg", "image/png", "image/tiff")))
1885          return true;
1886
1887       return false;
1888
1889    } // checkifImageSupported()
1890
1891    public function _error($text)
1892    {
1893       switch($this->cfg->logging) {
1894          default:
1895          case 'display':
1896             print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
1897             print $text ."<br />\n";
1898             break;
1899          case 'errorlog':  
1900             error_log($text);
1901             break;
1902          case 'logfile':
1903             error_log($text, 3, $his->cfg->log_file);
1904             break;
1905       }
1906
1907       $this->runtime_error = true;
1908
1909    } // _error()
1910
1911    /**
1912     * output calendard input fields
1913     */
1914    private function get_calendar($mode)
1915    {
1916       $year = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
1917       $month = isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
1918       $day = isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
1919
1920       $output = "<input type=\"text\" size=\"3\" id=\"". $mode ."year\" value=\"". $year ."\"";
1921       if(!isset($_SESSION[$mode .'_date']))
1922          $output.= " disabled=\"disabled\"";
1923       $output.= " />\n";
1924       $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."month\" value=\"". $month ."\"";
1925       if(!isset($_SESSION[$mode .'_date']))
1926          $output.= " disabled=\"disabled\"";
1927       $output.= " />\n";
1928       $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."day\" value=\"". $day ."\"";
1929       if(!isset($_SESSION[$mode .'_date']))
1930          $output.= " disabled=\"disabled\"";
1931       $output.= " />\n";
1932
1933       return $output;
1934
1935    } // get_calendar()
1936
1937    /**
1938     * output calendar matrix
1939     */
1940    public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
1941    {
1942       if (!isset($year)) $year = date('Y');
1943       if (!isset($month)) $month = date('m');
1944       if (!isset($day)) $day = date('d');
1945       $rows = 1;
1946       $cols = 1;
1947       $matrix = Array();
1948
1949       require_once CALENDAR_ROOT.'Month/Weekdays.php';
1950       require_once CALENDAR_ROOT.'Day.php';
1951
1952       // Build the month
1953       $month = new Calendar_Month_Weekdays($year,$month);
1954
1955       // Create links
1956       $prevStamp = $month->prevMonth(true);
1957       $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
1958       $nextStamp = $month->nextMonth(true);
1959       $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
1960
1961       $selectedDays = array (
1962          new Calendar_Day($year,$month,$day),
1963          new Calendar_Day($year,12,25),
1964       );
1965
1966       // Build the days in the month
1967       $month->build($selectedDays);
1968
1969       $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
1970       $this->tmpl->assign('prev_month', $prev);
1971       $this->tmpl->assign('next_month', $next);
1972
1973       while ( $day = $month->fetch() ) {
1974    
1975          if(!isset($matrix[$rows]))
1976             $matrix[$rows] = Array();
1977
1978          $string = "";
1979
1980          $dayStamp = $day->thisDay(true);
1981          $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
1982
1983          // isFirst() to find start of week
1984          if ( $day->isFirst() )
1985             $string.= "<tr>\n";
1986
1987          if ( $day->isSelected() ) {
1988             $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
1989          } else if ( $day->isEmpty() ) {
1990             $string.= "<td>&nbsp;</td>\n";
1991          } else {
1992             $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
1993          }
1994
1995          // isLast() to find end of week
1996          if ( $day->isLast() )
1997             $string.= "</tr>\n";
1998
1999          $matrix[$rows][$cols] = $string;
2000
2001          $cols++;
2002
2003          if($cols > 7) {
2004             $cols = 1;
2005             $rows++;
2006          }
2007       }
2008
2009       $this->tmpl->assign('matrix', $matrix);
2010       $this->tmpl->assign('rows', $rows);
2011       $this->tmpl->show("calendar.tpl");
2012
2013    } // get_calendar_matrix()
2014
2015    /**
2016     * output export page
2017     */
2018    public function getExport($mode)
2019    {
2020       $pictures = $this->getPhotoSelection();
2021       $current_tags = $this->getCurrentTags();  
2022
2023       foreach($pictures as $picture) {
2024
2025          $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2026          if($current_tags != "") {
2027             $orig_url.= "&tags=". $current_tags;
2028          } 
2029          if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2030             $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2031          }
2032
2033          $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2034
2035          switch($mode) {
2036
2037             case 'HTML':
2038                // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
2039                print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
2040                break;
2041                
2042             case 'MoinMoin':
2043                // "[%pictureurl% %thumbnailurl%]"
2044                print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2045                break;
2046
2047             case 'MoinMoinList':
2048                // " * [%pictureurl% %thumbnailurl%]"
2049                print "&nbsp;" . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2050                break;
2051          }
2052
2053       }
2054
2055    } // getExport()
2056
2057    /**
2058     * output RSS feed
2059     */
2060    public function getRSSFeed()
2061    {
2062       Header("Content-type: text/xml; charset=utf-8");
2063       print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2064 ?>
2065 <rss version="2.0"
2066    xmlns:media="http://search.yahoo.com/mrss/"
2067    xmlns:dc="http://purl.org/dc/elements/1.1/"
2068  >
2069  <channel>
2070   <title>phpfspot</title>
2071   <description>phpfspot RSS feed</description>
2072   <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2073   <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2074   <generator>phpfspot</generator>
2075 <?php
2076
2077       $pictures = $this->getPhotoSelection();
2078       $current_tags = $this->getCurrentTags();  
2079
2080       foreach($pictures as $picture) {
2081
2082          $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2083          if($current_tags != "") {
2084             $orig_url.= "&tags=". $current_tags;
2085          } 
2086          if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2087             $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2088          }
2089
2090          $details = $this->get_photo_details($picture);
2091
2092          $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2093          $thumb_html = htmlspecialchars("
2094 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2095 <br>
2096 ". $details['description']);
2097
2098          $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2099
2100          /* get EXIF information if JPEG */
2101          if($details['mime'] == "image/jpeg") {
2102             $meta = $this->get_meta_informations($orig_path);
2103          }
2104
2105          $meta_date = isset($meta['FileDateTime']) ? $meta['FileDateTime'] : filemtime($orig_path);
2106
2107 ?>
2108   <item>
2109    <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2110    <link><?php print htmlspecialchars($orig_url); ?></link>
2111    <guid><?php print htmlspecialchars($orig_url); ?></guid>
2112    <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $meta_date); ?></dc:date.Taken>
2113    <description>
2114     <?php print $thumb_html; ?> 
2115    </description>
2116    <pubDate><?php print strftime("%a, %d %b %Y %T %z", $meta_date); ?></pubDate>
2117   </item>
2118 <?php
2119
2120       }
2121 ?>
2122  </channel>
2123 </rss>
2124 <?php
2125
2126
2127    } // getExport()
2128
2129  
2130    /**
2131     * return all selected tags as one string
2132     */
2133    private function getCurrentTags()
2134    {
2135       $current_tags = "";
2136       if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2137          foreach($_SESSION['selected_tags'] as $tag)
2138             $current_tags.= $tag .",";
2139          $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2140       }
2141       return $current_tags;
2142
2143    } // getCurrentTags()
2144
2145    /**
2146     * return the current photo
2147     */
2148    public function getCurrentPhoto()
2149    {
2150       if(isset($_SESSION['current_photo'])) {
2151          print $_SESSION['current_photo'];
2152       }
2153    } // getCurrentPhoto()
2154
2155    /**
2156     * tells the client browser what to do
2157     *
2158     * this function is getting called via AJAX by the
2159     * client browsers. it will tell them what they have
2160     * to do next. This is necessary for directly jumping
2161     * into photo index or single photo view when the are
2162     * requested with specific URLs
2163     */
2164    public function whatToDo()
2165    {
2166       if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
2167          return "show_photo";
2168       }
2169       elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
2170          return "showpi_tags";
2171       }
2172       elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
2173          return "showpi";
2174       }
2175
2176       return "nothing special";
2177
2178    } // whatToDo()
2179
2180    /**
2181     * return the current process-user
2182     */
2183    private function getuid()
2184    {
2185       if($uid = posix_getuid()) {
2186          if($user = posix_getpwuid($uid)) {
2187             return $user['name'];
2188          }
2189       }
2190    
2191       return 'n/a';
2192    
2193    } // getuid()
2194
2195    /**
2196     * returns a select-dropdown box to select photo index sort parameters
2197     */
2198    public function smarty_sort_select_list($params, &$smarty)
2199    {
2200       $output = "";
2201
2202       foreach($this->sort_orders as $key => $value) {
2203          $output.= "<option value=\"". $key ."\"";
2204          if($key == $_SESSION['sort_order']) {
2205             $output.= " selected=\"selected\"";
2206          }
2207          $output.= ">". $value ."</option>";
2208       }
2209
2210       return $output;
2211
2212    } // smarty_sort_select_list()
2213
2214    /**
2215     * returns the currently selected sort order
2216     */ 
2217    private function get_sort_order()
2218    {
2219       switch($_SESSION['sort_order']) {
2220          case 'date_asc':
2221             return " ORDER BY p.time ASC";
2222             break;
2223          case 'date_desc':
2224             return " ORDER BY p.time DESC";
2225             break;
2226          case 'name_asc':
2227             if($this->dbver < 9) {
2228                return " ORDER BY p.name ASC";
2229             }
2230             else {
2231                return " ORDER BY basename(p.uri) ASC";
2232             }
2233             break;
2234          case 'name_desc':
2235             if($this->dbver < 9) {
2236                return " ORDER BY p.name DESC";
2237             }
2238             else {
2239                return " ORDER BY basename(p.uri) DESC";
2240             }
2241             break;
2242          case 'tags_asc':
2243             return " ORDER BY t.name ASC ,p.time ASC";
2244             break;
2245          case 'tags_desc':
2246             return " ORDER BY t.name DESC ,p.time ASC";
2247             break;
2248       }
2249
2250    } // get_sort_order()
2251
2252    /***
2253      * return the next to be shown slide show image
2254      *
2255      * this function returns the URL of the next image
2256      * in the slideshow sequence.
2257      */
2258    public function getNextSlideShowImage()
2259    {
2260       $all_photos = $this->getPhotoSelection();
2261
2262       if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1) 
2263          $_SESSION['slideshow_img'] = 0;
2264       else
2265          $_SESSION['slideshow_img']++;
2266
2267       return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2268
2269    } // getNextSlideShowImage()
2270
2271    /***
2272      * return the previous to be shown slide show image
2273      *
2274      * this function returns the URL of the previous image
2275      * in the slideshow sequence.
2276      */
2277    public function getPrevSlideShowImage()
2278    {
2279       $all_photos = $this->getPhotoSelection();
2280
2281       if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
2282          $_SESSION['slideshow_img'] = 0;
2283       else
2284          $_SESSION['slideshow_img']--;
2285
2286       return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2287
2288    } // getPrevSlideShowImage()
2289
2290    public function resetSlideShow()
2291    {
2292       if(isset($_SESSION['slideshow_img']))
2293          unset($_SESSION['slideshow_img']);
2294
2295    } // resetSlideShow()
2296    
2297    /***
2298      * get random photo
2299      *
2300      * this function will get all photos from the fspot
2301      * database and randomly return ONE entry
2302      *
2303      * saddly there is yet no sqlite3 function which returns
2304      * the bulk result in array, so we have to fill up our
2305      * own here.
2306      */ 
2307    public function get_random_photo()
2308    {
2309       $all = Array();
2310
2311       $result = $this->db->db_query("
2312          SELECT id
2313          FROM photos
2314       ");
2315       
2316       while($row = $this->db->db_fetch_object($result)) {
2317          array_push($all, $row['id']);
2318       }
2319
2320       return $all[array_rand($all)];
2321
2322    } // get_random_photo()
2323
2324    /**
2325     * validates provided date
2326     *
2327     * this function validates if the provided date
2328     * contains a valid date and will return true 
2329     * if it is.
2330     */
2331    public function isValidDate($date_str)
2332    {
2333       $timestamp = strtotime($date_str);
2334    
2335       if(is_numeric($timestamp))
2336          return true;
2337       
2338       return false;
2339
2340    } // isValidDate()
2341
2342    /**
2343     * timestamp to string conversion
2344     */
2345    private function ts2str($timestamp)
2346    {
2347       return strftime("%Y-%m-%d", $timestamp);
2348    } // ts2str()
2349
2350    private function extractTags($tags_str)
2351    {
2352       $not_validated = split(',', $_GET['tags']);
2353       $validated = array();
2354
2355       foreach($not_validated as $tag) {
2356          if(is_numeric($tag))
2357             array_push($validated, $tag);
2358       }
2359    
2360       return $validated;
2361    
2362    } // extractTags()
2363
2364    /**
2365     * returns the full path to a thumbnail
2366     */
2367    public function get_thumb_path($width, $photo)
2368    {
2369       $md5 = $this->getMD5($photo);
2370       $sub_path = substr($md5, 0, 2);
2371       return $this->cfg->thumb_path
2372          . "/"
2373          . $sub_path
2374          . "/"
2375          . $width
2376          . "_"
2377          . $md5;
2378
2379    } // get_thumb_path()
2380
2381    /**
2382     * returns server's virtual host name
2383     */
2384    private function get_server_name()
2385    {
2386       return $_SERVER['SERVER_NAME'];
2387    } // get_server_name()
2388
2389    /**
2390     * returns type of webprotocol which is
2391     * currently used
2392     */
2393    private function get_web_protocol()
2394    {
2395       if(!isset($_SERVER['HTTPS']))
2396          return "http";
2397       else
2398          return "https";
2399    } // get_web_protocol()
2400
2401    /**
2402     * return url to this phpfspot installation
2403     */
2404    private function get_phpfspot_url()
2405    {
2406       return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
2407    } // get_phpfspot_url()
2408
2409    /**
2410     * returns the number of photos which are tagged with $tag_id
2411     */
2412    public function get_num_photos($tag_id)
2413    {
2414       if($result = $this->db->db_fetchSingleRow("
2415          SELECT count(*) as number
2416          FROM photo_tags
2417          WHERE
2418             tag_id LIKE '". $tag_id ."'")) {
2419
2420          return $result['number'];
2421
2422       }
2423
2424       return 0;
2425       
2426    } // get_num_photos()
2427    
2428    /**
2429     * check file exists and is readable
2430     *
2431     * returns true, if everything is ok, otherwise false
2432     * if $silent is not set, this function will output and
2433     * error message
2434     */
2435    private function check_readable($file, $silent = null)
2436    {
2437       if(!file_exists($file)) {
2438          if(!isset($silent))
2439             print "File \"". $file ."\" does not exist.\n";
2440          return false;
2441       }
2442
2443       if(!is_readable($file)) {
2444          if(!isset($silent))
2445             print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
2446          return false;
2447       }
2448
2449       return true;
2450
2451    } // check_readable()
2452
2453    /**
2454     * check if all needed indices are present
2455     *
2456     * this function checks, if some needed indices are already
2457     * present, or if not, create them on the fly. they are
2458     * necessary to speed up some queries like that one look for
2459     * all tags, when show_tags is specified in the configuration.
2460     */
2461    private function checkDbIndices()
2462    {
2463       $result = $this->db->db_exec("
2464          CREATE INDEX IF NOT EXISTS
2465             phototag
2466          ON
2467             photo_tags
2468                (photo_id, tag_id)
2469       ");
2470
2471    } // checkDbIndices()
2472
2473    /**
2474     * retrive F-Spot database version
2475     *
2476     * this function will return the F-Spot database version number
2477     * It is stored within the sqlite3 database in the table meta
2478     */
2479    public function getFspotDBVersion()
2480    {
2481       if($result = $this->db->db_fetchSingleRow("
2482          SELECT data as version
2483          FROM meta
2484          WHERE
2485             name LIKE 'F-Spot Database Version'
2486       "))
2487          return $result['version'];
2488
2489       return null;
2490
2491    } // getFspotDBVersion()
2492
2493    /**
2494     * parse the provided URI and will returned the
2495     * requested chunk
2496     */
2497    public function parse_uri($uri, $mode)
2498    {
2499       if(($components = parse_url($uri)) !== false) {
2500
2501          switch($mode) {
2502             case 'filename':
2503                return basename($components['path']);
2504                break;
2505             case 'dirname':
2506                return dirname($components['path']);
2507                break;
2508             case 'fullpath':
2509                return $components['path'];
2510                break;
2511          }
2512       }
2513
2514       return $uri;
2515
2516    } // parse_uri()
2517
2518    /**
2519     * validate config options
2520     *
2521     * this function checks if all necessary configuration options are
2522     * specified and set.
2523     */
2524    private function check_config_options()
2525    {
2526       if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
2527          $this->_error("Please set \$page_title in phpfspot_cfg");
2528
2529       if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
2530          $this->_error("Please set \$base_path in phpfspot_cfg");
2531
2532       if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
2533          $this->_error("Please set \$web_path in phpfspot_cfg");
2534
2535       if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
2536          $this->_error("Please set \$thumb_path in phpfspot_cfg");
2537
2538       if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
2539          $this->_error("Please set \$smarty_path in phpfspot_cfg");
2540
2541       if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
2542          $this->_error("Please set \$fspot_db in phpfspot_cfg");
2543
2544       if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
2545          $this->_error("Please set \$db_access in phpfspot_cfg");
2546
2547       if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
2548          $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
2549
2550       if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
2551          $this->_error("Please set \$thumb_width in phpfspot_cfg");
2552
2553       if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
2554          $this->_error("Please set \$thumb_height in phpfspot_cfg");
2555
2556       if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
2557          $this->_error("Please set \$photo_width in phpfspot_cfg");
2558
2559       if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
2560          $this->_error("Please set \$mini_width in phpfspot_cfg");
2561
2562       if(!isset($this->cfg->thumbs_per_page))
2563          $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
2564
2565       if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
2566          $this->_error("Please set \$path_replace_from in phpfspot_cfg");
2567
2568       if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
2569          $this->_error("Please set \$path_replace_to in phpfspot_cfg");
2570
2571       if(!isset($this->cfg->hide_tags))
2572          $this->_error("Please set \$hide_tags in phpfspot_cfg");
2573
2574       if(!isset($this->cfg->theme_name))
2575          $this->_error("Please set \$theme_name in phpfspot_cfg");
2576
2577       if(!isset($this->cfg->logging))
2578          $this->_error("Please set \$logging in phpfspot_cfg");
2579
2580       if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
2581
2582          if(!isset($this->cfg->log_file))
2583             $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
2584
2585          if(!is_writeable($this->cfg->log_file))
2586             $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
2587
2588       }
2589
2590       /* check for pending slash on web_path */
2591       if(!preg_match("/\/$/", $this->cfg->web_path))
2592          $this->cfg->web_path.= "/";
2593
2594       return $this->runtime_error;
2595
2596    } // check_config_options()
2597
2598    /**
2599     * cleanup phpfspot own database
2600     *
2601     * When photos are getting delete from F-Spot, there will remain
2602     * remain some residues in phpfspot own database. This function
2603     * will try to wipe them out.
2604     */
2605    public function cleanup_phpfspot_db()
2606    {
2607       $to_delete = Array();
2608
2609       $result = $this->cfg_db->db_query("
2610          SELECT img_idx
2611          FROM images
2612          ORDER BY img_idx ASC
2613       ");
2614
2615       while($row = $this->cfg_db->db_fetch_object($result)) {
2616          if(!$this->db->db_fetchSingleRow("
2617             SELECT id
2618             FROM photos
2619             WHERE id='". $row['img_idx'] ."'")) {
2620
2621             array_push($to_delete, $row['img_idx'], ',');
2622          }
2623       }
2624
2625       print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
2626
2627       $this->cfg_db->db_exec("
2628          DELETE FROM images
2629          WHERE img_idx IN (". implode($to_delete) .")
2630       ");
2631
2632    } // cleanup_phpfspot_db()
2633
2634    /**
2635     * return first image of the page, the $current photo
2636     * lies in.
2637     *
2638     * this function is used to find out the first photo of the
2639     * current page, in which the $current photo lies. this is
2640     * used to display the correct photo, when calling showPhotoIndex()
2641     * from showImage()
2642     */
2643    private function getCurrentPage($current, $max)
2644    {
2645       if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
2646          for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
2647             if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
2648                return $page_start;
2649          }
2650       }
2651       return 0;
2652
2653    } // getCurrentPage()
2654
2655 } // class PHPFSPOT
2656
2657 ?>