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