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