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