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