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