used AND to add a show_tag constrait, but in case there hasn't been issued
[phpfspot.git] / phpfspot.class.php
1 <?php
2
3 /***************************************************************************
4  *
5  * Copyright (c) by Andreas Unterkircher, unki@netshadow.at
6  * All rights reserved
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program; if not, write to the Free Software
20  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21  *
22  ***************************************************************************/
23
24 require_once "phpfspot_cfg.php";
25 require_once "phpfspot_db.php";
26
27 class PHPFSPOT {
28
29    var $cfg;
30    var $db;
31    var $cfg_db;
32    var $tmpl;
33    var $tags;
34    var $avail_tags;
35    private $dbver;
36
37    /**
38     * class constructor
39     *
40     * this function will be called on class construct
41     * and will check requirements, loads configuration,
42     * open databases and start the user session
43     */
44    public function __construct()
45    {
46       $this->cfg = new PHPFSPOT_CFG;
47
48       /* set application name and version information */
49       $this->cfg->product = "phpfspot";
50       $this->cfg->version = "1.2";
51
52       $this->sort_orders= array(
53          'date_asc' => 'Date &uarr;',
54          'date_desc' => 'Date &darr;',
55          'name_asc' => 'Name &uarr;',
56          'name_desc' => 'Name &darr;'
57       );
58
59       /* Check necessary requirements */
60       if(!$this->checkRequirements()) {
61          exit(1);
62       }
63
64       $this->db  = new PHPFSPOT_DB($this, $this->cfg->fspot_db);
65       if(!is_writeable($this->cfg->fspot_db)) {
66          print $this->cfg->fspot_db ." is not writeable for user ". $this->getuid() ."\n";
67          exit(1);
68       }
69
70       $this->dbver = $this->getFspotDBVersion();
71
72       if(!is_writeable(dirname($this->cfg->phpfspot_db))) {
73          print dirname($this->cfg->phpfspot_db) .": directory is not writeable for user ". $this->getuid() ."\n";
74          exit(1);
75       }
76
77       if(!is_writeable($this->cfg->base_path ."/templates_c")) {
78          print $this->cfg->base_path ."/templates_c: directory is not writeable for user ". $this->getuid() ."\n";
79          exit(1);
80       }
81
82       $this->cfg_db = new PHPFSPOT_DB($this, $this->cfg->phpfspot_db);
83       if(!is_writeable($this->cfg->phpfspot_db)) {
84          print $this->cfg->phpfspot_db ." is not writeable for user ". $this->getuid() ."\n";
85          exit(1);
86       }
87
88       $this->check_config_table();
89
90       /* include Smarty template engine */
91       if(!$this->check_readable($this->cfg->smarty_path .'/libs/Smarty.class.php')) {
92          exit(1);
93       }
94       require $this->cfg->smarty_path .'/libs/Smarty.class.php';
95       /* overload Smarty class if our own template handler */
96       require_once "phpfspot_tmpl.php";
97       $this->tmpl = new PHPFSPOT_TMPL($this);
98
99       /* check if all necessary indices exist */
100       $this->checkDbIndices();
101
102       /* if session is not yet started, do it now */
103       if(session_id() == "")
104          session_start();
105
106       if(!isset($_SESSION['tag_condition']))
107          $_SESSION['tag_condition'] = 'or';
108
109       if(!isset($_SESSION['sort_order']))
110          $_SESSION['sort_order'] = 'date_asc';
111
112       if(!isset($_SESSION['searchfor']))
113          $_SESSION['searchfor'] = '';
114
115       // if begin_with is still set but thumbs_per_page is now 0, unset it
116       if(isset($_SESSION['begin_with']) && $this->cfg->thumbs_per_page == 0)
117          unset($_SESSION['begin_with']);
118
119    } // __construct()
120
121    public function __destruct()
122    {
123
124    } // __destruct()
125
126    /**
127     * show - generate html output
128     *
129     * this function can be called after the constructor has
130     * prepared everyhing. it will load the index.tpl smarty
131     * template. if necessary it will registere pre-selects
132     * (photo index, photo, tag search, date search) into
133     * users session.
134     */
135    public function show()
136    {
137       $this->tmpl->assign('searchfor', $_SESSION['searchfor']);
138       $this->tmpl->assign('page_title', $this->cfg->page_title);
139       $this->tmpl->assign('current_condition', $_SESSION['tag_condition']);
140       $this->tmpl->assign('template_path', 'themes/'. $this->cfg->theme_name);
141
142       if(isset($_GET['mode'])) {
143
144          $_SESSION['start_action'] = $_GET['mode'];
145
146          switch($_GET['mode']) {
147             case 'showpi':
148                if(isset($_GET['tags'])) {
149                   $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
150                }
151                if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
152                   $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
153                }
154                if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
155                   $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
156                }
157                break;
158             case 'showp':
159                if(isset($_GET['tags'])) {
160                   $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
161                   $_SESSION['start_action'] = 'showp';
162                }
163                if(isset($_GET['id']) && is_numeric($_GET['id'])) {
164                   $_SESSION['current_photo'] = $_GET['id'];
165                   $_SESSION['start_action'] = 'showp';
166                }
167                if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
168                   $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
169                }
170                if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
171                   $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
172                }
173                break;
174             case 'export':
175                $this->tmpl->show("export.tpl");
176                return;
177                break;
178             case 'slideshow':
179                $this->tmpl->show("slideshow.tpl");
180                return;
181                break;
182             case 'rss':
183                if(isset($_GET['tags'])) {
184                   $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
185                }
186                if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
187                   $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
188                }
189                if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
190                   $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
191                }
192                $this->getRSSFeed();
193                return;
194                break;
195          }
196       }
197
198       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date']))
199          $this->tmpl->assign('date_search_enabled', true);
200
201       $this->tmpl->register_function("sort_select_list", array(&$this, "smarty_sort_select_list"), false);
202       $this->tmpl->assign('from_date', $this->get_calendar('from'));
203       $this->tmpl->assign('to_date', $this->get_calendar('to'));
204       $this->tmpl->assign('content_page', 'welcome.tpl');
205       $this->tmpl->show("index.tpl");
206
207    } // show()
208
209    /**
210     * get_tags - grab all tags of f-spot's database
211     *
212     * this function will get all available tags from
213     * the f-spot database and store them within two
214     * arrays within this class for later usage. in
215     * fact, if the user requests (hide_tags) it will
216     * opt-out some of them.
217     *
218     * this function is getting called once by show()
219     */
220    private function get_tags()
221    {
222       $this->avail_tags = Array();
223       $count = 0;
224    
225       if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
226          $query_str="
227             SELECT
228                DISTINCT t1.id as id, t1.name as name
229             FROM  
230                photo_tags pt1
231             INNER JOIN photo_tags
232                pt2 ON pt1.photo_id=pt2.photo_id
233             INNER JOIN tags t1
234                ON t1.id=pt1.tag_id
235             INNER JOIN tags t2
236                ON t2.id=pt2.tag_id
237             WHERE
238                t2.name IN  ('".implode("','",$this->cfg->show_tags)."')
239             ORDER BY
240                t1.sort_priority ASC";
241
242          $result = $this->db->db_query($query_str);
243       }
244       else
245       {
246          $result = $this->db->db_query("
247             SELECT id,name
248             FROM tags
249             ORDER BY sort_priority ASC
250          ");
251       }
252       
253       while($row = $this->db->db_fetch_object($result)) {
254
255          $tag_id = $row['id'];
256          $tag_name = $row['name'];
257
258          /* if the user has specified to ignore this tag in phpfspot's
259             configuration, ignore it here so it does not get added to
260             the tag list.
261          */
262          if(in_array($row['name'], $this->cfg->hide_tags))
263             continue;
264
265          /* if you include the following if-clause and the user has specified
266             to only show certain tags which are specified in phpfspot's
267             configuration, ignore all others so they will not be added to the
268             tag list.
269          if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags) &&
270             !in_array($row['name'], $this->cfg->show_tags))
271             continue;
272          */
273
274          $this->tags[$tag_id] = $tag_name; 
275          $this->avail_tags[$count] = $tag_id;
276          $count++;
277
278       }
279
280    } // get_tags()
281
282    /** 
283     * extract all photo details
284     * 
285     * retrieve all available details from f-spot's
286     * database and return them as object
287     */
288    public function get_photo_details($idx)
289    {
290       if($this->dbver < 9) {
291          $query_str = "
292             SELECT p.id, p.name, p.time, p.directory_path, p.description
293             FROM photos p
294          ";
295       }
296       else {
297          $query_str = "
298             SELECT p.id, p.uri, p.time, p.description
299             FROM photos p
300          ";
301       }
302
303       /* if show_tags is set, only return details for photos which
304          are specified to be shown
305       */
306       if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
307          $query_str.= "
308             INNER JOIN photo_tags pt
309                ON p.id=pt.photo_id
310             INNER JOIN tags t
311                ON pt.tag_id=t.id
312             WHERE p.id='". $idx ."'
313             AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
314       }
315       else {
316          $query_str.= "
317             WHERE p.id='". $idx ."'
318          ";
319       }
320
321       if($result = $this->db->db_query($query_str)) {
322
323          $row = $this->db->db_fetch_object($result);
324
325          if($this->dbver < 9) {
326             $row['uri'] = "file://". $row['directory_path'] ."/". $row['name'];
327          }
328
329          return $row;
330
331       }
332    
333       return null;
334
335    } // get_photo_details
336
337    /**
338     * returns aligned photo names 
339     *
340     * this function returns aligned (length) names for
341     * an specific photo. If the length of the name exceeds
342     * $limit the name will be shrinked (...)
343     */
344    public function getPhotoName($idx, $limit = 0)
345    {
346       if($details = $this->get_photo_details($idx)) {
347          if($long_name = $this->parse_uri($details['uri'], 'filename')) {
348             $name = $this->shrink_text($long_name, $limit);
349             return $name;
350          }
351       }
352
353       return null;
354
355    } // getPhotoName()
356
357    /**
358     * shrink text according provided limit
359     *
360     * If the length of the name exceeds $limit the
361     * text will be shortend and some content in between
362     * will be replaced with "..." 
363     */
364    private function shrink_text($text, $limit)
365    {
366       if($limit != 0 && strlen($text) > $limit) {
367          $text = substr($text, 0, $limit-5) ."...". substr($text, -($limit-5));
368       }
369
370       return $text;
371
372    } // shrink_text();
373
374    /**
375     * translate f-spoth photo path
376     * 
377     * as the full-qualified path recorded in the f-spot database
378     * is usally not the same as on the webserver, this function
379     * will replace the path with that one specified in the cfg
380     */
381    public function translate_path($path, $width = 0)
382    {  
383       return str_replace($this->cfg->path_replace_from, $this->cfg->path_replace_to, $path);
384
385    } // translate_path
386
387    /**
388     * control HTML ouput for a single photo
389     *
390     * this function provides all the necessary information
391     * for the single photo template.
392     */
393    public function showPhoto($photo)
394    {
395       /* get all photos from the current photo selection */
396       $all_photos = $this->getPhotoSelection();
397       $count = count($all_photos);
398
399       for($i = 0; $i < $count; $i++) {
400          
401          // $get_next will be set, when the photo which has to
402          // be displayed has been found - this means that the
403          // next available is in fact the NEXT image (for the
404          // navigation icons) 
405          if(isset($get_next)) {
406             $next_img = $all_photos[$i];
407             break;
408          }
409
410          /* the next photo is our NEXT photo */
411          if($all_photos[$i] == $photo) {
412             $get_next = 1;
413          }
414          else {
415             $previous_img = $all_photos[$i];
416          }
417
418          if($photo == $all_photos[$i]) {
419                $current = $i;
420          }
421       }
422
423       $details = $this->get_photo_details($photo);
424
425       if(!$details) {
426          print "error";
427          return;
428       }
429
430       $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
431       $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
432
433       if(!file_exists($orig_path)) {
434          $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
435          return;
436       }
437
438       if(!is_readable($orig_path)) {
439          $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
440          return;
441       }
442
443       /* If the thumbnail doesn't exist yet, try to create it */
444       if(!file_exists($thumb_path)) {
445          $this->gen_thumb($photo, true);
446          $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
447       }
448
449       /* get f-spot database meta information */
450       $meta = $this->get_meta_informations($orig_path);
451
452       /* If EXIF data are available, use them */
453       if(isset($meta['ExifImageWidth'])) {
454          $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
455       } else {
456          $info = getimagesize($orig_path);
457          $meta_res = $info[0] ."x". $info[1]; 
458       }
459
460       $meta_date = isset($meta['FileDateTime']) ? strftime("%a %x %X", $meta['FileDateTime']) : "n/a";
461       $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
462       $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
463
464       $extern_link = "index.php?mode=showp&id=". $photo;
465       $current_tags = $this->getCurrentTags();
466       if($current_tags != "") {
467          $extern_link.= "&tags=". $current_tags;
468       }
469       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
470          $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
471       }
472
473       $this->tmpl->assign('extern_link', $extern_link);
474
475       if(!file_exists($thumb_path)) {
476          $this->_error("Can't open file ". $thumb_path ."\n");
477          return;
478       }
479
480       $info = getimagesize($thumb_path);
481
482       $this->tmpl->assign('description', $details['description']);
483       $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
484
485       $this->tmpl->assign('width', $info[0]);
486       $this->tmpl->assign('height', $info[1]);
487       $this->tmpl->assign('ExifMadeOn', $meta_date);
488       $this->tmpl->assign('ExifMadeWith', $meta_make);
489       $this->tmpl->assign('ExifOrigResolution', $meta_res);
490       $this->tmpl->assign('ExifFileSize', $meta_size);
491  
492       $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&amp;width=". $this->cfg->photo_width);
493       $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
494       $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
495
496       $this->tmpl->assign('tags', $this->get_photo_tags($photo));
497       $this->tmpl->assign('current', $current);
498
499       if($previous_img) {
500          $this->tmpl->assign('previous_url', "javascript:showImage(". $previous_img .");");
501          $this->tmpl->assign('prev_img', $previous_img);
502       }
503
504       if($next_img) {
505          $this->tmpl->assign('next_url', "javascript:showImage(". $next_img .");");
506          $this->tmpl->assign('next_img', $next_img);
507       }
508       $this->tmpl->assign('mini_width', $this->cfg->mini_width);
509       $this->tmpl->assign('photo_number', $i);
510       $this->tmpl->assign('photo_count', count($all_photos));
511
512       $this->tmpl->show("single_photo.tpl");
513
514    } // showPhoto()
515
516    /**
517     * all available tags and tag cloud
518     *
519     * this function outputs all available tags (time ordered)
520     * and in addition output them as tag cloud (tags which have
521     * many photos will appears more then others)
522     */
523    public function getAvailableTags()
524    {
525       /* retrive tags from database */
526       $this->get_tags();
527
528       $output = "";
529
530       $result = $this->db->db_query("
531          SELECT tag_id as id, count(tag_id) as quantity
532          FROM photo_tags
533          INNER JOIN tags t
534             ON t.id = tag_id
535          GROUP BY tag_id
536          ORDER BY t.name ASC
537       ");
538
539       $tags = Array();
540
541       while($row = $this->db->db_fetch_object($result)) {
542          $tags[$row['id']] = $row['quantity'];
543       }
544
545       // change these font sizes if you will
546       $max_size = 125; // max font size in %
547       $min_size = 75; // min font size in %
548
549       // get the largest and smallest array values
550       $max_qty = max(array_values($tags));
551       $min_qty = min(array_values($tags));
552
553       // find the range of values
554       $spread = $max_qty - $min_qty;
555       if (0 == $spread) { // we don't want to divide by zero
556          $spread = 1;
557       }
558
559       // determine the font-size increment
560       // this is the increase per tag quantity (times used)
561       $step = ($max_size - $min_size)/($spread);
562
563       // loop through our tag array
564       foreach ($tags as $key => $value) {
565
566          if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
567             continue;
568
569          // calculate CSS font-size
570          // find the $value in excess of $min_qty
571          // multiply by the font-size increment ($size)
572          // and add the $min_size set above
573          $size = $min_size + (($value - $min_qty) * $step);
574           // uncomment if you want sizes in whole %:
575          $size = ceil($size);
576
577          if(isset($this->tags[$key])) {
578             $output.= "<a href=\"javascript:Tags('add', ". $key .");\" class=\"tag\" style=\"font-size: ". $size ."%;\">". $this->tags[$key] ."</a>, ";
579          }
580
581       }
582
583       $output = substr($output, 0, strlen($output)-2);
584       print $output;
585
586    } // getAvailableTags()
587
588    /**
589     * output all selected tags
590     *
591     * this function output all tags which have been selected
592     * by the user. the selected tags are stored in the 
593     * session-variable $_SESSION['selected_tags']
594     */
595    public function getSelectedTags()
596    {
597       /* retrive tags from database */
598       $this->get_tags();
599
600       $output = "";
601
602       foreach($this->avail_tags as $tag)
603       {
604          // return all selected tags
605          if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
606             $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
607          }
608       }
609
610       if($output != "") {
611          $output = substr($output, 0, strlen($output)-2);
612          return $output;
613       }
614       else {
615          return "no tags selected";
616       }
617
618    } // getSelectedTags()
619
620    /**
621     * add tag to users session variable
622     *
623     * this function will add the specified to users current
624     * tag selection. if a date search has been made before
625     * it will be now cleared
626     */
627    public function addTag($tag)
628    {
629       if(!isset($_SESSION['selected_tags']))
630          $_SESSION['selected_tags'] = Array();
631
632       if(isset($_SESSION['searchfor']))
633          unset($_SESSION['searchfor']);
634
635       if(!in_array($tag, $_SESSION['selected_tags']))
636          array_push($_SESSION['selected_tags'], $tag);
637    
638    } // addTag()
639
640    /**
641     * remove tag to users session variable
642     *
643     * this function removes the specified tag from
644     * users current tag selection
645     */
646    public function delTag($tag)
647    {
648       if(isset($_SESSION['searchfor']))
649          unset($_SESSION['searchfor']);
650
651       if(isset($_SESSION['selected_tags'])) {
652          $key = array_search($tag, $_SESSION['selected_tags']);
653          unset($_SESSION['selected_tags'][$key]);
654          sort($_SESSION['selected_tags']);
655       }
656
657    } // delTag()
658
659    /**
660     * reset tag selection
661     *
662     * if there is any tag selection, it will be
663     * deleted now
664     */
665    public function resetTags()
666    {
667       if(isset($_SESSION['selected_tags']))
668          unset($_SESSION['selected_tags']);
669
670    } // resetTags()
671
672    /**
673     * reset single photo
674     *
675     * if a specific photo was requested (external link)
676     * unset the session variable now
677     */
678    public function resetPhotoView()
679    {
680       if(isset($_SESSION['current_photo']))
681          unset($_SESSION['current_photo']);
682
683    } // resetPhotoView();
684
685    /**
686     * reset tag search
687     *
688     * if any tag search has taken place, reset
689     * it now
690     */
691    public function resetTagSearch()
692    {
693       if(isset($_SESSION['searchfor']))
694          unset($_SESSION['searchfor']);
695
696    } // resetTagSearch()
697
698     /**
699     * reset date search
700     *
701     * if any date search has taken place, reset
702     * it now
703     */
704    public function resetDateSearch()
705    {
706       if(isset($_SESSION['from_date']))
707          unset($_SESSION['from_date']);
708       if(isset($_SESSION['to_date']))
709          unset($_SESSION['to_date']);
710
711    } // resetDateSearch();
712
713    /**
714     * return all photo according selection
715     *
716     * this function returns all photos based on
717     * the tag-selection, tag- or date-search.
718     * the tag-search also has to take care of AND
719     * and OR conjunctions
720     */
721    public function getPhotoSelection()
722    {  
723       $matched_photos = Array();
724
725       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
726          $from_date = $_SESSION['from_date'];
727          $to_date = $_SESSION['to_date'];
728          $additional_where_cond = "
729                p.time>='". $from_date ."'
730             AND
731                p.time<='". $to_date ."'
732          ";
733       } 
734
735       if(isset($_SESSION['sort_order'])) {
736          $order_str = $this->get_sort_order();
737       }
738
739       /* return a search result */
740       if(isset($_SESSION['searchfor']) && $_SESSION['searchfor'] != '') {
741          $query_str = "
742             SELECT DISTINCT pt1.photo_id
743                FROM photo_tags pt1
744             INNER JOIN photo_tags pt2
745                ON pt1.photo_id=pt2.photo_id
746             INNER JOIN tags t1
747                ON pt1.tag_id=t1.id
748             INNER JOIN photos p
749                ON pt1.photo_id=p.id
750             INNER JOIN tags t2
751                ON pt2.tag_id=t2.id
752             WHERE t1.name LIKE '%". $_SESSION['searchfor'] ."%' ";
753
754          if(isset($additional_where_cond))
755             $query_str.= "AND ". $additional_where_cond ." ";
756
757          if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
758             $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
759          }
760          
761          if(isset($order_str))
762             $query_str.= $order_str;
763
764          $result = $this->db->db_query($query_str);
765          while($row = $this->db->db_fetch_object($result)) {
766             array_push($matched_photos, $row['photo_id']);
767          }
768          return $matched_photos;
769       }
770
771       /* return according the selected tags */
772       if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
773          $selected = "";
774          foreach($_SESSION['selected_tags'] as $tag)
775             $selected.= $tag .",";
776          $selected = substr($selected, 0, strlen($selected)-1);
777
778          /* photo has to match at least on of the selected tags */
779          if($_SESSION['tag_condition'] == 'or') {
780             $query_str = "
781                SELECT DISTINCT pt1.photo_id
782                   FROM photo_tags pt1
783                INNER JOIN photo_tags pt2
784                   ON pt1.photo_id=pt2.photo_id
785                INNER JOIN tags t
786                   ON pt2.tag_id=t.id
787                INNER JOIN photos p
788                   ON pt1.photo_id=p.id
789                WHERE pt1.tag_id IN (". $selected .")
790             ";
791             if(isset($additional_where_cond)) 
792                $query_str.= "AND ". $additional_where_cond ." ";
793
794             if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
795                $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
796             }
797
798             if(isset($order_str))
799                $query_str.= $order_str;
800          }
801          /* photo has to match all selected tags */
802          elseif($_SESSION['tag_condition'] == 'and') {
803
804             if(count($_SESSION['selected_tags']) >= 32) {
805                print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
806                print "evaluate your tag selection. Please remove some tags from your selection.\n";
807                return Array();
808             } 
809
810             /* Join together a table looking like
811
812                pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
813
814                so the query can quickly return all images matching the
815                selected tags in an AND condition
816
817             */
818
819             $query_str = "
820                SELECT DISTINCT pt1.photo_id
821                   FROM photo_tags pt1
822             ";
823
824             if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
825                $query_str.= "
826                   INNER JOIN tags t
827                      ON pt1.tag_id=t.id
828                ";
829             }
830
831             for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
832                $query_str.= "
833                   INNER JOIN photo_tags pt". ($i+2) ."
834                      ON pt1.photo_id=pt". ($i+2) .".photo_id
835                ";
836             }
837             $query_str.= "
838                INNER JOIN photos p
839                   ON pt1.photo_id=p.id
840             ";
841             $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
842             for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
843                $query_str.= "
844                   AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
845                "; 
846             }
847             if(isset($additional_where_cond)) 
848                $query_str.= "AND ". $additional_where_cond;
849
850             if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
851                $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
852             }
853
854             if(isset($order_str))
855                $query_str.= $order_str;
856
857          }
858
859          $result = $this->db->db_query($query_str);
860          while($row = $this->db->db_fetch_object($result)) {
861             array_push($matched_photos, $row['photo_id']);
862          }
863          return $matched_photos;
864       }
865
866       /* return all available photos */
867       $query_str = "
868          SELECT p.id
869          FROM photos p
870          LEFT JOIN photo_tags pt
871             ON p.id=pt.photo_id
872          LEFT JOIN tags t
873             ON pt.tag_id=t.id
874       ";
875
876       if(isset($additional_where_cond)) 
877          $query_str.= "WHERE ". $additional_where_cond ." ";
878
879       if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
880          if(isset($additional_where_cond))
881             $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
882          else
883             $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
884       }
885  
886       if(isset($order_str))
887          $query_str.= $order_str;
888
889       $result = $this->db->db_query($query_str);
890       while($row = $this->db->db_fetch_object($result)) {
891          array_push($matched_photos, $row['id']);
892       }
893       return $matched_photos;
894
895    } // getPhotoSelection()
896
897     /**
898     * control HTML ouput for photo index
899     *
900     * this function provides all the necessary information
901     * for the photo index template.
902     */
903    public function showPhotoIndex()
904    {
905       $photos = $this->getPhotoSelection();
906
907       $count = count($photos);
908
909       if(isset($_SESSION['begin_with']) && $_SESSION['begin_with'] != "")
910          $anchor = $_SESSION['begin_with'];
911
912       if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
913
914          $begin_with = 0;
915          $end_with = $count;
916
917       }
918       elseif($this->cfg->thumbs_per_page > 0) {
919
920          if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
921             $begin_with = 0;
922          }
923          else {
924             $begin_with = $_SESSION['begin_with'];
925          }
926
927          $end_with = $begin_with + $this->cfg->thumbs_per_page;
928       }
929
930       $thumbs = 0;
931       $images[$thumbs] = Array();
932       $img_height[$thumbs] = Array();
933       $img_width[$thumbs] = Array();
934       $img_id[$thumbs] = Array();
935       $img_name[$thumbs] = Array();
936       $img_title = Array();
937
938       for($i = $begin_with; $i < $end_with; $i++) {
939
940          if(isset($photos[$i])) {
941
942             $images[$thumbs] = $photos[$i];
943             $img_id[$thumbs] = $i;
944             $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
945             $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
946
947             $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i]);
948
949             if(file_exists($thumb_path)) {
950                $info = getimagesize($thumb_path); 
951                $img_width[$thumbs] = $info[0];
952                $img_height[$thumbs] = $info[1];
953             }
954             $thumbs++;
955          } 
956       }
957
958       // +1 for for smarty's selection iteration
959       $thumbs++;
960
961       if(isset($_SESSION['searchfor']) && $_SESSION['searchfor'] != '')
962          $this->tmpl->assign('searchfor', $_SESSION['searchfor']);
963
964       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
965          $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
966          $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
967       }
968
969       if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
970          $this->tmpl->assign('tag_result', 1);
971       }
972
973       /* do we have to display the page selector ? */
974       if($this->cfg->thumbs_per_page != 0) {
975
976          $page_select = "";
977       
978          /* calculate the page switchers */
979          $previous_start = $begin_with - $this->cfg->thumbs_per_page;
980          $next_start = $begin_with + $this->cfg->thumbs_per_page;
981
982          if($begin_with != 0) 
983             $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");"); 
984          if($end_with < $count)
985             $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");"); 
986
987          $photo_per_page  = $this->cfg->thumbs_per_page;
988          $last_page = ceil($count / $photo_per_page);
989
990          /* get the current selected page */
991          if($begin_with == 0) {
992             $current_page = 1;
993          } else {
994             $current_page = 0;
995             for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
996                $current_page++;
997             }
998          } 
999
1000          $dotdot_made = 0;
1001
1002          for($i = 1; $i <= $last_page; $i++) {
1003
1004             if($current_page == $i)
1005                $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1006             elseif($current_page-1 == $i || $current_page+1 == $i)
1007                $style = "style=\"font-size: 105%;\"";
1008             elseif(($current_page-5 >= $i) && ($i != 1) ||
1009                ($current_page+5 <= $i) && ($i != $last_page))
1010                $style = "style=\"font-size: 75%;\"";
1011             else
1012                $style = "";
1013
1014             $select = "<a href=\"javascript:showPhotoIndex(". (($i*$photo_per_page)-$photo_per_page) .");\"";
1015                if($style != "")
1016                   $select.= $style;
1017             $select.= ">". $i ."</a>&nbsp;";
1018
1019             // until 9 pages we show the selector from 1-9
1020             if($last_page <= 9) {
1021                $page_select.= $select;
1022                continue;
1023             } else {
1024                if($i == 1 /* first page */ || 
1025                   $i == $last_page /* last page */ ||
1026                   $i == $current_page /* current page */ ||
1027                   $i == ceil($last_page * 0.25) /* first quater */ ||
1028                   $i == ceil($last_page * 0.5) /* half */ ||
1029                   $i == ceil($last_page * 0.75) /* third quater */ ||
1030                   (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1031                   (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 */ ||
1032                   $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1033                   $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1034
1035                   $page_select.= $select;
1036                   $dotdot_made = 0;
1037                   continue;
1038
1039                }
1040             }
1041
1042             if(!$dotdot_made) {
1043                $page_select.= ".........&nbsp;";
1044                $dotdot_made = 1;
1045             }
1046          }
1047
1048          /* only show the page selector if we have more then one page */
1049          if($last_page > 1)
1050             $this->tmpl->assign('page_selector', $page_select);
1051       }
1052
1053       
1054       $current_tags = $this->getCurrentTags();
1055       $extern_link = "index.php?mode=showpi";
1056       $rss_link = "index.php?mode=rss";
1057       if($current_tags != "") {
1058          $extern_link.= "&tags=". $current_tags;
1059          $rss_link.= "&tags=". $current_tags;
1060       }
1061       if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1062          $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1063          $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1064       }
1065
1066       $export_link = "index.php?mode=export";
1067       $slideshow_link = "index.php?mode=slideshow";
1068
1069       $this->tmpl->assign('extern_link', $extern_link);
1070       $this->tmpl->assign('slideshow_link', $slideshow_link);
1071       $this->tmpl->assign('export_link', $export_link);
1072       $this->tmpl->assign('rss_link', $rss_link);
1073       $this->tmpl->assign('count', $count);
1074       $this->tmpl->assign('width', $this->cfg->thumb_width);
1075       $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1076       $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1077       $this->tmpl->assign('images', $images);
1078       $this->tmpl->assign('img_width', $img_width);
1079       $this->tmpl->assign('img_height', $img_height);
1080       $this->tmpl->assign('img_id', $img_id);
1081       $this->tmpl->assign('img_name', $img_name);
1082       $this->tmpl->assign('img_title', $img_title);
1083       $this->tmpl->assign('thumbs', $thumbs);
1084
1085       $this->tmpl->show("photo_index.tpl");
1086
1087       if(isset($anchor))
1088          print "<script language=\"JavaScript\">self.location.hash = '#image". $anchor ."';</script>\n";
1089
1090    } // showPhotoIndex()
1091
1092    /**
1093     * show credit template
1094     */
1095    public function showCredits()
1096    {
1097       $this->tmpl->assign('version', $this->cfg->version);
1098       $this->tmpl->assign('product', $this->cfg->product);
1099       $this->tmpl->assign('db_version', $this->dbver);
1100       $this->tmpl->show("credits.tpl");
1101
1102    } // showCredits()
1103
1104    /**
1105     * create_thumbnails for the requested width
1106     *
1107     * this function creates image thumbnails of $orig_image
1108     * stored as $thumb_image. It will check if the image is
1109     * in a supported format, if necessary rotate the image
1110     * (based on EXIF orientation meta headers) and re-sizing.
1111     */
1112    public function create_thumbnail($orig_image, $thumb_image, $width)
1113    {  
1114       if(!file_exists($orig_image)) {
1115          return false;
1116       }
1117
1118       $details = getimagesize($orig_image);
1119       
1120       /* check if original photo is a support image type */
1121       if(!$this->checkifImageSupported($details['mime']))
1122          return false;
1123
1124       $meta = $this->get_meta_informations($orig_image);
1125
1126       $rotate = 0;
1127       $flip = false;
1128
1129       switch($meta['Orientation']) {
1130
1131          case 1: /* top, left */
1132             $rotate = 0; $flip = false; break;
1133          case 2: /* top, right */
1134             $rotate = 0; $flip = true; break;
1135          case 3: /* bottom, left */
1136             $rotate = 180; $flip = false; break;
1137          case 4: /* bottom, right */
1138             $rotate = 180; $flip = true; break;
1139          case 5: /* left side, top */
1140             $rotate = 90; $flip = true; break;
1141          case 6: /* right side, top */
1142             $rotate = 90; $flip = false; break;
1143          case 7: /* left side, bottom */
1144             $rotate = 270; $flip = true; break;
1145          case 8: /* right side, bottom */
1146             $rotate = 270; $flip = false; break;
1147       }
1148
1149       $src_img = @imagecreatefromjpeg($orig_image);
1150
1151       if(!$src_img) {
1152          print "Can't load image from ". $orig_image ."\n";
1153          return false;
1154       }
1155
1156       /* grabs the height and width */
1157       $cur_width = imagesx($src_img);
1158       $cur_height = imagesy($src_img);
1159
1160       // If requested width is more then the actual image width,
1161       // do not generate a thumbnail, instead safe the original
1162       // as thumbnail but with lower quality. But if the image
1163       // is to heigh too, then we still have to resize it.
1164       if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1165          $result = imagejpeg($src_img, $thumb_image, 75);
1166          imagedestroy($src_img);
1167          return true;
1168       }
1169
1170       // If the image will be rotate because EXIF orientation said so
1171       // 'virtually rotate' the image for further calculations
1172       if($rotate == 90 || $rotate == 270) {
1173          $tmp = $cur_width;
1174          $cur_width = $cur_height;
1175          $cur_height = $tmp;
1176       }
1177
1178       /* calculates aspect ratio */
1179       $aspect_ratio = $cur_height / $cur_width;
1180
1181       /* sets new size */
1182       if($aspect_ratio < 1) {
1183          $new_w = $width;
1184          $new_h = abs($new_w * $aspect_ratio);
1185       } else {
1186          /* 'virtually' rotate the image and calculate it's ratio */
1187          $tmp_w = $cur_height;
1188          $tmp_h = $cur_width;
1189          /* now get the ratio from the 'rotated' image */
1190          $tmp_ratio = $tmp_h/$tmp_w;
1191          /* now calculate the new dimensions */
1192          $tmp_w = $width;
1193          $tmp_h = abs($tmp_w * $tmp_ratio);
1194
1195          // now that we know, how high they photo should be, if it
1196          // gets rotated, use this high to scale the image
1197          $new_h = $tmp_h;
1198          $new_w = abs($new_h / $aspect_ratio);
1199
1200          // If the image will be rotate because EXIF orientation said so
1201          // now 'virtually rotate' back the image for the image manipulation
1202          if($rotate == 90 || $rotate == 270) {
1203             $tmp = $new_w;
1204             $new_w = $new_h;
1205             $new_h = $tmp;
1206          }
1207       }
1208
1209       /* creates new image of that size */
1210       $dst_img = imagecreatetruecolor($new_w, $new_h);
1211
1212       imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1213
1214       /* copies resized portion of original image into new image */
1215       imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1216
1217       /* needs the image to be flipped horizontal? */
1218       if($flip) {
1219          print "(FLIP)";
1220          $image = $dst_img;
1221          for($x = 0; $x < $new_w; $x++) {
1222             imagecopy($dst_img, $image, $x, 0, $w - $x - 1, 0, 1, $h);
1223          }
1224       }
1225
1226       if($rotate) {
1227          $this->_debug("(ROTATE)");
1228          $dst_img = $this->rotateImage($dst_img, $rotate);
1229       }
1230
1231       /* write down new generated file */
1232       $result = imagejpeg($dst_img, $thumb_image, 75);
1233
1234       /* free your mind */
1235       imagedestroy($dst_img);
1236       imagedestroy($src_img);
1237
1238       if($result === false) {
1239          print "Can't write thumbnail ". $thumb_image ."\n";
1240          return false;
1241       }
1242
1243       return true;
1244
1245    } // create_thumbnail()
1246
1247    /**
1248     * return all exif meta data from the file
1249     */
1250    public function get_meta_informations($file)
1251    {
1252       return exif_read_data($file);
1253
1254    } // get_meta_informations()
1255
1256    /**
1257     * create phpfspot own sqlite database
1258     *
1259     * this function creates phpfspots own sqlite database
1260     * if it does not exist yet. this own is used to store
1261     * some necessary informations (md5 sum's, ...).
1262     */
1263    public function check_config_table()
1264    {
1265       // if the config table doesn't exist yet, create it
1266       if(!$this->cfg_db->db_check_table_exists("images")) {
1267          $this->cfg_db->db_exec("
1268             CREATE TABLE images (
1269                img_idx int primary key,
1270                img_md5 varchar(32)
1271             )
1272             ");
1273       }
1274
1275    } // check_config_table
1276
1277    /**
1278     * Generates a thumbnail from photo idx
1279     *
1280     * This function will generate JPEG thumbnails from provided F-Spot photo
1281     * indizes.
1282     *
1283     * 1. Check if all thumbnail generations (width) are already in place and
1284     *    readable
1285     * 2. Check if the md5sum of the original file has changed
1286     * 3. Generate the thumbnails if needed
1287     */
1288    public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
1289    {
1290       $error = 0;
1291
1292       $resolutions = Array(
1293          $this->cfg->thumb_width,
1294          $this->cfg->photo_width,
1295          $this->cfg->mini_width,
1296       );
1297
1298       /* get details from F-Spot's database */
1299       $details = $this->get_photo_details($idx);
1300
1301       /* calculate file MD5 sum */
1302       $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
1303
1304       if(!file_exists($full_path)) {
1305          $this->_error("File ". $full_path ." does not exist\n");
1306          return;
1307       }
1308
1309       if(!is_readable($full_path)) {
1310          $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
1311          return;
1312       }
1313
1314       $file_md5 = md5_file($full_path);
1315
1316       $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
1317
1318       $changes = false;
1319
1320       foreach($resolutions as $resolution) {
1321    
1322          $generate_it = false;
1323
1324          $thumb_sub_path = substr($file_md5, 0, 2);
1325          $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
1326
1327          if(!file_exists(dirname($thumb_path))) {
1328             mkdir(dirname($thumb_path), 0755);
1329          }
1330
1331          /* if the thumbnail file doesn't exist, create it */
1332          if(!file_exists($thumb_path)) {
1333             $generate_it = true;
1334          }
1335          /* if the file hasn't changed there is no need to regen the thumb */
1336          elseif($file_md5 != $this->getMD5($idx) || $force) {
1337             $generate_it = true;
1338          }
1339
1340          if($generate_it || $overwrite) {
1341
1342             $this->_debug(" ". $resolution ."px");
1343             if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1344                $error = 1;
1345
1346             $changes = true;
1347          }
1348       }
1349
1350       if(!$changes) {
1351          $this->_debug(" already exist");
1352       }
1353
1354       /* set the new/changed MD5 sum for the current photo */
1355       if(!$error) {
1356          $this->setMD5($idx, $file_md5);
1357       }
1358
1359       $this->_debug("\n");
1360
1361    } // gen_thumb()
1362
1363    /**
1364     * returns stored md5 sum for a specific photo
1365     *
1366     * this function queries the phpfspot database for a
1367     * stored MD5 checksum of the specified photo
1368     */
1369    public function getMD5($idx)
1370    {
1371       $result = $this->cfg_db->db_query("
1372          SELECT img_md5 
1373          FROM images
1374          WHERE img_idx='". $idx ."'
1375       ");
1376
1377       if(!$result)
1378          return 0;
1379
1380       $img = $this->cfg_db->db_fetch_object($result);
1381       return $img['img_md5'];
1382       
1383    } // getMD5()
1384
1385    /**
1386     * set MD5 sum for the specific photo
1387     */
1388    private function setMD5($idx, $md5)
1389    {
1390       $result = $this->cfg_db->db_exec("
1391          REPLACE INTO images (img_idx, img_md5)
1392          VALUES ('". $idx ."', '". $md5 ."')
1393       ");
1394
1395    } // setMD5()
1396
1397    /**
1398     * store current tag condition
1399     *
1400     * this function stores the current tag condition
1401     * (AND or OR) in the users session variables
1402     */
1403    public function setTagCondition($mode)
1404    {
1405       $_SESSION['tag_condition'] = $mode;
1406
1407    } // setTagCondition()
1408
1409    /** 
1410     * invoke tag & date search 
1411     *
1412     * this function will return all matching tags and store
1413     * them in the session variable selected_tags. furthermore
1414     * it also handles the date search.
1415     * getPhotoSelection() will then only return the matching
1416     * photos.
1417     */
1418    public function startSearch($searchfor, $from = 0, $to = 0)
1419    {
1420       $this->get_tags();
1421
1422       $_SESSION['searchfor'] = $searchfor;
1423
1424       if($from != 0)
1425          $_SESSION['from_date'] = strtotime($from ." 00:00:00");
1426       else
1427          unset($_SESSION['from_date']);
1428
1429       if($to != 0)
1430          $_SESSION['to_date'] = strtotime($to ." 23:59:59");
1431       else
1432          unset($_SESSION['to_date']);
1433
1434       if($searchfor != "") {
1435          /* new search, reset the current selected tags */
1436          $_SESSION['selected_tags'] = Array();
1437          foreach($this->avail_tags as $tag) {
1438             if(preg_match('/'. $searchfor .'/i', $this->tags[$tag]))
1439                array_push($_SESSION['selected_tags'], $tag);
1440          }
1441       }
1442
1443    } // startSearch()
1444
1445    /**
1446     * updates sort order in session variable
1447     *
1448     * this function is invoked by RPC and will sort the requested
1449     * sort order in the session variable.
1450     */
1451    public function updateSortOrder($order)
1452    {
1453       if(isset($this->sort_orders[$order])) {
1454          $_SESSION['sort_order'] = $order;
1455          return "ok";
1456       }
1457
1458       return "unkown error";
1459
1460    } // updateSortOrder()
1461
1462    /**
1463     * rotate image
1464     *
1465     * this function rotates the image according the
1466     * specified angel.
1467     */
1468    private function rotateImage($img, $degrees)
1469    {
1470       if(function_exists("imagerotate")) {
1471          $img = imagerotate($img, $degrees, 0);
1472       } else {
1473          function imagerotate($src_img, $angle)
1474          {
1475             $src_x = imagesx($src_img);
1476             $src_y = imagesy($src_img);
1477             if ($angle == 180) {
1478                $dest_x = $src_x;
1479                $dest_y = $src_y;
1480             }
1481             elseif ($src_x <= $src_y) {
1482                $dest_x = $src_y;
1483                $dest_y = $src_x;
1484             }
1485             elseif ($src_x >= $src_y) {
1486                $dest_x = $src_y;
1487                $dest_y = $src_x;
1488             }
1489                
1490             $rotate=imagecreatetruecolor($dest_x,$dest_y);
1491             imagealphablending($rotate, false);
1492                
1493             switch ($angle) {
1494             
1495                case 90:
1496                   for ($y = 0; $y < ($src_y); $y++) {
1497                      for ($x = 0; $x < ($src_x); $x++) {
1498                         $color = imagecolorat($src_img, $x, $y);
1499                         imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
1500                      }
1501                   }
1502                   break;
1503
1504                case 270:
1505                   for ($y = 0; $y < ($src_y); $y++) {
1506                      for ($x = 0; $x < ($src_x); $x++) {
1507                         $color = imagecolorat($src_img, $x, $y);
1508                         imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
1509                      }
1510                   }
1511                   break;
1512
1513                case 180:
1514                   for ($y = 0; $y < ($src_y); $y++) {
1515                      for ($x = 0; $x < ($src_x); $x++) {
1516                         $color = imagecolorat($src_img, $x, $y);
1517                         imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
1518                      }
1519                   }
1520                   break;
1521
1522                default:
1523                   $rotate = $src_img;
1524                   break;
1525             };
1526
1527             return $rotate;
1528
1529          }
1530
1531          $img = imagerotate($img, $degrees);
1532
1533       }
1534
1535       return $img;
1536
1537    } // rotateImage()
1538
1539    /**
1540     * return all assigned tags for the specified photo
1541     */
1542    private function get_photo_tags($idx)
1543    {
1544       $result = $this->db->db_query("
1545          SELECT t.id, t.name
1546          FROM tags t
1547          INNER JOIN photo_tags pt
1548             ON t.id=pt.tag_id
1549          WHERE pt.photo_id='". $idx ."'
1550       ");
1551
1552       $tags = Array();
1553
1554       while($row = $this->db->db_fetch_object($result))
1555          $tags[$row['id']] = $row['name'];
1556
1557       return $tags;
1558
1559    } // get_photo_tags()
1560
1561    /**
1562     * create on-the-fly images with text within
1563     */
1564    public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
1565    {
1566       if (strlen($color) != 6) 
1567          $color = 000000;
1568
1569       $int = hexdec($color);
1570       $h = imagefontheight($font);
1571       $fw = imagefontwidth($font);
1572       $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
1573       $lines = count($txt);
1574       $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
1575       $bg = imagecolorallocate($im, 255, 255, 255);
1576       $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
1577       $y = 0;
1578
1579       foreach ($txt as $text) {
1580          $x = (($w - ($fw * strlen($text))) / 2);
1581          imagestring($im, $font, $x, $y, $text, $color);
1582          $y += ($h + $space);
1583       }
1584
1585       Header("Content-type: image/png");
1586       ImagePng($im);
1587
1588    } // showTextImage()
1589
1590    /**
1591     * check if all requirements are met
1592     */
1593    private function checkRequirements()
1594    {
1595       if(!function_exists("imagecreatefromjpeg")) {
1596          print "PHP GD library extension is missing<br />\n";
1597          $missing = true;
1598       }
1599
1600       if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
1601          print "PHP SQLite3 library extension is missing<br />\n";
1602          $missing = true;
1603       }
1604
1605       /* Check for HTML_AJAX PEAR package, lent from Horde project */
1606       ini_set('track_errors', 1);
1607       @include_once 'HTML/AJAX/Server.php';
1608       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1609          print "PEAR HTML_AJAX package is missing<br />\n";
1610          $missing = true;
1611       }
1612       @include_once 'Calendar/Calendar.php';
1613       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1614          print "PEAR Calendar package is missing<br />\n";
1615          $missing = true;
1616       }
1617       @include_once 'Console/Getopt.php';
1618       if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1619          print "PEAR Console_Getopt package is missing<br />\n";
1620          $missing = true;
1621       }
1622       ini_restore('track_errors');
1623
1624       if(isset($missing))
1625          return false;
1626
1627       return true;
1628
1629    } // checkRequirements()
1630
1631    private function _debug($text)
1632    {
1633       if($this->fromcmd) {
1634          print $text;
1635       }
1636
1637    } // _debug()
1638
1639    /**
1640     * check if specified MIME type is supported
1641     */
1642    public function checkifImageSupported($mime)
1643    {
1644       if(in_array($mime, Array("image/jpeg")))
1645          return true;
1646
1647       return false;
1648
1649    } // checkifImageSupported()
1650
1651    public function _error($text)
1652    {
1653       switch($this->cfg->logging) {
1654          case 'display':
1655             print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
1656             print $text;
1657             break;
1658          case 'errorlog':  
1659             error_log($text);
1660             break;
1661          case 'logfile':
1662             error_log($text, 3, $his->cfg->log_file);
1663             break;
1664       }
1665
1666    } // _error()
1667
1668    /**
1669     * output calendard input fields
1670     */
1671    private function get_calendar($mode)
1672    {
1673       $year = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
1674       $month = isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
1675       $day = isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
1676
1677       $output = "<input type=\"text\" size=\"3\" id=\"". $mode ."year\" value=\"". $year ."\"";
1678       if(!isset($_SESSION[$mode .'_date']))
1679          $output.= " disabled=\"disabled\"";
1680       $output.= " />\n";
1681       $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."month\" value=\"". $month ."\"";
1682       if(!isset($_SESSION[$mode .'_date']))
1683          $output.= " disabled=\"disabled\"";
1684       $output.= " />\n";
1685       $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."day\" value=\"". $day ."\"";
1686       if(!isset($_SESSION[$mode .'_date']))
1687          $output.= " disabled=\"disabled\"";
1688       $output.= " />\n";
1689
1690       return $output;
1691
1692    } // get_calendar()
1693
1694    /**
1695     * output calendar matrix
1696     */
1697    public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
1698    {
1699       if (!isset($year)) $year = date('Y');
1700       if (!isset($month)) $month = date('m');
1701       if (!isset($day)) $day = date('d');
1702       $rows = 1;
1703       $cols = 1;
1704       $matrix = Array();
1705
1706       require_once CALENDAR_ROOT.'Month/Weekdays.php';
1707       require_once CALENDAR_ROOT.'Day.php';
1708
1709       // Build the month
1710       $month = new Calendar_Month_Weekdays($year,$month);
1711
1712       // Create links
1713       $prevStamp = $month->prevMonth(true);
1714       $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
1715       $nextStamp = $month->nextMonth(true);
1716       $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
1717
1718       $selectedDays = array (
1719          new Calendar_Day($year,$month,$day),
1720          new Calendar_Day($year,12,25),
1721       );
1722
1723       // Build the days in the month
1724       $month->build($selectedDays);
1725
1726       $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
1727       $this->tmpl->assign('prev_month', $prev);
1728       $this->tmpl->assign('next_month', $next);
1729
1730       while ( $day = $month->fetch() ) {
1731    
1732          if(!isset($matrix[$rows]))
1733             $matrix[$rows] = Array();
1734
1735          $string = "";
1736
1737          $dayStamp = $day->thisDay(true);
1738          $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
1739
1740          // isFirst() to find start of week
1741          if ( $day->isFirst() )
1742             $string.= "<tr>\n";
1743
1744          if ( $day->isSelected() ) {
1745             $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
1746          } else if ( $day->isEmpty() ) {
1747             $string.= "<td>&nbsp;</td>\n";
1748          } else {
1749             $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
1750          }
1751
1752          // isLast() to find end of week
1753          if ( $day->isLast() )
1754             $string.= "</tr>\n";
1755
1756          $matrix[$rows][$cols] = $string;
1757
1758          $cols++;
1759
1760          if($cols > 7) {
1761             $cols = 1;
1762             $rows++;
1763          }
1764       }
1765
1766       $this->tmpl->assign('matrix', $matrix);
1767       $this->tmpl->assign('rows', $rows);
1768       $this->tmpl->show("calendar.tpl");
1769
1770    } // get_calendar_matrix()
1771
1772    /**
1773     * output export page
1774     */
1775    public function getExport($mode)
1776    {
1777       $pictures = $this->getPhotoSelection();
1778       $current_tags = $this->getCurrentTags();  
1779
1780       foreach($pictures as $picture) {
1781
1782          $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
1783          if($current_tags != "") {
1784             $orig_url.= "&tags=". $current_tags;
1785          } 
1786          if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1787             $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
1788          }
1789
1790          $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
1791
1792          switch($mode) {
1793
1794             case 'HTML':
1795                // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
1796                print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
1797                break;
1798                
1799             case 'MoinMoin':
1800                // "[%pictureurl% %thumbnailurl%]"
1801                print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
1802                break;
1803
1804             case 'MoinMoinList':
1805                // " * [%pictureurl% %thumbnailurl%]"
1806                print "&nbsp;" . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
1807                break;
1808          }
1809
1810       }
1811
1812    } // getExport()
1813
1814    /**
1815     * output RSS feed
1816     */
1817    public function getRSSFeed()
1818    {
1819       Header("Content-type: text/xml; charset=utf-8");
1820       print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1821 ?>
1822 <rss version="2.0"
1823    xmlns:media="http://search.yahoo.com/mrss/"
1824    xmlns:dc="http://purl.org/dc/elements/1.1/"
1825  >
1826  <channel>
1827   <title>phpfspot</title>
1828   <description>phpfspot RSS feed</description>
1829   <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
1830   <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
1831   <generator>phpfspot</generator>
1832 <?php
1833
1834       $pictures = $this->getPhotoSelection();
1835       $current_tags = $this->getCurrentTags();  
1836
1837       foreach($pictures as $picture) {
1838
1839          $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
1840          if($current_tags != "") {
1841             $orig_url.= "&tags=". $current_tags;
1842          } 
1843          if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1844             $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
1845          }
1846
1847          $details = $this->get_photo_details($picture);
1848
1849          $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
1850          $thumb_html = htmlspecialchars("
1851 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
1852 <br>
1853 ". $details['description']);
1854
1855          $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
1856          $meta = $this->get_meta_informations($orig_path);
1857          $meta_date = isset($meta['FileDateTime']) ? $meta['FileDateTime'] : filemtime($orig_path);
1858
1859 ?>
1860   <item>
1861    <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
1862    <link><?php print htmlspecialchars($orig_url); ?></link>
1863    <guid><?php print htmlspecialchars($orig_url); ?></guid>
1864    <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $meta_date); ?></dc:date.Taken>
1865    <description>
1866     <?php print $thumb_html; ?> 
1867    </description>
1868    <pubDate><?php print strftime("%a, %d %b %Y %T %z", $meta_date); ?></pubDate>
1869   </item>
1870 <?php
1871
1872       }
1873 ?>
1874  </channel>
1875 </rss>
1876 <?php
1877
1878
1879    } // getExport()
1880
1881  
1882    /**
1883     * return all selected tags as one string
1884     */
1885    private function getCurrentTags()
1886    {
1887       $current_tags = "";
1888       if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
1889          foreach($_SESSION['selected_tags'] as $tag)
1890             $current_tags.= $tag .",";
1891          $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
1892       }
1893       return $current_tags;
1894
1895    } // getCurrentTags()
1896
1897    /**
1898     * return the current photo
1899     */
1900    public function getCurrentPhoto()
1901    {
1902       if(isset($_SESSION['current_photo'])) {
1903          print $_SESSION['current_photo'];
1904       }
1905    } // getCurrentPhoto()
1906
1907    /**
1908     * tells the client browser what to do
1909     *
1910     * this function is getting called via AJAX by the
1911     * client browsers. it will tell them what they have
1912     * to do next. This is necessary for directly jumping
1913     * into photo index or single photo view when the are
1914     * requested with specific URLs
1915     */
1916    public function whatToDo()
1917    {
1918       if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
1919          return "show_photo";
1920       }
1921       elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1922          return "showpi_tags";
1923       }
1924       elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
1925          return "showpi";
1926       }
1927
1928       return "nothing special";
1929
1930    } // whatToDo()
1931
1932    /**
1933     * return the current process-user
1934     */
1935    private function getuid()
1936    {
1937       if($uid = posix_getuid()) {
1938          if($user = posix_getpwuid($uid)) {
1939             return $user['name'];
1940          }
1941       }
1942    
1943       return 'n/a';
1944    
1945    } // getuid()
1946
1947    /**
1948     * returns a select-dropdown box to select photo index sort parameters
1949     */
1950    public function smarty_sort_select_list($params, &$smarty)
1951    {
1952       $output = "";
1953
1954       foreach($this->sort_orders as $key => $value) {
1955          $output.= "<option value=\"". $key ."\"";
1956          if($key == $_SESSION['sort_order']) {
1957             $output.= " selected=\"selected\"";
1958          }
1959          $output.= ">". $value ."</option>";
1960       }
1961
1962       return $output;
1963
1964    } // smarty_sort_select_list()
1965
1966    /**
1967     * returns the currently selected sort order
1968     */ 
1969    private function get_sort_order()
1970    {
1971       switch($_SESSION['sort_order']) {
1972          case 'date_asc':
1973             return " ORDER BY p.time ASC";
1974             break;
1975          case 'date_desc':
1976             return " ORDER BY p.time DESC";
1977             break;
1978          case 'name_asc':
1979             if($this->dbver < 9) {
1980                return " ORDER BY p.name ASC";
1981             }
1982             else {
1983                return " ORDER BY basename(p.uri) ASC";
1984             }
1985             break;
1986          case 'name_desc':
1987             if($this->dbver < 9) {
1988                return " ORDER BY p.name DESC";
1989             }
1990             else {
1991                return " ORDER BY basename(p.uri) DESC";
1992             }
1993             break;
1994       }
1995
1996    } // get_sort_order()
1997
1998    /***
1999      * return the next to be shown slide show image
2000      *
2001      * this function returns the URL of the next image
2002      * in the slideshow sequence.
2003      */
2004    public function getNextSlideShowImage()
2005    {
2006       $all_photos = $this->getPhotoSelection();
2007
2008       if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1) 
2009          $_SESSION['slideshow_img'] = 0;
2010       else
2011          $_SESSION['slideshow_img']++;
2012
2013       return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2014
2015    } // getNextSlideShowImage()
2016
2017    /***
2018      * return the previous to be shown slide show image
2019      *
2020      * this function returns the URL of the previous image
2021      * in the slideshow sequence.
2022      */
2023    public function getPrevSlideShowImage()
2024    {
2025       $all_photos = $this->getPhotoSelection();
2026
2027       if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
2028          $_SESSION['slideshow_img'] = 0;
2029       else
2030          $_SESSION['slideshow_img']--;
2031
2032       return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2033
2034    } // getPrevSlideShowImage()
2035
2036    public function resetSlideShow()
2037    {
2038       if(isset($_SESSION['slideshow_img']))
2039          unset($_SESSION['slideshow_img']);
2040    } // resetSlideShow()
2041    
2042    /***
2043      * get random photo
2044      *
2045      * this function will get all photos from the fspot
2046      * database and randomly return ONE entry
2047      *
2048      * saddly there is yet no sqlite3 function which returns
2049      * the bulk result in array, so we have to fill up our
2050      * own here.
2051      */ 
2052    public function get_random_photo()
2053    {
2054       $all = Array();
2055
2056       $result = $this->db->db_query("
2057          SELECT id
2058          FROM photos
2059       ");
2060       
2061       while($row = $this->db->db_fetch_object($result)) {
2062          array_push($all, $row['id']);
2063       }
2064
2065       return $all[array_rand($all)];
2066
2067    } // get_random_photo()
2068
2069    /**
2070     * validates provided date
2071     *
2072     * this function validates if the provided date
2073     * contains a valid date and will return true 
2074     * if it is.
2075     */
2076    public function isValidDate($date_str)
2077    {
2078       $timestamp = strtotime($date_str);
2079    
2080       if(is_numeric($timestamp))
2081          return true;
2082       
2083       return false;
2084
2085    } // isValidDate()
2086
2087    /**
2088     * timestamp to string conversion
2089     */
2090    private function ts2str($timestamp)
2091    {
2092       return strftime("%Y-%m-%d", $timestamp);
2093    } // ts2str()
2094
2095    private function extractTags($tags_str)
2096    {
2097       $not_validated = split(',', $_GET['tags']);
2098       $validated = array();
2099
2100       foreach($not_validated as $tag) {
2101          if(is_numeric($tag))
2102             array_push($validated, $tag);
2103       }
2104    
2105       return $validated;
2106    
2107    } // extractTags()
2108
2109    /**
2110     * returns the full path to a thumbnail
2111     */
2112    public function get_thumb_path($width, $photo)
2113    {
2114       $md5 = $this->getMD5($photo);
2115       $sub_path = substr($md5, 0, 2);
2116       return $this->cfg->thumb_path
2117          . "/"
2118          . $sub_path
2119          . "/"
2120          . $width
2121          . "_"
2122          . $md5;
2123
2124    } // get_thumb_path()
2125
2126    /**
2127     * returns server's virtual host name
2128     */
2129    private function get_server_name()
2130    {
2131       return $_SERVER['SERVER_NAME'];
2132    } // get_server_name()
2133
2134    /**
2135     * returns type of webprotocol which is
2136     * currently used
2137     */
2138    private function get_web_protocol()
2139    {
2140       if(!isset($_SERVER['HTTPS']))
2141          return "http";
2142       else
2143          return "https";
2144    } // get_web_protocol()
2145
2146    /**
2147     * return url to this phpfspot installation
2148     */
2149    private function get_phpfspot_url()
2150    {
2151       return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
2152    } // get_phpfspot_url()
2153    
2154    /**
2155     * check file exists and is readable
2156     *
2157     * returns true, if everything is ok, otherwise false
2158     * if $silent is not set, this function will output and
2159     * error message
2160     */
2161    private function check_readable($file, $silent = null)
2162    {
2163       if(!file_exists($file)) {
2164          if(!isset($silent))
2165             print "File \"". $file ."\" does not exist.\n";
2166          return false;
2167       }
2168
2169       if(!is_readable($file)) {
2170          if(!isset($silent))
2171             print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
2172          return false;
2173       }
2174
2175       return true;
2176
2177    } // check_readable()
2178
2179    /**
2180     * check if all needed indices are present
2181     *
2182     * this function checks, if some needed indices are already
2183     * present, or if not, create them on the fly. they are
2184     * necessary to speed up some queries like that one look for
2185     * all tags, when show_tags is specified in the configuration.
2186     */
2187    private function checkDbIndices()
2188    {
2189       $result = $this->db->db_exec("
2190          CREATE INDEX IF NOT EXISTS
2191             phototag
2192          ON
2193             photo_tags
2194                (photo_id, tag_id)
2195       ");
2196
2197    } // checkDbIndices()
2198
2199    /**
2200     * retrive F-Spot database version
2201     *
2202     * this function will return the F-Spot database version number
2203     * It is stored within the sqlite3 database in the table meta
2204     */
2205    public function getFspotDBVersion()
2206    {
2207       if($result = $this->db->db_fetchSingleRow("
2208          SELECT data as version
2209          FROM meta
2210          WHERE
2211             name LIKE 'F-Spot Database Version'
2212       "))
2213          return $result['version'];
2214
2215       return null;
2216
2217    } // getFspotDBVersion()
2218
2219    /**
2220     * parse the provided URI and will returned the
2221     * requested chunk
2222     */
2223    public function parse_uri($uri, $mode)
2224    {
2225       if(($components = parse_url($uri)) !== false) {
2226
2227          switch($mode) {
2228             case 'filename':
2229                return basename($components['path']);
2230                break;
2231             case 'dirname':
2232                return dirname($components['path']);
2233                break;
2234             case 'fullpath':
2235                return $components['path'];
2236                break;
2237          }
2238       }
2239
2240       return $uri;
2241
2242    } // parse_uri()
2243
2244 } // class PHPFSPOT
2245
2246 ?>