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