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