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