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