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