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