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