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