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