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