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