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