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