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