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