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