Merge commit 'origin/master' into nefformat
[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             $handler = "gd";
1394             break;
1395
1396          case 'image/png':
1397
1398             $src_img = @imagecreatefrompng($orig_image);
1399             $handler = "gd";
1400             break;
1401
1402          case 'image/tiff':
1403
1404             $src_img = new Imagick($orig_image);
1405             print_r($src_img->queryFormats());
1406             
1407             $handler = "imagick";
1408             exit(1);
1409             break;
1410
1411       }
1412
1413
1414       switch($handler) {
1415
1416          case 'gd':
1417
1418             if(!isset($src_img) || empty($src_img)) {
1419                print "Can't load image from ". $orig_image ."\n";
1420                return false;
1421             }
1422
1423             /* grabs the height and width */
1424             $cur_width = imagesx($src_img);
1425             $cur_height = imagesy($src_img);
1426
1427             // If requested width is more then the actual image width,
1428             // do not generate a thumbnail, instead safe the original
1429             // as thumbnail but with lower quality. But if the image
1430             // is to heigh too, then we still have to resize it.
1431             if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1432                $result = imagejpeg($src_img, $thumb_image, 75);
1433                imagedestroy($src_img);
1434                return true;
1435             }
1436
1437             // If the image will be rotate because EXIF orientation said so
1438             // 'virtually rotate' the image for further calculations
1439             if($rotate == 90 || $rotate == 270) {
1440                $tmp = $cur_width;
1441                $cur_width = $cur_height;
1442                $cur_height = $tmp;
1443             }
1444
1445             /* calculates aspect ratio */
1446             $aspect_ratio = $cur_height / $cur_width;
1447
1448             /* sets new size */
1449             if($aspect_ratio < 1) {
1450                $new_w = $width;
1451                $new_h = abs($new_w * $aspect_ratio);
1452             } else {
1453                /* 'virtually' rotate the image and calculate it's ratio */
1454                $tmp_w = $cur_height;
1455                $tmp_h = $cur_width;
1456                /* now get the ratio from the 'rotated' image */
1457                $tmp_ratio = $tmp_h/$tmp_w;
1458                /* now calculate the new dimensions */
1459                $tmp_w = $width;
1460                $tmp_h = abs($tmp_w * $tmp_ratio);
1461
1462                // now that we know, how high they photo should be, if it
1463                // gets rotated, use this high to scale the image
1464                $new_h = $tmp_h;
1465                $new_w = abs($new_h / $aspect_ratio);
1466
1467                // If the image will be rotate because EXIF orientation said so
1468                // now 'virtually rotate' back the image for the image manipulation
1469                if($rotate == 90 || $rotate == 270) {
1470                   $tmp = $new_w;
1471                   $new_w = $new_h;
1472                   $new_h = $tmp;
1473                }
1474             }
1475
1476             /* creates new image of that size */
1477             $dst_img = imagecreatetruecolor($new_w, $new_h);
1478
1479             imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1480
1481             /* copies resized portion of original image into new image */
1482             imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1483
1484             /* needs the image to be flipped horizontal? */
1485             if($flip_hori) {
1486                $this->_debug("(FLIP)");
1487                $dst_img = $this->flipImage($dst_img, 'hori');
1488             }
1489             /* needs the image to be flipped vertical? */
1490             if($flip_vert) {
1491                $this->_debug("(FLIP)");
1492                $dst_img = $this->flipImage($dst_img, 'vert');
1493             }
1494
1495             if($rotate) {
1496                $this->_debug("(ROTATE)");
1497                $dst_img = $this->rotateImage($dst_img, $rotate);
1498             }
1499
1500             /* write down new generated file */
1501             $result = imagejpeg($dst_img, $thumb_image, 75);
1502
1503             /* free your mind */
1504             imagedestroy($dst_img);
1505             imagedestroy($src_img);
1506
1507             if($result === false) {
1508                print "Can't write thumbnail ". $thumb_image ."\n";
1509                return false;
1510             }
1511
1512             return true;
1513
1514             break;
1515
1516          case 'imagick':
1517
1518             break;
1519
1520       }
1521
1522    } // create_thumbnail()
1523
1524    /**
1525     * return all exif meta data from the file
1526     * @param string $file
1527     * @return array
1528     */
1529    public function get_meta_informations($file)
1530    {
1531       return exif_read_data($file);
1532
1533    } // get_meta_informations()
1534
1535    /**
1536     * create phpfspot own sqlite database
1537     *
1538     * this function creates phpfspots own sqlite database
1539     * if it does not exist yet. this own is used to store
1540     * some necessary informations (md5 sum's, ...).
1541     */
1542    public function check_config_table()
1543    {
1544       // if the config table doesn't exist yet, create it
1545       if(!$this->cfg_db->db_check_table_exists("images")) {
1546          $this->cfg_db->db_exec("
1547             CREATE TABLE images (
1548                img_idx int primary key,
1549                img_md5 varchar(32)
1550             )
1551             ");
1552       }
1553
1554    } // check_config_table
1555
1556    /**
1557     * Generates a thumbnail from photo idx
1558     *
1559     * This function will generate JPEG thumbnails from provided F-Spot photo
1560     * indizes.
1561     *
1562     * 1. Check if all thumbnail generations (width) are already in place and
1563     *    readable
1564     * 2. Check if the md5sum of the original file has changed
1565     * 3. Generate the thumbnails if needed
1566     * @param integer $idx
1567     * @param integer $force
1568     * @param boolean $overwrite
1569     */
1570    public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
1571    {
1572       $error = 0;
1573
1574       $resolutions = Array(
1575          $this->cfg->thumb_width,
1576          $this->cfg->photo_width,
1577          $this->cfg->mini_width,
1578       );
1579
1580       /* get details from F-Spot's database */
1581       $details = $this->get_photo_details($idx);
1582
1583       /* calculate file MD5 sum */
1584       $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
1585
1586       if(!file_exists($full_path)) {
1587          $this->_error("File ". $full_path ." does not exist\n");
1588          return;
1589       }
1590
1591       if(!is_readable($full_path)) {
1592          $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
1593          return;
1594       }
1595
1596       $file_md5 = md5_file($full_path);
1597
1598       $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
1599
1600       $changes = false;
1601
1602       foreach($resolutions as $resolution) {
1603    
1604          $generate_it = false;
1605
1606          $thumb_sub_path = substr($file_md5, 0, 2);
1607          $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
1608
1609          /* if thumbnail-subdirectory does not exist yet, create it */
1610          if(!file_exists(dirname($thumb_path))) {
1611             mkdir(dirname($thumb_path), 0755);
1612          }
1613
1614          /* if the thumbnail file doesn't exist, create it */
1615          if(!file_exists($thumb_path)) {
1616             $generate_it = true;
1617          }
1618          /* if the file hasn't changed there is no need to regen the thumb */
1619          elseif($file_md5 != $this->getMD5($idx) || $force) {
1620             $generate_it = true;
1621          }
1622
1623          if($generate_it || $overwrite) {
1624
1625             $this->_debug(" ". $resolution ."px");
1626             if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1627                $error = 1;
1628
1629             $changes = true;
1630          }
1631       }
1632
1633       if(!$changes) {
1634          $this->_debug(" already exist");
1635       }
1636
1637       /* set the new/changed MD5 sum for the current photo */
1638       if(!$error) {
1639          $this->setMD5($idx, $file_md5);
1640       }
1641
1642       $this->_debug("\n");
1643
1644    } // gen_thumb()
1645
1646    /**
1647     * returns stored md5 sum for a specific photo
1648     *
1649     * this function queries the phpfspot database for a
1650     * stored MD5 checksum of the specified photo
1651     * @param integer $idx
1652     * @return string|null
1653     */
1654    public function getMD5($idx)
1655    {
1656       $result = $this->cfg_db->db_query("
1657          SELECT img_md5 
1658          FROM images
1659          WHERE img_idx='". $idx ."'
1660       ");
1661
1662       if(!$result)
1663          return 0;
1664
1665       $img = $this->cfg_db->db_fetch_object($result);
1666       return $img['img_md5'];
1667       
1668    } // getMD5()
1669
1670    /**
1671     * set MD5 sum for the specific photo
1672     * @param integer $idx
1673     * @param string $md5
1674     */
1675    private function setMD5($idx, $md5)
1676    {
1677       $result = $this->cfg_db->db_exec("
1678          REPLACE INTO images (img_idx, img_md5)
1679          VALUES ('". $idx ."', '". $md5 ."')
1680       ");
1681
1682    } // setMD5()
1683
1684    /**
1685     * store current tag condition
1686     *
1687     * this function stores the current tag condition
1688     * (AND or OR) in the users session variables
1689     * @param string $mode
1690     * @return string
1691     */
1692    public function setTagCondition($mode)
1693    {
1694       $_SESSION['tag_condition'] = $mode;
1695
1696       return "ok";
1697
1698    } // setTagCondition()
1699
1700    /** 
1701     * invoke tag & date search 
1702     *
1703     * this function will return all matching tags and store
1704     * them in the session variable selected_tags. furthermore
1705     * it also handles the date search.
1706     * getPhotoSelection() will then only return the matching
1707     * photos.
1708     * @return string
1709     */
1710    public function startSearch()
1711    {
1712       if(isset($_POST['from']) && $this->isValidDate($_POST['from'])) {
1713          $from = $_POST['from'];
1714       }
1715       if(isset($_POST['to']) && $this->isValidDate($_POST['to'])) {
1716          $to = $_POST['to'];
1717       }
1718
1719       if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
1720          $searchfor_tag = $_POST['for_tag'];
1721          $_SESSION['searchfor_tag'] = $_POST['for_tag'];
1722       }
1723
1724       if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
1725          $searchfor_name = $_POST['for_name'];
1726          $_SESSION['searchfor_name'] = $_POST['for_name'];
1727       }
1728
1729       $this->get_tags();
1730
1731       if(isset($from) && !empty($from))
1732          $_SESSION['from_date'] = strtotime($from ." 00:00:00");
1733       else
1734          unset($_SESSION['from_date']);
1735
1736       if(isset($to) && !empty($to))
1737          $_SESSION['to_date'] = strtotime($to ." 23:59:59");
1738       else
1739          unset($_SESSION['to_date']);
1740
1741       if(isset($searchfor_tag) && !empty($searchfor_tag)) {
1742          /* new search, reset the current selected tags */
1743          $_SESSION['selected_tags'] = Array();
1744          foreach($this->avail_tags as $tag) {
1745             if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
1746                array_push($_SESSION['selected_tags'], $tag);
1747          }
1748       }
1749
1750       return "ok";
1751
1752    } // startSearch()
1753
1754    /**
1755     * updates sort order in session variable
1756     *
1757     * this function is invoked by RPC and will sort the requested
1758     * sort order in the session variable.
1759     * @param string $sort_order
1760     * @return string
1761     */
1762    public function updateSortOrder($order)
1763    {
1764       if(isset($this->sort_orders[$order])) {
1765          $_SESSION['sort_order'] = $order;
1766          return "ok";
1767       }
1768
1769       return "unkown error";
1770
1771    } // updateSortOrder()
1772
1773    /**
1774     * rotate image
1775     *
1776     * this function rotates the image according the
1777     * specified angel.
1778     * @param string $img
1779     * @param integer $degress
1780     * @return image
1781     */
1782    private function rotateImage($img, $degrees)
1783    {
1784       if(function_exists("imagerotate")) {
1785          $img = imagerotate($img, $degrees, 0);
1786       } else {
1787          function imagerotate($src_img, $angle)
1788          {
1789             $src_x = imagesx($src_img);
1790             $src_y = imagesy($src_img);
1791             if ($angle == 180) {
1792                $dest_x = $src_x;
1793                $dest_y = $src_y;
1794             }
1795             elseif ($src_x <= $src_y) {
1796                $dest_x = $src_y;
1797                $dest_y = $src_x;
1798             }
1799             elseif ($src_x >= $src_y) {
1800                $dest_x = $src_y;
1801                $dest_y = $src_x;
1802             }
1803                
1804             $rotate=imagecreatetruecolor($dest_x,$dest_y);
1805             imagealphablending($rotate, false);
1806                
1807             switch ($angle) {
1808             
1809                case 90:
1810                   for ($y = 0; $y < ($src_y); $y++) {
1811                      for ($x = 0; $x < ($src_x); $x++) {
1812                         $color = imagecolorat($src_img, $x, $y);
1813                         imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
1814                      }
1815                   }
1816                   break;
1817
1818                case 270:
1819                   for ($y = 0; $y < ($src_y); $y++) {
1820                      for ($x = 0; $x < ($src_x); $x++) {
1821                         $color = imagecolorat($src_img, $x, $y);
1822                         imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
1823                      }
1824                   }
1825                   break;
1826
1827                case 180:
1828                   for ($y = 0; $y < ($src_y); $y++) {
1829                      for ($x = 0; $x < ($src_x); $x++) {
1830                         $color = imagecolorat($src_img, $x, $y);
1831                         imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
1832                      }
1833                   }
1834                   break;
1835
1836                default:
1837                   $rotate = $src_img;
1838                   break;
1839             };
1840
1841             return $rotate;
1842
1843          }
1844
1845          $img = imagerotate($img, $degrees);
1846
1847       }
1848
1849       return $img;
1850
1851    } // rotateImage()
1852
1853    /**
1854     * returns flipped image
1855     *
1856     * this function will return an either horizontal or
1857     * vertical flipped truecolor image.
1858     * @param string $image
1859     * @param string $mode 
1860     * @return image
1861     */
1862    private function flipImage($image, $mode)
1863    {
1864       $w = imagesx($image);
1865       $h = imagesy($image);
1866       $flipped = imagecreatetruecolor($w, $h);
1867
1868       switch($mode) {
1869          case 'vert':
1870             for ($y = 0; $y < $h; $y++) {
1871                imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
1872             }
1873             break;
1874          case 'hori':
1875             for ($x = 0; $x < $w; $x++) {
1876                imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
1877             }
1878             break;
1879       }
1880
1881       return $flipped;
1882
1883    } // flipImage()
1884
1885    /**
1886     * return all assigned tags for the specified photo
1887     * @param integer $idx
1888     * @return array
1889     */
1890    private function get_photo_tags($idx)
1891    {
1892       $result = $this->db->db_query("
1893          SELECT t.id, t.name
1894          FROM tags t
1895          INNER JOIN photo_tags pt
1896             ON t.id=pt.tag_id
1897          WHERE pt.photo_id='". $idx ."'
1898       ");
1899
1900       $tags = Array();
1901
1902       while($row = $this->db->db_fetch_object($result))
1903          $tags[$row['id']] = $row['name'];
1904
1905       return $tags;
1906
1907    } // get_photo_tags()
1908
1909    /**
1910     * create on-the-fly images with text within
1911     * @param string $txt
1912     * @param string $color
1913     * @param integer $space
1914     * @param integer $font
1915     * @param integer $w
1916     */
1917    public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
1918    {
1919       if (strlen($color) != 6) 
1920          $color = 000000;
1921
1922       $int = hexdec($color);
1923       $h = imagefontheight($font);
1924       $fw = imagefontwidth($font);
1925       $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
1926       $lines = count($txt);
1927       $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
1928       $bg = imagecolorallocate($im, 255, 255, 255);
1929       $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
1930       $y = 0;
1931
1932       foreach ($txt as $text) {
1933          $x = (($w - ($fw * strlen($text))) / 2);
1934          imagestring($im, $font, $x, $y, $text, $color);
1935          $y += ($h + $space);
1936       }
1937
1938       Header("Content-type: image/png");
1939       ImagePng($im);
1940
1941    } // showTextImage()
1942
1943    /**
1944     * check if all requirements are met
1945     * @return boolean
1946     */
1947    private function check_requirements()
1948    {
1949       if(!function_exists("imagecreatefromjpeg")) {
1950          print "PHP GD library extension is missing<br />\n";
1951          $missing = true;
1952       }
1953
1954       if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
1955          print "PHP SQLite3 library extension is missing<br />\n";
1956          $missing = true;
1957       }
1958
1959       /* Check for HTML_AJAX PEAR package, lent from Horde project */
1960       ini_set('track_errors', 1);
1961       @include_once 'HTML/AJAX/Server.php';
1962       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1963          print "PEAR HTML_AJAX package is missing<br />\n";
1964          $missing = true;
1965       }
1966       @include_once 'Calendar/Calendar.php';
1967       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1968          print "PEAR Calendar package is missing<br />\n";
1969          $missing = true;
1970       }
1971       @include_once 'Console/Getopt.php';
1972       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1973          print "PEAR Console_Getopt package is missing<br />\n";
1974          $missing = true;
1975       }
1976       @include_once $this->cfg->smarty_path .'/libs/Smarty.class.php';
1977       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1978          print "Smarty template engine can not be found in ". $this->cfg->smarty_path ."/libs/Smarty.class.php<br />\n";
1979          $missing = true;
1980       }
1981       ini_restore('track_errors');
1982
1983       if(isset($missing))
1984          return false;
1985
1986       return true;
1987
1988    } // check_requirements()
1989
1990    private function _debug($text)
1991    {
1992       if($this->fromcmd) {
1993          print $text;
1994       }
1995
1996    } // _debug()
1997
1998    /**
1999     * check if specified MIME type is supported
2000     * @param string $mime
2001     * @return boolean
2002     */
2003    public function checkifImageSupported($mime)
2004    {
2005       if(in_array($mime, Array("image/jpeg", "image/png", "image/tiff")))
2006          return true;
2007
2008       return false;
2009
2010    } // checkifImageSupported()
2011
2012    /**
2013     * output error text
2014     * @param string $text
2015     */
2016    public function _error($text)
2017    {
2018       switch($this->cfg->logging) {
2019          default:
2020          case 'display':
2021             print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
2022             print $text ."<br />\n";
2023             break;
2024          case 'errorlog':  
2025             error_log($text);
2026             break;
2027          case 'logfile':
2028             error_log($text, 3, $his->cfg->log_file);
2029             break;
2030       }
2031
2032       $this->runtime_error = true;
2033
2034    } // _error()
2035
2036    /**
2037     * output calendard input fields
2038     * @param string $mode
2039     * @return string
2040     */
2041    private function get_calendar($mode)
2042    {
2043       $year = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
2044       $month = isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
2045       $day = isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
2046
2047       $output = "<input type=\"text\" size=\"3\" id=\"". $mode ."year\" value=\"". $year ."\"";
2048       if(!isset($_SESSION[$mode .'_date']))
2049          $output.= " disabled=\"disabled\"";
2050       $output.= " />\n";
2051       $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."month\" value=\"". $month ."\"";
2052       if(!isset($_SESSION[$mode .'_date']))
2053          $output.= " disabled=\"disabled\"";
2054       $output.= " />\n";
2055       $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."day\" value=\"". $day ."\"";
2056       if(!isset($_SESSION[$mode .'_date']))
2057          $output.= " disabled=\"disabled\"";
2058       $output.= " />\n";
2059
2060       return $output;
2061
2062    } // get_calendar()
2063
2064    /**
2065     * output calendar matrix
2066     * @param integer $year
2067     * @param integer $month
2068     * @param integer $day
2069     */
2070    public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
2071    {
2072       if (!isset($year)) $year = date('Y');
2073       if (!isset($month)) $month = date('m');
2074       if (!isset($day)) $day = date('d');
2075       $rows = 1;
2076       $cols = 1;
2077       $matrix = Array();
2078
2079       require_once CALENDAR_ROOT.'Month/Weekdays.php';
2080       require_once CALENDAR_ROOT.'Day.php';
2081
2082       // Build the month
2083       $month = new Calendar_Month_Weekdays($year,$month);
2084
2085       // Create links
2086       $prevStamp = $month->prevMonth(true);
2087       $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
2088       $nextStamp = $month->nextMonth(true);
2089       $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
2090
2091       $selectedDays = array (
2092          new Calendar_Day($year,$month,$day),
2093          new Calendar_Day($year,12,25),
2094       );
2095
2096       // Build the days in the month
2097       $month->build($selectedDays);
2098
2099       $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
2100       $this->tmpl->assign('prev_month', $prev);
2101       $this->tmpl->assign('next_month', $next);
2102
2103       while ( $day = $month->fetch() ) {
2104    
2105          if(!isset($matrix[$rows]))
2106             $matrix[$rows] = Array();
2107
2108          $string = "";
2109
2110          $dayStamp = $day->thisDay(true);
2111          $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
2112
2113          // isFirst() to find start of week
2114          if ( $day->isFirst() )
2115             $string.= "<tr>\n";
2116
2117          if ( $day->isSelected() ) {
2118             $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
2119          } else if ( $day->isEmpty() ) {
2120             $string.= "<td>&nbsp;</td>\n";
2121          } else {
2122             $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
2123          }
2124
2125          // isLast() to find end of week
2126          if ( $day->isLast() )
2127             $string.= "</tr>\n";
2128
2129          $matrix[$rows][$cols] = $string;
2130
2131          $cols++;
2132
2133          if($cols > 7) {
2134             $cols = 1;
2135             $rows++;
2136          }
2137       }
2138
2139       $this->tmpl->assign('matrix', $matrix);
2140       $this->tmpl->assign('rows', $rows);
2141       $this->tmpl->show("calendar.tpl");
2142
2143    } // get_calendar_matrix()
2144
2145    /**
2146     * output export page
2147     * @param string $mode
2148     */
2149    public function getExport($mode)
2150    {
2151       $pictures = $this->getPhotoSelection();
2152       $current_tags = $this->getCurrentTags();  
2153
2154       foreach($pictures as $picture) {
2155
2156          $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2157          if($current_tags != "") {
2158             $orig_url.= "&tags=". $current_tags;
2159          } 
2160          if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2161             $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2162          }
2163
2164          $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2165
2166          switch($mode) {
2167
2168             case 'HTML':
2169                // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
2170                print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
2171                break;
2172                
2173             case 'MoinMoin':
2174                // "[%pictureurl% %thumbnailurl%]"
2175                print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2176                break;
2177
2178             case 'MoinMoinList':
2179                // " * [%pictureurl% %thumbnailurl%]"
2180                print "&nbsp;" . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2181                break;
2182          }
2183
2184       }
2185
2186    } // getExport()
2187
2188    /**
2189     * output RSS feed
2190     */
2191    public function getRSSFeed()
2192    {
2193       Header("Content-type: text/xml; charset=utf-8");
2194       print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2195 ?>
2196 <rss version="2.0"
2197    xmlns:media="http://search.yahoo.com/mrss/"
2198    xmlns:dc="http://purl.org/dc/elements/1.1/"
2199  >
2200  <channel>
2201   <title>phpfspot</title>
2202   <description>phpfspot RSS feed</description>
2203   <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2204   <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2205   <generator>phpfspot</generator>
2206 <?php
2207
2208       $pictures = $this->getPhotoSelection();
2209       $current_tags = $this->getCurrentTags();  
2210
2211       foreach($pictures as $picture) {
2212
2213          $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2214          if($current_tags != "") {
2215             $orig_url.= "&tags=". $current_tags;
2216          } 
2217          if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2218             $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2219          }
2220
2221          $details = $this->get_photo_details($picture);
2222
2223          $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2224          $thumb_html = htmlspecialchars("
2225 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2226 <br>
2227 ". $details['description']);
2228
2229          $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2230
2231          /* get EXIF information if JPEG */
2232          if($details['mime'] == "image/jpeg") {
2233             $meta = $this->get_meta_informations($orig_path);
2234          }
2235
2236          $meta_date = isset($meta['FileDateTime']) ? $meta['FileDateTime'] : filemtime($orig_path);
2237
2238 ?>
2239   <item>
2240    <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2241    <link><?php print htmlspecialchars($orig_url); ?></link>
2242    <guid><?php print htmlspecialchars($orig_url); ?></guid>
2243    <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $meta_date); ?></dc:date.Taken>
2244    <description>
2245     <?php print $thumb_html; ?> 
2246    </description>
2247    <pubDate><?php print strftime("%a, %d %b %Y %T %z", $meta_date); ?></pubDate>
2248   </item>
2249 <?php
2250
2251       }
2252 ?>
2253  </channel>
2254 </rss>
2255 <?php
2256
2257
2258    } // getExport()
2259
2260  
2261    /**
2262     * return all selected tags as one string
2263     * @return array
2264     */
2265    private function getCurrentTags()
2266    {
2267       $current_tags = "";
2268       if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2269          foreach($_SESSION['selected_tags'] as $tag)
2270             $current_tags.= $tag .",";
2271          $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2272       }
2273       return $current_tags;
2274
2275    } // getCurrentTags()
2276
2277    /**
2278     * return the current photo
2279     */
2280    public function getCurrentPhoto()
2281    {
2282       if(isset($_SESSION['current_photo'])) {
2283          print $_SESSION['current_photo'];
2284       }
2285    } // getCurrentPhoto()
2286
2287    /**
2288     * tells the client browser what to do
2289     *
2290     * this function is getting called via AJAX by the
2291     * client browsers. it will tell them what they have
2292     * to do next. This is necessary for directly jumping
2293     * into photo index or single photo view when the are
2294     * requested with specific URLs
2295     * @return string
2296     */
2297    public function whatToDo()
2298    {
2299       if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
2300          return "show_photo";
2301       }
2302       elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
2303          return "showpi_tags";
2304       }
2305       elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
2306          return "showpi";
2307       }
2308
2309       return "nothing special";
2310
2311    } // whatToDo()
2312
2313    /**
2314     * return the current process-user
2315     * @return string
2316     */
2317    private function getuid()
2318    {
2319       if($uid = posix_getuid()) {
2320          if($user = posix_getpwuid($uid)) {
2321             return $user['name'];
2322          }
2323       }
2324    
2325       return 'n/a';
2326    
2327    } // getuid()
2328
2329    /**
2330     * returns a select-dropdown box to select photo index sort parameters
2331     * @param array $params
2332     * @param smarty $smarty
2333     * @return string
2334     */
2335    public function smarty_sort_select_list($params, &$smarty)
2336    {
2337       $output = "";
2338
2339       foreach($this->sort_orders as $key => $value) {
2340          $output.= "<option value=\"". $key ."\"";
2341          if($key == $_SESSION['sort_order']) {
2342             $output.= " selected=\"selected\"";
2343          }
2344          $output.= ">". $value ."</option>";
2345       }
2346
2347       return $output;
2348
2349    } // smarty_sort_select_list()
2350
2351    /**
2352     * returns the currently selected sort order
2353     * @return string
2354     */ 
2355    private function get_sort_order()
2356    {
2357       switch($_SESSION['sort_order']) {
2358          case 'date_asc':
2359             return " ORDER BY p.time ASC";
2360             break;
2361          case 'date_desc':
2362             return " ORDER BY p.time DESC";
2363             break;
2364          case 'name_asc':
2365             if($this->dbver < 9) {
2366                return " ORDER BY p.name ASC";
2367             }
2368             else {
2369                return " ORDER BY basename(p.uri) ASC";
2370             }
2371             break;
2372          case 'name_desc':
2373             if($this->dbver < 9) {
2374                return " ORDER BY p.name DESC";
2375             }
2376             else {
2377                return " ORDER BY basename(p.uri) DESC";
2378             }
2379             break;
2380          case 'tags_asc':
2381             return " ORDER BY t.name ASC ,p.time ASC";
2382             break;
2383          case 'tags_desc':
2384             return " ORDER BY t.name DESC ,p.time ASC";
2385             break;
2386       }
2387
2388    } // get_sort_order()
2389
2390    /**
2391     * return the next to be shown slide show image
2392     *
2393     * this function returns the URL of the next image
2394     * in the slideshow sequence.
2395     * @return string
2396     */
2397    public function getNextSlideShowImage()
2398    {
2399       $all_photos = $this->getPhotoSelection();
2400
2401       if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1) 
2402          $_SESSION['slideshow_img'] = 0;
2403       else
2404          $_SESSION['slideshow_img']++;
2405
2406       return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2407
2408    } // getNextSlideShowImage()
2409
2410    /**
2411     * return the previous to be shown slide show image
2412     *
2413     * this function returns the URL of the previous image
2414     * in the slideshow sequence.
2415     * @return string
2416     */
2417    public function getPrevSlideShowImage()
2418    {
2419       $all_photos = $this->getPhotoSelection();
2420
2421       if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
2422          $_SESSION['slideshow_img'] = 0;
2423       else
2424          $_SESSION['slideshow_img']--;
2425
2426       return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2427
2428    } // getPrevSlideShowImage()
2429
2430    public function resetSlideShow()
2431    {
2432       if(isset($_SESSION['slideshow_img']))
2433          unset($_SESSION['slideshow_img']);
2434
2435    } // resetSlideShow()
2436    
2437    /**
2438     * get random photo
2439     *
2440     * this function will get all photos from the fspot
2441     * database and randomly return ONE entry
2442     *
2443     * saddly there is yet no sqlite3 function which returns
2444     * the bulk result in array, so we have to fill up our
2445     * own here.
2446     * @return array
2447     */
2448    public function get_random_photo()
2449    {
2450       $all = Array();
2451
2452       $result = $this->db->db_query("
2453          SELECT id
2454          FROM photos
2455       ");
2456       
2457       while($row = $this->db->db_fetch_object($result)) {
2458          array_push($all, $row['id']);
2459       }
2460
2461       return $all[array_rand($all)];
2462
2463    } // get_random_photo()
2464
2465    /**
2466     * validates provided date
2467     *
2468     * this function validates if the provided date
2469     * contains a valid date and will return true 
2470     * if it is.
2471     * @param string $date_str
2472     * @return boolean
2473     */
2474    public function isValidDate($date_str)
2475    {
2476       $timestamp = strtotime($date_str);
2477    
2478       if(is_numeric($timestamp))
2479          return true;
2480       
2481       return false;
2482
2483    } // isValidDate()
2484
2485    /**
2486     * timestamp to string conversion
2487     * @param integer $timestamp
2488     * @return string
2489     */
2490    private function ts2str($timestamp)
2491    {
2492       return strftime("%Y-%m-%d", $timestamp);
2493    } // ts2str()
2494
2495    /**
2496     * extract tag-names from $_GET['tags']
2497     * @param string $tags_str
2498     * @return string
2499     */
2500    private function extractTags($tags_str)
2501    {
2502       $not_validated = split(',', $tags_str);
2503       $validated = array();
2504
2505       foreach($not_validated as $tag) {
2506          if(is_numeric($tag))
2507             array_push($validated, $tag);
2508       }
2509    
2510       return $validated;
2511    
2512    } // extractTags()
2513
2514    /**
2515     * returns the full path to a thumbnail
2516     * @param integer $width
2517     * @param integer $photo
2518     * @return string
2519     */
2520    public function get_thumb_path($width, $photo)
2521    {
2522       $md5 = $this->getMD5($photo);
2523       $sub_path = substr($md5, 0, 2);
2524       return $this->cfg->thumb_path
2525          . "/"
2526          . $sub_path
2527          . "/"
2528          . $width
2529          . "_"
2530          . $md5;
2531
2532    } // get_thumb_path()
2533
2534    /**
2535     * returns server's virtual host name
2536     * @return string
2537     */
2538    private function get_server_name()
2539    {
2540       return $_SERVER['SERVER_NAME'];
2541    } // get_server_name()
2542
2543    /**
2544     * returns type of webprotocol which is currently used
2545     * @return string
2546     */
2547    private function get_web_protocol()
2548    {
2549       if(!isset($_SERVER['HTTPS']))
2550          return "http";
2551       else
2552          return "https";
2553    } // get_web_protocol()
2554
2555    /**
2556     * return url to this phpfspot installation
2557     * @return string
2558     */
2559    private function get_phpfspot_url()
2560    {
2561       return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
2562    } // get_phpfspot_url()
2563
2564    /**
2565     * returns the number of photos which are tagged with $tag_id
2566     * @param integer $tag_id
2567     * @return integer
2568     */
2569    public function get_num_photos($tag_id)
2570    {
2571       if($result = $this->db->db_fetchSingleRow("
2572          SELECT count(*) as number
2573          FROM photo_tags
2574          WHERE
2575             tag_id LIKE '". $tag_id ."'")) {
2576
2577          return $result['number'];
2578
2579       }
2580
2581       return 0;
2582       
2583    } // get_num_photos()
2584    
2585    /**
2586     * check file exists and is readable
2587     *
2588     * returns true, if everything is ok, otherwise false
2589     * if $silent is not set, this function will output and
2590     * error message
2591     * @param string $file
2592     * @param boolean $silent
2593     * @return boolean
2594     */
2595    private function check_readable($file, $silent = null)
2596    {
2597       if(!file_exists($file)) {
2598          if(!isset($silent))
2599             print "File \"". $file ."\" does not exist.\n";
2600          return false;
2601       }
2602
2603       if(!is_readable($file)) {
2604          if(!isset($silent))
2605             print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
2606          return false;
2607       }
2608
2609       return true;
2610
2611    } // check_readable()
2612
2613    /**
2614     * check if all needed indices are present
2615     *
2616     * this function checks, if some needed indices are already
2617     * present, or if not, create them on the fly. they are
2618     * necessary to speed up some queries like that one look for
2619     * all tags, when show_tags is specified in the configuration.
2620     */
2621    private function checkDbIndices()
2622    {
2623       $result = $this->db->db_exec("
2624          CREATE INDEX IF NOT EXISTS
2625             phototag
2626          ON
2627             photo_tags
2628                (photo_id, tag_id)
2629       ");
2630
2631    } // checkDbIndices()
2632
2633    /**
2634     * retrive F-Spot database version
2635     *
2636     * this function will return the F-Spot database version number
2637     * It is stored within the sqlite3 database in the table meta
2638     * @return string|null
2639     */
2640    public function getFspotDBVersion()
2641    {
2642       if($result = $this->db->db_fetchSingleRow("
2643          SELECT data as version
2644          FROM meta
2645          WHERE
2646             name LIKE 'F-Spot Database Version'
2647       "))
2648          return $result['version'];
2649
2650       return null;
2651
2652    } // getFspotDBVersion()
2653
2654    /**
2655     * parse the provided URI and will returned the requested chunk
2656     * @param string $uri
2657     * @param string $mode
2658     * @return string
2659     */
2660    public function parse_uri($uri, $mode)
2661    {
2662       if(($components = parse_url($uri)) !== false) {
2663
2664          switch($mode) {
2665             case 'filename':
2666                return basename($components['path']);
2667                break;
2668             case 'dirname':
2669                return dirname($components['path']);
2670                break;
2671             case 'fullpath':
2672                return $components['path'];
2673                break;
2674          }
2675       }
2676
2677       return $uri;
2678
2679    } // parse_uri()
2680
2681    /**
2682     * validate config options
2683     *
2684     * this function checks if all necessary configuration options are
2685     * specified and set.
2686     * @return boolean
2687     */
2688    private function check_config_options()
2689    {
2690       if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
2691          $this->_error("Please set \$page_title in phpfspot_cfg");
2692
2693       if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
2694          $this->_error("Please set \$base_path in phpfspot_cfg");
2695
2696       if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
2697          $this->_error("Please set \$web_path in phpfspot_cfg");
2698
2699       if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
2700          $this->_error("Please set \$thumb_path in phpfspot_cfg");
2701
2702       if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
2703          $this->_error("Please set \$smarty_path in phpfspot_cfg");
2704
2705       if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
2706          $this->_error("Please set \$fspot_db in phpfspot_cfg");
2707
2708       if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
2709          $this->_error("Please set \$db_access in phpfspot_cfg");
2710
2711       if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
2712          $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
2713
2714       if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
2715          $this->_error("Please set \$thumb_width in phpfspot_cfg");
2716
2717       if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
2718          $this->_error("Please set \$thumb_height in phpfspot_cfg");
2719
2720       if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
2721          $this->_error("Please set \$photo_width in phpfspot_cfg");
2722
2723       if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
2724          $this->_error("Please set \$mini_width in phpfspot_cfg");
2725
2726       if(!isset($this->cfg->thumbs_per_page))
2727          $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
2728
2729       if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
2730          $this->_error("Please set \$path_replace_from in phpfspot_cfg");
2731
2732       if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
2733          $this->_error("Please set \$path_replace_to in phpfspot_cfg");
2734
2735       if(!isset($this->cfg->hide_tags))
2736          $this->_error("Please set \$hide_tags in phpfspot_cfg");
2737
2738       if(!isset($this->cfg->theme_name))
2739          $this->_error("Please set \$theme_name in phpfspot_cfg");
2740
2741       if(!isset($this->cfg->logging))
2742          $this->_error("Please set \$logging in phpfspot_cfg");
2743
2744       if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
2745
2746          if(!isset($this->cfg->log_file))
2747             $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
2748
2749          if(!is_writeable($this->cfg->log_file))
2750             $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
2751
2752       }
2753
2754       /* check for pending slash on web_path */
2755       if(!preg_match("/\/$/", $this->cfg->web_path))
2756          $this->cfg->web_path.= "/";
2757
2758       return $this->runtime_error;
2759
2760    } // check_config_options()
2761
2762    /**
2763     * cleanup phpfspot own database
2764     *
2765     * When photos are getting delete from F-Spot, there will remain
2766     * remain some residues in phpfspot own database. This function
2767     * will try to wipe them out.
2768     */
2769    public function cleanup_phpfspot_db()
2770    {
2771       $to_delete = Array();
2772
2773       $result = $this->cfg_db->db_query("
2774          SELECT img_idx
2775          FROM images
2776          ORDER BY img_idx ASC
2777       ");
2778
2779       while($row = $this->cfg_db->db_fetch_object($result)) {
2780          if(!$this->db->db_fetchSingleRow("
2781             SELECT id
2782             FROM photos
2783             WHERE id='". $row['img_idx'] ."'")) {
2784
2785             array_push($to_delete, $row['img_idx'], ',');
2786          }
2787       }
2788
2789       print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
2790
2791       $this->cfg_db->db_exec("
2792          DELETE FROM images
2793          WHERE img_idx IN (". implode($to_delete) .")
2794       ");
2795
2796    } // cleanup_phpfspot_db()
2797
2798    /**
2799     * return first image of the page, the $current photo
2800     * lies in.
2801     *
2802     * this function is used to find out the first photo of the
2803     * current page, in which the $current photo lies. this is
2804     * used to display the correct photo, when calling showPhotoIndex()
2805     * from showImage()
2806     * @param integer $current
2807     * @param integer $max
2808     * @return integer
2809     */
2810    private function getCurrentPage($current, $max)
2811    {
2812       if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
2813          for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
2814             if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
2815                return $page_start;
2816          }
2817       }
2818       return 0;
2819
2820    } // getCurrentPage()
2821
2822 } // class PHPFSPOT
2823
2824 ?>