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