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