issue121, initial support for F-Spot's rating system and sort-by rating
[phpfspot.git] / phpfspot.class.php
1 <?php
2
3 /***************************************************************************
4  *
5  * phpfspot, presents your F-Spot photo collection in Web browsers.
6  *
7  * Copyright (c) by Andreas Unterkircher
8  *
9  *  This program is free software; you can redistribute it and/or modify
10  *  it under the terms of the GNU General Public License as published by
11  *  the Free Software Foundation; either version 2 of the License, or
12  *  any later version.
13  *
14  *  This program is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License
20  *  along with this program; if not, write to the Free Software
21  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22  *
23  ***************************************************************************/
24
25 require_once "phpfspot_cfg.php";
26 require_once "phpfspot_db.php";
27
28 /**
29  * PHPFSPOT main class
30  *
31  * this class contains the most functions which will to the major
32  * work for phpfspot.
33  *
34  * @package phpfspot
35  */
36 class PHPFSPOT {
37
38    /**
39      * phpfspot configuration
40      * @access public
41      * @see PHPFSPOT_CFG()
42      * @var PHPFSPOT_CFG
43      */
44    var $cfg;
45
46    /**
47      * SQLite database handle to f-spot database
48      * @see PHPFSPOT_DB()
49      * @access public
50      * @var PHPFSPOT_DB
51      */
52    var $db;
53
54    /**
55      * SQLite database handle to phpfspot database
56      * @see PHPFSPOT_DB()
57      * @access public
58      * @var PHPFSPOT_DB
59      */
60    var $cfg_db;
61
62    /**
63     * Smarty template engine
64     * @link http://smarty.php.net smarty.php.net
65     * @see PHPFSPOT_TMPL()
66     * @access public
67     * @var PHPFSPOT_TMPL
68     */
69    var $tmpl;
70
71    /**
72     * full tag - list
73     * @access public
74     * @var array
75     */
76    var $tags;
77
78    /**
79     * list of available, not-selected, tags
80     * @access public
81     * @var array
82     */
83    var $avail_tags;
84
85    /**
86     * true if runtime error occued
87     * @access private
88     * @var boolean
89     */
90    private $runtime_error = false;
91
92    /**
93     * F-Spot database version
94     * @access private
95     * @var integer
96     */
97    private $dbver;
98
99    /**
100     * class constructor ($cfg, $db, $cfg_db, $tmpl, $db_ver)
101     *
102     * this function will be called on class construct
103     * and will check requirements, loads configuration,
104     * open databases and start the user session
105     */
106    public function __construct()
107    {
108       /**
109        * register PHPFSPOT class global
110        *
111        * @global PHPFSPOT $GLOBALS['phpfspot']
112        * @name $phpfspot
113        */
114       $GLOBALS['phpfspot'] =& $this;
115
116       $this->cfg = new PHPFSPOT_CFG;
117
118       /* verify config settings */
119       if($this->check_config_options()) {
120          exit(1);
121       }
122
123       /* set application name and version information */
124       $this->cfg->product = "phpfspot";
125       $this->cfg->version = "1.5";
126
127       $this->sort_orders= array(
128          'date_asc' => 'Date &uarr;',
129          'date_desc' => 'Date &darr;',
130          'name_asc' => 'Name &uarr;',
131          'name_desc' => 'Name &darr;',
132          'tags_asc' => 'Tags &uarr;',
133          'tags_desc' => 'Tags &darr;',
134       );
135
136       /* Check necessary requirements */
137       if(!$this->check_requirements()) {
138          exit(1);
139       }
140
141       /******* Opening F-Spot's sqlite database *********/
142
143       /* Check if database file is writeable */
144       if(!is_writeable($this->cfg->fspot_db)) {
145          print $this->cfg->fspot_db ." is not writeable for user ". $this->getuid() ."\n";
146          exit(1);
147       }
148
149       /* open the database */
150       $this->db  = new PHPFSPOT_DB($this, $this->cfg->fspot_db);
151
152       /* change sqlite temp directory, if requested */
153       if(isset($this->cfg->sqlite_temp_dir)) {
154          $this->db->db_exec("
155             PRAGMA
156                temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
157          ");
158       }
159
160       /* get F-Spot database version */
161       $this->dbver = $this->getFspotDBVersion();
162
163       if(!is_writeable($this->cfg->base_path ."/templates_c")) {
164          print $this->cfg->base_path ."/templates_c: directory is not writeable for user ". $this->getuid() ."\n";
165          exit(1);
166       }
167
168       if(!is_writeable($this->cfg->thumb_path)) {
169          print $this->cfg->thumb_path .": directory is not writeable for user ". $this->getuid() ."\n";
170          exit(1);
171       }
172
173       /******* Opening phpfspot's sqlite database *********/
174
175       /* Check if directory where the database file is stored is writeable  */
176       if(!is_writeable(dirname($this->cfg->phpfspot_db))) {
177          print dirname($this->cfg->phpfspot_db) .": directory is not writeable for user ". $this->getuid() ."\n";
178          exit(1);
179       }
180
181       /* Check if database file is writeable */
182       if(!is_writeable($this->cfg->phpfspot_db)) {
183          print $this->cfg->phpfspot_db ." is not writeable for user ". $this->getuid() ."\n";
184          exit(1);
185       }
186
187       /* open the database */
188       $this->cfg_db = new PHPFSPOT_DB($this, $this->cfg->phpfspot_db);
189
190       /* change sqlite temp directory, if requested */
191       if(isset($this->cfg->sqlite_temp_dir)) {
192          $this->cfg_db->db_exec("
193             PRAGMA
194                temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
195          ");
196       }
197
198       /* Check if some tables need to be created */
199       $this->check_config_table();
200
201       /* overload Smarty class with our own template handler */
202       require_once "phpfspot_tmpl.php";
203       $this->tmpl = new PHPFSPOT_TMPL();
204
205       $this->tmpl->assign('web_path', $this->cfg->web_path);
206
207       /* Starting with F-Spot 0.4.2, the rating-feature was available */
208       if($this->dbver > 10) {
209          $this->sort_orders = array_merge($this->sort_orders, array(
210             'rate_asc' => 'Rate &uarr;',
211             'rate_desc' => 'Rate &darr;',
212          ));
213       }
214
215       /* check if all necessary indices exist */
216       $this->checkDbIndices();
217
218       /* if session is not yet started, do it now */
219       if(session_id() == "")
220          session_start();
221
222       if(!isset($_SESSION['tag_condition']))
223          $_SESSION['tag_condition'] = 'or';
224
225       if(!isset($_SESSION['sort_order']))
226          $_SESSION['sort_order'] = 'date_desc';
227
228       if(!isset($_SESSION['searchfor_tag']))
229          $_SESSION['searchfor_tag'] = '';
230
231       // if begin_with is still set but thumbs_per_page is now 0, unset it
232       if(isset($_SESSION['begin_with']) && $this->cfg->thumbs_per_page == 0)
233          unset($_SESSION['begin_with']);
234
235       // if user-friendly-url's are enabled, set also a flag for the template handler
236       if($this->is_user_friendly_url()) {
237          $this->tmpl->assign('user_friendly_url', 'true');
238       }
239
240    } // __construct()
241
242    public function __destruct()
243    {
244
245    } // __destruct()
246
247    /**
248     * show - generate html output
249     *
250     * this function can be called after the constructor has
251     * prepared everyhing. it will load the index.tpl smarty
252     * template. if necessary it will registere pre-selects
253     * (photo index, photo, tag search, date search) into
254     * users session.
255     */
256    public function show()
257    {
258       $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
259       $this->tmpl->assign('page_title', $this->cfg->page_title);
260       $this->tmpl->assign('current_condition', $_SESSION['tag_condition']);
261       $this->tmpl->assign('template_path', 'themes/'. $this->cfg->theme_name);
262
263       /* parse URL */
264       if($this->is_user_friendly_url()) {
265          $content = $this->parse_user_friendly_url($_SERVER['REQUEST_URI']);
266       }
267
268       if(isset($_GET['mode'])) {
269
270          $_SESSION['start_action'] = $_GET['mode'];
271
272          switch($_GET['mode']) {
273             case 'showpi':
274                if(isset($_GET['tags'])) {
275                   $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
276                }
277                if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
278                   $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
279                }
280                if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
281                   $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
282                }
283                break;
284             case 'showp':
285                if(isset($_GET['tags'])) {
286                   $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
287                   $_SESSION['start_action'] = 'showp';
288                }
289                if(isset($_GET['id']) && is_numeric($_GET['id'])) {
290                   $_SESSION['current_photo'] = $_GET['id'];
291                   $_SESSION['start_action'] = 'showp';
292                }
293                if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
294                   $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
295                }
296                if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
297                   $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
298                }
299                break;
300             case 'export':
301                $this->tmpl->show("export.tpl");
302                return;
303                break;
304             case 'slideshow':
305                $this->tmpl->show("slideshow.tpl");
306                return;
307                break;
308             case 'rss':
309                if(isset($_GET['tags'])) {
310                   $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
311                }
312                if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
313                   $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
314                }
315                if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
316                   $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
317                }
318                $this->getRSSFeed();
319                return;
320                break;
321          }
322       }
323
324       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date']))
325          $this->tmpl->assign('date_search_enabled', true);
326
327       $this->tmpl->register_function("sort_select_list", array(&$this, "smarty_sort_select_list"), false);
328       $this->tmpl->assign('search_from_date', $this->get_calendar('from'));
329       $this->tmpl->assign('search_to_date', $this->get_calendar('to'));
330
331       $this->tmpl->assign('preset_selected_tags', $this->getSelectedTags());
332       $this->tmpl->assign('preset_available_tags', $this->getAvailableTags());
333
334       if(!isset($content)) {
335          if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags']))
336             $this->tmpl->assign('initial_content', $this->showPhotoIndex());
337          else
338             $this->tmpl->assign('initial_content', $this->tmpl->fetch('welcome.tpl'));
339       }
340       else
341          $this->tmpl->assign('initial_content', $content);
342
343       $this->tmpl->show("index.tpl");
344
345    } // show()
346
347    /**
348     * get_tags - grab all tags of f-spot's database
349     *
350     * this function will get all available tags from
351     * the f-spot database and store them within two
352     * arrays within this class for later usage. in
353     * fact, if the user requests (hide_tags) it will
354     * opt-out some of them.
355     *
356     * this function is getting called once by show()
357     */
358    private function get_tags()
359    {
360       $this->avail_tags = Array();
361       $count = 0;
362    
363       if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
364          $query_str="
365             SELECT
366                DISTINCT t1.id as id, t1.name as name
367             FROM  
368                photo_tags pt1
369             INNER JOIN photo_tags
370                pt2 ON pt1.photo_id=pt2.photo_id
371             INNER JOIN tags t1
372                ON t1.id=pt1.tag_id
373             INNER JOIN tags t2
374                ON t2.id=pt2.tag_id
375             WHERE
376                t2.name IN  ('".implode("','",$this->cfg->show_tags)."')
377             ORDER BY
378                t1.sort_priority ASC";
379
380          $result = $this->db->db_query($query_str);
381       }
382       else
383       {
384          $result = $this->db->db_query("
385             SELECT id,name
386             FROM tags
387             ORDER BY sort_priority ASC
388          ");
389       }
390       
391       while($row = $this->db->db_fetch_object($result)) {
392
393          $tag_id = $row['id'];
394          $tag_name = $row['name'];
395
396          /* if the user has specified to ignore this tag in phpfspot's
397             configuration, ignore it here so it does not get added to
398             the tag list.
399          */
400          if(in_array($row['name'], $this->cfg->hide_tags))
401             continue;
402
403          /* if you include the following if-clause and the user has specified
404             to only show certain tags which are specified in phpfspot's
405             configuration, ignore all others so they will not be added to the
406             tag list.
407          if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags) &&
408             !in_array($row['name'], $this->cfg->show_tags))
409             continue;
410          */
411
412          $this->tags[$tag_id] = $tag_name; 
413          $this->avail_tags[$count] = $tag_id;
414          $count++;
415
416       }
417
418    } // get_tags()
419
420    /** 
421     * extract all photo details
422     * 
423     * retrieve all available details from f-spot's
424     * database and return them as object
425     * @param integer $idx
426     * @return object|null
427     */
428    public function get_photo_details($idx)
429    {
430       if($this->dbver < 9) {
431          $query_str = "
432             SELECT p.id, p.name, p.time, p.directory_path, p.description
433             FROM photos p
434          ";
435       }
436       else {
437          /* till F-Spot version 0.4.1 */
438          if($this->dbver < 11) {
439             $query_str = "
440                SELECT p.id, p.uri, p.time, p.description
441                FROM photos p
442             ";
443          }
444          else {
445             $query_str = "
446                SELECT p.id, p.uri, p.time, p.description, p.rating
447                FROM photos p
448             ";
449          }
450       }
451
452       /* if show_tags is set, only return details for photos which
453          are specified to be shown
454       */
455       if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
456          $query_str.= "
457             INNER JOIN photo_tags pt
458                ON p.id=pt.photo_id
459             INNER JOIN tags t
460                ON pt.tag_id=t.id
461             WHERE p.id='". $idx ."'
462             AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
463       }
464       else {
465          $query_str.= "
466             WHERE p.id='". $idx ."'
467          ";
468       }
469
470       if($result = $this->db->db_query($query_str)) {
471
472          $row = $this->db->db_fetch_object($result);
473
474          if($this->dbver < 9) {
475             $row['uri'] = "file://". $row['directory_path'] ."/". $row['name'];
476          }
477
478          return $row;
479
480       }
481    
482       return null;
483
484    } // get_photo_details
485
486    /**
487     * returns aligned photo names 
488     *
489     * this function returns aligned (length) names for
490     * an specific photo. If the length of the name exceeds
491     * $limit the name will be shrinked (...)
492     * @param integer $idx
493     * @param integer $limit
494     * @return string|null
495     */
496    public function getPhotoName($idx, $limit = 0)
497    {
498       if($details = $this->get_photo_details($idx)) {
499          if($long_name = $this->parse_uri($details['uri'], 'filename')) {
500             $name = $this->shrink_text($long_name, $limit);
501             return $name;
502          }
503       }
504
505       return null;
506
507    } // getPhotoName()
508
509    /**
510     * shrink text according provided limit
511     *
512     * If the length of the name exceeds $limit the
513     * text will be shortend and some content in between
514     * will be replaced with "..." 
515     * @param string $ext
516     * @param integer $limit
517     * @return string
518     */
519    private function shrink_text($text, $limit)
520    {
521       if($limit != 0 && strlen($text) > $limit) {
522          $text = substr($text, 0, $limit-5) ."...". substr($text, -($limit-5));
523       }
524
525       return $text;
526
527    } // shrink_text();
528
529    /**
530     * translate f-spoth photo path
531     * 
532     * as the full-qualified path recorded in the f-spot database
533     * is usally not the same as on the webserver, this function
534     * will replace the path with that one specified in the cfg
535     * @param string $path
536     * @return string
537     */
538    public function translate_path($path)
539    {  
540       return str_replace($this->cfg->path_replace_from, $this->cfg->path_replace_to, $path);
541
542    } // translate_path
543
544    /**
545     * control HTML ouput for a single photo
546     *
547     * this function provides all the necessary information
548     * for the single photo template.
549     * @param integer photo
550     */
551    public function showPhoto($photo)
552    {
553       /* get all photos from the current photo selection */
554       $all_photos = $this->getPhotoSelection();
555       $count = count($all_photos);
556
557       for($i = 0; $i < $count; $i++) {
558          
559          // $get_next will be set, when the photo which has to
560          // be displayed has been found - this means that the
561          // next available is in fact the NEXT image (for the
562          // navigation icons) 
563          if(isset($get_next)) {
564             $next_img = $all_photos[$i];
565             break;
566          }
567
568          /* the next photo is our NEXT photo */
569          if($all_photos[$i] == $photo) {
570             $get_next = 1;
571          }
572          else {
573             $previous_img = $all_photos[$i];
574          }
575
576          if($photo == $all_photos[$i]) {
577                $current = $i;
578          }
579       }
580
581       $details = $this->get_photo_details($photo);
582
583       if(!$details) {
584          print "error";
585          return;
586       }
587
588       $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
589       $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
590
591       if(!file_exists($orig_path)) {
592          $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
593          return;
594       }
595
596       if(!is_readable($orig_path)) {
597          $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
598          return;
599       }
600
601       /* If the thumbnail doesn't exist yet, try to create it */
602       if(!file_exists($thumb_path)) {
603          $this->gen_thumb($photo, true);
604          $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
605       }
606
607       /* get mime-type, height and width from the original photo */
608       $info = getimagesize($orig_path);
609
610       /* get EXIF information if JPEG */
611       if($info['mime'] == "image/jpeg") {
612          $meta = $this->get_meta_informations($orig_path);
613       }
614
615       /* If EXIF data are available, use them */
616       if(isset($meta['ExifImageWidth'])) {
617          $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
618       } else {
619          $meta_res = $info[0] ."x". $info[1]; 
620       }
621
622       $meta_date = isset($meta['FileDateTime']) ? strftime("%a %x %X", $meta['FileDateTime']) : "n/a";
623       $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
624       $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
625
626       $extern_link = "index.php?mode=showp&id=". $photo;
627       $current_tags = $this->getCurrentTags();
628       if($current_tags != "") {
629          $extern_link.= "&tags=". $current_tags;
630       }
631       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
632          $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
633       }
634
635       $this->tmpl->assign('extern_link', $extern_link);
636
637       if(!file_exists($thumb_path)) {
638          $this->_error("Can't open file ". $thumb_path ."\n");
639          return;
640       }
641
642       $info_thumb = getimagesize($thumb_path);
643
644       $this->tmpl->assign('description', $details['description']);
645       $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
646
647       $this->tmpl->assign('width', $info_thumb[0]);
648       $this->tmpl->assign('height', $info_thumb[1]);
649       $this->tmpl->assign('ExifMadeOn', $meta_date);
650       $this->tmpl->assign('ExifMadeWith', $meta_make);
651       $this->tmpl->assign('ExifOrigResolution', $meta_res);
652       $this->tmpl->assign('ExifFileSize', $meta_size);
653  
654       if($this->is_user_friendly_url()) {
655          $this->tmpl->assign('image_url', '/photo/'. $photo ."/". $this->cfg->photo_width);
656          $this->tmpl->assign('image_url_full', '/photo/'. $photo);
657       }
658       else {
659          $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&amp;width=". $this->cfg->photo_width);
660          $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
661       }
662
663       $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
664
665       $this->tmpl->assign('tags', $this->get_photo_tags($photo));
666       $this->tmpl->assign('current_page', $this->getCurrentPage($current, $count));
667       $this->tmpl->assign('current_img', $photo);
668
669       if($previous_img) {
670          $this->tmpl->assign('previous_url', "javascript:showPhoto(". $previous_img .");");
671          $this->tmpl->assign('prev_img', $previous_img);
672       }
673
674       if($next_img) {
675          $this->tmpl->assign('next_url', "javascript:showPhoto(". $next_img .");");
676          $this->tmpl->assign('next_img', $next_img);
677       }
678       $this->tmpl->assign('mini_width', $this->cfg->mini_width);
679       $this->tmpl->assign('photo_width', $this->cfg->photo_width);
680       $this->tmpl->assign('photo_number', $i);
681       $this->tmpl->assign('photo_count', count($all_photos));
682
683       return $this->tmpl->fetch("single_photo.tpl");
684
685    } // showPhoto()
686
687    /**
688     * all available tags and tag cloud
689     *
690     * this function outputs all available tags (time ordered)
691     * and in addition output them as tag cloud (tags which have
692     * many photos will appears more then others)
693     */
694    public function getAvailableTags()
695    {
696       /* retrive tags from database */
697       $this->get_tags();
698
699       $output = "";
700
701       $result = $this->db->db_query("
702          SELECT tag_id as id, count(tag_id) as quantity
703          FROM photo_tags
704          INNER JOIN tags t
705             ON t.id = tag_id
706          GROUP BY tag_id
707          ORDER BY t.name ASC
708       ");
709
710       $tags = Array();
711
712       while($row = $this->db->db_fetch_object($result)) {
713          $tags[$row['id']] = $row['quantity'];
714       }
715
716       // change these font sizes if you will
717       $max_size = 125; // max font size in %
718       $min_size = 75; // min font size in %
719
720       // color
721       $max_sat = hexdec('cc');
722       $min_sat = hexdec('44');
723
724       // get the largest and smallest array values
725       $max_qty = max(array_values($tags));
726       $min_qty = min(array_values($tags));
727
728       // find the range of values
729       $spread = $max_qty - $min_qty;
730       if (0 == $spread) { // we don't want to divide by zero
731          $spread = 1;
732       }
733
734       // determine the font-size increment
735       // this is the increase per tag quantity (times used)
736       $step = ($max_size - $min_size)/($spread);
737       $step_sat = ($max_sat - $min_sat)/($spread);
738
739       // loop through our tag array
740       foreach ($tags as $key => $value) {
741
742          if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
743             continue;
744
745          // calculate CSS font-size
746          // find the $value in excess of $min_qty
747          // multiply by the font-size increment ($size)
748          // and add the $min_size set above
749          $size = $min_size + (($value - $min_qty) * $step);
750           // uncomment if you want sizes in whole %:
751          $size = ceil($size);
752
753          $color = $min_sat + ($value - $min_qty) * $step_sat;
754
755          $r = '44';
756          $g = dechex($color);
757          $b = '88';
758
759          if(isset($this->tags[$key])) {
760             if($this->is_user_friendly_url())
761                $output.= "<a href=\"". $this->cfg->web_path ."/tag/". $key ."\" onclick=\"Tags('add', ". $key ."); return false;\" class=\"tag\" style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\">". $this->tags[$key] ."</a>, ";
762             else
763                $output.= "<a href=\"". $this->cfg->web_path ."/index.php?mode=showpi\" onclick=\"Tags('add', ". $key ."); return false;\" class=\"tag\" style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\">". $this->tags[$key] ."</a>, ";
764          }
765       }
766
767       $output = substr($output, 0, strlen($output)-2);
768       return $output;
769
770    } // getAvailableTags()
771
772    /**
773     * output all selected tags
774     *
775     * this function output all tags which have been selected
776     * by the user. the selected tags are stored in the 
777     * session-variable $_SESSION['selected_tags']
778     * @return string
779     */
780    public function getSelectedTags($type = 'link')
781    {
782       /* retrive tags from database */
783       $this->get_tags();
784
785       $output = "";
786
787       foreach($this->avail_tags as $tag)
788       {
789          // return all selected tags
790          if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
791
792             switch($type) {
793                default:
794                case 'link':
795                   $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
796                   break;
797                case 'img':
798                   $output.= "
799                   <div class=\"tagresulttag\">
800                    <a href=\"javascript:Tags('del', ". $tag .");\" title=\"". $this->tags[$tag] ."\">
801                     <img src=\"". $this->cfg->web_path ."/phpfspot_img.php?tagidx=". $tag ."\" />
802                    </a>
803                   </div>
804                   ";
805                   break;
806             }
807          }
808       }
809
810       if($output != "") {
811          $output = substr($output, 0, strlen($output)-2);
812          return $output;
813       }
814       else {
815          return "no tags selected";
816       }
817
818    } // getSelectedTags()
819
820    /**
821     * add tag to users session variable
822     *
823     * this function will add the specified to users current
824     * tag selection. if a date search has been made before
825     * it will be now cleared
826     * @return string
827     */
828    public function addTag($tag)
829    {
830       if(!isset($_SESSION['selected_tags']))
831          $_SESSION['selected_tags'] = Array();
832
833       if(isset($_SESSION['searchfor_tag']))
834          unset($_SESSION['searchfor_tag']);
835
836       // has the user requested to hide this tag, and still someone,
837       // somehow tries to add it, don't allow this.
838       if(!isset($this->cfg->hide_tags) &&
839          in_array($this->get_tag_name($tag), $this->cfg->hide_tags))
840          return "ok";
841
842       if(!in_array($tag, $_SESSION['selected_tags']))
843          array_push($_SESSION['selected_tags'], $tag);
844
845       return "ok";
846    
847    } // addTag()
848
849    /**
850     * remove tag to users session variable
851     *
852     * this function removes the specified tag from
853     * users current tag selection
854     * @param string $tag
855     * @return string
856     */
857    public function delTag($tag)
858    {
859       if(isset($_SESSION['searchfor_tag']))
860          unset($_SESSION['searchfor_tag']);
861
862       if(isset($_SESSION['selected_tags'])) {
863          $key = array_search($tag, $_SESSION['selected_tags']);
864          unset($_SESSION['selected_tags'][$key]);
865          sort($_SESSION['selected_tags']);
866       }
867
868       return "ok";
869
870    } // delTag()
871
872    /**
873     * reset tag selection
874     *
875     * if there is any tag selection, it will be
876     * deleted now
877     */
878    public function resetTags()
879    {
880       if(isset($_SESSION['selected_tags']))
881          unset($_SESSION['selected_tags']);
882
883    } // resetTags()
884
885    /**
886     * returns the value for the autocomplete tag-search
887     * @return string
888     */
889    public function get_xml_tag_list()
890    {
891       if(!isset($_GET['search']) || !is_string($_GET['search']))
892          $_GET['search'] = '';
893       
894       $length = 15;
895       $i = 1;
896          
897       /* retrive tags from database */
898       $this->get_tags();
899
900       $matched_tags = Array();
901
902       header("Content-Type: text/xml");
903
904       $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
905       $string.= "<results>\n";
906
907       foreach($this->avail_tags as $tag)
908       {
909          if(!empty($_GET['search']) &&
910             preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) &&
911             count($matched_tags) < $length) {
912
913             $count = $this->get_num_photos($tag);
914
915             if($count == 1) {
916                $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n";
917             }
918             else {
919                $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n";
920
921             }
922             $i++;
923          }
924
925          /* if we have collected enough items, break out */
926          if(count($matched_tags) >= $length)
927             break;
928       }
929
930       $string.= "</results>\n";
931
932       return $string;
933
934    } // get_xml_tag_list()
935
936
937    /**
938     * reset single photo
939     *
940     * if a specific photo was requested (external link)
941     * unset the session variable now
942     */
943    public function resetPhotoView()
944    {
945       if(isset($_SESSION['current_photo']))
946          unset($_SESSION['current_photo']);
947
948    } // resetPhotoView();
949
950    /**
951     * reset tag search
952     *
953     * if any tag search has taken place, reset it now
954     */
955    public function resetTagSearch()
956    {
957       if(isset($_SESSION['searchfor_tag']))
958          unset($_SESSION['searchfor_tag']);
959
960    } // resetTagSearch()
961
962    /**
963     * reset name search
964     *
965     * if any name search has taken place, reset it now
966     */
967    public function resetNameSearch()
968    {
969       if(isset($_SESSION['searchfor_name']))
970          unset($_SESSION['searchfor_name']);
971
972    } // resetNameSearch()
973
974    /**
975     * reset date search
976     *
977     * if any date search has taken place, reset
978     * it now
979     */
980    public function resetDateSearch()
981    {
982       if(isset($_SESSION['from_date']))
983          unset($_SESSION['from_date']);
984       if(isset($_SESSION['to_date']))
985          unset($_SESSION['to_date']);
986
987    } // resetDateSearch();
988
989    /**
990     * return all photo according selection
991     *
992     * this function returns all photos based on
993     * the tag-selection, tag- or date-search.
994     * the tag-search also has to take care of AND
995     * and OR conjunctions
996     * @return array
997     */
998    public function getPhotoSelection()
999    {  
1000       $matched_photos = Array();
1001       $additional_where_cond = "";
1002
1003       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1004          $from_date = $_SESSION['from_date'];
1005          $to_date = $_SESSION['to_date'];
1006          $additional_where_cond.= "
1007                p.time>='". $from_date ."'
1008             AND
1009                p.time<='". $to_date ."'
1010          ";
1011       } 
1012
1013       if(isset($_SESSION['searchfor_name'])) {
1014          if($this->dbver < 9) {
1015             $additional_where_cond.= "
1016                   (
1017                         p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
1018                      OR
1019                         p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
1020                   )
1021             ";
1022          }
1023          else {
1024             $additional_where_cond.= "
1025                   (
1026                         basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
1027                      OR
1028                         p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
1029                   )
1030             ";
1031          }
1032       }
1033
1034       if(isset($_SESSION['sort_order'])) {
1035          $order_str = $this->get_sort_order();
1036       }
1037
1038       /* return a search result */
1039       if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
1040          $query_str = "
1041             SELECT DISTINCT pt1.photo_id
1042                FROM photo_tags pt1
1043             INNER JOIN photo_tags pt2
1044                ON pt1.photo_id=pt2.photo_id
1045             INNER JOIN tags t
1046                ON pt1.tag_id=t.id
1047             INNER JOIN photos p
1048                ON pt1.photo_id=p.id
1049             INNER JOIN tags t2
1050                ON pt2.tag_id=t2.id
1051             WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
1052
1053          if(isset($additional_where_cond) && !empty($additional_where_cond))
1054             $query_str.= "AND ". $additional_where_cond ." ";
1055
1056          if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1057             $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
1058          }
1059          
1060          if(isset($order_str))
1061             $query_str.= $order_str;
1062
1063          $result = $this->db->db_query($query_str);
1064          while($row = $this->db->db_fetch_object($result)) {
1065             array_push($matched_photos, $row['photo_id']);
1066          }
1067          return $matched_photos;
1068       }
1069
1070       /* return according the selected tags */
1071       if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1072          $selected = "";
1073          foreach($_SESSION['selected_tags'] as $tag)
1074             $selected.= $tag .",";
1075          $selected = substr($selected, 0, strlen($selected)-1);
1076
1077          /* photo has to match at least on of the selected tags */
1078          if($_SESSION['tag_condition'] == 'or') {
1079             $query_str = "
1080                SELECT DISTINCT pt1.photo_id
1081                   FROM photo_tags pt1
1082                INNER JOIN photo_tags pt2
1083                   ON pt1.photo_id=pt2.photo_id
1084                INNER JOIN tags t
1085                   ON pt2.tag_id=t.id
1086                INNER JOIN photos p
1087                   ON pt1.photo_id=p.id
1088                WHERE pt1.tag_id IN (". $selected .")
1089             ";
1090             if(isset($additional_where_cond) && !empty($additional_where_cond)) 
1091                $query_str.= "AND ". $additional_where_cond ." ";
1092
1093             if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1094                $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
1095             }
1096
1097             if(isset($order_str))
1098                $query_str.= $order_str;
1099          }
1100          /* photo has to match all selected tags */
1101          elseif($_SESSION['tag_condition'] == 'and') {
1102
1103             if(count($_SESSION['selected_tags']) >= 32) {
1104                print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
1105                print "evaluate your tag selection. Please remove some tags from your selection.\n";
1106                return Array();
1107             } 
1108
1109             /* Join together a table looking like
1110
1111                pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
1112
1113                so the query can quickly return all images matching the
1114                selected tags in an AND condition
1115
1116             */
1117
1118             $query_str = "
1119                SELECT DISTINCT pt1.photo_id
1120                   FROM photo_tags pt1
1121             ";
1122
1123             if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1124                $query_str.= "
1125                   INNER JOIN tags t
1126                      ON pt1.tag_id=t.id
1127                ";
1128             }
1129
1130             for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
1131                $query_str.= "
1132                   INNER JOIN photo_tags pt". ($i+2) ."
1133                      ON pt1.photo_id=pt". ($i+2) .".photo_id
1134                ";
1135             }
1136             $query_str.= "
1137                INNER JOIN photos p
1138                   ON pt1.photo_id=p.id
1139             ";
1140             $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
1141             for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
1142                $query_str.= "
1143                   AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
1144                "; 
1145             }
1146             if(isset($additional_where_cond) && !empty($additional_where_cond)) 
1147                $query_str.= "AND ". $additional_where_cond;
1148
1149             if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1150                $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1151             }
1152
1153             if(isset($order_str))
1154                $query_str.= $order_str;
1155
1156          }
1157
1158          $result = $this->db->db_query($query_str);
1159          while($row = $this->db->db_fetch_object($result)) {
1160             array_push($matched_photos, $row['photo_id']);
1161          }
1162          return $matched_photos;
1163       }
1164
1165       /* return all available photos */
1166       $query_str = "
1167          SELECT DISTINCT p.id
1168          FROM photos p
1169          LEFT JOIN photo_tags pt
1170             ON p.id=pt.photo_id
1171          LEFT JOIN tags t
1172             ON pt.tag_id=t.id
1173       ";
1174
1175       if(isset($additional_where_cond) && !empty($additional_where_cond)) 
1176          $query_str.= "WHERE ". $additional_where_cond ." ";
1177
1178       if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1179          if(isset($additional_where_cond) && !empty($additional_where_cond))
1180             $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1181          else
1182             $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1183       }
1184  
1185       if(isset($order_str))
1186          $query_str.= $order_str;
1187
1188       $result = $this->db->db_query($query_str);
1189       while($row = $this->db->db_fetch_object($result)) {
1190          array_push($matched_photos, $row['id']);
1191       }
1192       return $matched_photos;
1193
1194    } // getPhotoSelection()
1195
1196     /**
1197     * control HTML ouput for photo index
1198     *
1199     * this function provides all the necessary information
1200     * for the photo index template.
1201     * @return string
1202     */
1203    public function showPhotoIndex()
1204    {
1205       $photos = $this->getPhotoSelection();
1206
1207       $count = count($photos);
1208
1209       /* if all thumbnails should be shown on one page */
1210       if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
1211          $begin_with = 0;
1212          $end_with = $count;
1213       }
1214       /* thumbnails should be splitted up in several pages */
1215       elseif($this->cfg->thumbs_per_page > 0) {
1216
1217          if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
1218             $begin_with = 0;
1219          }
1220          else {
1221             $begin_with = $_SESSION['begin_with'];
1222          }
1223
1224          $end_with = $begin_with + $this->cfg->thumbs_per_page;
1225       }
1226
1227       $thumbs = 0;
1228       $images[$thumbs] = Array();
1229       $img_height[$thumbs] = Array();
1230       $img_width[$thumbs] = Array();
1231       $img_id[$thumbs] = Array();
1232       $img_name[$thumbs] = Array();
1233       $img_fullname[$thumbs] = Array();
1234       $img_title = Array();
1235
1236       for($i = $begin_with; $i < $end_with; $i++) {
1237
1238          if(isset($photos[$i])) {
1239
1240             $images[$thumbs] = $photos[$i];
1241             $img_id[$thumbs] = $i;
1242             $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
1243             $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0));
1244             $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
1245
1246             $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i]);
1247
1248             if(file_exists($thumb_path)) {
1249                $info = getimagesize($thumb_path); 
1250                $img_width[$thumbs] = $info[0];
1251                $img_height[$thumbs] = $info[1];
1252             }
1253             $thumbs++;
1254          } 
1255       }
1256
1257       // +1 for for smarty's selection iteration
1258       $thumbs++;
1259
1260       if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1261          $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1262
1263       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1264          $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1265          $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1266       }
1267
1268       if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1269          $this->tmpl->assign('tag_result', 1);
1270       }
1271
1272       /* do we have to display the page selector ? */
1273       if($this->cfg->thumbs_per_page != 0) {
1274
1275          $page_select = "";
1276       
1277          /* calculate the page switchers */
1278          $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1279          $next_start = $begin_with + $this->cfg->thumbs_per_page;
1280
1281          if($begin_with != 0) 
1282             $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");"); 
1283          if($end_with < $count)
1284             $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");"); 
1285
1286          $photo_per_page  = $this->cfg->thumbs_per_page;
1287          $last_page = ceil($count / $photo_per_page);
1288
1289          /* get the current selected page */
1290          if($begin_with == 0) {
1291             $current_page = 1;
1292          } else {
1293             $current_page = 0;
1294             for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1295                $current_page++;
1296             }
1297          } 
1298
1299          $dotdot_made = 0;
1300
1301          for($i = 1; $i <= $last_page; $i++) {
1302
1303             if($current_page == $i)
1304                $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1305             elseif($current_page-1 == $i || $current_page+1 == $i)
1306                $style = "style=\"font-size: 105%;\"";
1307             elseif(($current_page-5 >= $i) && ($i != 1) ||
1308                ($current_page+5 <= $i) && ($i != $last_page))
1309                $style = "style=\"font-size: 75%;\"";
1310             else
1311                $style = "";
1312
1313             $start_with = ($i*$photo_per_page)-$photo_per_page;
1314
1315             if($this->is_user_friendly_url()) {
1316                $select = "<a href=\"". $this->cfg->web_path ."/tag/205/". $start_with ."\"";
1317             }
1318             else {
1319                $select = "<a href=\"". $this->cfg->web_path ."/index.php?mode=showpi&nbsp;tags=". $current_tags ."&nbsp;begin_with=". $begin_with ."\"";
1320             }
1321             $select.= " onclick=\"showPhotoIndex(". $start_with ."); return false;\"";
1322
1323                if($style != "")
1324                   $select.= $style;
1325             $select.= ">". $i ."</a>&nbsp;";
1326
1327             // until 9 pages we show the selector from 1-9
1328             if($last_page <= 9) {
1329                $page_select.= $select;
1330                continue;
1331             } else {
1332                if($i == 1 /* first page */ || 
1333                   $i == $last_page /* last page */ ||
1334                   $i == $current_page /* current page */ ||
1335                   $i == ceil($last_page * 0.25) /* first quater */ ||
1336                   $i == ceil($last_page * 0.5) /* half */ ||
1337                   $i == ceil($last_page * 0.75) /* third quater */ ||
1338                   (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1339                   (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 */ ||
1340                   $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1341                   $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1342
1343                   $page_select.= $select;
1344                   $dotdot_made = 0;
1345                   continue;
1346
1347                }
1348             }
1349
1350             if(!$dotdot_made) {
1351                $page_select.= ".........&nbsp;";
1352                $dotdot_made = 1;
1353             }
1354          }
1355
1356          /* only show the page selector if we have more then one page */
1357          if($last_page > 1)
1358             $this->tmpl->assign('page_selector', $page_select);
1359       }
1360       
1361       $current_tags = $this->getCurrentTags();
1362       $extern_link = "index.php?mode=showpi";
1363       $rss_link = "index.php?mode=rss";
1364       if($current_tags != "") {
1365          $extern_link.= "&tags=". $current_tags;
1366          $rss_link.= "&tags=". $current_tags;
1367       }
1368       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1369          $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1370          $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1371       }
1372
1373       $export_link = "index.php?mode=export";
1374       $slideshow_link = "index.php?mode=slideshow";
1375
1376       $this->tmpl->assign('extern_link', $extern_link);
1377       $this->tmpl->assign('slideshow_link', $slideshow_link);
1378       $this->tmpl->assign('export_link', $export_link);
1379       $this->tmpl->assign('rss_link', $rss_link);
1380       $this->tmpl->assign('count', $count);
1381       $this->tmpl->assign('width', $this->cfg->thumb_width);
1382       $this->tmpl->assign('preview_width', $this->cfg->photo_width);
1383       $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1384       $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1385       $this->tmpl->assign('images', $images);
1386       $this->tmpl->assign('img_width', $img_width);
1387       $this->tmpl->assign('img_height', $img_height);
1388       $this->tmpl->assign('img_id', $img_id);
1389       $this->tmpl->assign('img_name', $img_name);
1390       $this->tmpl->assign('img_fullname', $img_fullname);
1391       $this->tmpl->assign('img_title', $img_title);
1392       $this->tmpl->assign('thumbs', $thumbs);
1393       $this->tmpl->assign('selected_tags', $this->getSelectedTags('img'));
1394
1395       $result = $this->tmpl->fetch("photo_index.tpl");
1396
1397       /* if we are returning to photo index from an photo-view,
1398          scroll the window to the last shown photo-thumbnail.
1399          after this, unset the last_photo session variable.
1400       */
1401       if(isset($_SESSION['last_photo'])) {
1402          $result.= "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n";
1403          unset($_SESSION['last_photo']);
1404       }
1405
1406       return $result;
1407
1408    } // showPhotoIndex()
1409
1410    /**
1411     * show credit template
1412     */
1413    public function showCredits()
1414    {
1415       $this->tmpl->assign('version', $this->cfg->version);
1416       $this->tmpl->assign('product', $this->cfg->product);
1417       $this->tmpl->assign('db_version', $this->dbver);
1418       $this->tmpl->show("credits.tpl");
1419
1420    } // showCredits()
1421
1422    /**
1423     * create thumbnails for the requested width
1424     *
1425     * this function creates image thumbnails of $orig_image
1426     * stored as $thumb_image. It will check if the image is
1427     * in a supported format, if necessary rotate the image
1428     * (based on EXIF orientation meta headers) and re-sizing.
1429     * @param string $orig_image
1430     * @param string $thumb_image
1431     * @param integer $width
1432     * @return boolean
1433     */
1434    public function create_thumbnail($orig_image, $thumb_image, $width)
1435    {  
1436       if(!file_exists($orig_image)) {
1437          return false;
1438       }
1439
1440       $mime = $this->get_mime_info($orig_image);
1441
1442       /* check if original photo is a support image type */
1443       if(!$this->checkifImageSupported($mime))
1444          return false;
1445
1446       switch($mime) {
1447
1448          case 'image/jpeg':
1449
1450             $meta = $this->get_meta_informations($orig_image);
1451
1452             $rotate = 0;
1453             $flip_hori = false;
1454             $flip_vert = false;
1455
1456             switch($meta['Orientation']) {
1457                case 1: /* top, left */
1458                   /* nothing to do */ break;
1459                case 2: /* top, right */
1460                   $rotate = 0; $flip_hori = true; break;
1461                case 3: /* bottom, left */
1462                   $rotate = 180; break;
1463                case 4: /* bottom, right */
1464                   $flip_vert = true; break;
1465                case 5: /* left side, top */
1466                   $rotate = 90; $flip_vert = true; break;
1467                case 6: /* right side, top */
1468                   $rotate = 90; break;
1469                case 7: /* left side, bottom */
1470                   $rotate = 270; $flip_vert = true; break;
1471                case 8: /* right side, bottom */
1472                   $rotate = 270; break;
1473             }
1474
1475             $src_img = @imagecreatefromjpeg($orig_image);
1476             $handler = "gd";
1477             break;
1478
1479          case 'image/png':
1480
1481             $src_img = @imagecreatefrompng($orig_image);
1482             $handler = "gd";
1483             break;
1484
1485          case 'image/x-portable-pixmap':
1486
1487             $src_img = new Imagick($orig_image);
1488             $handler = "imagick";
1489             break;
1490
1491       }
1492
1493       if(!isset($src_img) || empty($src_img)) {
1494          print "Can't load image from ". $orig_image ."\n";
1495          return false;
1496       }
1497
1498       switch($handler) {
1499
1500          case 'gd':
1501
1502             /* grabs the height and width */
1503             $cur_width = imagesx($src_img);
1504             $cur_height = imagesy($src_img);
1505
1506             // If requested width is more then the actual image width,
1507             // do not generate a thumbnail, instead safe the original
1508             // as thumbnail but with lower quality. But if the image
1509             // is to heigh too, then we still have to resize it.
1510             if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1511                $result = imagejpeg($src_img, $thumb_image, 75);
1512                imagedestroy($src_img);
1513                return true;
1514             }
1515             break;
1516
1517          case 'imagick':
1518
1519             $cur_width = $src_img->getImageWidth();
1520             $cur_height = $src_img->getImageHeight();
1521
1522             // If requested width is more then the actual image width,
1523             // do not generate a thumbnail, instead safe the original
1524             // as thumbnail but with lower quality. But if the image
1525             // is to heigh too, then we still have to resize it.
1526             if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1527                $src_img->setCompressionQuality(75);
1528                $src_img->setImageFormat('jpeg');
1529                $src_img->writeImage($thumb_image);
1530                $src_img->clear();
1531                $src_img->destroy();
1532                return true;
1533             }
1534             break;
1535
1536       }
1537
1538       // If the image will be rotate because EXIF orientation said so
1539       // 'virtually rotate' the image for further calculations
1540       if($rotate == 90 || $rotate == 270) {
1541          $tmp = $cur_width;
1542          $cur_width = $cur_height;
1543          $cur_height = $tmp;
1544       }
1545
1546       /* calculates aspect ratio */
1547       $aspect_ratio = $cur_height / $cur_width;
1548
1549       /* sets new size */
1550       if($aspect_ratio < 1) {
1551          $new_w = $width;
1552          $new_h = abs($new_w * $aspect_ratio);
1553       } else {
1554          /* 'virtually' rotate the image and calculate it's ratio */
1555          $tmp_w = $cur_height;
1556          $tmp_h = $cur_width;
1557          /* now get the ratio from the 'rotated' image */
1558          $tmp_ratio = $tmp_h/$tmp_w;
1559          /* now calculate the new dimensions */
1560          $tmp_w = $width;
1561          $tmp_h = abs($tmp_w * $tmp_ratio);
1562
1563          // now that we know, how high they photo should be, if it
1564          // gets rotated, use this high to scale the image
1565          $new_h = $tmp_h;
1566          $new_w = abs($new_h / $aspect_ratio);
1567
1568          // If the image will be rotate because EXIF orientation said so
1569          // now 'virtually rotate' back the image for the image manipulation
1570          if($rotate == 90 || $rotate == 270) {
1571             $tmp = $new_w;
1572             $new_w = $new_h;
1573             $new_h = $tmp;
1574          }
1575       }
1576
1577       switch($handler) {
1578
1579          case 'gd':
1580
1581             /* creates new image of that size */
1582             $dst_img = imagecreatetruecolor($new_w, $new_h);
1583
1584             imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1585
1586             /* copies resized portion of original image into new image */
1587             imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1588
1589             /* needs the image to be flipped horizontal? */
1590             if($flip_hori) {
1591                $this->_debug("(FLIP)");
1592                $dst_img = $this->flipImage($dst_img, 'hori');
1593             }
1594             /* needs the image to be flipped vertical? */
1595             if($flip_vert) {
1596                $this->_debug("(FLIP)");
1597                $dst_img = $this->flipImage($dst_img, 'vert');
1598             }
1599
1600             if($rotate) {
1601                $this->_debug("(ROTATE)");
1602                $dst_img = $this->rotateImage($dst_img, $rotate);
1603             }
1604
1605             /* write down new generated file */
1606             $result = imagejpeg($dst_img, $thumb_image, 75);
1607
1608             /* free your mind */
1609             imagedestroy($dst_img);
1610             imagedestroy($src_img);
1611
1612             if($result === false) {
1613                print "Can't write thumbnail ". $thumb_image ."\n";
1614                return false;
1615             }
1616
1617             return true;
1618
1619             break;
1620
1621          case 'imagick':
1622
1623             $src_img->resizeImage($new_w, $new_h, Imagick::FILTER_LANCZOS, 1);
1624
1625             /* needs the image to be flipped horizontal? */
1626             if($flip_hori) {
1627                $this->_debug("(FLIP)");
1628                $src_img->rotateImage(new ImagickPixel(), 90);
1629                $src_img->flipImage();
1630                $src_img->rotateImage(new ImagickPixel(), -90);
1631             }
1632             /* needs the image to be flipped vertical? */
1633             if($flip_vert) {
1634                $this->_debug("(FLIP)");
1635                $src_img->flipImage();
1636             }
1637
1638             if($rotate) {
1639                $this->_debug("(ROTATE)");
1640                $src_img->rotateImage(new ImagickPixel(), $rotate);
1641             }
1642
1643             $src_img->setCompressionQuality(75);
1644             $src_img->setImageFormat('jpeg');
1645
1646             if(!$src_img->writeImage($thumb_image)) {
1647                print "Can't write thumbnail ". $thumb_image ."\n";
1648                return false;
1649             }
1650
1651             $src_img->clear();
1652             $src_img->destroy();
1653             return true;
1654
1655             break;
1656
1657       }
1658
1659    } // create_thumbnail()
1660
1661    /**
1662     * return all exif meta data from the file
1663     * @param string $file
1664     * @return array
1665     */
1666    public function get_meta_informations($file)
1667    {
1668       return exif_read_data($file);
1669
1670    } // get_meta_informations()
1671
1672    /**
1673     * create phpfspot own sqlite database
1674     *
1675     * this function creates phpfspots own sqlite database
1676     * if it does not exist yet. this own is used to store
1677     * some necessary informations (md5 sum's, ...).
1678     */
1679    public function check_config_table()
1680    {
1681       // if the config table doesn't exist yet, create it
1682       if(!$this->cfg_db->db_check_table_exists("images")) {
1683          $this->cfg_db->db_exec("
1684             CREATE TABLE images (
1685                img_idx int primary key,
1686                img_md5 varchar(32)
1687             )
1688             ");
1689       }
1690
1691    } // check_config_table
1692
1693    /**
1694     * Generates a thumbnail from photo idx
1695     *
1696     * This function will generate JPEG thumbnails from provided F-Spot photo
1697     * indizes.
1698     *
1699     * 1. Check if all thumbnail generations (width) are already in place and
1700     *    readable
1701     * 2. Check if the md5sum of the original file has changed
1702     * 3. Generate the thumbnails if needed
1703     * @param integer $idx
1704     * @param integer $force
1705     * @param boolean $overwrite
1706     */
1707    public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
1708    {
1709       $error = 0;
1710
1711       $resolutions = Array(
1712          $this->cfg->thumb_width,
1713          $this->cfg->photo_width,
1714          $this->cfg->mini_width,
1715          30,
1716       );
1717
1718       /* get details from F-Spot's database */
1719       $details = $this->get_photo_details($idx);
1720
1721       /* calculate file MD5 sum */
1722       $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
1723
1724       if(!file_exists($full_path)) {
1725          $this->_error("File ". $full_path ." does not exist\n");
1726          return;
1727       }
1728
1729       if(!is_readable($full_path)) {
1730          $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
1731          return;
1732       }
1733
1734       $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
1735
1736       /* If Nikon NEF format, we need to treat it another way */
1737       if(isset($this->cfg->dcraw_bin) &&
1738          file_exists($this->cfg->dcraw_bin) &&
1739          is_executable($this->cfg->dcraw_bin) &&
1740          preg_match('/\.nef$/i', $details['uri'])) {
1741
1742          $ppm_path = preg_replace('/\.nef$/i', '.ppm', $full_path);
1743
1744          /* if PPM file does not exist, let dcraw convert it from NEF */
1745          if(!file_exists($ppm_path)) {
1746             system($this->cfg->dcraw_bin ." -a ". $full_path);
1747          }
1748
1749          /* for now we handle the PPM instead of the NEF */
1750          $full_path = $ppm_path;
1751
1752       }
1753
1754       $file_md5 = md5_file($full_path);
1755       $changes = false;
1756
1757       foreach($resolutions as $resolution) {
1758    
1759          $generate_it = false;
1760
1761          $thumb_sub_path = substr($file_md5, 0, 2);
1762          $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
1763
1764          /* if thumbnail-subdirectory does not exist yet, create it */
1765          if(!file_exists(dirname($thumb_path))) {
1766             mkdir(dirname($thumb_path), 0755);
1767          }
1768
1769          /* if the thumbnail file doesn't exist, create it */
1770          if(!file_exists($thumb_path)) {
1771             $generate_it = true;
1772          }
1773          /* if the file hasn't changed there is no need to regen the thumb */
1774          elseif($file_md5 != $this->getMD5($idx) || $force) {
1775             $generate_it = true;
1776          }
1777
1778          if($generate_it || $overwrite) {
1779
1780             $this->_debug(" ". $resolution ."px");
1781             if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1782                $error = 1;
1783
1784             $changes = true;
1785          }
1786       }
1787
1788       if(!$changes) {
1789          $this->_debug(" already exist");
1790       }
1791
1792       /* set the new/changed MD5 sum for the current photo */
1793       if(!$error) {
1794          $this->setMD5($idx, $file_md5);
1795       }
1796
1797       $this->_debug("\n");
1798
1799    } // gen_thumb()
1800
1801    /**
1802     * returns stored md5 sum for a specific photo
1803     *
1804     * this function queries the phpfspot database for a
1805     * stored MD5 checksum of the specified photo
1806     * @param integer $idx
1807     * @return string|null
1808     */
1809    public function getMD5($idx)
1810    {
1811       $result = $this->cfg_db->db_query("
1812          SELECT img_md5 
1813          FROM images
1814          WHERE img_idx='". $idx ."'
1815       ");
1816
1817       if(!$result)
1818          return 0;
1819
1820       $img = $this->cfg_db->db_fetch_object($result);
1821       return $img['img_md5'];
1822       
1823    } // getMD5()
1824
1825    /**
1826     * set MD5 sum for the specific photo
1827     * @param integer $idx
1828     * @param string $md5
1829     */
1830    private function setMD5($idx, $md5)
1831    {
1832       $result = $this->cfg_db->db_exec("
1833          REPLACE INTO images (img_idx, img_md5)
1834          VALUES ('". $idx ."', '". $md5 ."')
1835       ");
1836
1837    } // setMD5()
1838
1839    /**
1840     * store current tag condition
1841     *
1842     * this function stores the current tag condition
1843     * (AND or OR) in the users session variables
1844     * @param string $mode
1845     * @return string
1846     */
1847    public function setTagCondition($mode)
1848    {
1849       $_SESSION['tag_condition'] = $mode;
1850
1851       return "ok";
1852
1853    } // setTagCondition()
1854
1855    /** 
1856     * invoke tag & date search 
1857     *
1858     * this function will return all matching tags and store
1859     * them in the session variable selected_tags. furthermore
1860     * it also handles the date search.
1861     * getPhotoSelection() will then only return the matching
1862     * photos.
1863     * @return string
1864     */
1865    public function startSearch()
1866    {
1867       if(isset($_POST['from']) && $this->isValidDate($_POST['from'])) {
1868          $from = $_POST['from'];
1869       }
1870       if(isset($_POST['to']) && $this->isValidDate($_POST['to'])) {
1871          $to = $_POST['to'];
1872       }
1873
1874       if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
1875          $searchfor_tag = $_POST['for_tag'];
1876          $_SESSION['searchfor_tag'] = $_POST['for_tag'];
1877       }
1878
1879       if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
1880          $searchfor_name = $_POST['for_name'];
1881          $_SESSION['searchfor_name'] = $_POST['for_name'];
1882       }
1883
1884       $this->get_tags();
1885
1886       if(isset($from) && !empty($from))
1887          $_SESSION['from_date'] = strtotime($from ." 00:00:00");
1888       else
1889          unset($_SESSION['from_date']);
1890
1891       if(isset($to) && !empty($to))
1892          $_SESSION['to_date'] = strtotime($to ." 23:59:59");
1893       else
1894          unset($_SESSION['to_date']);
1895
1896       if(isset($searchfor_tag) && !empty($searchfor_tag)) {
1897          /* new search, reset the current selected tags */
1898          $_SESSION['selected_tags'] = Array();
1899          foreach($this->avail_tags as $tag) {
1900             if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
1901                array_push($_SESSION['selected_tags'], $tag);
1902          }
1903       }
1904
1905       return "ok";
1906
1907    } // startSearch()
1908
1909    /**
1910     * updates sort order in session variable
1911     *
1912     * this function is invoked by RPC and will sort the requested
1913     * sort order in the session variable.
1914     * @param string $sort_order
1915     * @return string
1916     */
1917    public function updateSortOrder($order)
1918    {
1919       if(isset($this->sort_orders[$order])) {
1920          $_SESSION['sort_order'] = $order;
1921          return "ok";
1922       }
1923
1924       return "unkown error";
1925
1926    } // updateSortOrder()
1927
1928    /**
1929     * rotate image
1930     *
1931     * this function rotates the image according the
1932     * specified angel.
1933     * @param string $img
1934     * @param integer $degress
1935     * @return image
1936     */
1937    private function rotateImage($img, $degrees)
1938    {
1939       if(function_exists("imagerotate")) {
1940          $img = imagerotate($img, $degrees, 0);
1941       } else {
1942          function imagerotate($src_img, $angle)
1943          {
1944             $src_x = imagesx($src_img);
1945             $src_y = imagesy($src_img);
1946             if ($angle == 180) {
1947                $dest_x = $src_x;
1948                $dest_y = $src_y;
1949             }
1950             elseif ($src_x <= $src_y) {
1951                $dest_x = $src_y;
1952                $dest_y = $src_x;
1953             }
1954             elseif ($src_x >= $src_y) {
1955                $dest_x = $src_y;
1956                $dest_y = $src_x;
1957             }
1958                
1959             $rotate=imagecreatetruecolor($dest_x,$dest_y);
1960             imagealphablending($rotate, false);
1961                
1962             switch ($angle) {
1963             
1964                case 90:
1965                   for ($y = 0; $y < ($src_y); $y++) {
1966                      for ($x = 0; $x < ($src_x); $x++) {
1967                         $color = imagecolorat($src_img, $x, $y);
1968                         imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
1969                      }
1970                   }
1971                   break;
1972
1973                case 270:
1974                   for ($y = 0; $y < ($src_y); $y++) {
1975                      for ($x = 0; $x < ($src_x); $x++) {
1976                         $color = imagecolorat($src_img, $x, $y);
1977                         imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
1978                      }
1979                   }
1980                   break;
1981
1982                case 180:
1983                   for ($y = 0; $y < ($src_y); $y++) {
1984                      for ($x = 0; $x < ($src_x); $x++) {
1985                         $color = imagecolorat($src_img, $x, $y);
1986                         imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
1987                      }
1988                   }
1989                   break;
1990
1991                default:
1992                   $rotate = $src_img;
1993                   break;
1994             };
1995
1996             return $rotate;
1997
1998          }
1999
2000          $img = imagerotate($img, $degrees);
2001
2002       }
2003
2004       return $img;
2005
2006    } // rotateImage()
2007
2008    /**
2009     * returns flipped image
2010     *
2011     * this function will return an either horizontal or
2012     * vertical flipped truecolor image.
2013     * @param string $image
2014     * @param string $mode 
2015     * @return image
2016     */
2017    private function flipImage($image, $mode)
2018    {
2019       $w = imagesx($image);
2020       $h = imagesy($image);
2021       $flipped = imagecreatetruecolor($w, $h);
2022
2023       switch($mode) {
2024          case 'vert':
2025             for ($y = 0; $y < $h; $y++) {
2026                imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
2027             }
2028             break;
2029          case 'hori':
2030             for ($x = 0; $x < $w; $x++) {
2031                imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
2032             }
2033             break;
2034       }
2035
2036       return $flipped;
2037
2038    } // flipImage()
2039
2040    /**
2041     * return all assigned tags for the specified photo
2042     * @param integer $idx
2043     * @return array
2044     */
2045    private function get_photo_tags($idx)
2046    {
2047       $result = $this->db->db_query("
2048          SELECT t.id, t.name
2049          FROM tags t
2050          INNER JOIN photo_tags pt
2051             ON t.id=pt.tag_id
2052          WHERE pt.photo_id='". $idx ."'
2053       ");
2054
2055       $tags = Array();
2056
2057       while($row = $this->db->db_fetch_object($result)) {
2058          if(isset($this->cfg->hide_tags) && in_array($row['name'], $this->cfg->hide_tags))
2059             continue;
2060          $tags[$row['id']] = $row['name'];
2061       }
2062
2063       return $tags;
2064
2065    } // get_photo_tags()
2066
2067    /**
2068     * create on-the-fly images with text within
2069     * @param string $txt
2070     * @param string $color
2071     * @param integer $space
2072     * @param integer $font
2073     * @param integer $w
2074     */
2075    public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
2076    {
2077       if (strlen($color) != 6) 
2078          $color = 000000;
2079
2080       $int = hexdec($color);
2081       $h = imagefontheight($font);
2082       $fw = imagefontwidth($font);
2083       $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
2084       $lines = count($txt);
2085       $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
2086       $bg = imagecolorallocate($im, 255, 255, 255);
2087       $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
2088       $y = 0;
2089
2090       foreach ($txt as $text) {
2091          $x = (($w - ($fw * strlen($text))) / 2);
2092          imagestring($im, $font, $x, $y, $text, $color);
2093          $y += ($h + $space);
2094       }
2095
2096       Header("Content-type: image/png");
2097       ImagePng($im);
2098
2099    } // showTextImage()
2100
2101    /**
2102     * check if all requirements are met
2103     * @return boolean
2104     */
2105    private function check_requirements()
2106    {
2107       if(!function_exists("imagecreatefromjpeg")) {
2108          print "PHP GD library extension is missing<br />\n";
2109          $missing = true;
2110       }
2111
2112       if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
2113          print "PHP SQLite3 library extension is missing<br />\n";
2114          $missing = true;
2115       }
2116
2117       /* Check for HTML_AJAX PEAR package, lent from Horde project */
2118       ini_set('track_errors', 1);
2119       @include_once 'HTML/AJAX/Server.php';
2120       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2121          print "PEAR HTML_AJAX package is missing<br />\n";
2122          $missing = true;
2123       }
2124       @include_once 'Calendar/Calendar.php';
2125       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2126          print "PEAR Calendar package is missing<br />\n";
2127          $missing = true;
2128       }
2129       @include_once 'Console/Getopt.php';
2130       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2131          print "PEAR Console_Getopt package is missing<br />\n";
2132          $missing = true;
2133       }
2134       @include_once $this->cfg->smarty_path .'/libs/Smarty.class.php';
2135       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2136          print "Smarty template engine can not be found in ". $this->cfg->smarty_path ."/libs/Smarty.class.php<br />\n";
2137          $missing = true;
2138       }
2139       ini_restore('track_errors');
2140
2141       if(isset($missing))
2142          return false;
2143
2144       return true;
2145
2146    } // check_requirements()
2147
2148    private function _debug($text)
2149    {
2150       if($this->fromcmd) {
2151          print $text;
2152       }
2153
2154    } // _debug()
2155
2156    /**
2157     * check if specified MIME type is supported
2158     * @param string $mime
2159     * @return boolean
2160     */
2161    public function checkifImageSupported($mime)
2162    {
2163       $supported_types =  Array(
2164          "image/jpeg",
2165          "image/png",
2166          "image/x-portable-pixmap",
2167          "image/tiff"
2168       );
2169
2170       if(in_array($mime, $supported_types))
2171          return true;
2172
2173       return false;
2174
2175    } // checkifImageSupported()
2176
2177    /**
2178     * output error text
2179     * @param string $text
2180     */
2181    public function _error($text)
2182    {
2183       switch($this->cfg->logging) {
2184          default:
2185          case 'display':
2186             print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
2187             print $text ."<br />\n";
2188             break;
2189          case 'errorlog':  
2190             error_log($text);
2191             break;
2192          case 'logfile':
2193             error_log($text, 3, $his->cfg->log_file);
2194             break;
2195       }
2196
2197       $this->runtime_error = true;
2198
2199    } // _error()
2200
2201    /**
2202     * output calendard input fields
2203     * @param string $mode
2204     * @return string
2205     */
2206    private function get_calendar($mode)
2207    {
2208       $year = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
2209       $month = isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
2210       $day = isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
2211
2212       $output = "<input type=\"text\" size=\"3\" id=\"". $mode ."year\" value=\"". $year ."\"";
2213       if(!isset($_SESSION[$mode .'_date']))
2214          $output.= " disabled=\"disabled\"";
2215       $output.= " />\n";
2216       $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."month\" value=\"". $month ."\"";
2217       if(!isset($_SESSION[$mode .'_date']))
2218          $output.= " disabled=\"disabled\"";
2219       $output.= " />\n";
2220       $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."day\" value=\"". $day ."\"";
2221       if(!isset($_SESSION[$mode .'_date']))
2222          $output.= " disabled=\"disabled\"";
2223       $output.= " />\n";
2224
2225       return $output;
2226
2227    } // get_calendar()
2228
2229    /**
2230     * output calendar matrix
2231     * @param integer $year
2232     * @param integer $month
2233     * @param integer $day
2234     */
2235    public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
2236    {
2237       if (!isset($year)) $year = date('Y');
2238       if (!isset($month)) $month = date('m');
2239       if (!isset($day)) $day = date('d');
2240       $rows = 1;
2241       $cols = 1;
2242       $matrix = Array();
2243
2244       require_once CALENDAR_ROOT.'Month/Weekdays.php';
2245       require_once CALENDAR_ROOT.'Day.php';
2246
2247       // Build the month
2248       $month = new Calendar_Month_Weekdays($year,$month);
2249
2250       // Create links
2251       $prevStamp = $month->prevMonth(true);
2252       $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
2253       $nextStamp = $month->nextMonth(true);
2254       $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
2255
2256       $selectedDays = array (
2257          new Calendar_Day($year,$month,$day),
2258          new Calendar_Day($year,12,25),
2259       );
2260
2261       // Build the days in the month
2262       $month->build($selectedDays);
2263
2264       $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
2265       $this->tmpl->assign('prev_month', $prev);
2266       $this->tmpl->assign('next_month', $next);
2267
2268       while ( $day = $month->fetch() ) {
2269    
2270          if(!isset($matrix[$rows]))
2271             $matrix[$rows] = Array();
2272
2273          $string = "";
2274
2275          $dayStamp = $day->thisDay(true);
2276          $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
2277
2278          // isFirst() to find start of week
2279          if ( $day->isFirst() )
2280             $string.= "<tr>\n";
2281
2282          if ( $day->isSelected() ) {
2283             $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
2284          } else if ( $day->isEmpty() ) {
2285             $string.= "<td>&nbsp;</td>\n";
2286          } else {
2287             $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
2288          }
2289
2290          // isLast() to find end of week
2291          if ( $day->isLast() )
2292             $string.= "</tr>\n";
2293
2294          $matrix[$rows][$cols] = $string;
2295
2296          $cols++;
2297
2298          if($cols > 7) {
2299             $cols = 1;
2300             $rows++;
2301          }
2302       }
2303
2304       $this->tmpl->assign('matrix', $matrix);
2305       $this->tmpl->assign('rows', $rows);
2306       $this->tmpl->show("calendar.tpl");
2307
2308    } // get_calendar_matrix()
2309
2310    /**
2311     * output export page
2312     * @param string $mode
2313     */
2314    public function getExport($mode)
2315    {
2316       $pictures = $this->getPhotoSelection();
2317       $current_tags = $this->getCurrentTags();  
2318
2319       foreach($pictures as $picture) {
2320
2321          $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture;
2322          if($current_tags != "") {
2323             $orig_url.= "&tags=". $current_tags;
2324          } 
2325          if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2326             $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2327          }
2328
2329          if($this->is_user_friendly_url()) {
2330             $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width;
2331          }
2332          else {
2333             $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2334          }
2335
2336          switch($mode) {
2337
2338             case 'HTML':
2339                // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
2340                print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
2341                break;
2342                
2343             case 'MoinMoin':
2344                // "[%pictureurl% %thumbnailurl%]"
2345                print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2346                break;
2347
2348             case 'MoinMoinList':
2349                // " * [%pictureurl% %thumbnailurl%]"
2350                print "&nbsp;" . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2351                break;
2352          }
2353
2354       }
2355
2356    } // getExport()
2357
2358    /**
2359     * output RSS feed
2360     */
2361    public function getRSSFeed()
2362    {
2363       Header("Content-type: text/xml; charset=utf-8");
2364       print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2365 ?>
2366 <rss version="2.0"
2367    xmlns:media="http://search.yahoo.com/mrss/"
2368    xmlns:dc="http://purl.org/dc/elements/1.1/"
2369  >
2370  <channel>
2371   <title>phpfspot</title>
2372   <description>phpfspot RSS feed</description>
2373   <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2374   <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2375   <generator>phpfspot</generator>
2376 <?php
2377
2378       $pictures = $this->getPhotoSelection();
2379       $current_tags = $this->getCurrentTags();  
2380
2381       foreach($pictures as $picture) {
2382
2383          $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture;
2384          if($current_tags != "") {
2385             $orig_url.= "&tags=". $current_tags;
2386          } 
2387          if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2388             $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2389          }
2390
2391          $details = $this->get_photo_details($picture);
2392
2393          if($this->is_user_friendly_url()) {
2394             $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width;
2395          }
2396          else {
2397             $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2398          }
2399
2400          $thumb_html = htmlspecialchars("
2401 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2402 <br>
2403 ". $details['description']);
2404
2405          $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2406
2407          /* get EXIF information if JPEG */
2408          if($details['mime'] == "image/jpeg") {
2409             $meta = $this->get_meta_informations($orig_path);
2410          }
2411
2412          $meta_date = isset($meta['FileDateTime']) ? $meta['FileDateTime'] : filemtime($orig_path);
2413
2414 ?>
2415   <item>
2416    <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2417    <link><?php print htmlspecialchars($orig_url); ?></link>
2418    <guid><?php print htmlspecialchars($orig_url); ?></guid>
2419    <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $meta_date); ?></dc:date.Taken>
2420    <description>
2421     <?php print $thumb_html; ?> 
2422    </description>
2423    <pubDate><?php print strftime("%a, %d %b %Y %T %z", $meta_date); ?></pubDate>
2424   </item>
2425 <?php
2426
2427       }
2428 ?>
2429  </channel>
2430 </rss>
2431 <?php
2432
2433
2434    } // getExport()
2435
2436  
2437    /**
2438     * return all selected tags as one string
2439     * @return array
2440     */
2441    private function getCurrentTags()
2442    {
2443       $current_tags = "";
2444       if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2445          foreach($_SESSION['selected_tags'] as $tag)
2446             $current_tags.= $tag .",";
2447          $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2448       }
2449       return $current_tags;
2450
2451    } // getCurrentTags()
2452
2453    /**
2454     * return the current photo
2455     */
2456    public function getCurrentPhoto()
2457    {
2458       if(isset($_SESSION['current_photo'])) {
2459          print $_SESSION['current_photo'];
2460       }
2461    } // getCurrentPhoto()
2462
2463    /**
2464     * tells the client browser what to do
2465     *
2466     * this function is getting called via AJAX by the
2467     * client browsers. it will tell them what they have
2468     * to do next. This is necessary for directly jumping
2469     * into photo index or single photo view when the are
2470     * requested with specific URLs
2471     * @return string
2472     */
2473    public function whatToDo()
2474    {
2475       if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
2476       }
2477       elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
2478          return "showpi_tags";
2479       }
2480       elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
2481          return "showpi";
2482       }
2483
2484    } // whatToDo()
2485
2486    /**
2487     * return the current process-user
2488     * @return string
2489     */
2490    private function getuid()
2491    {
2492       if($uid = posix_getuid()) {
2493          if($user = posix_getpwuid($uid)) {
2494             return $user['name'];
2495          }
2496       }
2497    
2498       return 'n/a';
2499    
2500    } // getuid()
2501
2502    /**
2503     * returns a select-dropdown box to select photo index sort parameters
2504     * @param array $params
2505     * @param smarty $smarty
2506     * @return string
2507     */
2508    public function smarty_sort_select_list($params, &$smarty)
2509    {
2510       $output = "";
2511
2512       foreach($this->sort_orders as $key => $value) {
2513          $output.= "<option value=\"". $key ."\"";
2514          if($key == $_SESSION['sort_order']) {
2515             $output.= " selected=\"selected\"";
2516          }
2517          $output.= ">". $value ."</option>";
2518       }
2519
2520       return $output;
2521
2522    } // smarty_sort_select_list()
2523
2524    /**
2525     * returns the currently selected sort order
2526     * @return string
2527     */ 
2528    private function get_sort_order()
2529    {
2530       switch($_SESSION['sort_order']) {
2531          case 'date_asc':
2532             return " ORDER BY p.time ASC";
2533             break;
2534          case 'date_desc':
2535             return " ORDER BY p.time DESC";
2536             break;
2537          case 'name_asc':
2538             if($this->dbver < 9) {
2539                return " ORDER BY p.name ASC";
2540             }
2541             else {
2542                return " ORDER BY basename(p.uri) ASC";
2543             }
2544             break;
2545          case 'name_desc':
2546             if($this->dbver < 9) {
2547                return " ORDER BY p.name DESC";
2548             }
2549             else {
2550                return " ORDER BY basename(p.uri) DESC";
2551             }
2552             break;
2553          case 'tags_asc':
2554             return " ORDER BY t.name ASC ,p.time ASC";
2555             break;
2556          case 'tags_desc':
2557             return " ORDER BY t.name DESC ,p.time ASC";
2558             break;
2559          case 'rate_asc':
2560             return " ORDER BY t.name ASC, p.rating ASC";
2561             break;
2562          case 'rate_desc':
2563             return " ORDER BY t.name DESC, p.rating DESC";
2564             break;
2565       }
2566
2567    } // get_sort_order()
2568
2569    /**
2570     * return the next to be shown slide show image
2571     *
2572     * this function returns the URL of the next image
2573     * in the slideshow sequence.
2574     * @return string
2575     */
2576    public function getNextSlideShowImage()
2577    {
2578       $all_photos = $this->getPhotoSelection();
2579
2580       if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1) 
2581          $_SESSION['slideshow_img'] = 0;
2582       else
2583          $_SESSION['slideshow_img']++;
2584
2585       if($this->is_user_friendly_url()) {
2586          return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width;
2587       }
2588
2589       return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2590
2591    } // getNextSlideShowImage()
2592
2593    /**
2594     * return the previous to be shown slide show image
2595     *
2596     * this function returns the URL of the previous image
2597     * in the slideshow sequence.
2598     * @return string
2599     */
2600    public function getPrevSlideShowImage()
2601    {
2602       $all_photos = $this->getPhotoSelection();
2603
2604       if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
2605          $_SESSION['slideshow_img'] = 0;
2606       else
2607          $_SESSION['slideshow_img']--;
2608
2609       if($this->is_user_friendly_url()) {
2610          return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width;
2611       }
2612
2613       return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2614
2615    } // getPrevSlideShowImage()
2616
2617    public function resetSlideShow()
2618    {
2619       if(isset($_SESSION['slideshow_img']))
2620          unset($_SESSION['slideshow_img']);
2621
2622    } // resetSlideShow()
2623    
2624    /**
2625     * get random photo
2626     *
2627     * this function will get all photos from the fspot
2628     * database and randomly return ONE entry
2629     *
2630     * saddly there is yet no sqlite3 function which returns
2631     * the bulk result in array, so we have to fill up our
2632     * own here.
2633     * @return array
2634     */
2635    public function get_random_photo()
2636    {
2637       $all = Array();
2638
2639       $query_str = "
2640          SELECT p.id
2641          FROM photos p
2642       ";
2643
2644       /* if show_tags is set, only return details for photos which
2645          are specified to be shown
2646       */
2647       if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
2648          $query_str.= "
2649             INNER JOIN photo_tags pt
2650                ON p.id=pt.photo_id
2651             INNER JOIN tags t
2652                ON pt.tag_id=t.id
2653             WHERE
2654                t.name IN ('".implode("','",$this->cfg->show_tags)."')";
2655       }
2656
2657       $result = $this->db->db_query($query_str);
2658
2659       while($row = $this->db->db_fetch_object($result)) {
2660          array_push($all, $row['id']);
2661       }
2662
2663       return $all[array_rand($all)];
2664
2665    } // get_random_photo()
2666
2667    /**
2668     * get random photo tag photo
2669     *
2670     * this function will get all photos tagged with the requested
2671     * tag from the fspot database and randomly return ONE entry
2672     *
2673     * saddly there is yet no sqlite3 function which returns
2674     * the bulk result in array, so we have to fill up our
2675     * own here.
2676     * @return array
2677     */
2678    public function get_random_tag_photo($tagidx)
2679    {
2680       $all = Array();
2681
2682       $query_str = "
2683          SELECT p.id
2684          FROM photos p
2685          INNER JOIN photo_tags pt
2686             ON p.id=pt.photo_id
2687       ";
2688
2689       if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
2690          $query_str.= "
2691             INNER JOIN tags t
2692                ON pt.tag_id=t.id
2693          ";
2694       }
2695       $query_str.= "
2696          WHERE
2697             pt.tag_id LIKE '". $tagidx ."'
2698       ";
2699
2700       /*if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
2701          $query_str.= "
2702            AND
2703                t.name IN ('".implode("','",$this->cfg->show_tags)."')
2704          ";
2705       }*/
2706
2707       $result = $this->db->db_query($query_str);
2708
2709       while($row = $this->db->db_fetch_object($result)) {
2710          array_push($all, $row['id']);
2711       }
2712
2713       return $all[array_rand($all)];
2714
2715    } // get_random_tag_photo()
2716
2717    /**
2718     * validates provided date
2719     *
2720     * this function validates if the provided date
2721     * contains a valid date and will return true 
2722     * if it is.
2723     * @param string $date_str
2724     * @return boolean
2725     */
2726    public function isValidDate($date_str)
2727    {
2728       $timestamp = strtotime($date_str);
2729    
2730       if(is_numeric($timestamp))
2731          return true;
2732       
2733       return false;
2734
2735    } // isValidDate()
2736
2737    /**
2738     * timestamp to string conversion
2739     * @param integer $timestamp
2740     * @return string
2741     */
2742    private function ts2str($timestamp)
2743    {
2744       if(!empty($timestamp) && is_numeric($timestamp))
2745          return strftime("%Y-%m-%d", $timestamp);
2746
2747    } // ts2str()
2748
2749    /**
2750     * extract tag-names from $_GET['tags']
2751     * @param string $tags_str
2752     * @return string
2753     */
2754    private function extractTags($tags_str)
2755    {
2756       $not_validated = split(',', $tags_str);
2757       $validated = array();
2758
2759       foreach($not_validated as $tag) {
2760          if(is_numeric($tag))
2761             array_push($validated, $tag);
2762       }
2763    
2764       return $validated;
2765    
2766    } // extractTags()
2767
2768    /**
2769     * returns the full path to a thumbnail
2770     * @param integer $width
2771     * @param integer $photo
2772     * @return string
2773     */
2774    public function get_thumb_path($width, $photo)
2775    {
2776       $md5 = $this->getMD5($photo);
2777       $sub_path = substr($md5, 0, 2);
2778       return $this->cfg->thumb_path
2779          . "/"
2780          . $sub_path
2781          . "/"
2782          . $width
2783          . "_"
2784          . $md5;
2785
2786    } // get_thumb_path()
2787
2788    /**
2789     * returns server's virtual host name
2790     * @return string
2791     */
2792    private function get_server_name()
2793    {
2794       return $_SERVER['SERVER_NAME'];
2795    } // get_server_name()
2796
2797    /**
2798     * returns type of webprotocol which is currently used
2799     * @return string
2800     */
2801    private function get_web_protocol()
2802    {
2803       if(!isset($_SERVER['HTTPS']))
2804          return "http";
2805       else
2806          return "https";
2807    } // get_web_protocol()
2808
2809    /**
2810     * return url to this phpfspot installation
2811     * @return string
2812     */
2813    private function get_phpfspot_url()
2814    {
2815       return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
2816
2817    } // get_phpfspot_url()
2818
2819    /**
2820     * returns the number of photos which are tagged with $tag_id
2821     * @param integer $tag_id
2822     * @return integer
2823     */
2824    public function get_num_photos($tag_id)
2825    {
2826       if($result = $this->db->db_fetchSingleRow("
2827          SELECT count(*) as number
2828          FROM photo_tags
2829          WHERE
2830             tag_id LIKE '". $tag_id ."'")) {
2831
2832          return $result['number'];
2833
2834       }
2835
2836       return 0;
2837       
2838    } // get_num_photos()
2839    
2840    /**
2841     * check file exists and is readable
2842     *
2843     * returns true, if everything is ok, otherwise false
2844     * if $silent is not set, this function will output and
2845     * error message
2846     * @param string $file
2847     * @param boolean $silent
2848     * @return boolean
2849     */
2850    private function check_readable($file, $silent = null)
2851    {
2852       if(!file_exists($file)) {
2853          if(!isset($silent))
2854             print "File \"". $file ."\" does not exist.\n";
2855          return false;
2856       }
2857
2858       if(!is_readable($file)) {
2859          if(!isset($silent))
2860             print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
2861          return false;
2862       }
2863
2864       return true;
2865
2866    } // check_readable()
2867
2868    /**
2869     * check if all needed indices are present
2870     *
2871     * this function checks, if some needed indices are already
2872     * present, or if not, create them on the fly. they are
2873     * necessary to speed up some queries like that one look for
2874     * all tags, when show_tags is specified in the configuration.
2875     */
2876    private function checkDbIndices()
2877    {
2878       $result = $this->db->db_exec("
2879          CREATE INDEX IF NOT EXISTS
2880             phototag
2881          ON
2882             photo_tags
2883                (photo_id, tag_id)
2884       ");
2885
2886    } // checkDbIndices()
2887
2888    /**
2889     * retrive F-Spot database version
2890     *
2891     * this function will return the F-Spot database version number
2892     * It is stored within the sqlite3 database in the table meta
2893     * @return string|null
2894     */
2895    public function getFspotDBVersion()
2896    {
2897       if($result = $this->db->db_fetchSingleRow("
2898          SELECT data as version
2899          FROM meta
2900          WHERE
2901             name LIKE 'F-Spot Database Version'
2902       "))
2903          return $result['version'];
2904
2905       return null;
2906
2907    } // getFspotDBVersion()
2908
2909    /**
2910     * parse the provided URI and will returned the requested chunk
2911     * @param string $uri
2912     * @param string $mode
2913     * @return string
2914     */
2915    public function parse_uri($uri, $mode)
2916    {
2917       if(($components = parse_url($uri)) !== false) {
2918
2919          switch($mode) {
2920             case 'filename':
2921                return basename($components['path']);
2922                break;
2923             case 'dirname':
2924                return dirname($components['path']);
2925                break;
2926             case 'fullpath':
2927                return $components['path'];
2928                break;
2929          }
2930       }
2931
2932       return $uri;
2933
2934    } // parse_uri()
2935
2936    /**
2937     * validate config options
2938     *
2939     * this function checks if all necessary configuration options are
2940     * specified and set.
2941     * @return boolean
2942     */
2943    private function check_config_options()
2944    {
2945       if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
2946          $this->_error("Please set \$page_title in phpfspot_cfg");
2947
2948       if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
2949          $this->_error("Please set \$base_path in phpfspot_cfg");
2950
2951       if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
2952          $this->_error("Please set \$web_path in phpfspot_cfg");
2953
2954       if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
2955          $this->_error("Please set \$thumb_path in phpfspot_cfg");
2956
2957       if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
2958          $this->_error("Please set \$smarty_path in phpfspot_cfg");
2959
2960       if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
2961          $this->_error("Please set \$fspot_db in phpfspot_cfg");
2962
2963       if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
2964          $this->_error("Please set \$db_access in phpfspot_cfg");
2965
2966       if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
2967          $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
2968
2969       if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
2970          $this->_error("Please set \$thumb_width in phpfspot_cfg");
2971
2972       if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
2973          $this->_error("Please set \$thumb_height in phpfspot_cfg");
2974
2975       if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
2976          $this->_error("Please set \$photo_width in phpfspot_cfg");
2977
2978       if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
2979          $this->_error("Please set \$mini_width in phpfspot_cfg");
2980
2981       if(!isset($this->cfg->thumbs_per_page))
2982          $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
2983
2984       if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
2985          $this->_error("Please set \$path_replace_from in phpfspot_cfg");
2986
2987       if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
2988          $this->_error("Please set \$path_replace_to in phpfspot_cfg");
2989
2990       if(!isset($this->cfg->hide_tags))
2991          $this->_error("Please set \$hide_tags in phpfspot_cfg");
2992
2993       if(!isset($this->cfg->theme_name))
2994          $this->_error("Please set \$theme_name in phpfspot_cfg");
2995
2996       if(!isset($this->cfg->logging))
2997          $this->_error("Please set \$logging in phpfspot_cfg");
2998
2999       if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
3000
3001          if(!isset($this->cfg->log_file))
3002             $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
3003
3004          if(!is_writeable($this->cfg->log_file))
3005             $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
3006
3007       }
3008
3009       /* remove trailing slash, if set */
3010       if($this->cfg->web_path == "/")
3011          $this->cfg->web_path = "";
3012       elseif(preg_match('/\/$/', $this->cfg->web_path))
3013          $this->cfg->web_path = preg_replace('/\/$/', '', $this->cfg->web_path);
3014
3015       return $this->runtime_error;
3016
3017    } // check_config_options()
3018
3019    /**
3020     * cleanup phpfspot own database
3021     *
3022     * When photos are getting delete from F-Spot, there will remain
3023     * remain some residues in phpfspot own database. This function
3024     * will try to wipe them out.
3025     */
3026    public function cleanup_phpfspot_db()
3027    {
3028       $to_delete = Array();
3029
3030       $result = $this->cfg_db->db_query("
3031          SELECT img_idx
3032          FROM images
3033          ORDER BY img_idx ASC
3034       ");
3035
3036       while($row = $this->cfg_db->db_fetch_object($result)) {
3037          if(!$this->db->db_fetchSingleRow("
3038             SELECT id
3039             FROM photos
3040             WHERE id='". $row['img_idx'] ."'")) {
3041
3042             array_push($to_delete, $row['img_idx'], ',');
3043          }
3044       }
3045
3046       print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
3047
3048       $this->cfg_db->db_exec("
3049          DELETE FROM images
3050          WHERE img_idx IN (". implode($to_delete) .")
3051       ");
3052
3053    } // cleanup_phpfspot_db()
3054
3055    /**
3056     * return first image of the page, the $current photo
3057     * lies in.
3058     *
3059     * this function is used to find out the first photo of the
3060     * current page, in which the $current photo lies. this is
3061     * used to display the correct photo, when calling showPhotoIndex()
3062     * from showImage()
3063     * @param integer $current
3064     * @param integer $max
3065     * @return integer
3066     */
3067    private function getCurrentPage($current, $max)
3068    {
3069       if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
3070          for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
3071             if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
3072                return $page_start;
3073          }
3074       }
3075       return 0;
3076
3077    } // getCurrentPage()
3078
3079    /**
3080     * return mime info
3081     *
3082     * this function tries to find out the correct mime-type
3083     * for the provided file.
3084     * @param string $file
3085     * @return string
3086     */
3087    public function get_mime_info($file)
3088    {
3089       $details = getimagesize($file);
3090
3091       /* if getimagesize() returns empty, try at least to find out the
3092          mime type.
3093       */
3094       if(empty($details) && function_exists('mime_content_type')) {
3095
3096          // mime_content_type is marked as deprecated in the documentation,
3097          // but is it really necessary to force users to install a PECL
3098          // extension?
3099          $details['mime'] = mime_content_type($file);
3100       }
3101
3102       return $details['mime'];
3103
3104    } // get_mime_info()
3105
3106    /**
3107     * return tag-name by tag-idx
3108     *
3109     * this function returns the tag-name for the requested
3110     * tag specified by tag-idx.
3111     * @param integer $idx
3112     * @return string
3113     */
3114    public function get_tag_name($idx)
3115    {
3116        if($result = $this->db->db_fetchSingleRow("
3117          SELECT name
3118          FROM tags
3119          WHERE
3120             id LIKE '". $idx ."'")) {
3121
3122          return $result['name'];
3123
3124       }
3125
3126       return 0;
3127       
3128    } // get_tag_name()
3129
3130    /**
3131     * parse user friendly url which got rewritten by the websever
3132     * @param string $request_uri
3133     * @return string
3134     */
3135    private function parse_user_friendly_url($request_uri)
3136    {
3137       if(preg_match('/\/photoview\/|\/photo\/|\/tag\//', $request_uri)) {
3138
3139          $options = explode('/', $request_uri);
3140
3141          switch($options[1]) {
3142             case 'photoview':
3143                if(is_numeric($options[2])) {
3144                   $this->session_cleanup();
3145                   //unset($_SESSION['start_action']);
3146                   //unset($_SESSION['selected_tags']);
3147                   $_GET['mode'] = 'showp';
3148                   return $this->showPhoto($options[2]);
3149                }
3150                break;
3151             case 'photo':
3152                if(is_numeric($options[2])) {
3153                   require_once "phpfspot_img.php";
3154                   $img = new PHPFSPOT_IMG;
3155                   if(isset($options[3]) && is_numeric($options[3]))
3156                      $img->showImg($options[2], $options[3]);
3157                   else
3158                      $img->showImg($options[2]);
3159                }
3160                exit;
3161                break;
3162             case 'tag':
3163                if(is_numeric($options[2])) {
3164                   $this->session_cleanup();
3165                   $_GET['tags'] = $options[2];
3166                   $_SESSION['selected_tags'] = Array($options[2]);
3167                   if(isset($options[3]) && is_numeric($options[3]))
3168                      $_SESSION['begin_with'] = $options[3];
3169                   return $this->showPhotoIndex();
3170                }
3171                break;
3172          }
3173       }
3174
3175    } // parse_user_friendly_url()
3176
3177    /**
3178     * check if user-friendly-urls are enabled
3179     *
3180     * this function will return true, if the config option
3181     * $user_friendly_url has been set. Otherwise false.
3182     * @return boolean
3183     */
3184    private function is_user_friendly_url()
3185    {
3186       if(isset($this->cfg->user_friendly_url) && $this->cfg->user_friendly_url)
3187          return true;
3188
3189       return false;
3190
3191    } // is_user_friendly_url()
3192
3193    /**
3194     * session cleanup
3195     *
3196     * this function will cleanup user's session information
3197     */
3198    private function session_cleanup()
3199    {
3200       unset($_SESSION['begin_with']);
3201       $this->resetDateSearch();
3202       $this->resetPhotoView();
3203       $this->resetTagSearch();
3204       $this->resetNameSearch();
3205       $this->resetDateSearch();
3206       $this->resetTags();
3207
3208    } // session_cleanup()
3209
3210 } // class PHPFSPOT
3211
3212 ?>