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