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