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