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