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