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