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