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