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