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