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