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