issue116, do not show hidden tags in the photo-details box
[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          if(isset($this->cfg->hide_tags) && in_array($row['name'], $this->cfg->hide_tags))
1975             continue;
1976          $tags[$row['id']] = $row['name'];
1977       }
1978
1979       return $tags;
1980
1981    } // get_photo_tags()
1982
1983    /**
1984     * create on-the-fly images with text within
1985     * @param string $txt
1986     * @param string $color
1987     * @param integer $space
1988     * @param integer $font
1989     * @param integer $w
1990     */
1991    public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
1992    {
1993       if (strlen($color) != 6) 
1994          $color = 000000;
1995
1996       $int = hexdec($color);
1997       $h = imagefontheight($font);
1998       $fw = imagefontwidth($font);
1999       $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
2000       $lines = count($txt);
2001       $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
2002       $bg = imagecolorallocate($im, 255, 255, 255);
2003       $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
2004       $y = 0;
2005
2006       foreach ($txt as $text) {
2007          $x = (($w - ($fw * strlen($text))) / 2);
2008          imagestring($im, $font, $x, $y, $text, $color);
2009          $y += ($h + $space);
2010       }
2011
2012       Header("Content-type: image/png");
2013       ImagePng($im);
2014
2015    } // showTextImage()
2016
2017    /**
2018     * check if all requirements are met
2019     * @return boolean
2020     */
2021    private function check_requirements()
2022    {
2023       if(!function_exists("imagecreatefromjpeg")) {
2024          print "PHP GD library extension is missing<br />\n";
2025          $missing = true;
2026       }
2027
2028       if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
2029          print "PHP SQLite3 library extension is missing<br />\n";
2030          $missing = true;
2031       }
2032
2033       /* Check for HTML_AJAX PEAR package, lent from Horde project */
2034       ini_set('track_errors', 1);
2035       @include_once 'HTML/AJAX/Server.php';
2036       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2037          print "PEAR HTML_AJAX package is missing<br />\n";
2038          $missing = true;
2039       }
2040       @include_once 'Calendar/Calendar.php';
2041       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2042          print "PEAR Calendar package is missing<br />\n";
2043          $missing = true;
2044       }
2045       @include_once 'Console/Getopt.php';
2046       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2047          print "PEAR Console_Getopt package is missing<br />\n";
2048          $missing = true;
2049       }
2050       @include_once $this->cfg->smarty_path .'/libs/Smarty.class.php';
2051       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
2052          print "Smarty template engine can not be found in ". $this->cfg->smarty_path ."/libs/Smarty.class.php<br />\n";
2053          $missing = true;
2054       }
2055       ini_restore('track_errors');
2056
2057       if(isset($missing))
2058          return false;
2059
2060       return true;
2061
2062    } // check_requirements()
2063
2064    private function _debug($text)
2065    {
2066       if($this->fromcmd) {
2067          print $text;
2068       }
2069
2070    } // _debug()
2071
2072    /**
2073     * check if specified MIME type is supported
2074     * @param string $mime
2075     * @return boolean
2076     */
2077    public function checkifImageSupported($mime)
2078    {
2079       $supported_types =  Array(
2080          "image/jpeg",
2081          "image/png",
2082          "image/x-portable-pixmap",
2083          "image/tiff"
2084       );
2085
2086       if(in_array($mime, $supported_types))
2087          return true;
2088
2089       return false;
2090
2091    } // checkifImageSupported()
2092
2093    /**
2094     * output error text
2095     * @param string $text
2096     */
2097    public function _error($text)
2098    {
2099       switch($this->cfg->logging) {
2100          default:
2101          case 'display':
2102             print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
2103             print $text ."<br />\n";
2104             break;
2105          case 'errorlog':  
2106             error_log($text);
2107             break;
2108          case 'logfile':
2109             error_log($text, 3, $his->cfg->log_file);
2110             break;
2111       }
2112
2113       $this->runtime_error = true;
2114
2115    } // _error()
2116
2117    /**
2118     * output calendard input fields
2119     * @param string $mode
2120     * @return string
2121     */
2122    private function get_calendar($mode)
2123    {
2124       $year = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
2125       $month = isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
2126       $day = isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
2127
2128       $output = "<input type=\"text\" size=\"3\" id=\"". $mode ."year\" value=\"". $year ."\"";
2129       if(!isset($_SESSION[$mode .'_date']))
2130          $output.= " disabled=\"disabled\"";
2131       $output.= " />\n";
2132       $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."month\" value=\"". $month ."\"";
2133       if(!isset($_SESSION[$mode .'_date']))
2134          $output.= " disabled=\"disabled\"";
2135       $output.= " />\n";
2136       $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."day\" value=\"". $day ."\"";
2137       if(!isset($_SESSION[$mode .'_date']))
2138          $output.= " disabled=\"disabled\"";
2139       $output.= " />\n";
2140
2141       return $output;
2142
2143    } // get_calendar()
2144
2145    /**
2146     * output calendar matrix
2147     * @param integer $year
2148     * @param integer $month
2149     * @param integer $day
2150     */
2151    public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
2152    {
2153       if (!isset($year)) $year = date('Y');
2154       if (!isset($month)) $month = date('m');
2155       if (!isset($day)) $day = date('d');
2156       $rows = 1;
2157       $cols = 1;
2158       $matrix = Array();
2159
2160       require_once CALENDAR_ROOT.'Month/Weekdays.php';
2161       require_once CALENDAR_ROOT.'Day.php';
2162
2163       // Build the month
2164       $month = new Calendar_Month_Weekdays($year,$month);
2165
2166       // Create links
2167       $prevStamp = $month->prevMonth(true);
2168       $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
2169       $nextStamp = $month->nextMonth(true);
2170       $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
2171
2172       $selectedDays = array (
2173          new Calendar_Day($year,$month,$day),
2174          new Calendar_Day($year,12,25),
2175       );
2176
2177       // Build the days in the month
2178       $month->build($selectedDays);
2179
2180       $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
2181       $this->tmpl->assign('prev_month', $prev);
2182       $this->tmpl->assign('next_month', $next);
2183
2184       while ( $day = $month->fetch() ) {
2185    
2186          if(!isset($matrix[$rows]))
2187             $matrix[$rows] = Array();
2188
2189          $string = "";
2190
2191          $dayStamp = $day->thisDay(true);
2192          $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
2193
2194          // isFirst() to find start of week
2195          if ( $day->isFirst() )
2196             $string.= "<tr>\n";
2197
2198          if ( $day->isSelected() ) {
2199             $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
2200          } else if ( $day->isEmpty() ) {
2201             $string.= "<td>&nbsp;</td>\n";
2202          } else {
2203             $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
2204          }
2205
2206          // isLast() to find end of week
2207          if ( $day->isLast() )
2208             $string.= "</tr>\n";
2209
2210          $matrix[$rows][$cols] = $string;
2211
2212          $cols++;
2213
2214          if($cols > 7) {
2215             $cols = 1;
2216             $rows++;
2217          }
2218       }
2219
2220       $this->tmpl->assign('matrix', $matrix);
2221       $this->tmpl->assign('rows', $rows);
2222       $this->tmpl->show("calendar.tpl");
2223
2224    } // get_calendar_matrix()
2225
2226    /**
2227     * output export page
2228     * @param string $mode
2229     */
2230    public function getExport($mode)
2231    {
2232       $pictures = $this->getPhotoSelection();
2233       $current_tags = $this->getCurrentTags();  
2234
2235       foreach($pictures as $picture) {
2236
2237          $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2238          if($current_tags != "") {
2239             $orig_url.= "&tags=". $current_tags;
2240          } 
2241          if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2242             $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2243          }
2244
2245          $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2246
2247          switch($mode) {
2248
2249             case 'HTML':
2250                // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
2251                print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
2252                break;
2253                
2254             case 'MoinMoin':
2255                // "[%pictureurl% %thumbnailurl%]"
2256                print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2257                break;
2258
2259             case 'MoinMoinList':
2260                // " * [%pictureurl% %thumbnailurl%]"
2261                print "&nbsp;" . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2262                break;
2263          }
2264
2265       }
2266
2267    } // getExport()
2268
2269    /**
2270     * output RSS feed
2271     */
2272    public function getRSSFeed()
2273    {
2274       Header("Content-type: text/xml; charset=utf-8");
2275       print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2276 ?>
2277 <rss version="2.0"
2278    xmlns:media="http://search.yahoo.com/mrss/"
2279    xmlns:dc="http://purl.org/dc/elements/1.1/"
2280  >
2281  <channel>
2282   <title>phpfspot</title>
2283   <description>phpfspot RSS feed</description>
2284   <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2285   <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2286   <generator>phpfspot</generator>
2287 <?php
2288
2289       $pictures = $this->getPhotoSelection();
2290       $current_tags = $this->getCurrentTags();  
2291
2292       foreach($pictures as $picture) {
2293
2294          $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2295          if($current_tags != "") {
2296             $orig_url.= "&tags=". $current_tags;
2297          } 
2298          if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2299             $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2300          }
2301
2302          $details = $this->get_photo_details($picture);
2303
2304          $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2305          $thumb_html = htmlspecialchars("
2306 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2307 <br>
2308 ". $details['description']);
2309
2310          $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2311
2312          /* get EXIF information if JPEG */
2313          if($details['mime'] == "image/jpeg") {
2314             $meta = $this->get_meta_informations($orig_path);
2315          }
2316
2317          $meta_date = isset($meta['FileDateTime']) ? $meta['FileDateTime'] : filemtime($orig_path);
2318
2319 ?>
2320   <item>
2321    <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2322    <link><?php print htmlspecialchars($orig_url); ?></link>
2323    <guid><?php print htmlspecialchars($orig_url); ?></guid>
2324    <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $meta_date); ?></dc:date.Taken>
2325    <description>
2326     <?php print $thumb_html; ?> 
2327    </description>
2328    <pubDate><?php print strftime("%a, %d %b %Y %T %z", $meta_date); ?></pubDate>
2329   </item>
2330 <?php
2331
2332       }
2333 ?>
2334  </channel>
2335 </rss>
2336 <?php
2337
2338
2339    } // getExport()
2340
2341  
2342    /**
2343     * return all selected tags as one string
2344     * @return array
2345     */
2346    private function getCurrentTags()
2347    {
2348       $current_tags = "";
2349       if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2350          foreach($_SESSION['selected_tags'] as $tag)
2351             $current_tags.= $tag .",";
2352          $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2353       }
2354       return $current_tags;
2355
2356    } // getCurrentTags()
2357
2358    /**
2359     * return the current photo
2360     */
2361    public function getCurrentPhoto()
2362    {
2363       if(isset($_SESSION['current_photo'])) {
2364          print $_SESSION['current_photo'];
2365       }
2366    } // getCurrentPhoto()
2367
2368    /**
2369     * tells the client browser what to do
2370     *
2371     * this function is getting called via AJAX by the
2372     * client browsers. it will tell them what they have
2373     * to do next. This is necessary for directly jumping
2374     * into photo index or single photo view when the are
2375     * requested with specific URLs
2376     * @return string
2377     */
2378    public function whatToDo()
2379    {
2380       if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
2381          return "show_photo";
2382       }
2383       elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
2384          return "showpi_tags";
2385       }
2386       elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
2387          return "showpi";
2388       }
2389
2390       return "nothing special";
2391
2392    } // whatToDo()
2393
2394    /**
2395     * return the current process-user
2396     * @return string
2397     */
2398    private function getuid()
2399    {
2400       if($uid = posix_getuid()) {
2401          if($user = posix_getpwuid($uid)) {
2402             return $user['name'];
2403          }
2404       }
2405    
2406       return 'n/a';
2407    
2408    } // getuid()
2409
2410    /**
2411     * returns a select-dropdown box to select photo index sort parameters
2412     * @param array $params
2413     * @param smarty $smarty
2414     * @return string
2415     */
2416    public function smarty_sort_select_list($params, &$smarty)
2417    {
2418       $output = "";
2419
2420       foreach($this->sort_orders as $key => $value) {
2421          $output.= "<option value=\"". $key ."\"";
2422          if($key == $_SESSION['sort_order']) {
2423             $output.= " selected=\"selected\"";
2424          }
2425          $output.= ">". $value ."</option>";
2426       }
2427
2428       return $output;
2429
2430    } // smarty_sort_select_list()
2431
2432    /**
2433     * returns the currently selected sort order
2434     * @return string
2435     */ 
2436    private function get_sort_order()
2437    {
2438       switch($_SESSION['sort_order']) {
2439          case 'date_asc':
2440             return " ORDER BY p.time ASC";
2441             break;
2442          case 'date_desc':
2443             return " ORDER BY p.time DESC";
2444             break;
2445          case 'name_asc':
2446             if($this->dbver < 9) {
2447                return " ORDER BY p.name ASC";
2448             }
2449             else {
2450                return " ORDER BY basename(p.uri) ASC";
2451             }
2452             break;
2453          case 'name_desc':
2454             if($this->dbver < 9) {
2455                return " ORDER BY p.name DESC";
2456             }
2457             else {
2458                return " ORDER BY basename(p.uri) DESC";
2459             }
2460             break;
2461          case 'tags_asc':
2462             return " ORDER BY t.name ASC ,p.time ASC";
2463             break;
2464          case 'tags_desc':
2465             return " ORDER BY t.name DESC ,p.time ASC";
2466             break;
2467       }
2468
2469    } // get_sort_order()
2470
2471    /**
2472     * return the next to be shown slide show image
2473     *
2474     * this function returns the URL of the next image
2475     * in the slideshow sequence.
2476     * @return string
2477     */
2478    public function getNextSlideShowImage()
2479    {
2480       $all_photos = $this->getPhotoSelection();
2481
2482       if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1) 
2483          $_SESSION['slideshow_img'] = 0;
2484       else
2485          $_SESSION['slideshow_img']++;
2486
2487       return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2488
2489    } // getNextSlideShowImage()
2490
2491    /**
2492     * return the previous to be shown slide show image
2493     *
2494     * this function returns the URL of the previous image
2495     * in the slideshow sequence.
2496     * @return string
2497     */
2498    public function getPrevSlideShowImage()
2499    {
2500       $all_photos = $this->getPhotoSelection();
2501
2502       if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
2503          $_SESSION['slideshow_img'] = 0;
2504       else
2505          $_SESSION['slideshow_img']--;
2506
2507       return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2508
2509    } // getPrevSlideShowImage()
2510
2511    public function resetSlideShow()
2512    {
2513       if(isset($_SESSION['slideshow_img']))
2514          unset($_SESSION['slideshow_img']);
2515
2516    } // resetSlideShow()
2517    
2518    /**
2519     * get random photo
2520     *
2521     * this function will get all photos from the fspot
2522     * database and randomly return ONE entry
2523     *
2524     * saddly there is yet no sqlite3 function which returns
2525     * the bulk result in array, so we have to fill up our
2526     * own here.
2527     * @return array
2528     */
2529    public function get_random_photo()
2530    {
2531       $all = Array();
2532
2533       $result = $this->db->db_query("
2534          SELECT id
2535          FROM photos
2536       ");
2537       
2538       while($row = $this->db->db_fetch_object($result)) {
2539          array_push($all, $row['id']);
2540       }
2541
2542       return $all[array_rand($all)];
2543
2544    } // get_random_photo()
2545
2546    /**
2547     * validates provided date
2548     *
2549     * this function validates if the provided date
2550     * contains a valid date and will return true 
2551     * if it is.
2552     * @param string $date_str
2553     * @return boolean
2554     */
2555    public function isValidDate($date_str)
2556    {
2557       $timestamp = strtotime($date_str);
2558    
2559       if(is_numeric($timestamp))
2560          return true;
2561       
2562       return false;
2563
2564    } // isValidDate()
2565
2566    /**
2567     * timestamp to string conversion
2568     * @param integer $timestamp
2569     * @return string
2570     */
2571    private function ts2str($timestamp)
2572    {
2573       return strftime("%Y-%m-%d", $timestamp);
2574    } // ts2str()
2575
2576    /**
2577     * extract tag-names from $_GET['tags']
2578     * @param string $tags_str
2579     * @return string
2580     */
2581    private function extractTags($tags_str)
2582    {
2583       $not_validated = split(',', $tags_str);
2584       $validated = array();
2585
2586       foreach($not_validated as $tag) {
2587          if(is_numeric($tag))
2588             array_push($validated, $tag);
2589       }
2590    
2591       return $validated;
2592    
2593    } // extractTags()
2594
2595    /**
2596     * returns the full path to a thumbnail
2597     * @param integer $width
2598     * @param integer $photo
2599     * @return string
2600     */
2601    public function get_thumb_path($width, $photo)
2602    {
2603       $md5 = $this->getMD5($photo);
2604       $sub_path = substr($md5, 0, 2);
2605       return $this->cfg->thumb_path
2606          . "/"
2607          . $sub_path
2608          . "/"
2609          . $width
2610          . "_"
2611          . $md5;
2612
2613    } // get_thumb_path()
2614
2615    /**
2616     * returns server's virtual host name
2617     * @return string
2618     */
2619    private function get_server_name()
2620    {
2621       return $_SERVER['SERVER_NAME'];
2622    } // get_server_name()
2623
2624    /**
2625     * returns type of webprotocol which is currently used
2626     * @return string
2627     */
2628    private function get_web_protocol()
2629    {
2630       if(!isset($_SERVER['HTTPS']))
2631          return "http";
2632       else
2633          return "https";
2634    } // get_web_protocol()
2635
2636    /**
2637     * return url to this phpfspot installation
2638     * @return string
2639     */
2640    private function get_phpfspot_url()
2641    {
2642       return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
2643    } // get_phpfspot_url()
2644
2645    /**
2646     * returns the number of photos which are tagged with $tag_id
2647     * @param integer $tag_id
2648     * @return integer
2649     */
2650    public function get_num_photos($tag_id)
2651    {
2652       if($result = $this->db->db_fetchSingleRow("
2653          SELECT count(*) as number
2654          FROM photo_tags
2655          WHERE
2656             tag_id LIKE '". $tag_id ."'")) {
2657
2658          return $result['number'];
2659
2660       }
2661
2662       return 0;
2663       
2664    } // get_num_photos()
2665    
2666    /**
2667     * check file exists and is readable
2668     *
2669     * returns true, if everything is ok, otherwise false
2670     * if $silent is not set, this function will output and
2671     * error message
2672     * @param string $file
2673     * @param boolean $silent
2674     * @return boolean
2675     */
2676    private function check_readable($file, $silent = null)
2677    {
2678       if(!file_exists($file)) {
2679          if(!isset($silent))
2680             print "File \"". $file ."\" does not exist.\n";
2681          return false;
2682       }
2683
2684       if(!is_readable($file)) {
2685          if(!isset($silent))
2686             print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
2687          return false;
2688       }
2689
2690       return true;
2691
2692    } // check_readable()
2693
2694    /**
2695     * check if all needed indices are present
2696     *
2697     * this function checks, if some needed indices are already
2698     * present, or if not, create them on the fly. they are
2699     * necessary to speed up some queries like that one look for
2700     * all tags, when show_tags is specified in the configuration.
2701     */
2702    private function checkDbIndices()
2703    {
2704       $result = $this->db->db_exec("
2705          CREATE INDEX IF NOT EXISTS
2706             phototag
2707          ON
2708             photo_tags
2709                (photo_id, tag_id)
2710       ");
2711
2712    } // checkDbIndices()
2713
2714    /**
2715     * retrive F-Spot database version
2716     *
2717     * this function will return the F-Spot database version number
2718     * It is stored within the sqlite3 database in the table meta
2719     * @return string|null
2720     */
2721    public function getFspotDBVersion()
2722    {
2723       if($result = $this->db->db_fetchSingleRow("
2724          SELECT data as version
2725          FROM meta
2726          WHERE
2727             name LIKE 'F-Spot Database Version'
2728       "))
2729          return $result['version'];
2730
2731       return null;
2732
2733    } // getFspotDBVersion()
2734
2735    /**
2736     * parse the provided URI and will returned the requested chunk
2737     * @param string $uri
2738     * @param string $mode
2739     * @return string
2740     */
2741    public function parse_uri($uri, $mode)
2742    {
2743       if(($components = parse_url($uri)) !== false) {
2744
2745          switch($mode) {
2746             case 'filename':
2747                return basename($components['path']);
2748                break;
2749             case 'dirname':
2750                return dirname($components['path']);
2751                break;
2752             case 'fullpath':
2753                return $components['path'];
2754                break;
2755          }
2756       }
2757
2758       return $uri;
2759
2760    } // parse_uri()
2761
2762    /**
2763     * validate config options
2764     *
2765     * this function checks if all necessary configuration options are
2766     * specified and set.
2767     * @return boolean
2768     */
2769    private function check_config_options()
2770    {
2771       if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
2772          $this->_error("Please set \$page_title in phpfspot_cfg");
2773
2774       if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
2775          $this->_error("Please set \$base_path in phpfspot_cfg");
2776
2777       if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
2778          $this->_error("Please set \$web_path in phpfspot_cfg");
2779
2780       if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
2781          $this->_error("Please set \$thumb_path in phpfspot_cfg");
2782
2783       if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
2784          $this->_error("Please set \$smarty_path in phpfspot_cfg");
2785
2786       if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
2787          $this->_error("Please set \$fspot_db in phpfspot_cfg");
2788
2789       if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
2790          $this->_error("Please set \$db_access in phpfspot_cfg");
2791
2792       if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
2793          $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
2794
2795       if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
2796          $this->_error("Please set \$thumb_width in phpfspot_cfg");
2797
2798       if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
2799          $this->_error("Please set \$thumb_height in phpfspot_cfg");
2800
2801       if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
2802          $this->_error("Please set \$photo_width in phpfspot_cfg");
2803
2804       if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
2805          $this->_error("Please set \$mini_width in phpfspot_cfg");
2806
2807       if(!isset($this->cfg->thumbs_per_page))
2808          $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
2809
2810       if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
2811          $this->_error("Please set \$path_replace_from in phpfspot_cfg");
2812
2813       if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
2814          $this->_error("Please set \$path_replace_to in phpfspot_cfg");
2815
2816       if(!isset($this->cfg->hide_tags))
2817          $this->_error("Please set \$hide_tags in phpfspot_cfg");
2818
2819       if(!isset($this->cfg->theme_name))
2820          $this->_error("Please set \$theme_name in phpfspot_cfg");
2821
2822       if(!isset($this->cfg->logging))
2823          $this->_error("Please set \$logging in phpfspot_cfg");
2824
2825       if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
2826
2827          if(!isset($this->cfg->log_file))
2828             $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
2829
2830          if(!is_writeable($this->cfg->log_file))
2831             $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
2832
2833       }
2834
2835       /* check for pending slash on web_path */
2836       if(!preg_match("/\/$/", $this->cfg->web_path))
2837          $this->cfg->web_path.= "/";
2838
2839       return $this->runtime_error;
2840
2841    } // check_config_options()
2842
2843    /**
2844     * cleanup phpfspot own database
2845     *
2846     * When photos are getting delete from F-Spot, there will remain
2847     * remain some residues in phpfspot own database. This function
2848     * will try to wipe them out.
2849     */
2850    public function cleanup_phpfspot_db()
2851    {
2852       $to_delete = Array();
2853
2854       $result = $this->cfg_db->db_query("
2855          SELECT img_idx
2856          FROM images
2857          ORDER BY img_idx ASC
2858       ");
2859
2860       while($row = $this->cfg_db->db_fetch_object($result)) {
2861          if(!$this->db->db_fetchSingleRow("
2862             SELECT id
2863             FROM photos
2864             WHERE id='". $row['img_idx'] ."'")) {
2865
2866             array_push($to_delete, $row['img_idx'], ',');
2867          }
2868       }
2869
2870       print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
2871
2872       $this->cfg_db->db_exec("
2873          DELETE FROM images
2874          WHERE img_idx IN (". implode($to_delete) .")
2875       ");
2876
2877    } // cleanup_phpfspot_db()
2878
2879    /**
2880     * return first image of the page, the $current photo
2881     * lies in.
2882     *
2883     * this function is used to find out the first photo of the
2884     * current page, in which the $current photo lies. this is
2885     * used to display the correct photo, when calling showPhotoIndex()
2886     * from showImage()
2887     * @param integer $current
2888     * @param integer $max
2889     * @return integer
2890     */
2891    private function getCurrentPage($current, $max)
2892    {
2893       if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
2894          for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
2895             if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
2896                return $page_start;
2897          }
2898       }
2899       return 0;
2900
2901    } // getCurrentPage()
2902
2903    /**
2904     * return mime info
2905     *
2906     * this function tries to find out the correct mime-type
2907     * for the provided file.
2908     * @param string $file
2909     * @return string
2910     */
2911    public function get_mime_info($file)
2912    {
2913       $details = getimagesize($orig_image);
2914
2915       /* if getimagesize() returns empty, try at least to find out the
2916          mime type.
2917       */
2918       if(empty($details) && function_exists('mime_content_type')) {
2919
2920          // mime_content_type is marked as deprecated in the documentation,
2921          // but is it really necessary to force users to install a PECL
2922          // extension?
2923          $details['mime'] = mime_content_type($file);
2924       }
2925
2926       return $details['mime'];
2927
2928    } // get_mime_info()
2929
2930 } // class PHPFSPOT
2931
2932 ?>