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