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