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