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