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