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