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