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