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