Merge branch 'master' of /var/cache/git/phpfspot
[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, $this->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       if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
3149          $query_str.= "
3150             SELECT
3151                DISTINCT pt1.photo_id as id
3152             FROM
3153                photo_tags pt1
3154             INNER JOIN photo_tags
3155                pt2 ON pt1.photo_id=pt2.photo_id
3156             INNER JOIN tags t1
3157                ON t1.id=pt1.tag_id
3158             INNER JOIN tags t2
3159                ON t2.id=pt2.tag_id
3160             WHERE
3161                pt1.tag_id LIKE '". $tagidx ."'
3162             AND
3163                t2.name IN  ('".implode("','",$this->cfg->show_tags)."')
3164             ORDER BY
3165                t1.sort_priority ASC";
3166       }
3167       else {
3168          $query_str = "
3169             SELECT
3170                p.id
3171             FROM
3172                photos p
3173             INNER JOIN photo_tags pt
3174                ON p.id=pt.photo_id
3175             WHERE
3176                pt.tag_id LIKE '". $tagidx ."'";
3177       }
3178
3179       $result = $this->db->db_query($query_str);
3180
3181       while($row = $this->db->db_fetch_object($result)) {
3182          array_push($all, $row['id']);
3183       }
3184
3185       return $all[array_rand($all)];
3186
3187    } // get_random_tag_photo()
3188
3189    /**
3190     * validates provided date
3191     *
3192     * this function validates if the provided date
3193     * contains a valid date and will return true 
3194     * if it is.
3195     * @param string $date_str
3196     * @return boolean
3197     */
3198    public function isValidDate($date_str)
3199    {
3200       $timestamp = strtotime($date_str);
3201    
3202       if(is_numeric($timestamp))
3203          return true;
3204       
3205       return false;
3206
3207    } // isValidDate()
3208
3209    /**
3210     * timestamp to string conversion
3211     * @param integer $timestamp
3212     * @return string
3213     */
3214    private function ts2str($timestamp)
3215    {
3216       if(!empty($timestamp) && is_numeric($timestamp))
3217          return strftime("%Y-%m-%d", $timestamp);
3218
3219    } // ts2str()
3220
3221    /**
3222     * extract tag-names from $_GET['tags']
3223     * @param string $tags_str
3224     * @return string
3225     */
3226    private function extractTags($tags_str)
3227    {
3228       $not_validated = split(',', $tags_str);
3229       $validated = array();
3230
3231       foreach($not_validated as $tag) {
3232          if(is_numeric($tag))
3233             array_push($validated, $tag);
3234       }
3235    
3236       return $validated;
3237    
3238    } // extractTags()
3239
3240    /**
3241     * returns the full path to a thumbnail
3242     * @param integer $width
3243     * @param integer $photo
3244     * @return string
3245     */
3246    public function get_thumb_path($width, $photo_idx, $version_idx)
3247    {
3248       $md5 = $this->getMD5($photo_idx, $version_idx);
3249       $sub_path = substr($md5, 0, 2);
3250       return $this->cfg->thumb_path
3251          . "/"
3252          . $sub_path
3253          . "/"
3254          . $width
3255          . "_"
3256          . $md5;
3257
3258    } // get_thumb_path()
3259
3260    /**
3261     * returns server's virtual host name
3262     * @return string
3263     */
3264    private function get_server_name()
3265    {
3266       return $_SERVER['SERVER_NAME'];
3267    } // get_server_name()
3268
3269    /**
3270     * returns type of webprotocol which is currently used
3271     * @return string
3272     */
3273    private function get_web_protocol()
3274    {
3275       if(!isset($_SERVER['HTTPS']))
3276          return "http";
3277       else
3278          return "https";
3279    } // get_web_protocol()
3280
3281    /**
3282     * return url to this phpfspot installation
3283     * @return string
3284     */
3285    private function get_phpfspot_url()
3286    {
3287       return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
3288
3289    } // get_phpfspot_url()
3290
3291    /**
3292     * returns the number of photos which are tagged with $tag_id
3293     * @param integer $tag_id
3294     * @return integer
3295     */
3296    public function get_num_photos($tag_id)
3297    {
3298       if($result = $this->db->db_fetchSingleRow("
3299          SELECT count(*) as number
3300          FROM photo_tags
3301          WHERE
3302             tag_id LIKE '". $tag_id ."'")) {
3303
3304          return $result['number'];
3305
3306       }
3307
3308       return 0;
3309       
3310    } // get_num_photos()
3311    
3312    /**
3313     * check file exists and is readable
3314     *
3315     * returns true, if everything is ok, otherwise false
3316     * if $silent is not set, this function will output and
3317     * error message
3318     * @param string $file
3319     * @param boolean $silent
3320     * @return boolean
3321     */
3322    private function check_readable($file, $silent = null)
3323    {
3324       if(!file_exists($file)) {
3325          if(!isset($silent))
3326             print "File \"". $file ."\" does not exist.\n";
3327          return false;
3328       }
3329
3330       if(!is_readable($file)) {
3331          if(!isset($silent))
3332             print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
3333          return false;
3334       }
3335
3336       return true;
3337
3338    } // check_readable()
3339
3340    /**
3341     * check if all needed indices are present
3342     *
3343     * this function checks, if some needed indices are already
3344     * present, or if not, create them on the fly. they are
3345     * necessary to speed up some queries like that one look for
3346     * all tags, when show_tags is specified in the configuration.
3347     */
3348    private function checkDbIndices()
3349    {
3350       $result = $this->db->db_exec("
3351          CREATE INDEX IF NOT EXISTS
3352             phototag
3353          ON
3354             photo_tags
3355                (photo_id, tag_id)
3356       ");
3357
3358    } // checkDbIndices()
3359
3360    /**
3361     * retrive F-Spot database version
3362     *
3363     * this function will return the F-Spot database version number
3364     * It is stored within the sqlite3 database in the table meta
3365     * @return string|null
3366     */
3367    public function getFspotDBVersion()
3368    {
3369       if($result = $this->db->db_fetchSingleRow("
3370          SELECT data as version
3371          FROM meta
3372          WHERE
3373             name LIKE 'F-Spot Database Version'
3374       "))
3375          return $result['version'];
3376
3377       return null;
3378
3379    } // getFspotDBVersion()
3380
3381    /**
3382     * parse the provided URI and will returned the requested chunk
3383     * @param string $uri
3384     * @param string $mode
3385     * @return string
3386     */
3387    public function parse_uri($uri, $mode)
3388    {
3389       if(($components = parse_url($uri)) !== false) {
3390
3391          switch($mode) {
3392             case 'filename':
3393                return basename($components['path']);
3394                break;
3395             case 'dirname':
3396                return dirname($components['path']);
3397                break;
3398             case 'fullpath':
3399                return $components['path'];
3400                break;
3401          }
3402       }
3403
3404       return $uri;
3405
3406    } // parse_uri()
3407
3408    /**
3409     * validate config options
3410     *
3411     * this function checks if all necessary configuration options are
3412     * specified and set.
3413     * @return boolean
3414     */
3415    private function check_config_options()
3416    {
3417       if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
3418          $this->_error("Please set \$page_title in phpfspot_cfg");
3419
3420       if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
3421          $this->_error("Please set \$base_path in phpfspot_cfg");
3422
3423       if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
3424          $this->_error("Please set \$web_path in phpfspot_cfg");
3425
3426       if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
3427          $this->_error("Please set \$thumb_path in phpfspot_cfg");
3428
3429       if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
3430          $this->_error("Please set \$smarty_path in phpfspot_cfg");
3431
3432       if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
3433          $this->_error("Please set \$fspot_db in phpfspot_cfg");
3434
3435       if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
3436          $this->_error("Please set \$db_access in phpfspot_cfg");
3437
3438       if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
3439          $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
3440
3441       if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
3442          $this->_error("Please set \$thumb_width in phpfspot_cfg");
3443
3444       if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
3445          $this->_error("Please set \$thumb_height in phpfspot_cfg");
3446
3447       if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
3448          $this->_error("Please set \$photo_width in phpfspot_cfg");
3449
3450       if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
3451          $this->_error("Please set \$mini_width in phpfspot_cfg");
3452
3453       if(!isset($this->cfg->thumbs_per_page))
3454          $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
3455
3456       if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
3457          $this->_error("Please set \$path_replace_from in phpfspot_cfg");
3458
3459       if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
3460          $this->_error("Please set \$path_replace_to in phpfspot_cfg");
3461
3462       if(!isset($this->cfg->hide_tags))
3463          $this->_error("Please set \$hide_tags in phpfspot_cfg");
3464
3465       if(!isset($this->cfg->theme_name))
3466          $this->_error("Please set \$theme_name in phpfspot_cfg");
3467
3468       if(!isset($this->cfg->logging))
3469          $this->_error("Please set \$logging in phpfspot_cfg");
3470
3471       if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
3472
3473          if(!isset($this->cfg->log_file))
3474             $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
3475
3476          if(!is_writeable($this->cfg->log_file))
3477             $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
3478
3479       }
3480
3481       /* remove trailing slash, if set */
3482       if($this->cfg->web_path == "/")
3483          $this->cfg->web_path = "";
3484       elseif(preg_match('/\/$/', $this->cfg->web_path))
3485          $this->cfg->web_path = preg_replace('/\/$/', '', $this->cfg->web_path);
3486
3487       return $this->runtime_error;
3488
3489    } // check_config_options()
3490
3491    /**
3492     * cleanup phpfspot own database
3493     *
3494     * When photos are getting delete from F-Spot, there will remain
3495     * remain some residues in phpfspot own database. This function
3496     * will try to wipe them out.
3497     */
3498    public function cleanup_phpfspot_db()
3499    {
3500       $to_delete = Array();
3501
3502       $result = $this->cfg_db->db_query("
3503          SELECT img_idx
3504          FROM images
3505          ORDER BY img_idx ASC
3506       ");
3507
3508       while($row = $this->cfg_db->db_fetch_object($result)) {
3509          if(!$this->db->db_fetchSingleRow("
3510             SELECT id
3511             FROM photos
3512             WHERE id='". $row['img_idx'] ."'")) {
3513
3514             array_push($to_delete, $row['img_idx'], ',');
3515          }
3516       }
3517
3518       print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
3519
3520       $this->cfg_db->db_exec("
3521          DELETE FROM images
3522          WHERE img_idx IN (". implode($to_delete) .")
3523       ");
3524
3525    } // cleanup_phpfspot_db()
3526
3527    /**
3528     * return first image of the page, the $current photo
3529     * lies in.
3530     *
3531     * this function is used to find out the first photo of the
3532     * current page, in which the $current photo lies. this is
3533     * used to display the correct photo, when calling showPhotoIndex()
3534     * from showImage()
3535     * @param integer $current
3536     * @param integer $max
3537     * @return integer
3538     */
3539    private function getCurrentPage($current, $max)
3540    {
3541       if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
3542          for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
3543             if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
3544                return $page_start;
3545          }
3546       }
3547       return 0;
3548
3549    } // getCurrentPage()
3550
3551    /**
3552     * return mime info
3553     *
3554     * this function tries to find out the correct mime-type
3555     * for the provided file.
3556     * @param string $file
3557     * @return string
3558     */
3559    public function get_mime_info($file)
3560    {
3561       $details = getimagesize($file);
3562
3563       /* if getimagesize() returns empty, try at least to find out the
3564          mime type.
3565       */
3566       if(empty($details) && function_exists('mime_content_type')) {
3567
3568          // mime_content_type is marked as deprecated in the documentation,
3569          // but is it really necessary to force users to install a PECL
3570          // extension?
3571          $details['mime'] = mime_content_type($file);
3572       }
3573
3574       return $details['mime'];
3575
3576    } // get_mime_info()
3577
3578    /**
3579     * return tag-name by tag-idx
3580     *
3581     * this function returns the tag-name for the requested
3582     * tag specified by tag-idx.
3583     * @param integer $idx
3584     * @return string
3585     */
3586    public function get_tag_name($idx)
3587    {
3588        if($result = $this->db->db_fetchSingleRow("
3589          SELECT name
3590          FROM tags
3591          WHERE
3592             id LIKE '". $idx ."'")) {
3593
3594          return $result['name'];
3595
3596       }
3597
3598       return 0;
3599       
3600    } // get_tag_name()
3601
3602    /**
3603     * parse user friendly url which got rewritten by the websever
3604     * @param string $request_uri
3605     * @return string
3606     */
3607    private function parse_user_friendly_url($request_uri)
3608    {
3609       if(preg_match('/\/photoview\/|\/photo\/|\/tag\//', $request_uri)) {
3610
3611          $options = explode('/', $request_uri);
3612
3613          switch($options[1]) {
3614             case 'photoview':
3615                if(is_numeric($options[2])) {
3616                   $this->session_cleanup();
3617                   //unset($_SESSION['start_action']);
3618                   //unset($_SESSION['selected_tags']);
3619                   $_GET['mode'] = 'showp';
3620                   return $this->showPhoto($options[2]);
3621                }
3622                break;
3623             case 'photo':
3624                if(is_numeric($options[2])) {
3625                   $width = NULL;
3626                   $version = NULL;
3627                   if(isset($options[3]) && is_numeric($options[3]))
3628                      $width = $options[3];
3629                   if(isset($options[4]) && is_numeric($options[4]))
3630                      $version = $options[4];
3631                   require_once "phpfspot_img.php";
3632                   $img = new PHPFSPOT_IMG;
3633                   $img->showImg($options[2], $width, $version);
3634                }
3635                exit;
3636                break;
3637             case 'tag':
3638                if(is_numeric($options[2])) {
3639                   $this->session_cleanup();
3640                   $_GET['tags'] = $options[2];
3641                   $_SESSION['selected_tags'] = Array($options[2]);
3642                   if(isset($options[3]) && is_numeric($options[3]))
3643                      $_SESSION['begin_with'] = $options[3];
3644                   return $this->showPhotoIndex();
3645                }
3646                break;
3647          }
3648       }
3649
3650    } // parse_user_friendly_url()
3651
3652    /**
3653     * check if user-friendly-urls are enabled
3654     *
3655     * this function will return true, if the config option
3656     * $user_friendly_url has been set. Otherwise false.
3657     * @return boolean
3658     */
3659    private function is_user_friendly_url()
3660    {
3661       if(isset($this->cfg->user_friendly_url) && $this->cfg->user_friendly_url)
3662          return true;
3663
3664       return false;
3665
3666    } // is_user_friendly_url()
3667
3668    /**
3669     * session cleanup
3670     *
3671     * this function will cleanup user's session information
3672     */
3673    private function session_cleanup()
3674    {
3675       unset($_SESSION['begin_with']);
3676       $this->resetDateSearch();
3677       $this->resetPhotoView();
3678       $this->resetTagSearch();
3679       $this->resetNameSearch();
3680       $this->resetDateSearch();
3681       $this->resetTags();
3682
3683    } // session_cleanup()
3684
3685    /**
3686     * get database version
3687     *
3688     * this function queries the meta table
3689     * and returns the current database version.
3690     *
3691     * @return integer
3692     */
3693    public function get_db_version()
3694    {
3695       if($row = $this->cfg_db->db_fetchSingleRow("
3696          SELECT meta_value
3697          FROM
3698             meta
3699          WHERE
3700             meta_key LIKE 'phpfspot Database Version'
3701          ")) {
3702
3703          return $row['meta_value'];
3704
3705       }
3706
3707       return 0;
3708
3709    } // get_db_version()
3710
3711    /**
3712     * get photo versions
3713     *
3714     * this function returns an array of all available
3715     * alterntaive versions of the provided photo id.
3716     * has alternative photo versions available
3717     *
3718     * @param int $idx
3719     * @return array
3720     */
3721    public function get_photo_versions($idx)
3722    {
3723       $versions = Array();
3724
3725       $result = $this->db->db_query("
3726          SELECT
3727             version_id
3728          FROM
3729             photo_versions
3730          WHERE
3731             photo_id LIKE '". $idx ."'");
3732
3733       while($row = $this->cfg_db->db_fetch_object($result)) {
3734          array_push($versions, $row['version_id']);
3735       }
3736
3737       return $versions;
3738
3739    } // get_photo_versions()
3740
3741    /**
3742     * check for invalid version of photo
3743     *
3744     * this function validates the provided photo-id and version-id
3745     *
3746     * @param int $photo_idx
3747     * @param int $version_idx
3748     * @return bool
3749     */
3750    public function is_valid_version($photo_idx, $version_idx)
3751    {
3752       /* the original version is always valid */
3753       if($version_idx == 0)
3754          return true;
3755
3756       if($versions = $this->get_photo_versions($photo_idx)) {
3757          if(in_array($version_idx, $versions))
3758             return true;
3759       }
3760
3761       return false;
3762
3763    } // is_valid_version()
3764
3765    /**
3766     * get photo version name
3767     *
3768     * this function returns the name of the version
3769     * identified by the photo-id and version-id.
3770     *
3771     * @param int $photo_idx
3772     * @param int $version_idx
3773     * @return string
3774     */
3775    public function get_photo_version_name($photo_idx, $version_idx)
3776    {
3777       if($row = $this->db->db_fetchSingleRow("
3778          SELECT
3779             name
3780          FROM
3781             photo_versions
3782          WHERE
3783             photo_id LIKE '". $photo_idx ."'
3784          AND
3785             version_id LIKE '". $version_idx ."'")) {
3786
3787          return $row['name'];
3788
3789       }
3790
3791       return false;
3792
3793    } // get_photo_version_name()
3794
3795    /**
3796     */
3797    public function is_valid_width($image_width)
3798    {
3799       if(in_array($image_width,
3800          Array($this->cfg->thumb_width,
3801                $this->cfg->photo_width,
3802                $this->cfg->mini_width,
3803                30)))
3804
3805          return true;
3806
3807       return false;
3808
3809    } // is_valid_width()
3810
3811 } // class PHPFSPOT
3812
3813 ?>