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