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