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