9e78d1dcfe7ba06000fa5a7a19f1215cf3ac1eb0
[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']);
169                }
170                if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
171                   $_SESSION['to_date'] = strtotime($_GET['to_date']);
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 DISTINCT photo_id
870             FROM photo_tags pt
871          INNER JOIN photos p
872             ON p.id=pt.photo_id
873          INNER JOIN tags t
874             ON pt.tag_id=t.id
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['photo_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('images', $images);
1098       $this->tmpl->assign('img_width', $img_width);
1099       $this->tmpl->assign('img_height', $img_height);
1100       $this->tmpl->assign('img_id', $img_id);
1101       $this->tmpl->assign('img_name', $img_name);
1102       $this->tmpl->assign('img_title', $img_title);
1103       $this->tmpl->assign('rows', $rows);
1104       $this->tmpl->assign('columns', $this->cfg->thumbs_per_row);
1105
1106       $this->tmpl->show("photo_index.tpl");
1107
1108       if(isset($anchor))
1109          print "<script language=\"JavaScript\">self.location.hash = '#image". $anchor ."';</script>\n";
1110
1111    } // showPhotoIndex()
1112
1113    /**
1114     * show credit template
1115     */
1116    public function showCredits()
1117    {
1118       $this->tmpl->assign('version', $this->cfg->version);
1119       $this->tmpl->assign('product', $this->cfg->product);
1120       $this->tmpl->assign('db_version', $this->dbver);
1121       $this->tmpl->show("credits.tpl");
1122
1123    } // showCredits()
1124
1125    /**
1126     * create_thumbnails for the requested width
1127     *
1128     * this function creates image thumbnails of $orig_image
1129     * stored as $thumb_image. It will check if the image is
1130     * in a supported format, if necessary rotate the image
1131     * (based on EXIF orientation meta headers) and re-sizing.
1132     */
1133    public function create_thumbnail($orig_image, $thumb_image, $width)
1134    {  
1135       if(!file_exists($orig_image)) {
1136          return false;
1137       }
1138
1139       $details = getimagesize($orig_image);
1140       
1141       /* check if original photo is a support image type */
1142       if(!$this->checkifImageSupported($details['mime']))
1143          return false;
1144
1145       $meta = $this->get_meta_informations($orig_image);
1146
1147       $rotate = 0;
1148       $flip = false;
1149
1150       switch($meta['Orientation']) {
1151
1152          case 1: /* top, left */
1153             $rotate = 0; $flip = false; break;
1154          case 2: /* top, right */
1155             $rotate = 0; $flip = true; break;
1156          case 3: /* bottom, left */
1157             $rotate = 180; $flip = false; break;
1158          case 4: /* bottom, right */
1159             $rotate = 180; $flip = true; break;
1160          case 5: /* left side, top */
1161             $rotate = 90; $flip = true; break;
1162          case 6: /* right side, top */
1163             $rotate = 90; $flip = false; break;
1164          case 7: /* left side, bottom */
1165             $rotate = 270; $flip = true; break;
1166          case 8: /* right side, bottom */
1167             $rotate = 270; $flip = false; break;
1168       }
1169
1170       $src_img = @imagecreatefromjpeg($orig_image);
1171
1172       if(!$src_img) {
1173          print "Can't load image from ". $orig_image ."\n";
1174          return false;
1175       }
1176
1177       /* grabs the height and width */
1178       $cur_width = imagesx($src_img);
1179       $cur_height = imagesy($src_img);
1180
1181       // If requested width is more then the actual image width,
1182       // do not generate a thumbnail, instead safe the original
1183       // as thumbnail but with lower quality. But if the image
1184       // is to heigh too, then we still have to resize it.
1185       if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1186          $result = imagejpeg($src_img, $thumb_image, 75);
1187          imagedestroy($src_img);
1188          return true;
1189       }
1190
1191       // If the image will be rotate because EXIF orientation said so
1192       // 'virtually rotate' the image for further calculations
1193       if($rotate == 90 || $rotate == 270) {
1194          $tmp = $cur_width;
1195          $cur_width = $cur_height;
1196          $cur_height = $tmp;
1197       }
1198
1199       /* calculates aspect ratio */
1200       $aspect_ratio = $cur_height / $cur_width;
1201
1202       /* sets new size */
1203       if($aspect_ratio < 1) {
1204          $new_w = $width;
1205          $new_h = abs($new_w * $aspect_ratio);
1206       } else {
1207          /* 'virtually' rotate the image and calculate it's ratio */
1208          $tmp_w = $cur_height;
1209          $tmp_h = $cur_width;
1210          /* now get the ratio from the 'rotated' image */
1211          $tmp_ratio = $tmp_h/$tmp_w;
1212          /* now calculate the new dimensions */
1213          $tmp_w = $width;
1214          $tmp_h = abs($tmp_w * $tmp_ratio);
1215
1216          // now that we know, how high they photo should be, if it
1217          // gets rotated, use this high to scale the image
1218          $new_h = $tmp_h;
1219          $new_w = abs($new_h / $aspect_ratio);
1220
1221          // If the image will be rotate because EXIF orientation said so
1222          // now 'virtually rotate' back the image for the image manipulation
1223          if($rotate == 90 || $rotate == 270) {
1224             $tmp = $new_w;
1225             $new_w = $new_h;
1226             $new_h = $tmp;
1227          }
1228       }
1229
1230       /* creates new image of that size */
1231       $dst_img = imagecreatetruecolor($new_w, $new_h);
1232
1233       imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1234
1235       /* copies resized portion of original image into new image */
1236       imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1237
1238       /* needs the image to be flipped horizontal? */
1239       if($flip) {
1240          print "(FLIP)";
1241          $image = $dst_img;
1242          for($x = 0; $x < $new_w; $x++) {
1243             imagecopy($dst_img, $image, $x, 0, $w - $x - 1, 0, 1, $h);
1244          }
1245       }
1246
1247       if($rotate) {
1248          $this->_debug("(ROTATE)");
1249          $dst_img = $this->rotateImage($dst_img, $rotate);
1250       }
1251
1252       /* write down new generated file */
1253       $result = imagejpeg($dst_img, $thumb_image, 75);
1254
1255       /* free your mind */
1256       imagedestroy($dst_img);
1257       imagedestroy($src_img);
1258
1259       if($result === false) {
1260          print "Can't write thumbnail ". $thumb_image ."\n";
1261          return false;
1262       }
1263
1264       return true;
1265
1266    } // create_thumbnail()
1267
1268    /**
1269     * return all exif meta data from the file
1270     */
1271    public function get_meta_informations($file)
1272    {
1273       return exif_read_data($file);
1274
1275    } // get_meta_informations()
1276
1277    /**
1278     * create phpfspot own sqlite database
1279     *
1280     * this function creates phpfspots own sqlite database
1281     * if it does not exist yet. this own is used to store
1282     * some necessary informations (md5 sum's, ...).
1283     */
1284    public function check_config_table()
1285    {
1286       // if the config table doesn't exist yet, create it
1287       if(!$this->cfg_db->db_check_table_exists("images")) {
1288          $this->cfg_db->db_exec("
1289             CREATE TABLE images (
1290                img_idx int primary key,
1291                img_md5 varchar(32)
1292             )
1293             ");
1294       }
1295
1296    } // check_config_table
1297
1298    /**
1299     * Generates a thumbnail from photo idx
1300     *
1301     * This function will generate JPEG thumbnails from provided F-Spot photo
1302     * indizes.
1303     *
1304     * 1. Check if all thumbnail generations (width) are already in place and
1305     *    readable
1306     * 2. Check if the md5sum of the original file has changed
1307     * 3. Generate the thumbnails if needed
1308     */
1309    public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
1310    {
1311       $error = 0;
1312
1313       $resolutions = Array(
1314          $this->cfg->thumb_width,
1315          $this->cfg->photo_width,
1316          $this->cfg->mini_width,
1317       );
1318
1319       /* get details from F-Spot's database */
1320       $details = $this->get_photo_details($idx);
1321
1322       /* calculate file MD5 sum */
1323       $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
1324
1325       if(!file_exists($full_path)) {
1326          $this->_error("File ". $full_path ." does not exist\n");
1327          return;
1328       }
1329
1330       if(!is_readable($full_path)) {
1331          $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
1332          return;
1333       }
1334
1335       $file_md5 = md5_file($full_path);
1336
1337       $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
1338
1339       $changes = false;
1340
1341       foreach($resolutions as $resolution) {
1342    
1343          $generate_it = false;
1344
1345          $thumb_sub_path = substr($file_md5, 0, 2);
1346          $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
1347
1348          if(!file_exists(dirname($thumb_path))) {
1349             mkdir(dirname($thumb_path), 0755);
1350          }
1351
1352          /* if the thumbnail file doesn't exist, create it */
1353          if(!file_exists($thumb_path)) {
1354             $generate_it = true;
1355          }
1356          /* if the file hasn't changed there is no need to regen the thumb */
1357          elseif($file_md5 != $this->getMD5($idx) || $force) {
1358             $generate_it = true;
1359          }
1360
1361          if($generate_it || $overwrite) {
1362
1363             $this->_debug(" ". $resolution ."px");
1364             if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1365                $error = 1;
1366
1367             $changes = true;
1368          }
1369       }
1370
1371       if(!$changes) {
1372          $this->_debug(" already exist");
1373       }
1374
1375       /* set the new/changed MD5 sum for the current photo */
1376       if(!$error) {
1377          $this->setMD5($idx, $file_md5);
1378       }
1379
1380       $this->_debug("\n");
1381
1382    } // gen_thumb()
1383
1384    /**
1385     * returns stored md5 sum for a specific photo
1386     *
1387     * this function queries the phpfspot database for a
1388     * stored MD5 checksum of the specified photo
1389     */
1390    public function getMD5($idx)
1391    {
1392       $result = $this->cfg_db->db_query("
1393          SELECT img_md5 
1394          FROM images
1395          WHERE img_idx='". $idx ."'
1396       ");
1397
1398       if(!$result)
1399          return 0;
1400
1401       $img = $this->cfg_db->db_fetch_object($result);
1402       return $img['img_md5'];
1403       
1404    } // getMD5()
1405
1406    /**
1407     * set MD5 sum for the specific photo
1408     */
1409    private function setMD5($idx, $md5)
1410    {
1411       $result = $this->cfg_db->db_exec("
1412          REPLACE INTO images (img_idx, img_md5)
1413          VALUES ('". $idx ."', '". $md5 ."')
1414       ");
1415
1416    } // setMD5()
1417
1418    /**
1419     * store current tag condition
1420     *
1421     * this function stores the current tag condition
1422     * (AND or OR) in the users session variables
1423     */
1424    public function setTagCondition($mode)
1425    {
1426       $_SESSION['tag_condition'] = $mode;
1427
1428    } // setTagCondition()
1429
1430    /** 
1431     * invoke tag & date search 
1432     *
1433     * this function will return all matching tags and store
1434     * them in the session variable selected_tags. furthermore
1435     * it also handles the date search.
1436     * getPhotoSelection() will then only return the matching
1437     * photos.
1438     */
1439    public function startSearch($searchfor, $from = 0, $to = 0)
1440    {
1441       $this->get_tags();
1442
1443       $_SESSION['searchfor'] = $searchfor;
1444       if($from != 0)
1445          $_SESSION['from_date'] = strtotime($from);
1446       else
1447          unset($_SESSION['from_date']);
1448       if($to != 0)
1449          $_SESSION['to_date'] = strtotime($to);
1450       else
1451          unset($_SESSION['to_date']);
1452
1453       if($searchfor != "") {
1454          /* new search, reset the current selected tags */
1455          $_SESSION['selected_tags'] = Array();
1456          foreach($this->avail_tags as $tag) {
1457             if(preg_match('/'. $searchfor .'/i', $this->tags[$tag]))
1458                array_push($_SESSION['selected_tags'], $tag);
1459          }
1460       }
1461
1462    } // startSearch()
1463
1464    /**
1465     * updates sort order in session variable
1466     *
1467     * this function is invoked by RPC and will sort the requested
1468     * sort order in the session variable.
1469     */
1470    public function updateSortOrder($order)
1471    {
1472       if(isset($this->sort_orders[$order])) {
1473          $_SESSION['sort_order'] = $order;
1474          return "ok";
1475       }
1476
1477       return "unkown error";
1478
1479    } // updateSortOrder()
1480
1481    /**
1482     * rotate image
1483     *
1484     * this function rotates the image according the
1485     * specified angel.
1486     */
1487    private function rotateImage($img, $degrees)
1488    {
1489       if(function_exists("imagerotate")) {
1490          $img = imagerotate($img, $degrees, 0);
1491       } else {
1492          function imagerotate($src_img, $angle)
1493          {
1494             $src_x = imagesx($src_img);
1495             $src_y = imagesy($src_img);
1496             if ($angle == 180) {
1497                $dest_x = $src_x;
1498                $dest_y = $src_y;
1499             }
1500             elseif ($src_x <= $src_y) {
1501                $dest_x = $src_y;
1502                $dest_y = $src_x;
1503             }
1504             elseif ($src_x >= $src_y) {
1505                $dest_x = $src_y;
1506                $dest_y = $src_x;
1507             }
1508                
1509             $rotate=imagecreatetruecolor($dest_x,$dest_y);
1510             imagealphablending($rotate, false);
1511                
1512             switch ($angle) {
1513             
1514                case 90:
1515                   for ($y = 0; $y < ($src_y); $y++) {
1516                      for ($x = 0; $x < ($src_x); $x++) {
1517                         $color = imagecolorat($src_img, $x, $y);
1518                         imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
1519                      }
1520                   }
1521                   break;
1522
1523                case 270:
1524                   for ($y = 0; $y < ($src_y); $y++) {
1525                      for ($x = 0; $x < ($src_x); $x++) {
1526                         $color = imagecolorat($src_img, $x, $y);
1527                         imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
1528                      }
1529                   }
1530                   break;
1531
1532                case 180:
1533                   for ($y = 0; $y < ($src_y); $y++) {
1534                      for ($x = 0; $x < ($src_x); $x++) {
1535                         $color = imagecolorat($src_img, $x, $y);
1536                         imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
1537                      }
1538                   }
1539                   break;
1540
1541                default:
1542                   $rotate = $src_img;
1543                   break;
1544             };
1545
1546             return $rotate;
1547
1548          }
1549
1550          $img = imagerotate($img, $degrees);
1551
1552       }
1553
1554       return $img;
1555
1556    } // rotateImage()
1557
1558    /**
1559     * return all assigned tags for the specified photo
1560     */
1561    private function get_photo_tags($idx)
1562    {
1563       $result = $this->db->db_query("
1564          SELECT t.id, t.name
1565          FROM tags t
1566          INNER JOIN photo_tags pt
1567             ON t.id=pt.tag_id
1568          WHERE pt.photo_id='". $idx ."'
1569       ");
1570
1571       $tags = Array();
1572
1573       while($row = $this->db->db_fetch_object($result))
1574          $tags[$row['id']] = $row['name'];
1575
1576       return $tags;
1577
1578    } // get_photo_tags()
1579
1580    /**
1581     * create on-the-fly images with text within
1582     */
1583    public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
1584    {
1585       if (strlen($color) != 6) 
1586          $color = 000000;
1587
1588       $int = hexdec($color);
1589       $h = imagefontheight($font);
1590       $fw = imagefontwidth($font);
1591       $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
1592       $lines = count($txt);
1593       $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
1594       $bg = imagecolorallocate($im, 255, 255, 255);
1595       $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
1596       $y = 0;
1597
1598       foreach ($txt as $text) {
1599          $x = (($w - ($fw * strlen($text))) / 2);
1600          imagestring($im, $font, $x, $y, $text, $color);
1601          $y += ($h + $space);
1602       }
1603
1604       Header("Content-type: image/png");
1605       ImagePng($im);
1606
1607    } // showTextImage()
1608
1609    /**
1610     * check if all requirements are met
1611     */
1612    private function checkRequirements()
1613    {
1614       if(!function_exists("imagecreatefromjpeg")) {
1615          print "PHP GD library extension is missing<br />\n";
1616          $missing = true;
1617       }
1618
1619       if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
1620          print "PHP SQLite3 library extension is missing<br />\n";
1621          $missing = true;
1622       }
1623
1624       /* Check for HTML_AJAX PEAR package, lent from Horde project */
1625       ini_set('track_errors', 1);
1626       @include_once 'HTML/AJAX/Server.php';
1627       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1628          print "PEAR HTML_AJAX package is missing<br />\n";
1629          $missing = true;
1630       }
1631       @include_once 'Calendar/Calendar.php';
1632       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1633          print "PEAR Calendar package is missing<br />\n";
1634          $missing = true;
1635       }
1636       @include_once 'Console/Getopt.php';
1637       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1638          print "PEAR Console_Getopt package is missing<br />\n";
1639          $missing = true;
1640       }
1641       ini_restore('track_errors');
1642
1643       if(isset($missing))
1644          return false;
1645
1646       return true;
1647
1648    } // checkRequirements()
1649
1650    private function _debug($text)
1651    {
1652       if($this->fromcmd) {
1653          print $text;
1654       }
1655
1656    } // _debug()
1657
1658    /**
1659     * check if specified MIME type is supported
1660     */
1661    public function checkifImageSupported($mime)
1662    {
1663       if(in_array($mime, Array("image/jpeg")))
1664          return true;
1665
1666       return false;
1667
1668    } // checkifImageSupported()
1669
1670    public function _error($text)
1671    {
1672       switch($this->cfg->logging) {
1673          case 'display':
1674             print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
1675             print $text;
1676             break;
1677          case 'errorlog':  
1678             error_log($text);
1679             break;
1680          case 'logfile':
1681             error_log($text, 3, $his->cfg->log_file);
1682             break;
1683       }
1684
1685    } // _error()
1686
1687    /**
1688     * output calendard input fields
1689     */
1690    private function get_calendar($mode)
1691    {
1692       $year = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
1693       $month = isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
1694       $day = isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
1695
1696       $output = "<input type=\"text\" size=\"3\" id=\"". $mode ."year\" value=\"". $year ."\"";
1697       if(!isset($_SESSION[$mode .'_date']))
1698          $output.= " disabled=\"disabled\"";
1699       $output.= " />\n";
1700       $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."month\" value=\"". $month ."\"";
1701       if(!isset($_SESSION[$mode .'_date']))
1702          $output.= " disabled=\"disabled\"";
1703       $output.= " />\n";
1704       $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."day\" value=\"". $day ."\"";
1705       if(!isset($_SESSION[$mode .'_date']))
1706          $output.= " disabled=\"disabled\"";
1707       $output.= " />\n";
1708
1709       return $output;
1710
1711    } // get_calendar()
1712
1713    /**
1714     * output calendar matrix
1715     */
1716    public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
1717    {
1718       if (!isset($year)) $year = date('Y');
1719       if (!isset($month)) $month = date('m');
1720       if (!isset($day)) $day = date('d');
1721       $rows = 1;
1722       $cols = 1;
1723       $matrix = Array();
1724
1725       require_once CALENDAR_ROOT.'Month/Weekdays.php';
1726       require_once CALENDAR_ROOT.'Day.php';
1727
1728       // Build the month
1729       $month = new Calendar_Month_Weekdays($year,$month);
1730
1731       // Create links
1732       $prevStamp = $month->prevMonth(true);
1733       $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
1734       $nextStamp = $month->nextMonth(true);
1735       $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
1736
1737       $selectedDays = array (
1738          new Calendar_Day($year,$month,$day),
1739          new Calendar_Day($year,12,25),
1740       );
1741
1742       // Build the days in the month
1743       $month->build($selectedDays);
1744
1745       $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
1746       $this->tmpl->assign('prev_month', $prev);
1747       $this->tmpl->assign('next_month', $next);
1748
1749       while ( $day = $month->fetch() ) {
1750    
1751          if(!isset($matrix[$rows]))
1752             $matrix[$rows] = Array();
1753
1754          $string = "";
1755
1756          $dayStamp = $day->thisDay(true);
1757          $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
1758
1759          // isFirst() to find start of week
1760          if ( $day->isFirst() )
1761             $string.= "<tr>\n";
1762
1763          if ( $day->isSelected() ) {
1764             $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
1765          } else if ( $day->isEmpty() ) {
1766             $string.= "<td>&nbsp;</td>\n";
1767          } else {
1768             $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
1769          }
1770
1771          // isLast() to find end of week
1772          if ( $day->isLast() )
1773             $string.= "</tr>\n";
1774
1775          $matrix[$rows][$cols] = $string;
1776
1777          $cols++;
1778
1779          if($cols > 7) {
1780             $cols = 1;
1781             $rows++;
1782          }
1783       }
1784
1785       $this->tmpl->assign('matrix', $matrix);
1786       $this->tmpl->assign('rows', $rows);
1787       $this->tmpl->show("calendar.tpl");
1788
1789    } // get_calendar_matrix()
1790
1791    /**
1792     * output export page
1793     */
1794    public function getExport($mode)
1795    {
1796       $pictures = $this->getPhotoSelection();
1797       $current_tags = $this->getCurrentTags();  
1798
1799       foreach($pictures as $picture) {
1800
1801          $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
1802          if($current_tags != "") {
1803             $orig_url.= "&tags=". $current_tags;
1804          } 
1805          if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1806             $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
1807          }
1808
1809          $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
1810
1811          switch($mode) {
1812
1813             case 'HTML':
1814                // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
1815                print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
1816                break;
1817                
1818             case 'MoinMoin':
1819                // "[%pictureurl% %thumbnailurl%]"
1820                print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
1821                break;
1822
1823             case 'MoinMoinList':
1824                // " * [%pictureurl% %thumbnailurl%]"
1825                print "&nbsp;" . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
1826                break;
1827          }
1828
1829       }
1830
1831    } // getExport()
1832
1833    /**
1834     * output RSS feed
1835     */
1836    public function getRSSFeed()
1837    {
1838       Header("Content-type: text/xml; charset=utf-8");
1839       print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1840 ?>
1841 <rss version="2.0"
1842    xmlns:media="http://search.yahoo.com/mrss/"
1843    xmlns:dc="http://purl.org/dc/elements/1.1/"
1844  >
1845  <channel>
1846   <title>phpfspot</title>
1847   <description>phpfspot RSS feed</description>
1848   <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
1849   <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
1850   <generator>phpfspot</generator>
1851 <?php
1852
1853       $pictures = $this->getPhotoSelection();
1854       $current_tags = $this->getCurrentTags();  
1855
1856       foreach($pictures as $picture) {
1857
1858          $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
1859          if($current_tags != "") {
1860             $orig_url.= "&tags=". $current_tags;
1861          } 
1862          if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1863             $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
1864          }
1865
1866          $details = $this->get_photo_details($picture);
1867
1868          $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
1869          $thumb_html = htmlspecialchars("
1870 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
1871 <br>
1872 ". $details['description']);
1873
1874          $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
1875          $meta = $this->get_meta_informations($orig_path);
1876          $meta_date = isset($meta['FileDateTime']) ? $meta['FileDateTime'] : filemtime($orig_path);
1877
1878 ?>
1879   <item>
1880    <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
1881    <link><?php print htmlspecialchars($orig_url); ?></link>
1882    <guid><?php print htmlspecialchars($orig_url); ?></guid>
1883    <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $meta_date); ?></dc:date.Taken>
1884    <description>
1885     <?php print $thumb_html; ?> 
1886    </description>
1887    <pubDate><?php print strftime("%a, %d %b %Y %T %z", $meta_date); ?></pubDate>
1888   </item>
1889 <?php
1890
1891       }
1892 ?>
1893  </channel>
1894 </rss>
1895 <?php
1896
1897
1898    } // getExport()
1899
1900  
1901    /**
1902     * return all selected tags as one string
1903     */
1904    private function getCurrentTags()
1905    {
1906       $current_tags = "";
1907       if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
1908          foreach($_SESSION['selected_tags'] as $tag)
1909             $current_tags.= $tag .",";
1910          $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
1911       }
1912       return $current_tags;
1913
1914    } // getCurrentTags()
1915
1916    /**
1917     * return the current photo
1918     */
1919    public function getCurrentPhoto()
1920    {
1921       if(isset($_SESSION['current_photo'])) {
1922          print $_SESSION['current_photo'];
1923       }
1924    } // getCurrentPhoto()
1925
1926    /**
1927     * tells the client browser what to do
1928     *
1929     * this function is getting called via AJAX by the
1930     * client browsers. it will tell them what they have
1931     * to do next. This is necessary for directly jumping
1932     * into photo index or single photo view when the are
1933     * requested with specific URLs
1934     */
1935    public function whatToDo()
1936    {
1937       if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
1938          return "show_photo";
1939       }
1940       elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1941          return "showpi_tags";
1942       }
1943       elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
1944          return "showpi";
1945       }
1946
1947       return "nothing special";
1948
1949    } // whatToDo()
1950
1951    /**
1952     * return the current process-user
1953     */
1954    private function getuid()
1955    {
1956       if($uid = posix_getuid()) {
1957          if($user = posix_getpwuid($uid)) {
1958             return $user['name'];
1959          }
1960       }
1961    
1962       return 'n/a';
1963    
1964    } // getuid()
1965
1966    /**
1967     * returns a select-dropdown box to select photo index sort parameters
1968     */
1969    public function smarty_sort_select_list($params, &$smarty)
1970    {
1971       $output = "";
1972
1973       foreach($this->sort_orders as $key => $value) {
1974          $output.= "<option value=\"". $key ."\"";
1975          if($key == $_SESSION['sort_order']) {
1976             $output.= " selected=\"selected\"";
1977          }
1978          $output.= ">". $value ."</option>";
1979       }
1980
1981       return $output;
1982
1983    } // smarty_sort_select_list()
1984
1985    /**
1986     * returns the currently selected sort order
1987     */ 
1988    private function get_sort_order()
1989    {
1990       switch($_SESSION['sort_order']) {
1991          case 'date_asc':
1992             return " ORDER BY p.time ASC";
1993             break;
1994          case 'date_desc':
1995             return " ORDER BY p.time DESC";
1996             break;
1997          case 'name_asc':
1998             if($this->dbver < 9) {
1999                return " ORDER BY p.name ASC";
2000             }
2001             else {
2002                return " ORDER BY basename(p.uri) ASC";
2003             }
2004             break;
2005          case 'name_desc':
2006             if($this->dbver < 9) {
2007                return " ORDER BY p.name DESC";
2008             }
2009             else {
2010                return " ORDER BY basename(p.uri) DESC";
2011             }
2012             break;
2013       }
2014
2015    } // get_sort_order()
2016
2017    /***
2018      * return the next to be shown slide show image
2019      *
2020      * this function returns the URL of the next image
2021      * in the slideshow sequence.
2022      */
2023    public function getNextSlideShowImage()
2024    {
2025       $all_photos = $this->getPhotoSelection();
2026
2027       if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1) 
2028          $_SESSION['slideshow_img'] = 0;
2029       else
2030          $_SESSION['slideshow_img']++;
2031
2032       return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2033
2034    } // getNextSlideShowImage()
2035
2036    /***
2037      * return the previous to be shown slide show image
2038      *
2039      * this function returns the URL of the previous image
2040      * in the slideshow sequence.
2041      */
2042    public function getPrevSlideShowImage()
2043    {
2044       $all_photos = $this->getPhotoSelection();
2045
2046       if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
2047          $_SESSION['slideshow_img'] = 0;
2048       else
2049          $_SESSION['slideshow_img']--;
2050
2051       return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2052
2053    } // getPrevSlideShowImage()
2054
2055    public function resetSlideShow()
2056    {
2057       if(isset($_SESSION['slideshow_img']))
2058          unset($_SESSION['slideshow_img']);
2059    } // resetSlideShow()
2060    
2061    /***
2062      * get random photo
2063      *
2064      * this function will get all photos from the fspot
2065      * database and randomly return ONE entry
2066      *
2067      * saddly there is yet no sqlite3 function which returns
2068      * the bulk result in array, so we have to fill up our
2069      * own here.
2070      */ 
2071    public function get_random_photo()
2072    {
2073       $all = Array();
2074
2075       $result = $this->db->db_query("
2076          SELECT id
2077          FROM photos
2078       ");
2079       
2080       while($row = $this->db->db_fetch_object($result)) {
2081          array_push($all, $row['id']);
2082       }
2083
2084       return $all[array_rand($all)];
2085
2086    } // get_random_photo()
2087
2088    /**
2089     * validates provided date
2090     *
2091     * this function validates if the provided date
2092     * contains a valid date and will return true 
2093     * if it is.
2094     */
2095    public function isValidDate($date_str)
2096    {
2097       $timestamp = strtotime($date_str);
2098    
2099       if(is_numeric($timestamp))
2100          return true;
2101       
2102       return false;
2103
2104    } // isValidDate()
2105
2106    /**
2107     * timestamp to string conversion
2108     */
2109    private function ts2str($timestamp)
2110    {
2111       return strftime("%Y-%m-%d", $timestamp);
2112    } // ts2str()
2113
2114    private function extractTags($tags_str)
2115    {
2116       $not_validated = split(',', $_GET['tags']);
2117       $validated = array();
2118
2119       foreach($not_validated as $tag) {
2120          if(is_numeric($tag))
2121             array_push($validated, $tag);
2122       }
2123    
2124       return $validated;
2125    
2126    } // extractTags()
2127
2128    /**
2129     * returns the full path to a thumbnail
2130     */
2131    public function get_thumb_path($width, $photo)
2132    {
2133       $md5 = $this->getMD5($photo);
2134       $sub_path = substr($md5, 0, 2);
2135       return $this->cfg->thumb_path
2136          . "/"
2137          . $sub_path
2138          . "/"
2139          . $width
2140          . "_"
2141          . $md5;
2142
2143    } // get_thumb_path()
2144
2145    /**
2146     * returns server's virtual host name
2147     */
2148    private function get_server_name()
2149    {
2150       return $_SERVER['SERVER_NAME'];
2151    } // get_server_name()
2152
2153    /**
2154     * returns type of webprotocol which is
2155     * currently used
2156     */
2157    private function get_web_protocol()
2158    {
2159       if(!isset($_SERVER['HTTPS']))
2160          return "http";
2161       else
2162          return "https";
2163    } // get_web_protocol()
2164
2165    /**
2166     * return url to this phpfspot installation
2167     */
2168    private function get_phpfspot_url()
2169    {
2170       return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
2171    } // get_phpfspot_url()
2172    
2173    /**
2174     * check file exists and is readable
2175     *
2176     * returns true, if everything is ok, otherwise false
2177     * if $silent is not set, this function will output and
2178     * error message
2179     */
2180    private function check_readable($file, $silent = null)
2181    {
2182       if(!file_exists($file)) {
2183          if(!isset($silent))
2184             print "File \"". $file ."\" does not exist.\n";
2185          return false;
2186       }
2187
2188       if(!is_readable($file)) {
2189          if(!isset($silent))
2190             print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
2191          return false;
2192       }
2193
2194       return true;
2195
2196    } // check_readable()
2197
2198    /**
2199     * check if all needed indices are present
2200     *
2201     * this function checks, if some needed indices are already
2202     * present, or if not, create them on the fly. they are
2203     * necessary to speed up some queries like that one look for
2204     * all tags, when show_tags is specified in the configuration.
2205     */
2206    private function checkDbIndices()
2207    {
2208       $result = $this->db->db_exec("
2209          CREATE INDEX IF NOT EXISTS
2210             phototag
2211          ON
2212             photo_tags
2213                (photo_id, tag_id)
2214       ");
2215
2216    } // checkDbIndices()
2217
2218    /**
2219     * retrive F-Spot database version
2220     *
2221     * this function will return the F-Spot database version number
2222     * It is stored within the sqlite3 database in the table meta
2223     */
2224    public function getFspotDBVersion()
2225    {
2226       if($result = $this->db->db_fetchSingleRow("
2227          SELECT data as version
2228          FROM meta
2229          WHERE
2230             name LIKE 'F-Spot Database Version'
2231       "))
2232          return $result['version'];
2233
2234       return null;
2235
2236    } // getFspotDBVersion()
2237
2238    /**
2239     * parse the provided URI and will returned the
2240     * requested chunk
2241     */
2242    public function parse_uri($uri, $mode)
2243    {
2244       if(($components = parse_url($uri)) !== false) {
2245
2246          switch($mode) {
2247             case 'filename':
2248                return basename($components['path']);
2249                break;
2250             case 'dirname':
2251                return dirname($components['path']);
2252                break;
2253             case 'fullpath':
2254                return $components['path'];
2255                break;
2256          }
2257       }
2258
2259       return $uri;
2260
2261    } // parse_uri()
2262
2263 } // class PHPFSPOT
2264
2265 ?>