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