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