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