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