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