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