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