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