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