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