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