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