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