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