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