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