Merge branch 'master' of /var/cache/git/phpfspot
[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       $changes = false;
1294
1295       foreach($resolutions as $resolution) {
1296
1297          $thumb_sub_path = substr($file_md5, 0, 2);
1298          $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
1299
1300          if(!file_exists(dirname($thumb_path))) {
1301             mkdir(dirname($thumb_path), 0755);
1302          }
1303
1304          /* if the thumbnail file doesn't exist, create it */
1305          if(!file_exists($thumb_path)) {
1306
1307             $this->_debug(" ". $resolution ."px");
1308             if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1309                $error = 1;
1310
1311             $changes = true;
1312          }
1313          /* if the file hasn't changed there is no need to regen the thumb */
1314          elseif($file_md5 != $this->getMD5($idx) || $force) {
1315
1316             $this->_debug(" ". $resolution ."px");
1317             if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1318                $error = 1;
1319
1320             $changes = true;
1321          }
1322       }
1323
1324       if(!$changes) {
1325          $this->_debug(" already exist");
1326       }
1327
1328       /* set the new/changed MD5 sum for the current photo */
1329       if(!$error) {
1330          $this->setMD5($idx, $file_md5);
1331       }
1332
1333       $this->_debug("\n");
1334
1335    } // gen_thumb()
1336
1337    /**
1338     * returns stored md5 sum for a specific photo
1339     *
1340     * this function queries the phpfspot database for a
1341     * stored MD5 checksum of the specified photo
1342     */
1343    public function getMD5($idx)
1344    {
1345       $result = $this->cfg_db->db_query("
1346          SELECT img_md5 
1347          FROM images
1348          WHERE img_idx='". $idx ."'
1349       ");
1350
1351       if(!$result)
1352          return 0;
1353
1354       $img = $this->cfg_db->db_fetch_object($result);
1355       return $img['img_md5'];
1356       
1357    } // getMD5()
1358
1359    /**
1360     * set MD5 sum for the specific photo
1361     */
1362    private function setMD5($idx, $md5)
1363    {
1364       $result = $this->cfg_db->db_exec("
1365          REPLACE INTO images (img_idx, img_md5)
1366          VALUES ('". $idx ."', '". $md5 ."')
1367       ");
1368
1369    } // setMD5()
1370
1371    /**
1372     * store current tag condition
1373     *
1374     * this function stores the current tag condition
1375     * (AND or OR) in the users session variables
1376     */
1377    public function setTagCondition($mode)
1378    {
1379       $_SESSION['tag_condition'] = $mode;
1380
1381    } // setTagCondition()
1382
1383    /** 
1384     * invoke tag & date search 
1385     *
1386     * this function will return all matching tags and store
1387     * them in the session variable selected_tags. furthermore
1388     * it also handles the date search.
1389     * getPhotoSelection() will then only return the matching
1390     * photos.
1391     */
1392    public function startSearch($searchfor, $sort_order, $from = 0, $to = 0)
1393    {
1394       $this->get_tags();
1395
1396       $_SESSION['searchfor'] = $searchfor;
1397       $_SESSION['sort_order'] = $sort_order;
1398       if($from != 0)
1399          $_SESSION['from_date'] = strtotime($from);
1400       else
1401          unset($_SESSION['from_date']);
1402       if($to != 0)
1403          $_SESSION['to_date'] = strtotime($to);
1404       else
1405          unset($_SESSION['to_date']);
1406
1407       if($searchfor != "") {
1408          /* new search, reset the current selected tags */
1409          $_SESSION['selected_tags'] = Array();
1410          foreach($this->avail_tags as $tag) {
1411             if(preg_match('/'. $searchfor .'/i', $this->tags[$tag]))
1412                array_push($_SESSION['selected_tags'], $tag);
1413          }
1414       }
1415
1416    } // startSearch()
1417
1418    /**
1419     * rotate image
1420     *
1421     * this function rotates the image according the
1422     * specified angel.
1423     */
1424    private function rotateImage($img, $degrees)
1425    {
1426       if(function_exists("imagerotate")) {
1427          $img = imagerotate($img, $degrees, 0);
1428       } else {
1429          function imagerotate($src_img, $angle)
1430          {
1431             $src_x = imagesx($src_img);
1432             $src_y = imagesy($src_img);
1433             if ($angle == 180) {
1434                $dest_x = $src_x;
1435                $dest_y = $src_y;
1436             }
1437             elseif ($src_x <= $src_y) {
1438                $dest_x = $src_y;
1439                $dest_y = $src_x;
1440             }
1441             elseif ($src_x >= $src_y) {
1442                $dest_x = $src_y;
1443                $dest_y = $src_x;
1444             }
1445                
1446             $rotate=imagecreatetruecolor($dest_x,$dest_y);
1447             imagealphablending($rotate, false);
1448                
1449             switch ($angle) {
1450             
1451                case 90:
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, $dest_x - $y - 1, $x, $color);
1456                      }
1457                   }
1458                   break;
1459
1460                case 270:
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, $y, $dest_y - $x - 1, $color);
1465                      }
1466                   }
1467                   break;
1468
1469                case 180:
1470                   for ($y = 0; $y < ($src_y); $y++) {
1471                      for ($x = 0; $x < ($src_x); $x++) {
1472                         $color = imagecolorat($src_img, $x, $y);
1473                         imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
1474                      }
1475                   }
1476                   break;
1477
1478                default:
1479                   $rotate = $src_img;
1480                   break;
1481             };
1482
1483             return $rotate;
1484
1485          }
1486
1487          $img = imagerotate($img, $degrees);
1488
1489       }
1490
1491       return $img;
1492
1493    } // rotateImage()
1494
1495    /**
1496     * return all assigned tags for the specified photo
1497     */
1498    private function get_photo_tags($idx)
1499    {
1500       $result = $this->db->db_query("
1501          SELECT t.id, t.name
1502          FROM tags t
1503          INNER JOIN photo_tags pt
1504             ON t.id=pt.tag_id
1505          WHERE pt.photo_id='". $idx ."'
1506       ");
1507
1508       $tags = Array();
1509
1510       while($row = $this->db->db_fetch_object($result))
1511          $tags[$row['id']] = $row['name'];
1512
1513       return $tags;
1514
1515    } // get_photo_tags()
1516
1517    /**
1518     * create on-the-fly images with text within
1519     */
1520    public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
1521    {
1522       if (strlen($color) != 6) 
1523          $color = 000000;
1524
1525       $int = hexdec($color);
1526       $h = imagefontheight($font);
1527       $fw = imagefontwidth($font);
1528       $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
1529       $lines = count($txt);
1530       $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
1531       $bg = imagecolorallocate($im, 255, 255, 255);
1532       $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
1533       $y = 0;
1534
1535       foreach ($txt as $text) {
1536          $x = (($w - ($fw * strlen($text))) / 2);
1537          imagestring($im, $font, $x, $y, $text, $color);
1538          $y += ($h + $space);
1539       }
1540
1541       Header("Content-type: image/png");
1542       ImagePng($im);
1543
1544    } // showTextImage()
1545
1546    /**
1547     * check if all requirements are met
1548     */
1549    private function checkRequirements()
1550    {
1551       if(!function_exists("imagecreatefromjpeg")) {
1552          print "PHP GD library extension is missing<br />\n";
1553          $missing = true;
1554       }
1555
1556       if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
1557          print "PHP SQLite3 library extension is missing<br />\n";
1558          $missing = true;
1559       }
1560
1561       /* Check for HTML_AJAX PEAR package, lent from Horde project */
1562       ini_set('track_errors', 1);
1563       @include_once 'HTML/AJAX/Server.php';
1564       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1565          print "PEAR HTML_AJAX package is missing<br />\n";
1566          $missing = true;
1567       }
1568       @include_once 'Calendar/Calendar.php';
1569       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1570          print "PEAR Calendar package is missing<br />\n";
1571          $missing = true;
1572       }
1573       ini_restore('track_errors');
1574
1575       if(isset($missing))
1576          return false;
1577
1578       return true;
1579
1580    } // checkRequirements()
1581
1582    private function _debug($text)
1583    {
1584       if($this->fromcmd) {
1585          print $text;
1586       }
1587
1588    } // _debug()
1589
1590    /**
1591     * check if specified MIME type is supported
1592     */
1593    public function checkifImageSupported($mime)
1594    {
1595       if(in_array($mime, Array("image/jpeg")))
1596          return true;
1597
1598       return false;
1599
1600    } // checkifImageSupported()
1601
1602    public function _error($text)
1603    {
1604       switch($this->cfg->logging) {
1605          case 'display':
1606             print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
1607             print $text;
1608             break;
1609          case 'errorlog':  
1610             error_log($text);
1611             break;
1612          case 'logfile':
1613             error_log($text, 3, $his->cfg->log_file);
1614             break;
1615       }
1616
1617    } // _error()
1618
1619    /**
1620     * output calendard input fields
1621     */
1622    private function get_calendar($mode)
1623    {
1624       $year = $_SESSION[$mode .'_date'] ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
1625       $month = $_SESSION[$mode .'_date'] ? date("m", $_SESSION[$mode .'_date']) : date("m");
1626       $day = $_SESSION[$mode .'_date'] ? date("d", $_SESSION[$mode .'_date']) : date("d");
1627
1628       $output = "<input type=\"text\" size=\"3\" id=\"". $mode ."year\" value=\"". $year ."\"";
1629       if(!isset($_SESSION[$mode .'_date'])) $output.= " disabled=\"disabled\"";
1630       $output.= " />\n";
1631       $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."month\" value=\"". $month ."\"";
1632       if(!isset($_SESSION[$mode .'_date'])) $output.= " disabled=\"disabled\"";
1633       $output.= " />\n";
1634       $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."day\" value=\"". $day ."\"";
1635       if(!isset($_SESSION[$mode .'_date'])) $output.= " disabled=\"disabled\"";
1636       $output.= " />\n";
1637       return $output;
1638
1639    } // get_calendar()
1640
1641    /**
1642     * output calendar matrix
1643     */
1644    public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
1645    {
1646       if (!isset($year)) $year = date('Y');
1647       if (!isset($month)) $month = date('m');
1648       if (!isset($day)) $day = date('d');
1649       $rows = 1;
1650       $cols = 1;
1651       $matrix = Array();
1652
1653       require_once CALENDAR_ROOT.'Month/Weekdays.php';
1654       require_once CALENDAR_ROOT.'Day.php';
1655
1656       // Build the month
1657       $month = new Calendar_Month_Weekdays($year,$month);
1658
1659       // Create links
1660       $prevStamp = $month->prevMonth(true);
1661       $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
1662       $nextStamp = $month->nextMonth(true);
1663       $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
1664
1665       $selectedDays = array (
1666          new Calendar_Day($year,$month,$day),
1667          new Calendar_Day($year,12,25),
1668       );
1669
1670       // Build the days in the month
1671       $month->build($selectedDays);
1672
1673       $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
1674       $this->tmpl->assign('prev_month', $prev);
1675       $this->tmpl->assign('next_month', $next);
1676
1677       while ( $day = $month->fetch() ) {
1678    
1679          if(!isset($matrix[$rows]))
1680             $matrix[$rows] = Array();
1681
1682          $string = "";
1683
1684          $dayStamp = $day->thisDay(true);
1685          $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
1686
1687          // isFirst() to find start of week
1688          if ( $day->isFirst() )
1689             $string.= "<tr>\n";
1690
1691          if ( $day->isSelected() ) {
1692             $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
1693          } else if ( $day->isEmpty() ) {
1694             $string.= "<td>&nbsp;</td>\n";
1695          } else {
1696             $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
1697          }
1698
1699          // isLast() to find end of week
1700          if ( $day->isLast() )
1701             $string.= "</tr>\n";
1702
1703          $matrix[$rows][$cols] = $string;
1704
1705          $cols++;
1706
1707          if($cols > 7) {
1708             $cols = 1;
1709             $rows++;
1710          }
1711       }
1712
1713       $this->tmpl->assign('matrix', $matrix);
1714       $this->tmpl->assign('rows', $rows);
1715       $this->tmpl->show("calendar.tpl");
1716
1717    } // get_calendar_matrix()
1718
1719    /**
1720     * output export page
1721     */
1722    public function getExport($mode)
1723    {
1724       $pictures = $this->getPhotoSelection();
1725       $current_tags = $this->getCurrentTags();  
1726
1727       foreach($pictures as $picture) {
1728
1729          $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
1730          if($current_tags != "") {
1731             $orig_url.= "&tags=". $current_tags;
1732          } 
1733          if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1734             $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
1735          }
1736
1737          $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
1738
1739          switch($mode) {
1740
1741             case 'HTML':
1742                // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
1743                print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
1744                break;
1745                
1746             case 'MoinMoin':
1747                // "[%pictureurl% %thumbnailurl%]"
1748                print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
1749                break;
1750
1751             case 'MoinMoinList':
1752                // " * [%pictureurl% %thumbnailurl%]"
1753                print "&nbsp;" . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
1754                break;
1755          }
1756
1757       }
1758
1759    } // getExport()
1760
1761    /**
1762     * output RSS feed
1763     */
1764    public function getRSSFeed()
1765    {
1766       Header("Content-type: text/xml; charset=utf-8");
1767       print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1768 ?>
1769 <rss version="2.0"
1770    xmlns:media="http://search.yahoo.com/mrss/"
1771    xmlns:dc="http://purl.org/dc/elements/1.1/"
1772  >
1773  <channel>
1774   <title>phpfspot</title>
1775   <description>phpfspot RSS feed</description>
1776   <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
1777   <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
1778   <generator>phpfspot</generator>
1779 <?php
1780
1781       $pictures = $this->getPhotoSelection();
1782       $current_tags = $this->getCurrentTags();  
1783
1784       foreach($pictures as $picture) {
1785
1786          $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
1787          if($current_tags != "") {
1788             $orig_url.= "&tags=". $current_tags;
1789          } 
1790          if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1791             $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
1792          }
1793
1794          $details = $this->get_photo_details($picture);
1795
1796          $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
1797          $thumb_html = htmlspecialchars("
1798 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
1799 <br>
1800 ". $details['description']);
1801
1802          $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
1803          $meta = $this->get_meta_informations($orig_path);
1804          $meta_date = isset($meta['FileDateTime']) ? $meta['FileDateTime'] : filemtime($orig_path);
1805
1806 ?>
1807   <item>
1808    <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
1809    <link><?php print htmlspecialchars($orig_url); ?></link>
1810    <guid><?php print htmlspecialchars($orig_url); ?></guid>
1811    <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $meta_date); ?></dc:date.Taken>
1812    <description>
1813     <?php print $thumb_html; ?> 
1814    </description>
1815    <pubDate><?php print strftime("%a, %d %b %Y %T %z", $meta_date); ?></pubDate>
1816   </item>
1817 <?php
1818
1819       }
1820 ?>
1821  </channel>
1822 </rss>
1823 <?php
1824
1825
1826    } // getExport()
1827
1828  
1829    /**
1830     * return all selected tags as one string
1831     */
1832    private function getCurrentTags()
1833    {
1834       $current_tags = "";
1835       if($_SESSION['selected_tags'] != "") {
1836          foreach($_SESSION['selected_tags'] as $tag)
1837             $current_tags.= $tag .",";
1838          $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
1839       }
1840       return $current_tags;
1841
1842    } // getCurrentTags()
1843
1844    /**
1845     * return the current photo
1846     */
1847    public function getCurrentPhoto()
1848    {
1849       if(isset($_SESSION['current_photo'])) {
1850          print $_SESSION['current_photo'];
1851       }
1852    } // getCurrentPhoto()
1853
1854    /**
1855     * tells the client browser what to do
1856     *
1857     * this function is getting called via AJAX by the
1858     * client browsers. it will tell them what they have
1859     * to do next. This is necessary for directly jumping
1860     * into photo index or single photo view when the are
1861     * requested with specific URLs
1862     */
1863    public function whatToDo()
1864    {
1865       if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
1866          return "show_photo";
1867       }
1868       elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1869          return "showpi_tags";
1870       }
1871       elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
1872          return "showpi";
1873       }
1874
1875       return "nothing special";
1876
1877    } // whatToDo()
1878
1879    /**
1880     * return the current process-user
1881     */
1882    private function getuid()
1883    {
1884       if($uid = posix_getuid()) {
1885          if($user = posix_getpwuid($uid)) {
1886             return $user['name'];
1887          }
1888       }
1889    
1890       return 'n/a';
1891    
1892    } // getuid()
1893
1894    /**
1895     * returns a select-dropdown box to select photo index sort parameters
1896     */
1897    private function get_sort_field()
1898    {
1899       $output = "<select name=\"sort_order\">";
1900       foreach(array('date_asc', 'date_desc', 'name_asc', 'name_desc') as $sort_order) {
1901          $output.= "<option value=\"". $sort_order ."\"";
1902          if($sort_order == $_SESSION['sort_order']) {
1903             $output.= " selected=\"selected\"";
1904          }
1905          $output.= ">". $sort_order ."</option>";
1906       }
1907       $output.= "</select>";
1908       return $output;
1909
1910    } // get_sort_field()
1911
1912    /**
1913     * returns the currently selected sort order
1914     */ 
1915    private function get_sort_order()
1916    {
1917       switch($_SESSION['sort_order']) {
1918          case 'date_asc':
1919             return " ORDER BY p.time ASC";
1920             break;
1921          case 'date_desc':
1922             return " ORDER BY p.time DESC";
1923             break;
1924          case 'name_asc':
1925             return " ORDER BY p.name ASC";
1926             break;
1927          case 'name_desc':
1928             return " ORDER BY p.name DESC";
1929             break;
1930       }
1931
1932    } // get_sort_order()
1933
1934    /***
1935      * return the next to be shown slide show image
1936      *
1937      * this function returns the URL of the next image
1938      * in the slideshow sequence.
1939      */
1940    public function getNextSlideShowImage()
1941    {
1942       $all_photos = $this->getPhotoSelection();
1943
1944       if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1) 
1945          $_SESSION['slideshow_img'] = 0;
1946       else
1947          $_SESSION['slideshow_img']++;
1948
1949       return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
1950
1951    } // getNextSlideShowImage()
1952
1953    /***
1954      * return the previous to be shown slide show image
1955      *
1956      * this function returns the URL of the previous image
1957      * in the slideshow sequence.
1958      */
1959    public function getPrevSlideShowImage()
1960    {
1961       $all_photos = $this->getPhotoSelection();
1962
1963       if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
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    } // getPrevSlideShowImage()
1971
1972    public function resetSlideShow()
1973    {
1974       if(isset($_SESSION['slideshow_img']))
1975          unset($_SESSION['slideshow_img']);
1976    } // resetSlideShow()
1977    
1978    /***
1979      * get random photo
1980      *
1981      * this function will get all photos from the fspot
1982      * database and randomly return ONE entry
1983      *
1984      * saddly there is yet no sqlite3 function which returns
1985      * the bulk result in array, so we have to fill up our
1986      * own here.
1987      */ 
1988    public function get_random_photo()
1989    {
1990       $all = Array();
1991
1992       $result = $this->db->db_query("
1993          SELECT id
1994          FROM photos
1995       ");
1996       
1997       while($row = $this->db->db_fetch_object($result)) {
1998          array_push($all, $row['id']);
1999       }
2000
2001       return $all[array_rand($all)];
2002
2003    } // get_random_photo()
2004
2005    /**
2006     * validates provided date
2007     *
2008     * this function validates if the provided date
2009     * contains a valid date and will return true 
2010     * if it is.
2011     */
2012    public function isValidDate($date_str)
2013    {
2014       $timestamp = strtotime($date_str);
2015    
2016       if(is_numeric($timestamp))
2017          return true;
2018       
2019       return false;
2020
2021    } // isValidDate()
2022
2023    /**
2024     * timestamp to string conversion
2025     */
2026    private function ts2str($timestamp)
2027    {
2028       return strftime("%Y-%m-%d", $timestamp);
2029    } // ts2str()
2030
2031    private function extractTags($tags_str)
2032    {
2033       $not_validated = split(',', $_GET['tags']);
2034       $validated = array();
2035
2036       foreach($not_validated as $tag) {
2037          if(is_numeric($tag))
2038             array_push($validated, $tag);
2039       }
2040    
2041       return $validated;
2042    
2043    } // extractTags()
2044
2045    /**
2046     * returns the full path to a thumbnail
2047     */
2048    public function get_thumb_path($width, $photo)
2049    {
2050       $md5 = $this->getMD5($photo);
2051       $sub_path = substr($md5, 0, 2);
2052       return $this->cfg->thumb_path
2053          . "/"
2054          . $sub_path
2055          . "/"
2056          . $width
2057          . "_"
2058          . $md5;
2059
2060    } // get_thumb_path()
2061
2062    /**
2063     * returns server's virtual host name
2064     */
2065    private function get_server_name()
2066    {
2067       return $_SERVER['SERVER_NAME'];
2068    } // get_server_name()
2069
2070    /**
2071     * returns type of webprotocol which is
2072     * currently used
2073     */
2074    private function get_web_protocol()
2075    {
2076       if(!isset($_SERVER['HTTPS']))
2077          return "http";
2078       else
2079          return "https";
2080    } // get_web_protocol()
2081
2082    /**
2083     * return url to this phpfspot installation
2084     */
2085    private function get_phpfspot_url()
2086    {
2087       return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
2088    } // get_phpfspot_url()
2089    
2090    /**
2091     * check file exists and is readable
2092     *
2093     * returns true, if everything is ok, otherwise false
2094     * if $silent is not set, this function will output and
2095     * error message
2096     */
2097    private function check_readable($file, $silent = null)
2098    {
2099       if(!file_exists($file)) {
2100          if(!isset($silent))
2101             print "File \"". $file ."\" does not exist.\n";
2102          return false;
2103       }
2104
2105       if(!is_readable($file)) {
2106          if(!isset($silent))
2107             print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
2108          return false;
2109       }
2110
2111       return true;
2112
2113    } // check_readable()
2114
2115    /**
2116     * check if all needed indices are present
2117     *
2118     * this function checks, if some needed indices are already
2119     * present, or if not, create them on the fly. they are
2120     * necessary to speed up some queries like that one look for
2121     * all tags, when show_tags is specified in the configuration.
2122     */
2123    private function checkDbIndices()
2124    {
2125       $result = $this->db->db_exec("
2126          CREATE INDEX IF NOT EXISTS
2127             phototag
2128          ON
2129             photo_tags
2130                (photo_id, tag_id)
2131       ");
2132
2133    } // checkDbIndices()
2134
2135    /**
2136     * retrive F-Spot database version
2137     *
2138     * this function will return the F-Spot database version number
2139     * It is stored within the sqlite3 database in the table meta
2140     */
2141    public function getFspotDBVersion()
2142    {
2143       if($result = $this->db->db_fetchSingleRow("
2144          SELECT data as version
2145          FROM meta
2146          WHERE
2147             name LIKE 'F-Spot Database Version'
2148       "))
2149          return $result['version'];
2150
2151       return null;
2152
2153    } // getFspotDBVersion()
2154
2155    /**
2156     * parse the provided URI and will returned the
2157     * requested chunk
2158     */
2159    public function parse_uri($uri, $mode)
2160    {
2161       if(($components = parse_url($uri)) !== false) {
2162
2163          switch($mode) {
2164             case 'filename':
2165                return basename($components['path']);
2166                break;
2167             case 'dirname':
2168                return dirname($components['path']);
2169                break;
2170             case 'fullpath':
2171                return $components['path'];
2172                break;
2173          }
2174       }
2175
2176       return $uri;
2177
2178    } // parse_uri()
2179
2180 } // class PHPFSPOT
2181
2182 ?>