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