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