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