issue60, input validation for provided dates via RPC or $_GET
[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 require_once "phpfspot_tmpl.php";
27
28 class PHPFSPOT {
29
30    var $cfg;
31    var $db;
32    var $cfg_db;
33    var $tmpl;
34    var $tags;
35    var $avail_tags;
36
37    /**
38     * class constructor
39     *
40     * this function will be called on class construct
41     * and will check requirements, loads configuration,
42     * open databases and start the user session
43     */
44    public function __construct()
45    {
46       /* Check necessary requirements */
47       if(!$this->checkRequirements()) {
48          exit(1);
49       }
50
51       $this->cfg = new PHPFSPOT_CFG;
52
53       $this->db  = new PHPFSPOT_DB(&$this, $this->cfg->fspot_db);
54       
55       if(!is_writeable(dirname($this->cfg->phpfspot_db))) {
56          print dirname($this->cfg->phpfspot_db) .": directory is not writeable!";
57          exit(1);
58       }
59          
60       $this->cfg_db = new PHPFSPOT_DB(&$this, $this->cfg->phpfspot_db);
61       if(!is_writeable($this->cfg->phpfspot_db)) {
62          print $this->cfg->phpfspot_db ." is not writeable for user ". $this->getuid() ."\n";
63          exit(1);
64       }
65       $this->check_config_table();
66
67
68       $this->tmpl = new PHPFSPOT_TMPL($this);
69
70       $this->get_tags();
71
72       session_start();
73
74       if(!isset($_SESSION['tag_condition']))
75          $_SESSION['tag_condition'] = 'or';
76
77       if(!isset($_SESSION['sort_order']))
78          $_SESSION['sort_order'] = 'date_asc';
79
80       if(!isset($_SESSION['searchfor']))
81          $_SESSION['searchfor'] = '';
82
83       // if begin_with is still set but rows_per_page is now 0, unset it
84       if(isset($_SESSION['begin_with']) && $this->cfg->rows_per_page == 0)
85          unset($_SESSION['begin_with']);
86
87    } // __construct()
88
89    public function __destruct()
90    {
91
92    } // __destruct()
93
94    /**
95     * show - generate html output
96     *
97     * this function can be called after the constructor has
98     * prepared everyhing. it will load the index.tpl smarty
99     * template. if necessary it will registere pre-selects
100     * (photo index, photo, tag search, date search) into
101     * users session.
102     */
103    public function show()
104    {
105       $this->tmpl->assign('searchfor', $_SESSION['searchfor']);
106       $this->tmpl->assign('page_title', $this->cfg->page_title);
107       $this->tmpl->assign('current_condition', $_SESSION['tag_condition']);
108       $this->tmpl->assign('template_path', 'themes/'. $this->cfg->theme_name);
109
110       $_SESSION['start_action'] = $_GET['mode'];
111
112       switch($_GET['mode']) {
113          case 'showpi':
114             if(isset($_GET['tags'])) {
115                $_SESSION['selected_tags'] = split(',', $_GET['tags']);
116             }
117             if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
118                $_SESSION['from_date'] = strtotime($_GET['from_date']);
119             }
120             if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
121                $_SESSION['to_date'] = strtotime($_GET['to_date']);
122             }
123             break;
124          case 'showp':
125             if(isset($_GET['tags'])) {
126                $_SESSION['selected_tags'] = split(',', $_GET['tags']);
127                $_SESSION['start_action'] = 'showp';
128             }
129             if(isset($_GET['id'])) {
130                $_SESSION['current_photo'] = $_GET['id'];
131                $_SESSION['start_action'] = 'showp';
132             }
133             if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
134                $_SESSION['from_date'] = strtotime($_GET['from_date']);
135             }
136             if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
137                $_SESSION['to_date'] = strtotime($_GET['to_date']);
138             }
139             break;
140          case 'export':
141             $this->tmpl->show("export.tpl");
142             return;
143             break;
144          case 'slideshow':
145             $this->tmpl->show("slideshow.tpl");
146             return;
147             break;
148       }
149
150       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date']))
151          $this->tmpl->assign('date_search_enabled', true);
152
153       $this->tmpl->assign('from_date', $this->get_calendar('from'));
154       $this->tmpl->assign('to_date', $this->get_calendar('to'));
155       $this->tmpl->assign('sort_field', $this->get_sort_field());
156       $this->tmpl->assign('content_page', 'welcome.tpl');
157       $this->tmpl->show("index.tpl");
158
159
160    } // show()
161
162    /**
163     * get_tags - grab all tags of f-spot's database
164     *
165     * this function will get all available tags from
166     * the f-spot database and store them within two
167     * arrays within this clase for later usage. in
168     * fact, if the user requests (hide_tags) it will
169     * opt-out some of them.
170     *
171     * this function is getting called once by show()
172     */
173    private function get_tags()
174    {
175       $this->avail_tags = Array();
176       $count = 0;
177    
178       $result = $this->db->db_query("
179          SELECT id,name
180          FROM tags
181          ORDER BY sort_priority ASC
182       ");
183       
184       while($row = $this->db->db_fetch_object($result)) {
185
186          $tag_id = $row['id'];
187          $tag_name = $row['name'];
188
189          /* check if config requests to ignore this tag */
190          if(in_array($row['name'], $this->cfg->hide_tags))
191             continue;
192
193          $this->tags[$tag_id] = $tag_name; 
194          $this->avail_tags[$count] = $tag_id;
195
196          $count++;
197
198       }
199
200    } // get_tags()
201
202    /** 
203     * extract all photo details
204     * 
205     * retrieve all available details from f-spot's
206     * database and return them as object
207     */
208    public function get_photo_details($idx)
209    {
210       $result = $this->db->db_query("
211          SELECT *
212          FROM photos
213          WHERE id='". $idx ."'
214       ");
215       
216       return $this->db->db_fetch_object($result);
217
218    } // get_photo_details
219
220    /**
221     * returns aligned photo names 
222     *
223     * this function returns aligned (length) names for
224     * an specific photo. If the length of the name exceeds
225     * $limit the name will be shrinked (...)
226     */
227    public function getPhotoName($idx, $limit = 0)
228    {
229       if($details = $this->get_photo_details($idx)) {
230          $name = $details['name'];
231          if($limit != 0 && strlen($name) > $limit) {
232             $name = substr($name, 0, $limit-5) ."...". substr($name, -($limit-5));
233          }
234          return $name;
235       }
236
237    } // getPhotoName()
238
239    /**
240     * translate f-spoth photo path
241     * 
242     * as the full-qualified path recorded in the f-spot database
243     * is usally not the same as on the webserver, this function
244     * will replace the path with that one specified in the cfg
245     */
246    public function translate_path($path, $width = 0)
247    {  
248       return str_replace($this->cfg->path_replace_from, $this->cfg->path_replace_to, $path);
249
250    } // translate_path
251
252    /**
253     * control HTML ouput for a single photo
254     *
255     * this function provides all the necessary information
256     * for the single photo template.
257     */
258    public function showPhoto($photo)
259    {
260       /* get all photos from the current photo selection */
261       $all_photos = $this->getPhotoSelection();
262       $count = count($all_photos);
263
264       for($i = 0; $i < $count; $i++) {
265          
266          // $get_next will be set, when the photo which has to
267          // be displayed has been found - this means that the
268          // next available is in fact the NEXT image (for the
269          // navigation icons) 
270          if(isset($get_next)) {
271             $next_img = $all_photos[$i];
272             break;
273          }
274
275          /* the next photo is our NEXT photo */
276          if($all_photos[$i] == $photo) {
277             $get_next = 1;
278          }
279          else {
280             $previous_img = $all_photos[$i];
281          }
282
283          if($photo == $all_photos[$i]) {
284                $current = $i;
285          }
286       }
287
288       $details = $this->get_photo_details($photo);
289
290       if(!$details) {
291          print "error";
292          return;
293       }
294
295       $orig_path = $this->translate_path($details['directory_path']) ."/". $details['name'];
296       $thumb_path = $this->cfg->base_path ."/thumbs/". $this->cfg->photo_width ."_". $this->getMD5($photo);
297
298       if(!file_exists($orig_path)) {
299          $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
300       }
301
302       if(!is_readable($orig_path)) {
303          $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
304       }
305
306       /* If the thumbnail doesn't exist yet, try to create it */
307       if(!file_exists($thumb_path)) {
308          $this->gen_thumb($photo, true);
309          $thumb_path = $this->cfg->base_path ."/thumbs/". $this->cfg->photo_width ."_". $this->getMD5($photo);
310       }
311
312       /* get f-spot database meta information */
313       $meta = $this->get_meta_informations($orig_path);
314
315       /* If EXIF data are available, use them */
316       if(isset($meta['ExifImageWidth'])) {
317          $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
318       } else {
319          $info = getimagesize($orig_path);
320          $meta_res = $info[0] ."x". $info[1]; 
321       }
322
323       $meta_date = isset($meta['FileDateTime']) ? strftime("%a %x %X", $meta['FileDateTime']) : "n/a";
324       $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
325       $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
326
327       $extern_link = "index.php?mode=showp&id=". $photo;
328       $current_tags = $this->getCurrentTags();
329       if($current_tags != "") {
330          $extern_link.= "&tags=". $current_tags;
331       }
332       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
333          $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
334       }
335
336       $this->tmpl->assign('extern_link', $extern_link);
337
338       if(file_exists($thumb_path)) {
339
340          $info = getimagesize($thumb_path);
341
342          $this->tmpl->assign('description', $details['description']);
343          $this->tmpl->assign('image_name', $details['name']);
344
345          $this->tmpl->assign('width', $info[0]);
346          $this->tmpl->assign('height', $info[1]);
347          $this->tmpl->assign('ExifMadeOn', $meta_date);
348          $this->tmpl->assign('ExifMadeWith', $meta_make);
349          $this->tmpl->assign('ExifOrigResolution', $meta_res);
350          $this->tmpl->assign('ExifFileSize', $meta_size);
351     
352          $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&amp;width=". $this->cfg->photo_width);
353          $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
354
355          $this->tmpl->assign('tags', $this->get_photo_tags($photo));
356          $this->tmpl->assign('current', $current);
357       }
358       else {
359          $this->_error("Can't open file ". $thumb_path ."\n");
360          return;
361       }
362
363       if($previous_img) {
364          $this->tmpl->assign('previous_url', "javascript:showImage(". $previous_img .");");
365          $this->tmpl->assign('prev_img', $previous_img);
366       }
367
368       if($next_img) {
369          $this->tmpl->assign('next_url', "javascript:showImage(". $next_img .");");
370          $this->tmpl->assign('next_img', $next_img);
371       }
372       $this->tmpl->assign('mini_width', $this->cfg->mini_width);
373       $this->tmpl->assign('photo_number', $i);
374       $this->tmpl->assign('photo_count', count($all_photos));
375
376       $this->tmpl->show("single_photo.tpl");
377
378    } // showPhoto()
379
380    /**
381     * all available tags and tag cloud
382     *
383     * this function outputs all available tags (time ordered)
384     * and in addition output them as tag cloud (tags which have
385     * many photos will appears more then others)
386     */
387    public function getAvailableTags()
388    {
389       $output = "";
390
391       $result = $this->db->db_query("
392          SELECT tag_id as id, count(tag_id) as quantity
393          FROM photo_tags
394          INNER JOIN tags t
395             ON t.id = tag_id
396          GROUP BY tag_id
397          ORDER BY t.name ASC
398       ");
399
400       $tags = Array();
401
402       while($row = $this->db->db_fetch_object($result)) {
403          $tags[$row['id']] = $row['quantity'];
404       }
405
406       // change these font sizes if you will
407       $max_size = 125; // max font size in %
408       $min_size = 75; // min font size in %
409
410       // get the largest and smallest array values
411       $max_qty = max(array_values($tags));
412       $min_qty = min(array_values($tags));
413
414       // find the range of values
415       $spread = $max_qty - $min_qty;
416       if (0 == $spread) { // we don't want to divide by zero
417          $spread = 1;
418       }
419
420       // determine the font-size increment
421       // this is the increase per tag quantity (times used)
422       $step = ($max_size - $min_size)/($spread);
423
424       // loop through our tag array
425       foreach ($tags as $key => $value) {
426
427          if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
428             continue;
429
430           // calculate CSS font-size
431           // find the $value in excess of $min_qty
432           // multiply by the font-size increment ($size)
433           // and add the $min_size set above
434          $size = $min_size + (($value - $min_qty) * $step);
435           // uncomment if you want sizes in whole %:
436          $size = ceil($size);
437
438          $output.= "<a href=\"javascript:Tags('add', ". $key .");\" class=\"tag\" style=\"font-size: ". $size ."%;\">". $this->tags[$key] ."</a>, ";
439
440       }
441
442       $output = substr($output, 0, strlen($output)-2);
443       print $output;
444
445    } // getAvailableTags()
446
447    /**
448     * output all selected tags
449     *
450     * this function output all tags which have been selected
451     * by the user. the selected tags are stored in the 
452     * session-variable $_SESSION['selected_tags']
453     */
454    public function getSelectedTags()
455    {
456       $output = "";
457       foreach($this->avail_tags as $tag)
458       {
459          // return all selected tags
460          if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
461             $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
462          }
463       }
464
465       $output = substr($output, 0, strlen($output)-2);
466       print $output;
467
468    } // getSelectedTags()
469
470    /**
471     * add tag to users session variable
472     *
473     * this function will add the specified to users current
474     * tag selection. if a date search has been made before
475     * it will be now cleared
476     */
477    public function addTag($tag)
478    {
479       if(!isset($_SESSION['selected_tags']))
480          $_SESSION['selected_tags'] = Array();
481
482       if(!in_array($tag, $_SESSION['selected_tags']))
483          array_push($_SESSION['selected_tags'], $tag);
484    
485    } // addTag()
486
487    /**
488     * remove tag to users session variable
489     *
490     * this function removes the specified tag from
491     * users current tag selection
492     */
493    public function delTag($tag)
494    {
495       if(isset($_SESSION['selected_tags'])) {
496          $key = array_search($tag, $_SESSION['selected_tags']);
497          unset($_SESSION['selected_tags'][$key]);
498          sort($_SESSION['selected_tags']);
499       }
500
501    } // delTag()
502
503    /**
504     * reset tag selection
505     *
506     * if there is any tag selection, it will be
507     * deleted now
508     */
509    public function resetTags()
510    {
511       if(isset($_SESSION['selected_tags']))
512          unset($_SESSION['selected_tags']);
513
514    } // resetTags()
515
516    /**
517     * reset single photo
518     *
519     * if a specific photo was requested (external link)
520     * unset the session variable now
521     */
522    public function resetPhotoView()
523    {
524       if(isset($_SESSION['current_photo']))
525          unset($_SESSION['current_photo']);
526
527    } // resetPhotoView();
528
529    /**
530     * reset tag search
531     *
532     * if any tag search has taken place, reset
533     * it now
534     */
535    public function resetTagSearch()
536    {
537       if(isset($_SESSION['searchfor']))
538          unset($_SESSION['searchfor']);
539
540    } // resetTagSearch()
541
542     /**
543     * reset date search
544     *
545     * if any date search has taken place, reset
546     * it now
547     */
548    public function resetDateSearch()
549    {
550       if(isset($_SESSION['from_date']))
551          unset($_SESSION['from_date']);
552       if(isset($_SESSION['to_date']))
553          unset($_SESSION['to_date']);
554
555    } // resetDateSearch();
556
557    /**
558     * return all photo according selection
559     *
560     * this function returns all photos based on
561     * the tag-selection, tag- or date-search.
562     * the tag-search also has to take care of AND
563     * and OR conjunctions
564     */
565    public function getPhotoSelection()
566    {  
567       $matched_photos = Array();
568
569       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
570          $from_date = $_SESSION['from_date'];
571          $to_date = $_SESSION['to_date'];
572          $additional_where_cond = "
573                p.time>='". $from_date ."'
574             AND
575                p.time<='". $to_date ."'
576          ";
577       } 
578
579       if(isset($_SESSION['sort_order'])) {
580          $order_str = $this->get_sort_order();
581       }
582
583       /* return a search result */
584       if(isset($_SESSION['searchfor']) && $_SESSION['searchfor'] != '') {
585          $query_str = "
586             SELECT DISTINCT photo_id
587                FROM photo_tags pt
588             INNER JOIN photos p
589                ON p.id=pt.photo_id
590             INNER JOIN tags t
591                ON pt.tag_id=t.id
592             WHERE t.name LIKE '%". $_SESSION['searchfor'] ."%'";
593
594          if(isset($additional_where_cond))
595             $query_str.= "AND ". $additional_where_cond ." ";
596          if(isset($order_str))
597             $query_str.= $order_str;
598
599          $result = $this->db->db_query($query_str);
600          while($row = $this->db->db_fetch_object($result)) {
601             array_push($matched_photos, $row['photo_id']);
602          }
603          return $matched_photos;
604       }
605
606       /* return according the selected tags */
607       if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
608          $selected = "";
609          foreach($_SESSION['selected_tags'] as $tag)
610             $selected.= $tag .",";
611          $selected = substr($selected, 0, strlen($selected)-1);
612
613          if($_SESSION['tag_condition'] == 'or') {
614             $query_str = "
615                SELECT DISTINCT photo_id
616                   FROM photo_tags pt
617                INNER JOIN photos p
618                   ON p.id=pt.photo_id
619                WHERE pt.tag_id IN (". $selected .")
620             ";
621             if(isset($additional_where_cond)) 
622                $query_str.= "AND ". $additional_where_cond ." ";
623             if(isset($order_str))
624                $query_str.= $order_str;
625          }
626          elseif($_SESSION['tag_condition'] == 'and') {
627
628             if(count($_SESSION['selected_tags']) >= 32) {
629                print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
630                print "evaluate your tag selection. Please remove some tags from your selection.\n";
631                return Array();
632             } 
633
634             /* Join together a table looking like
635
636                pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
637
638                so the query can quickly return all images matching the
639                selected tags in an AND condition
640
641             */
642
643             $query_str = "
644                SELECT DISTINCT pt1.photo_id
645                   FROM photo_tags pt1
646             ";
647
648             for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
649                $query_str.= "
650                   INNER JOIN photo_tags pt". ($i+2) ."
651                      ON pt1.photo_id=pt". ($i+2) .".photo_id
652                ";
653             }
654             $query_str.= "
655                INNER JOIN photos p
656                   ON pt1.photo_id=p.id
657             ";
658             $query_str.= "WHERE pt1.tag_id=". $_SESSION['selected_tags'][0];
659             for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
660                $query_str.= "
661                   AND pt". ($i+1) .".tag_id=". $_SESSION['selected_tags'][$i] ."
662                "; 
663             }
664             if(isset($additional_where_cond)) 
665                $query_str.= "AND ". $additional_where_cond;
666             if(isset($order_str))
667                $query_str.= $order_str;
668          }
669
670          $result = $this->db->db_query($query_str);
671          while($row = $this->db->db_fetch_object($result)) {
672             array_push($matched_photos, $row['photo_id']);
673          }
674          return $matched_photos;
675       }
676
677       /* return all available photos */
678       $query_str = "
679          SELECT DISTINCT photo_id
680             FROM photo_tags pt
681          INNER JOIN photos p
682             ON p.id=pt.photo_id
683       ";
684       if(isset($additional_where_cond)) 
685          $query_str.= "WHERE ". $additional_where_cond ." ";
686       if(isset($order_str))
687          $query_str.= $order_str;
688
689       $result = $this->db->db_query($query_str);
690       while($row = $this->db->db_fetch_object($result)) {
691          array_push($matched_photos, $row['photo_id']);
692       }
693       return $matched_photos;
694
695    } // getPhotoSelection()
696
697     /**
698     * control HTML ouput for photo index
699     *
700     * this function provides all the necessary information
701     * for the photo index template.
702     */
703    public function showPhotoIndex()
704    {
705       $photos = $this->getPhotoSelection();
706
707       $count = count($photos);
708
709       if(isset($_SESSION['begin_with']) && $_SESSION['begin_with'] != "")
710          $anchor = $_SESSION['begin_with'];
711
712       if(!isset($this->cfg->rows_per_page) || $this->cfg->rows_per_page == 0) {
713
714          $begin_with = 0;
715          $end_with = $count;
716
717       }
718       elseif($this->cfg->rows_per_page > 0) {
719
720          if(!$_SESSION['begin_with'] || $_SESSION['begin_with'] == 0)
721             $begin_with = 0;
722          else {
723
724             $begin_with = $_SESSION['begin_with'];
725
726             // verify $begin_with - perhaps the thumbs-per-rows or
727             // rows-per-page variables have changed or the jump back
728             // from a photo wasn't exact - so calculate the real new
729             // starting point
730             $multiplicator = $this->cfg->rows_per_page * $this->cfg->thumbs_per_row;
731             for($i = 0; $i <= $count; $i+=$multiplicator) {
732                if($begin_with >= $i && $begin_with < $i+$multiplicator) {
733                   $begin_with = $i;
734                   break;
735                }
736             }
737          }
738
739          $end_with = $begin_with + ($this->cfg->rows_per_page * $this->cfg->thumbs_per_row);
740       }
741
742    
743       $rows = 0;
744       $cols = 0;
745       $images[$rows] = Array();
746       $img_height[$rows] = Array();
747       $img_width[$rows] = Array();
748       $img_id[$rows] = Array();
749       $img_name[$rows] = Array();
750       $img_title = Array();
751
752       for($i = $begin_with; $i < $end_with; $i++) {
753
754          $images[$rows][$cols] = $photos[$i];
755          $img_id[$rows][$cols] = $i;
756          $img_name[$rows][$cols] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
757          $img_title[$rows][$cols] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
758
759          $thumb_path = $this->cfg->base_path ."/thumbs/". $this->cfg->thumb_width ."_". $this->getMD5($photos[$i]);
760
761          if(file_exists($thumb_path)) {
762             $info = getimagesize($thumb_path); 
763             $img_width[$rows][$cols] = $info[0];
764             $img_height[$rows][$cols] = $info[1];
765          }
766
767          if($cols == $this->cfg->thumbs_per_row-1) {
768             $cols = 0;
769             $rows++;
770             $images[$rows] = Array();
771             $img_width[$rows] = Array();
772             $img_height[$rows] = Array();
773          }
774          else {
775             $cols++;
776          }
777       } 
778
779       // +1 for for smarty's selection iteration
780       $rows++;
781
782       if(isset($_SESSION['searchfor']) && $_SESSION['searchfor'] != '')
783          $this->tmpl->assign('searchfor', $_SESSION['searchfor']);
784
785       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
786          $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
787          $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
788       }
789
790       if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
791          $this->tmpl->assign('tag_result', 1);
792       }
793
794       /* do we have to display the page selector ? */
795       if($this->cfg->rows_per_page != 0) {
796       
797          /* calculate the page switchers */
798          $previous_start = $begin_with - ($this->cfg->rows_per_page * $this->cfg->thumbs_per_row);
799          $next_start = $begin_with + ($this->cfg->rows_per_page * $this->cfg->thumbs_per_row);
800
801          if($begin_with != 0) 
802             $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");"); 
803          if($end_with < $count)
804             $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");"); 
805
806          $photo_per_page  = $this->cfg->rows_per_page * $this->cfg->thumbs_per_row;
807          $last_page = ceil($count / $photo_per_page);
808
809          /* get the current selected page */
810          if($begin_with == 0) {
811             $current_page = 1;
812          } else {
813             $current_page = 0;
814             for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
815                $current_page++;
816             }
817          } 
818
819          $dotdot_made = 0;
820
821          for($i = 1; $i <= $last_page; $i++) {
822
823             if($current_page == $i)
824                $style = "style=\"font-size: 125%; text-decoration: underline;\"";
825             elseif($current_page-1 == $i || $current_page+1 == $i)
826                $style = "style=\"font-size: 105%;\"";
827             elseif(($current_page-5 >= $i) && ($i != 1) ||
828                ($current_page+5 <= $i) && ($i != $last_page))
829                $style = "style=\"font-size: 75%;\"";
830             else
831                $style = "";
832
833             $select = "<a href=\"javascript:showPhotoIndex(". (($i*$photo_per_page)-$photo_per_page) .");\"";
834                if($style != "")
835                   $select.= $style;
836             $select.= ">". $i ."</a>&nbsp;";
837
838             // until 9 pages we show the selector from 1-9
839             if($last_page <= 9) {
840                $page_select.= $select;
841                continue;
842             } else {
843                if($i == 1 /* first page */ || 
844                   $i == $last_page /* last page */ ||
845                   $i == $current_page /* current page */ ||
846                   $i == ceil($last_page * 0.25) /* first quater */ ||
847                   $i == ceil($last_page * 0.5) /* half */ ||
848                   $i == ceil($last_page * 0.75) /* third quater */ ||
849                   (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
850                   (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 */ ||
851                   $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
852                   $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
853
854                   $page_select.= $select;
855                   $dotdot_made = 0;
856                   continue;
857
858                }
859             }
860
861             if(!$dotdot_made) {
862                $page_select.= ".........&nbsp;";
863                $dotdot_made = 1;
864             }
865          }
866
867          /* only show the page selector if we have more then one page */
868          if($last_page > 1)
869             $this->tmpl->assign('page_selector', $page_select);
870       }
871
872       
873       $current_tags = $this->getCurrentTags();
874       $extern_link = "index.php?mode=showpi";
875       if($current_tags != "") {
876          $extern_link.= "&tags=". $current_tags;
877       }
878       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
879          $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
880       }
881
882       $export_link = "index.php?mode=export";
883       $slideshow_link = "index.php?mode=slideshow";
884
885       $this->tmpl->assign('extern_link', $extern_link);
886       $this->tmpl->assign('slideshow_link', $slideshow_link);
887       $this->tmpl->assign('export_link', $export_link);
888       $this->tmpl->assign('count', $count);
889       $this->tmpl->assign('width', $this->cfg->thumb_width);
890       $this->tmpl->assign('images', $images);
891       $this->tmpl->assign('img_width', $img_width);
892       $this->tmpl->assign('img_height', $img_height);
893       $this->tmpl->assign('img_id', $img_id);
894       $this->tmpl->assign('img_name', $img_name);
895       $this->tmpl->assign('img_title', $img_title);
896       $this->tmpl->assign('rows', $rows);
897       $this->tmpl->assign('columns', $this->cfg->thumbs_per_row);
898
899       $this->tmpl->show("photo_index.tpl");
900
901       if(isset($anchor))
902          print "<script language=\"JavaScript\">self.location.hash = '#image". $anchor ."';</script>\n";
903
904    } // showPhotoIndex()
905
906    /**
907     * show credit template
908     */
909    public function showCredits()
910    {
911       $this->tmpl->assign('version', $this->cfg->version);
912       $this->tmpl->assign('product', $this->cfg->product);
913       $this->tmpl->show("credits.tpl");
914
915    } // showCredits()
916
917    /**
918     * create_thumbnails for the requested width
919     *
920     * this function creates image thumbnails of $orig_image
921     * stored as $thumb_image. It will check if the image is
922     * in a supported format, if necessary rotate the image
923     * (based on EXIF orientation meta headers) and re-sizing.
924     */
925    public function create_thumbnail($orig_image, $thumb_image, $width)
926    {  
927       if(!file_exists($orig_image)) {
928          return false;
929       }
930
931       $details = getimagesize($orig_image);
932       
933       /* check if original photo is a support image type */
934       if(!$this->checkifImageSupported($details['mime']))
935          return false;
936
937       $meta = $this->get_meta_informations($orig_image);
938
939       $rotate = 0;
940       $flip = false;
941
942       switch($meta['Orientation']) {
943
944          case 1: /* top, left */
945             $rotate = 0; $flip = false; break;
946          case 2: /* top, right */
947             $rotate = 0; $flip = true; break;
948          case 3: /* bottom, left */
949             $rotate = 180; $flip = false; break;
950          case 4: /* bottom, right */
951             $rotate = 180; $flip = true; break;
952          case 5: /* left side, top */
953             $rotate = 90; $flip = true; break;
954          case 6: /* right side, top */
955             $rotate = 90; $flip = false; break;
956          case 7: /* left side, bottom */
957             $rotate = 270; $flip = true; break;
958          case 8: /* right side, bottom */
959             $rotate = 270; $flip = false; break;
960       }
961
962       $src_img = @imagecreatefromjpeg($orig_image);
963
964       if(!$src_img) {
965          print "Can't load image from ". $orig_image ."\n";
966          return false;
967       }
968
969       /* grabs the height and width */
970       $cur_width = imagesx($src_img);
971       $cur_height = imagesy($src_img);
972
973       // If requested width is more then the actual image width,
974       // do not generate a thumbnail, instead safe the original
975       // as thumbnail but with lower quality
976
977       if($width >= $cur_width) {
978          $result = imagejpeg($src_img, $thumb_image, 75);
979          imagedestroy($src_img);
980          return true;
981       }
982
983       // If the image will be rotate because EXIF orientation said so
984       // 'virtually rotate' the image for further calculations
985       if($rotate == 90 || $rotate == 270) {
986          $tmp = $cur_width;
987          $cur_width = $cur_height;
988          $cur_height = $tmp;
989       }
990
991       /* calculates aspect ratio */
992       $aspect_ratio = $cur_height / $cur_width;
993
994       /* sets new size */
995       if($aspect_ratio < 1) {
996          $new_w = $width;
997          $new_h = abs($new_w * $aspect_ratio);
998       } else {
999          /* 'virtually' rotate the image and calculate it's ratio */
1000          $tmp_w = $cur_height;
1001          $tmp_h = $cur_width;
1002          /* now get the ratio from the 'rotated' image */
1003          $tmp_ratio = $tmp_h/$tmp_w;
1004          /* now calculate the new dimensions */
1005          $tmp_w = $width;
1006          $tmp_h = abs($tmp_w * $tmp_ratio);
1007
1008          // now that we know, how high they photo should be, if it
1009          // gets rotated, use this high to scale the image
1010          $new_h = $tmp_h;
1011          $new_w = abs($new_h / $aspect_ratio);
1012
1013          // If the image will be rotate because EXIF orientation said so
1014          // now 'virtually rotate' back the image for the image manipulation
1015          if($rotate == 90 || $rotate == 270) {
1016             $tmp = $new_w;
1017             $new_w = $new_h;
1018             $new_h = $tmp;
1019          }
1020       }
1021
1022       /* creates new image of that size */
1023       $dst_img = imagecreatetruecolor($new_w, $new_h);
1024
1025       imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1026
1027       /* copies resized portion of original image into new image */
1028       imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1029
1030       /* needs the image to be flipped horizontal? */
1031       if($flip) {
1032          print "(FLIP)";
1033          $image = $dst_img;
1034          for($x = 0; $x < $new_w; $x++) {
1035             imagecopy($dst_img, $image, $x, 0, $w - $x - 1, 0, 1, $h);
1036          }
1037       }
1038
1039       if($rotate) {
1040          $this->_debug("(ROTATE)");
1041          $dst_img = $this->rotateImage($dst_img, $rotate);
1042       }
1043
1044       /* write down new generated file */
1045       $result = imagejpeg($dst_img, $thumb_image, 75);
1046
1047       /* free your mind */
1048       imagedestroy($dst_img);
1049       imagedestroy($src_img);
1050
1051       if($result === false) {
1052          print "Can't write thumbnail ". $thumb_image ."\n";
1053          return false;
1054       }
1055
1056       return true;
1057
1058    } // create_thumbnail()
1059
1060    /**
1061     * return all exif meta data from the file
1062     */
1063    public function get_meta_informations($file)
1064    {
1065       return exif_read_data($file);
1066
1067    } // get_meta_informations()
1068
1069    /**
1070     * create phpfspot own sqlite database
1071     *
1072     * this function creates phpfspots own sqlite database
1073     * if it does not exist yet. this own is used to store
1074     * some necessary informations (md5 sum's, ...).
1075     */
1076    public function check_config_table()
1077    {
1078       // if the config table doesn't exist yet, create it
1079       if(!$this->cfg_db->db_check_table_exists("images")) {
1080          $this->cfg_db->db_exec("
1081             CREATE TABLE images (
1082                img_idx int primary key,
1083                img_md5 varchar(32)
1084             )
1085             ");
1086       }
1087
1088    } // check_config_table
1089
1090    /**
1091     * Generates a thumbnail from photo idx
1092     *
1093     * This function will generate JPEG thumbnails from provided F-Spot photo
1094     * indizes.
1095     *
1096     * 1. Check if all thumbnail generations (width) are already in place and
1097     *    readable
1098     * 2. Check if the md5sum of the original file has changed
1099     * 3. Generate the thumbnails if needed
1100     */
1101    public function gen_thumb($idx = 0, $force = 0)
1102    {
1103       $error = 0;
1104
1105       $resolutions = Array(
1106          $this->cfg->thumb_width,
1107          $this->cfg->photo_width,
1108          $this->cfg->mini_width,
1109       );
1110
1111       /* get details from F-Spot's database */
1112       $details = $this->get_photo_details($idx);
1113
1114       /* calculate file MD5 sum */
1115       $full_path = $this->translate_path($details['directory_path'])  ."/". $details['name'];
1116
1117       if(!file_exists($full_path)) {
1118          $this->_error("File ". $full_path ." does not exist\n");
1119          return;
1120       }
1121
1122       if(!is_readable($full_path)) {
1123          $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
1124          return;
1125       }
1126
1127       $file_md5 = md5_file($full_path);
1128
1129       $this->_debug("Image [". $idx ."] ". $details['name'] ." Thumbnails:");
1130
1131       foreach($resolutions as $resolution) {
1132
1133          $thumb_path = $this->cfg->base_path ."/thumbs/". $resolution ."_". $file_md5;
1134
1135          /* if the thumbnail file doesn't exist, create it */
1136          if(!file_exists($thumb_path)) {
1137
1138             $this->_debug(" ". $resolution ."px");
1139             if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1140                $error = 1;
1141          }
1142          /* if the file hasn't changed there is no need to regen the thumb */
1143          elseif($file_md5 != $this->getMD5($idx) || $force) {
1144
1145             $this->_debug(" ". $resolution ."px");
1146             if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1147                $error = 1;
1148
1149          }
1150       }
1151
1152       /* set the new/changed MD5 sum for the current photo */
1153       if(!$error) {
1154          $this->setMD5($idx, $file_md5);
1155       }
1156
1157       $this->_debug("\n");
1158
1159    } // gen_thumb()
1160
1161    /**
1162     * returns stored md5 sum for a specific photo
1163     *
1164     * this function queries the phpfspot database for a
1165     * stored MD5 checksum of the specified photo
1166     */
1167    public function getMD5($idx)
1168    {
1169       $result = $this->cfg_db->db_query("
1170          SELECT img_md5 
1171          FROM images
1172          WHERE img_idx='". $idx ."'
1173       ");
1174
1175       if(!$result)
1176          return 0;
1177
1178       $img = $this->cfg_db->db_fetch_object($result);
1179       return $img['img_md5'];
1180       
1181    } // getMD5()
1182
1183    /**
1184     * set MD5 sum for the specific photo
1185     */
1186    private function setMD5($idx, $md5)
1187    {
1188       $result = $this->cfg_db->db_exec("
1189          REPLACE INTO images (img_idx, img_md5)
1190          VALUES ('". $idx ."', '". $md5 ."')
1191       ");
1192
1193    } // setMD5()
1194
1195    /**
1196     * store current tag condition
1197     *
1198     * this function stores the current tag condition
1199     * (AND or OR) in the users session variables
1200     */
1201    public function setTagCondition($mode)
1202    {
1203       $_SESSION['tag_condition'] = $mode;
1204
1205    } // setTagCondition()
1206
1207    /** 
1208     * invoke tag & date search 
1209     *
1210     * this function will return all matching tags and store
1211     * them in the session variable selected_tags. furthermore
1212     * it also handles the date search.
1213     * getPhotoSelection() will then only return the matching
1214     * photos.
1215     */
1216    public function startSearch($searchfor, $sort_order, $from = 0, $to = 0)
1217    {
1218       $_SESSION['searchfor'] = $searchfor;
1219       $_SESSION['sort_order'] = $sort_order;
1220       if($from != 0)
1221          $_SESSION['from_date'] = strtotime($from);
1222       else
1223          unset($_SESSION['from_date']);
1224       if($to != 0)
1225          $_SESSION['to_date'] = strtotime($to);
1226       else
1227          unset($_SESSION['to_date']);
1228
1229       if($searchfor != "") {
1230          /* new search, reset the current selected tags */
1231          $_SESSION['selected_tags'] = Array();
1232          foreach($this->avail_tags as $tag) {
1233             if(preg_match('/'. $searchfor .'/i', $this->tags[$tag]))
1234                array_push($_SESSION['selected_tags'], $tag);
1235          }
1236       }
1237
1238    } // startSearch()
1239
1240    /**
1241     * rotate image
1242     *
1243     * this function rotates the image according the
1244     * specified angel.
1245     */
1246    private function rotateImage($img, $degrees)
1247    {
1248       if(function_exists("imagerotate")) {
1249          $img = imagerotate($img, $degrees, 0);
1250       } else {
1251          function imagerotate($src_img, $angle)
1252          {
1253             $src_x = imagesx($src_img);
1254             $src_y = imagesy($src_img);
1255             if ($angle == 180) {
1256                $dest_x = $src_x;
1257                $dest_y = $src_y;
1258             }
1259             elseif ($src_x <= $src_y) {
1260                $dest_x = $src_y;
1261                $dest_y = $src_x;
1262             }
1263             elseif ($src_x >= $src_y) {
1264                $dest_x = $src_y;
1265                $dest_y = $src_x;
1266             }
1267                
1268             $rotate=imagecreatetruecolor($dest_x,$dest_y);
1269             imagealphablending($rotate, false);
1270                
1271             switch ($angle) {
1272             
1273                case 90:
1274                   for ($y = 0; $y < ($src_y); $y++) {
1275                      for ($x = 0; $x < ($src_x); $x++) {
1276                         $color = imagecolorat($src_img, $x, $y);
1277                         imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
1278                      }
1279                   }
1280                   break;
1281
1282                case 270:
1283                   for ($y = 0; $y < ($src_y); $y++) {
1284                      for ($x = 0; $x < ($src_x); $x++) {
1285                         $color = imagecolorat($src_img, $x, $y);
1286                         imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
1287                      }
1288                   }
1289                   break;
1290
1291                case 180:
1292                   for ($y = 0; $y < ($src_y); $y++) {
1293                      for ($x = 0; $x < ($src_x); $x++) {
1294                         $color = imagecolorat($src_img, $x, $y);
1295                         imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
1296                      }
1297                   }
1298                   break;
1299
1300                default:
1301                   $rotate = $src_img;
1302                   break;
1303             };
1304
1305             return $rotate;
1306
1307          }
1308
1309          $img = imagerotate($img, $degrees);
1310
1311       }
1312
1313       return $img;
1314
1315    } // rotateImage()
1316
1317    /**
1318     * return all assigned tags for the specified photo
1319     */
1320    private function get_photo_tags($idx)
1321    {
1322       $result = $this->db->db_query("
1323          SELECT t.id, t.name
1324          FROM tags t
1325          INNER JOIN photo_tags pt
1326             ON t.id=pt.tag_id
1327          WHERE pt.photo_id='". $idx ."'
1328       ");
1329
1330       $tags = Array();
1331
1332       while($row = $this->db->db_fetch_object($result))
1333          $tags[$row['id']] = $row['name'];
1334
1335       return $tags;
1336
1337    } // get_photo_tags()
1338
1339    /**
1340     * create on-the-fly images with text within
1341     */
1342    public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
1343    {
1344       if (strlen($color) != 6) 
1345          $color = 000000;
1346
1347       $int = hexdec($color);
1348       $h = imagefontheight($font);
1349       $fw = imagefontwidth($font);
1350       $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
1351       $lines = count($txt);
1352       $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
1353       $bg = imagecolorallocate($im, 255, 255, 255);
1354       $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
1355       $y = 0;
1356
1357       foreach ($txt as $text) {
1358          $x = (($w - ($fw * strlen($text))) / 2);
1359          imagestring($im, $font, $x, $y, $text, $color);
1360          $y += ($h + $space);
1361       }
1362
1363       Header("Content-type: image/png");
1364       ImagePng($im);
1365
1366    } // showTextImage()
1367
1368    /**
1369     * check if all requirements are met
1370     */
1371    private function checkRequirements()
1372    {
1373       if(!function_exists("imagecreatefromjpeg")) {
1374          print "PHP GD library extension is missing<br />\n";
1375          $missing = true;
1376       }
1377
1378       if(!function_exists("sqlite3_open")) {
1379          print "PHP SQLite3 library extension is missing<br />\n";
1380          $missing = true;
1381       }
1382
1383       /* Check for HTML_AJAX PEAR package, lent from Horde project */
1384       ini_set('track_errors', 1);
1385       @include_once 'HTML/AJAX/Server.php';
1386       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1387          print "PEAR HTML_AJAX package is missing<br />\n";
1388          $missing = true;
1389       }
1390       @include_once 'Calendar/Calendar.php';
1391       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1392          print "PEAR Calendar package is missing<br />\n";
1393          $missing = true;
1394       }
1395       ini_restore('track_errors');
1396
1397       if(isset($missing))
1398          return false;
1399
1400       return true;
1401
1402    } // checkRequirements()
1403
1404    private function _debug($text)
1405    {
1406       if($this->fromcmd) {
1407          print $text;
1408       }
1409
1410    } // _debug()
1411
1412    /**
1413     * check if specified MIME type is supported
1414     */
1415    public function checkifImageSupported($mime)
1416    {
1417       if(in_array($mime, Array("image/jpeg")))
1418          return true;
1419
1420       return false;
1421
1422    } // checkifImageSupported()
1423
1424    public function _error($text)
1425    {
1426       switch($this->cfg->logging) {
1427          case 'display':
1428             print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
1429             print $text;
1430             break;
1431          case 'errorlog':  
1432             error_log($text);
1433             break;
1434          case 'logfile':
1435             error_log($text, 3, $his->cfg->log_file);
1436             break;
1437       }
1438
1439    } // _error()
1440
1441    /**
1442     * output calendard input fields
1443     */
1444    private function get_calendar($mode)
1445    {
1446       $year = $_SESSION[$mode .'_date'] ? date("Y", strtotime($_SESSION[$mode .'_date'])) : date("Y");
1447       $month = $_SESSION[$mode .'_date'] ? date("m", strtotime($_SESSION[$mode .'_date'])) : date("m");
1448       $day = $_SESSION[$mode .'_date'] ? date("d", strtotime($_SESSION[$mode .'_date'])) : date("d");
1449
1450       $output = "<input type=\"text\" size=\"3\" id=\"". $mode ."year\" value=\"". $year ."\"";
1451       if(!isset($_SESSION[$mode .'_date'])) $output.= " disabled=\"disabled\"";
1452       $output.= " />\n";
1453       $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."month\" value=\"". $month ."\"";
1454       if(!isset($_SESSION[$mode .'_date'])) $output.= " disabled=\"disabled\"";
1455       $output.= " />\n";
1456       $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."day\" value=\"". $day ."\"";
1457       if(!isset($_SESSION[$mode .'_date'])) $output.= " disabled=\"disabled\"";
1458       $output.= " />\n";
1459       return $output;
1460
1461    } // get_calendar()
1462
1463    /**
1464     * output calendar matrix
1465     */
1466    public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
1467    {
1468       if (!isset($year)) $year = date('Y');
1469       if (!isset($month)) $month = date('m');
1470       if (!isset($day)) $day = date('d');
1471       $rows = 1;
1472       $cols = 1;
1473       $matrix = Array();
1474
1475       require_once CALENDAR_ROOT.'Month/Weekdays.php';
1476       require_once CALENDAR_ROOT.'Day.php';
1477
1478       // Build the month
1479       $month = new Calendar_Month_Weekdays($year,$month);
1480
1481       // Create links
1482       $prevStamp = $month->prevMonth(true);
1483       $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
1484       $nextStamp = $month->nextMonth(true);
1485       $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
1486
1487       $selectedDays = array (
1488          new Calendar_Day($year,$month,$day),
1489          new Calendar_Day($year,12,25),
1490       );
1491
1492       // Build the days in the month
1493       $month->build($selectedDays);
1494
1495       $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
1496       $this->tmpl->assign('prev_month', $prev);
1497       $this->tmpl->assign('next_month', $next);
1498
1499       while ( $day = $month->fetch() ) {
1500    
1501          if(!isset($matrix[$rows]))
1502             $matrix[$rows] = Array();
1503
1504          $string = "";
1505
1506          $dayStamp = $day->thisDay(true);
1507          $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
1508
1509          // isFirst() to find start of week
1510          if ( $day->isFirst() )
1511             $string.= "<tr>\n";
1512
1513          if ( $day->isSelected() ) {
1514             $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
1515          } else if ( $day->isEmpty() ) {
1516             $string.= "<td>&nbsp;</td>\n";
1517          } else {
1518             $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
1519          }
1520
1521          // isLast() to find end of week
1522          if ( $day->isLast() )
1523             $string.= "</tr>\n";
1524
1525          $matrix[$rows][$cols] = $string;
1526
1527          $cols++;
1528
1529          if($cols > 7) {
1530             $cols = 1;
1531             $rows++;
1532          }
1533       }
1534
1535       $this->tmpl->assign('matrix', $matrix);
1536       $this->tmpl->assign('rows', $rows);
1537       $this->tmpl->show("calendar.tpl");
1538
1539    } // get_calendar_matrix()
1540
1541    /**
1542     * output export page
1543     */
1544    public function getExport($mode)
1545    {
1546       $pictures = $this->getPhotoSelection();
1547       $current_tags = $this->getCurrentTags();  
1548
1549       if(!isset($_SERVER['HTTPS'])) $protocol = "http";
1550       else $protocol = "https";
1551
1552       $server_name = $_SERVER['SERVER_NAME'];
1553
1554       foreach($pictures as $picture) {
1555
1556          $orig_url = $protocol ."://". $server_name . $this->cfg->web_path ."index.php?mode=showp&id=". $picture;
1557          if($current_tags != "") {
1558             $orig_url.= "&tags=". $current_tags;
1559          } 
1560          if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1561             $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
1562          }
1563
1564          $thumb_url = $protocol ."://". $server_name . $this->cfg->web_path ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
1565
1566          switch($mode) {
1567
1568             case 'HTML':
1569                // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
1570                print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
1571                break;
1572                
1573             case 'MoinMoin':
1574                // "[%pictureurl% %thumbnailurl%]"
1575                print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
1576                break;
1577
1578             case 'MoinMoinList':
1579                // " * [%pictureurl% %thumbnailurl%]"
1580                print "&nbsp;" . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
1581                break;
1582          }
1583
1584       }
1585
1586    } // getExport()
1587
1588    /**
1589     * return all selected tags as one string
1590     */
1591    private function getCurrentTags()
1592    {
1593       $current_tags = "";
1594       if($_SESSION['selected_tags'] != "") {
1595          foreach($_SESSION['selected_tags'] as $tag)
1596             $current_tags.= $tag .",";
1597          $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
1598       }
1599       return $current_tags;
1600
1601    } // getCurrentTags()
1602
1603    /**
1604     * return the current photo
1605     */
1606    public function getCurrentPhoto()
1607    {
1608       if(isset($_SESSION['current_photo'])) {
1609          print $_SESSION['current_photo'];
1610       }
1611    } // getCurrentPhoto()
1612
1613    /**
1614     * tells the client browser what to do
1615     *
1616     * this function is getting called via AJAX by the
1617     * client browsers. it will tell them what they have
1618     * to do next. This is necessary for directly jumping
1619     * into photo index or single photo view when the are
1620     * requested with specific URLs
1621     */
1622    public function whatToDo()
1623    {
1624       if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
1625          return "show_photo";
1626       }
1627       elseif((isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) ||
1628          (isset($_SESSION['from_date']) && isset($_SESSION['to_date']))) {
1629          return "showpi";
1630       }
1631       elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
1632          return "showpi";
1633       }
1634
1635       return "nothing special";
1636
1637    } // whatToDo()
1638
1639    /**
1640     * return the current process-user
1641     */
1642    private function getuid()
1643    {
1644       if($uid = posix_getuid()) {
1645          if($user = posix_getpwuid($uid)) {
1646             return $user['name'];
1647          }
1648       }
1649    
1650       return 'n/a';
1651    
1652    } // getuid()
1653
1654    /**
1655     * returns a select-dropdown box to select photo index sort parameters
1656     */
1657    private function get_sort_field()
1658    {
1659       $output = "<select name=\"sort_order\">";
1660       foreach(array('date_asc', 'date_desc', 'name_asc', 'name_desc') as $sort_order) {
1661          $output.= "<option value=\"". $sort_order ."\"";
1662          if($sort_order == $_SESSION['sort_order']) {
1663             $output.= " selected=\"selected\"";
1664          }
1665          $output.= ">". $sort_order ."</option>";
1666       }
1667       $output.= "</select>";
1668       return $output;
1669
1670    } // get_sort_field()
1671
1672    /**
1673     * returns the currently selected sort order
1674     */ 
1675    private function get_sort_order()
1676    {
1677       switch($_SESSION['sort_order']) {
1678          case 'date_asc':
1679             return " ORDER BY p.time ASC";
1680             break;
1681          case 'date_desc':
1682             return " ORDER BY p.time DESC";
1683             break;
1684          case 'name_asc':
1685             return " ORDER BY p.name ASC";
1686             break;
1687          case 'name_desc':
1688             return " ORDER BY p.name DESC";
1689             break;
1690       }
1691
1692    } // get_sort_order()
1693
1694    /***
1695      * return the next to be shown slide show image
1696      *
1697      * this function returns the URL of the next image
1698      * in the slideshow sequence.
1699      */
1700    public function getNextSlideShowImage()
1701    {
1702       $all_photos = $this->getPhotoSelection();
1703
1704       if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1) 
1705          $_SESSION['slideshow_img'] = 0;
1706       else
1707          $_SESSION['slideshow_img']++;
1708
1709       $server_name = $_SERVER['SERVER_NAME'];
1710       if(!isset($_SERVER['HTTPS'])) $protocol = "http";
1711       else $protocol = "https";
1712
1713       return $protocol ."://". $server_name . $this->cfg->web_path ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
1714
1715    } // getNextSlideShowImage()
1716
1717    /***
1718      * return the previous to be shown slide show image
1719      *
1720      * this function returns the URL of the previous image
1721      * in the slideshow sequence.
1722      */
1723    public function getPrevSlideShowImage()
1724    {
1725       $all_photos = $this->getPhotoSelection();
1726
1727       if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
1728          $_SESSION['slideshow_img'] = 0;
1729       else
1730          $_SESSION['slideshow_img']--;
1731
1732       $server_name = $_SERVER['SERVER_NAME'];
1733       if(!isset($_SERVER['HTTPS'])) $protocol = "http";
1734       else $protocol = "https";
1735
1736       return $protocol ."://". $server_name . $this->cfg->web_path ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
1737
1738    } // getPrevSlideShowImage()
1739
1740    public function resetSlideShow()
1741    {
1742       if(isset($_SESSION['slideshow_img']))
1743          unset($_SESSION['slideshow_img']);
1744    } // resetSlideShow()
1745    
1746    /***
1747      * get random photo
1748      *
1749      * this function will get all photos from the fspot
1750      * database and randomly return ONE entry
1751      *
1752      * saddly there is yet no sqlite3 function which returns
1753      * the bulk result in array, so we have to fill up our
1754      * own here.
1755      */ 
1756    public function get_random_photo()
1757    {
1758       $all = Array();
1759
1760       $result = $this->db->db_query("
1761          SELECT id
1762          FROM photos
1763       ");
1764       
1765       while($row = $this->db->db_fetch_object($result)) {
1766          array_push($all, $row['id']);
1767       }
1768
1769       return $all[array_rand($all)];
1770
1771    } // get_random_photo()
1772
1773    /**
1774     * validates provided date
1775     *
1776     * this function validates if the provided date
1777     * contains a valid date and will return true 
1778     * if it is.
1779     */
1780    public function isValidDate($date_str)
1781    {
1782       $timestamp = strtotime($date_str);
1783    
1784       if(is_numeric($timestamp))
1785          return true;
1786       
1787       return false;
1788
1789    } // isValidDate()
1790
1791    /**
1792     * timestamp to string conversion
1793     */
1794    private function ts2str($timestamp)
1795    {
1796       return strftime("%Y-%m-%d", $timestamp);
1797    } // ts2str()
1798
1799 }
1800
1801 ?>