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