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