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