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