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