3 /***************************************************************************
5 * phpfspot, presents your F-Spot photo collection in Web browsers.
7 * Copyright (c) by Andreas Unterkircher
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 ***************************************************************************/
25 require_once "phpfspot_cfg.php";
26 require_once "phpfspot_db.php";
37 private $runtime_error = false;
43 * this function will be called on class construct
44 * and will check requirements, loads configuration,
45 * open databases and start the user session
47 public function __construct()
49 $this->cfg = new PHPFSPOT_CFG;
51 /* verify config settings */
52 if($this->check_config_options()) {
56 /* set application name and version information */
57 $this->cfg->product = "phpfspot";
58 $this->cfg->version = "1.4";
60 $this->sort_orders= array(
61 'date_asc' => 'Date ↑',
62 'date_desc' => 'Date ↓',
63 'name_asc' => 'Name ↑',
64 'name_desc' => 'Name ↓',
65 'tags_asc' => 'Tags ↑',
66 'tags_desc' => 'Tags ↓',
69 /* Check necessary requirements */
70 if(!$this->check_requirements()) {
74 /******* Opening F-Spot's sqlite database *********/
76 /* Check if database file is writeable */
77 if(!is_writeable($this->cfg->fspot_db)) {
78 print $this->cfg->fspot_db ." is not writeable for user ". $this->getuid() ."\n";
82 /* open the database */
83 $this->db = new PHPFSPOT_DB($this, $this->cfg->fspot_db);
85 /* change sqlite temp directory, if requested */
86 if(isset($this->cfg->sqlite_temp_dir)) {
89 temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
93 $this->dbver = $this->getFspotDBVersion();
95 if(!is_writeable($this->cfg->base_path ."/templates_c")) {
96 print $this->cfg->base_path ."/templates_c: directory is not writeable for user ". $this->getuid() ."\n";
100 if(!is_writeable($this->cfg->thumb_path)) {
101 print $this->cfg->thumb_path .": directory is not writeable for user ". $this->getuid() ."\n";
105 /******* Opening phpfspot's sqlite database *********/
107 /* Check if directory where the database file is stored is writeable */
108 if(!is_writeable(dirname($this->cfg->phpfspot_db))) {
109 print dirname($this->cfg->phpfspot_db) .": directory is not writeable for user ". $this->getuid() ."\n";
113 /* Check if database file is writeable */
114 if(!is_writeable($this->cfg->phpfspot_db)) {
115 print $this->cfg->phpfspot_db ." is not writeable for user ". $this->getuid() ."\n";
119 /* open the database */
120 $this->cfg_db = new PHPFSPOT_DB($this, $this->cfg->phpfspot_db);
122 /* change sqlite temp directory, if requested */
123 if(isset($this->cfg->sqlite_temp_dir)) {
124 $this->cfg_db->db_exec("
126 temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
130 /* Check if some tables need to be created */
131 $this->check_config_table();
133 /* overload Smarty class with our own template handler */
134 require_once "phpfspot_tmpl.php";
135 $this->tmpl = new PHPFSPOT_TMPL($this);
137 /* check if all necessary indices exist */
138 $this->checkDbIndices();
140 /* if session is not yet started, do it now */
141 if(session_id() == "")
144 if(!isset($_SESSION['tag_condition']))
145 $_SESSION['tag_condition'] = 'or';
147 if(!isset($_SESSION['sort_order']))
148 $_SESSION['sort_order'] = 'date_desc';
150 if(!isset($_SESSION['searchfor_tag']))
151 $_SESSION['searchfor_tag'] = '';
153 // if begin_with is still set but thumbs_per_page is now 0, unset it
154 if(isset($_SESSION['begin_with']) && $this->cfg->thumbs_per_page == 0)
155 unset($_SESSION['begin_with']);
159 public function __destruct()
165 * show - generate html output
167 * this function can be called after the constructor has
168 * prepared everyhing. it will load the index.tpl smarty
169 * template. if necessary it will registere pre-selects
170 * (photo index, photo, tag search, date search) into
173 public function show()
175 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
176 $this->tmpl->assign('page_title', $this->cfg->page_title);
177 $this->tmpl->assign('current_condition', $_SESSION['tag_condition']);
178 $this->tmpl->assign('template_path', 'themes/'. $this->cfg->theme_name);
180 if(isset($_GET['mode'])) {
182 $_SESSION['start_action'] = $_GET['mode'];
184 switch($_GET['mode']) {
186 if(isset($_GET['tags'])) {
187 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
189 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
190 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
192 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
193 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
197 if(isset($_GET['tags'])) {
198 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
199 $_SESSION['start_action'] = 'showp';
201 if(isset($_GET['id']) && is_numeric($_GET['id'])) {
202 $_SESSION['current_photo'] = $_GET['id'];
203 $_SESSION['start_action'] = 'showp';
205 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
206 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
208 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
209 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
213 $this->tmpl->show("export.tpl");
217 $this->tmpl->show("slideshow.tpl");
221 if(isset($_GET['tags'])) {
222 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
224 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
225 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
227 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
228 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
236 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date']))
237 $this->tmpl->assign('date_search_enabled', true);
239 $this->tmpl->register_function("sort_select_list", array(&$this, "smarty_sort_select_list"), false);
240 $this->tmpl->assign('from_date', $this->get_calendar('from'));
241 $this->tmpl->assign('to_date', $this->get_calendar('to'));
242 $this->tmpl->assign('content_page', 'welcome.tpl');
243 $this->tmpl->show("index.tpl");
248 * get_tags - grab all tags of f-spot's database
250 * this function will get all available tags from
251 * the f-spot database and store them within two
252 * arrays within this class for later usage. in
253 * fact, if the user requests (hide_tags) it will
254 * opt-out some of them.
256 * this function is getting called once by show()
258 private function get_tags()
260 $this->avail_tags = Array();
263 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
266 DISTINCT t1.id as id, t1.name as name
269 INNER JOIN photo_tags
270 pt2 ON pt1.photo_id=pt2.photo_id
276 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
278 t1.sort_priority ASC";
280 $result = $this->db->db_query($query_str);
284 $result = $this->db->db_query("
287 ORDER BY sort_priority ASC
291 while($row = $this->db->db_fetch_object($result)) {
293 $tag_id = $row['id'];
294 $tag_name = $row['name'];
296 /* if the user has specified to ignore this tag in phpfspot's
297 configuration, ignore it here so it does not get added to
300 if(in_array($row['name'], $this->cfg->hide_tags))
303 /* if you include the following if-clause and the user has specified
304 to only show certain tags which are specified in phpfspot's
305 configuration, ignore all others so they will not be added to the
307 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags) &&
308 !in_array($row['name'], $this->cfg->show_tags))
312 $this->tags[$tag_id] = $tag_name;
313 $this->avail_tags[$count] = $tag_id;
321 * extract all photo details
323 * retrieve all available details from f-spot's
324 * database and return them as object
326 public function get_photo_details($idx)
328 if($this->dbver < 9) {
330 SELECT p.id, p.name, p.time, p.directory_path, p.description
336 SELECT p.id, p.uri, p.time, p.description
341 /* if show_tags is set, only return details for photos which
342 are specified to be shown
344 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
346 INNER JOIN photo_tags pt
350 WHERE p.id='". $idx ."'
351 AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
355 WHERE p.id='". $idx ."'
359 if($result = $this->db->db_query($query_str)) {
361 $row = $this->db->db_fetch_object($result);
363 if($this->dbver < 9) {
364 $row['uri'] = "file://". $row['directory_path'] ."/". $row['name'];
373 } // get_photo_details
376 * returns aligned photo names
378 * this function returns aligned (length) names for
379 * an specific photo. If the length of the name exceeds
380 * $limit the name will be shrinked (...)
382 public function getPhotoName($idx, $limit = 0)
384 if($details = $this->get_photo_details($idx)) {
385 if($long_name = $this->parse_uri($details['uri'], 'filename')) {
386 $name = $this->shrink_text($long_name, $limit);
396 * shrink text according provided limit
398 * If the length of the name exceeds $limit the
399 * text will be shortend and some content in between
400 * will be replaced with "..."
402 private function shrink_text($text, $limit)
404 if($limit != 0 && strlen($text) > $limit) {
405 $text = substr($text, 0, $limit-5) ."...". substr($text, -($limit-5));
413 * translate f-spoth photo path
415 * as the full-qualified path recorded in the f-spot database
416 * is usally not the same as on the webserver, this function
417 * will replace the path with that one specified in the cfg
419 public function translate_path($path, $width = 0)
421 return str_replace($this->cfg->path_replace_from, $this->cfg->path_replace_to, $path);
426 * control HTML ouput for a single photo
428 * this function provides all the necessary information
429 * for the single photo template.
431 public function showPhoto($photo)
433 /* get all photos from the current photo selection */
434 $all_photos = $this->getPhotoSelection();
435 $count = count($all_photos);
437 for($i = 0; $i < $count; $i++) {
439 // $get_next will be set, when the photo which has to
440 // be displayed has been found - this means that the
441 // next available is in fact the NEXT image (for the
443 if(isset($get_next)) {
444 $next_img = $all_photos[$i];
448 /* the next photo is our NEXT photo */
449 if($all_photos[$i] == $photo) {
453 $previous_img = $all_photos[$i];
456 if($photo == $all_photos[$i]) {
461 $details = $this->get_photo_details($photo);
468 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
469 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
471 if(!file_exists($orig_path)) {
472 $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
476 if(!is_readable($orig_path)) {
477 $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
481 /* If the thumbnail doesn't exist yet, try to create it */
482 if(!file_exists($thumb_path)) {
483 $this->gen_thumb($photo, true);
484 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
487 /* get mime-type, height and width from the original photo */
488 $info = getimagesize($orig_path);
490 /* get EXIF information if JPEG */
491 if($info['mime'] == "image/jpeg") {
492 $meta = $this->get_meta_informations($orig_path);
495 /* If EXIF data are available, use them */
496 if(isset($meta['ExifImageWidth'])) {
497 $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
499 $meta_res = $info[0] ."x". $info[1];
502 $meta_date = isset($meta['FileDateTime']) ? strftime("%a %x %X", $meta['FileDateTime']) : "n/a";
503 $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
504 $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
506 $extern_link = "index.php?mode=showp&id=". $photo;
507 $current_tags = $this->getCurrentTags();
508 if($current_tags != "") {
509 $extern_link.= "&tags=". $current_tags;
511 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
512 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
515 $this->tmpl->assign('extern_link', $extern_link);
517 if(!file_exists($thumb_path)) {
518 $this->_error("Can't open file ". $thumb_path ."\n");
522 $info_thumb = getimagesize($thumb_path);
524 $this->tmpl->assign('description', $details['description']);
525 $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
527 $this->tmpl->assign('width', $info_thumb[0]);
528 $this->tmpl->assign('height', $info_thumb[1]);
529 $this->tmpl->assign('ExifMadeOn', $meta_date);
530 $this->tmpl->assign('ExifMadeWith', $meta_make);
531 $this->tmpl->assign('ExifOrigResolution', $meta_res);
532 $this->tmpl->assign('ExifFileSize', $meta_size);
534 $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&width=". $this->cfg->photo_width);
535 $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
536 $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
538 $this->tmpl->assign('tags', $this->get_photo_tags($photo));
539 $this->tmpl->assign('current_page', $this->getCurrentPage($current, $count));
540 $this->tmpl->assign('current_img', $photo);
543 $this->tmpl->assign('previous_url', "javascript:showImage(". $previous_img .");");
544 $this->tmpl->assign('prev_img', $previous_img);
548 $this->tmpl->assign('next_url', "javascript:showImage(". $next_img .");");
549 $this->tmpl->assign('next_img', $next_img);
551 $this->tmpl->assign('mini_width', $this->cfg->mini_width);
552 $this->tmpl->assign('photo_width', $this->cfg->photo_width);
553 $this->tmpl->assign('photo_number', $i);
554 $this->tmpl->assign('photo_count', count($all_photos));
556 $this->tmpl->show("single_photo.tpl");
561 * all available tags and tag cloud
563 * this function outputs all available tags (time ordered)
564 * and in addition output them as tag cloud (tags which have
565 * many photos will appears more then others)
567 public function getAvailableTags()
569 /* retrive tags from database */
574 $result = $this->db->db_query("
575 SELECT tag_id as id, count(tag_id) as quantity
585 while($row = $this->db->db_fetch_object($result)) {
586 $tags[$row['id']] = $row['quantity'];
589 // change these font sizes if you will
590 $max_size = 125; // max font size in %
591 $min_size = 75; // min font size in %
594 $max_sat = hexdec('cc');
595 $min_sat = hexdec('44');
597 // get the largest and smallest array values
598 $max_qty = max(array_values($tags));
599 $min_qty = min(array_values($tags));
601 // find the range of values
602 $spread = $max_qty - $min_qty;
603 if (0 == $spread) { // we don't want to divide by zero
607 // determine the font-size increment
608 // this is the increase per tag quantity (times used)
609 $step = ($max_size - $min_size)/($spread);
610 $step_sat = ($max_sat - $min_sat)/($spread);
612 // loop through our tag array
613 foreach ($tags as $key => $value) {
615 if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
618 // calculate CSS font-size
619 // find the $value in excess of $min_qty
620 // multiply by the font-size increment ($size)
621 // and add the $min_size set above
622 $size = $min_size + (($value - $min_qty) * $step);
623 // uncomment if you want sizes in whole %:
626 $color = $min_sat + ($value - $min_qty) * $step_sat;
632 if(isset($this->tags[$key])) {
633 $output.= "<a href=\"javascript:Tags('add', ". $key .");\" class=\"tag\" style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\">". $this->tags[$key] ."</a>, ";
638 $output = substr($output, 0, strlen($output)-2);
641 } // getAvailableTags()
644 * output all selected tags
646 * this function output all tags which have been selected
647 * by the user. the selected tags are stored in the
648 * session-variable $_SESSION['selected_tags']
650 public function getSelectedTags()
652 /* retrive tags from database */
657 foreach($this->avail_tags as $tag)
659 // return all selected tags
660 if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
661 $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
666 $output = substr($output, 0, strlen($output)-2);
670 return "no tags selected";
673 } // getSelectedTags()
676 * add tag to users session variable
678 * this function will add the specified to users current
679 * tag selection. if a date search has been made before
680 * it will be now cleared
682 public function addTag($tag)
684 if(!isset($_SESSION['selected_tags']))
685 $_SESSION['selected_tags'] = Array();
687 if(isset($_SESSION['searchfor_tag']))
688 unset($_SESSION['searchfor_tag']);
690 if(!in_array($tag, $_SESSION['selected_tags']))
691 array_push($_SESSION['selected_tags'], $tag);
699 * remove tag to users session variable
701 * this function removes the specified tag from
702 * users current tag selection
704 public function delTag($tag)
706 if(isset($_SESSION['searchfor_tag']))
707 unset($_SESSION['searchfor_tag']);
709 if(isset($_SESSION['selected_tags'])) {
710 $key = array_search($tag, $_SESSION['selected_tags']);
711 unset($_SESSION['selected_tags'][$key]);
712 sort($_SESSION['selected_tags']);
720 * reset tag selection
722 * if there is any tag selection, it will be
725 public function resetTags()
727 if(isset($_SESSION['selected_tags']))
728 unset($_SESSION['selected_tags']);
733 * returns the value for the autocomplet tag-search
735 public function get_xml_tag_list()
737 if(!isset($_GET['search']) || !is_string($_GET['search']))
738 $_GET['search'] = '';
743 /* retrive tags from database */
746 $matched_tags = Array();
748 header("Content-Type: text/xml");
750 $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
751 $string.= "<results>\n";
753 foreach($this->avail_tags as $tag)
755 if(!empty($_GET['search']) &&
756 preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) &&
757 count($matched_tags) < $length) {
759 $count = $this->get_num_photos($tag);
762 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n";
765 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n";
771 /* if we have collected enough items, break out */
772 if(count($matched_tags) >= $length)
776 $string.= "</results>\n";
780 } // get_xml_tag_list()
786 * if a specific photo was requested (external link)
787 * unset the session variable now
789 public function resetPhotoView()
791 if(isset($_SESSION['current_photo']))
792 unset($_SESSION['current_photo']);
794 } // resetPhotoView();
799 * if any tag search has taken place, reset it now
801 public function resetTagSearch()
803 if(isset($_SESSION['searchfor_tag']))
804 unset($_SESSION['searchfor_tag']);
806 } // resetTagSearch()
811 * if any name search has taken place, reset it now
813 public function resetNameSearch()
815 if(isset($_SESSION['searchfor_name']))
816 unset($_SESSION['searchfor_name']);
818 } // resetNameSearch()
823 * if any date search has taken place, reset
826 public function resetDateSearch()
828 if(isset($_SESSION['from_date']))
829 unset($_SESSION['from_date']);
830 if(isset($_SESSION['to_date']))
831 unset($_SESSION['to_date']);
833 } // resetDateSearch();
836 * return all photo according selection
838 * this function returns all photos based on
839 * the tag-selection, tag- or date-search.
840 * the tag-search also has to take care of AND
841 * and OR conjunctions
843 public function getPhotoSelection()
845 $matched_photos = Array();
846 $additional_where_cond = "";
848 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
849 $from_date = $_SESSION['from_date'];
850 $to_date = $_SESSION['to_date'];
851 $additional_where_cond.= "
852 p.time>='". $from_date ."'
854 p.time<='". $to_date ."'
858 if(isset($_SESSION['searchfor_name'])) {
859 if($this->dbver < 9) {
860 $additional_where_cond.= "
862 p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
864 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
869 $additional_where_cond.= "
871 basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
873 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
879 if(isset($_SESSION['sort_order'])) {
880 $order_str = $this->get_sort_order();
883 /* return a search result */
884 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
886 SELECT DISTINCT pt1.photo_id
888 INNER JOIN photo_tags pt2
889 ON pt1.photo_id=pt2.photo_id
896 WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
898 if(isset($additional_where_cond) && !empty($additional_where_cond))
899 $query_str.= "AND ". $additional_where_cond ." ";
901 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
902 $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
905 if(isset($order_str))
906 $query_str.= $order_str;
908 $result = $this->db->db_query($query_str);
909 while($row = $this->db->db_fetch_object($result)) {
910 array_push($matched_photos, $row['photo_id']);
912 return $matched_photos;
915 /* return according the selected tags */
916 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
918 foreach($_SESSION['selected_tags'] as $tag)
919 $selected.= $tag .",";
920 $selected = substr($selected, 0, strlen($selected)-1);
922 /* photo has to match at least on of the selected tags */
923 if($_SESSION['tag_condition'] == 'or') {
925 SELECT DISTINCT pt1.photo_id
927 INNER JOIN photo_tags pt2
928 ON pt1.photo_id=pt2.photo_id
933 WHERE pt1.tag_id IN (". $selected .")
935 if(isset($additional_where_cond) && !empty($additional_where_cond))
936 $query_str.= "AND ". $additional_where_cond ." ";
938 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
939 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
942 if(isset($order_str))
943 $query_str.= $order_str;
945 /* photo has to match all selected tags */
946 elseif($_SESSION['tag_condition'] == 'and') {
948 if(count($_SESSION['selected_tags']) >= 32) {
949 print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
950 print "evaluate your tag selection. Please remove some tags from your selection.\n";
954 /* Join together a table looking like
956 pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
958 so the query can quickly return all images matching the
959 selected tags in an AND condition
964 SELECT DISTINCT pt1.photo_id
968 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
975 for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
977 INNER JOIN photo_tags pt". ($i+2) ."
978 ON pt1.photo_id=pt". ($i+2) .".photo_id
985 $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
986 for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
988 AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
991 if(isset($additional_where_cond) && !empty($additional_where_cond))
992 $query_str.= "AND ". $additional_where_cond;
994 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
995 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
998 if(isset($order_str))
999 $query_str.= $order_str;
1003 $result = $this->db->db_query($query_str);
1004 while($row = $this->db->db_fetch_object($result)) {
1005 array_push($matched_photos, $row['photo_id']);
1007 return $matched_photos;
1010 /* return all available photos */
1012 SELECT DISTINCT p.id
1014 LEFT JOIN photo_tags pt
1020 if(isset($additional_where_cond) && !empty($additional_where_cond))
1021 $query_str.= "WHERE ". $additional_where_cond ." ";
1023 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1024 if(isset($additional_where_cond) && !empty($additional_where_cond))
1025 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1027 $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1030 if(isset($order_str))
1031 $query_str.= $order_str;
1033 $result = $this->db->db_query($query_str);
1034 while($row = $this->db->db_fetch_object($result)) {
1035 array_push($matched_photos, $row['id']);
1037 return $matched_photos;
1039 } // getPhotoSelection()
1042 * control HTML ouput for photo index
1044 * this function provides all the necessary information
1045 * for the photo index template.
1047 public function showPhotoIndex()
1049 $photos = $this->getPhotoSelection();
1051 $count = count($photos);
1053 /* if all thumbnails should be shown on one page */
1054 if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
1058 /* thumbnails should be splitted up in several pages */
1059 elseif($this->cfg->thumbs_per_page > 0) {
1061 if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
1065 $begin_with = $_SESSION['begin_with'];
1068 $end_with = $begin_with + $this->cfg->thumbs_per_page;
1072 $images[$thumbs] = Array();
1073 $img_height[$thumbs] = Array();
1074 $img_width[$thumbs] = Array();
1075 $img_id[$thumbs] = Array();
1076 $img_name[$thumbs] = Array();
1077 $img_fullname[$thumbs] = Array();
1078 $img_title = Array();
1080 for($i = $begin_with; $i < $end_with; $i++) {
1082 if(isset($photos[$i])) {
1084 $images[$thumbs] = $photos[$i];
1085 $img_id[$thumbs] = $i;
1086 $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
1087 $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0));
1088 $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
1090 $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i]);
1092 if(file_exists($thumb_path)) {
1093 $info = getimagesize($thumb_path);
1094 $img_width[$thumbs] = $info[0];
1095 $img_height[$thumbs] = $info[1];
1101 // +1 for for smarty's selection iteration
1104 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1105 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1107 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1108 $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1109 $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1112 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1113 $this->tmpl->assign('tag_result', 1);
1116 /* do we have to display the page selector ? */
1117 if($this->cfg->thumbs_per_page != 0) {
1121 /* calculate the page switchers */
1122 $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1123 $next_start = $begin_with + $this->cfg->thumbs_per_page;
1125 if($begin_with != 0)
1126 $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");");
1127 if($end_with < $count)
1128 $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");");
1130 $photo_per_page = $this->cfg->thumbs_per_page;
1131 $last_page = ceil($count / $photo_per_page);
1133 /* get the current selected page */
1134 if($begin_with == 0) {
1138 for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1145 for($i = 1; $i <= $last_page; $i++) {
1147 if($current_page == $i)
1148 $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1149 elseif($current_page-1 == $i || $current_page+1 == $i)
1150 $style = "style=\"font-size: 105%;\"";
1151 elseif(($current_page-5 >= $i) && ($i != 1) ||
1152 ($current_page+5 <= $i) && ($i != $last_page))
1153 $style = "style=\"font-size: 75%;\"";
1157 $select = "<a href=\"javascript:showPhotoIndex(". (($i*$photo_per_page)-$photo_per_page) .");\"";
1160 $select.= ">". $i ."</a> ";
1162 // until 9 pages we show the selector from 1-9
1163 if($last_page <= 9) {
1164 $page_select.= $select;
1167 if($i == 1 /* first page */ ||
1168 $i == $last_page /* last page */ ||
1169 $i == $current_page /* current page */ ||
1170 $i == ceil($last_page * 0.25) /* first quater */ ||
1171 $i == ceil($last_page * 0.5) /* half */ ||
1172 $i == ceil($last_page * 0.75) /* third quater */ ||
1173 (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1174 (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 */ ||
1175 $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1176 $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1178 $page_select.= $select;
1186 $page_select.= "......... ";
1191 /* only show the page selector if we have more then one page */
1193 $this->tmpl->assign('page_selector', $page_select);
1197 $current_tags = $this->getCurrentTags();
1198 $extern_link = "index.php?mode=showpi";
1199 $rss_link = "index.php?mode=rss";
1200 if($current_tags != "") {
1201 $extern_link.= "&tags=". $current_tags;
1202 $rss_link.= "&tags=". $current_tags;
1204 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1205 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1206 $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1209 $export_link = "index.php?mode=export";
1210 $slideshow_link = "index.php?mode=slideshow";
1212 $this->tmpl->assign('extern_link', $extern_link);
1213 $this->tmpl->assign('slideshow_link', $slideshow_link);
1214 $this->tmpl->assign('export_link', $export_link);
1215 $this->tmpl->assign('rss_link', $rss_link);
1216 $this->tmpl->assign('count', $count);
1217 $this->tmpl->assign('width', $this->cfg->thumb_width);
1218 $this->tmpl->assign('preview_width', $this->cfg->photo_width);
1219 $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1220 $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1221 $this->tmpl->assign('images', $images);
1222 $this->tmpl->assign('img_width', $img_width);
1223 $this->tmpl->assign('img_height', $img_height);
1224 $this->tmpl->assign('img_id', $img_id);
1225 $this->tmpl->assign('img_name', $img_name);
1226 $this->tmpl->assign('img_fullname', $img_fullname);
1227 $this->tmpl->assign('img_title', $img_title);
1228 $this->tmpl->assign('thumbs', $thumbs);
1230 $this->tmpl->show("photo_index.tpl");
1232 /* if we are returning to photo index from an photo-view,
1233 scroll the window to the last shown photo-thumbnail.
1234 after this, unset the last_photo session variable.
1236 if(isset($_SESSION['last_photo'])) {
1237 print "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n";
1238 unset($_SESSION['last_photo']);
1241 } // showPhotoIndex()
1244 * show credit template
1246 public function showCredits()
1248 $this->tmpl->assign('version', $this->cfg->version);
1249 $this->tmpl->assign('product', $this->cfg->product);
1250 $this->tmpl->assign('db_version', $this->dbver);
1251 $this->tmpl->show("credits.tpl");
1256 * create_thumbnails for the requested width
1258 * this function creates image thumbnails of $orig_image
1259 * stored as $thumb_image. It will check if the image is
1260 * in a supported format, if necessary rotate the image
1261 * (based on EXIF orientation meta headers) and re-sizing.
1263 public function create_thumbnail($orig_image, $thumb_image, $width)
1265 if(!file_exists($orig_image)) {
1269 $details = getimagesize($orig_image);
1271 /* check if original photo is a support image type */
1272 if(!$this->checkifImageSupported($details['mime']))
1275 switch($details['mime']) {
1279 $meta = $this->get_meta_informations($orig_image);
1285 switch($meta['Orientation']) {
1286 case 1: /* top, left */
1287 /* nothing to do */ break;
1288 case 2: /* top, right */
1289 $rotate = 0; $flip_hori = true; break;
1290 case 3: /* bottom, left */
1291 $rotate = 180; break;
1292 case 4: /* bottom, right */
1293 $flip_vert = true; break;
1294 case 5: /* left side, top */
1295 $rotate = 90; $flip_vert = true; break;
1296 case 6: /* right side, top */
1297 $rotate = 90; break;
1298 case 7: /* left side, bottom */
1299 $rotate = 270; $flip_vert = true; break;
1300 case 8: /* right side, bottom */
1301 $rotate = 270; break;
1304 $src_img = @imagecreatefromjpeg($orig_image);
1309 $src_img = @imagecreatefrompng($orig_image);
1315 print "Can't load image from ". $orig_image ."\n";
1319 /* grabs the height and width */
1320 $cur_width = imagesx($src_img);
1321 $cur_height = imagesy($src_img);
1323 // If requested width is more then the actual image width,
1324 // do not generate a thumbnail, instead safe the original
1325 // as thumbnail but with lower quality. But if the image
1326 // is to heigh too, then we still have to resize it.
1327 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1328 $result = imagejpeg($src_img, $thumb_image, 75);
1329 imagedestroy($src_img);
1333 // If the image will be rotate because EXIF orientation said so
1334 // 'virtually rotate' the image for further calculations
1335 if($rotate == 90 || $rotate == 270) {
1337 $cur_width = $cur_height;
1341 /* calculates aspect ratio */
1342 $aspect_ratio = $cur_height / $cur_width;
1345 if($aspect_ratio < 1) {
1347 $new_h = abs($new_w * $aspect_ratio);
1349 /* 'virtually' rotate the image and calculate it's ratio */
1350 $tmp_w = $cur_height;
1351 $tmp_h = $cur_width;
1352 /* now get the ratio from the 'rotated' image */
1353 $tmp_ratio = $tmp_h/$tmp_w;
1354 /* now calculate the new dimensions */
1356 $tmp_h = abs($tmp_w * $tmp_ratio);
1358 // now that we know, how high they photo should be, if it
1359 // gets rotated, use this high to scale the image
1361 $new_w = abs($new_h / $aspect_ratio);
1363 // If the image will be rotate because EXIF orientation said so
1364 // now 'virtually rotate' back the image for the image manipulation
1365 if($rotate == 90 || $rotate == 270) {
1372 /* creates new image of that size */
1373 $dst_img = imagecreatetruecolor($new_w, $new_h);
1375 imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1377 /* copies resized portion of original image into new image */
1378 imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1380 /* needs the image to be flipped horizontal? */
1382 $this->_debug("(FLIP)");
1383 $dst_img = $this->flipImage($dst_img, 'hori');
1385 /* needs the image to be flipped vertical? */
1387 $this->_debug("(FLIP)");
1388 $dst_img = $this->flipImage($dst_img, 'vert');
1392 $this->_debug("(ROTATE)");
1393 $dst_img = $this->rotateImage($dst_img, $rotate);
1396 /* write down new generated file */
1397 $result = imagejpeg($dst_img, $thumb_image, 75);
1399 /* free your mind */
1400 imagedestroy($dst_img);
1401 imagedestroy($src_img);
1403 if($result === false) {
1404 print "Can't write thumbnail ". $thumb_image ."\n";
1410 } // create_thumbnail()
1413 * return all exif meta data from the file
1415 public function get_meta_informations($file)
1417 return exif_read_data($file);
1419 } // get_meta_informations()
1422 * create phpfspot own sqlite database
1424 * this function creates phpfspots own sqlite database
1425 * if it does not exist yet. this own is used to store
1426 * some necessary informations (md5 sum's, ...).
1428 public function check_config_table()
1430 // if the config table doesn't exist yet, create it
1431 if(!$this->cfg_db->db_check_table_exists("images")) {
1432 $this->cfg_db->db_exec("
1433 CREATE TABLE images (
1434 img_idx int primary key,
1440 } // check_config_table
1443 * Generates a thumbnail from photo idx
1445 * This function will generate JPEG thumbnails from provided F-Spot photo
1448 * 1. Check if all thumbnail generations (width) are already in place and
1450 * 2. Check if the md5sum of the original file has changed
1451 * 3. Generate the thumbnails if needed
1453 public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
1457 $resolutions = Array(
1458 $this->cfg->thumb_width,
1459 $this->cfg->photo_width,
1460 $this->cfg->mini_width,
1463 /* get details from F-Spot's database */
1464 $details = $this->get_photo_details($idx);
1466 /* calculate file MD5 sum */
1467 $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
1469 if(!file_exists($full_path)) {
1470 $this->_error("File ". $full_path ." does not exist\n");
1474 if(!is_readable($full_path)) {
1475 $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
1479 $file_md5 = md5_file($full_path);
1481 $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
1485 foreach($resolutions as $resolution) {
1487 $generate_it = false;
1489 $thumb_sub_path = substr($file_md5, 0, 2);
1490 $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
1492 /* if thumbnail-subdirectory does not exist yet, create it */
1493 if(!file_exists(dirname($thumb_path))) {
1494 mkdir(dirname($thumb_path), 0755);
1497 /* if the thumbnail file doesn't exist, create it */
1498 if(!file_exists($thumb_path)) {
1499 $generate_it = true;
1501 /* if the file hasn't changed there is no need to regen the thumb */
1502 elseif($file_md5 != $this->getMD5($idx) || $force) {
1503 $generate_it = true;
1506 if($generate_it || $overwrite) {
1508 $this->_debug(" ". $resolution ."px");
1509 if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1517 $this->_debug(" already exist");
1520 /* set the new/changed MD5 sum for the current photo */
1522 $this->setMD5($idx, $file_md5);
1525 $this->_debug("\n");
1530 * returns stored md5 sum for a specific photo
1532 * this function queries the phpfspot database for a
1533 * stored MD5 checksum of the specified photo
1535 public function getMD5($idx)
1537 $result = $this->cfg_db->db_query("
1540 WHERE img_idx='". $idx ."'
1546 $img = $this->cfg_db->db_fetch_object($result);
1547 return $img['img_md5'];
1552 * set MD5 sum for the specific photo
1554 private function setMD5($idx, $md5)
1556 $result = $this->cfg_db->db_exec("
1557 REPLACE INTO images (img_idx, img_md5)
1558 VALUES ('". $idx ."', '". $md5 ."')
1564 * store current tag condition
1566 * this function stores the current tag condition
1567 * (AND or OR) in the users session variables
1569 public function setTagCondition($mode)
1571 $_SESSION['tag_condition'] = $mode;
1575 } // setTagCondition()
1578 * invoke tag & date search
1580 * this function will return all matching tags and store
1581 * them in the session variable selected_tags. furthermore
1582 * it also handles the date search.
1583 * getPhotoSelection() will then only return the matching
1586 public function startSearch()
1588 if(isset($_POST['from']) && $this->isValidDate($_POST['from'])) {
1589 $from = $_POST['from'];
1591 if(isset($_POST['to']) && $this->isValidDate($_POST['to'])) {
1595 if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
1596 $searchfor_tag = $_POST['for_tag'];
1597 $_SESSION['searchfor_tag'] = $_POST['for_tag'];
1600 if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
1601 $searchfor_name = $_POST['for_name'];
1602 $_SESSION['searchfor_name'] = $_POST['for_name'];
1607 if(isset($from) && !empty($from))
1608 $_SESSION['from_date'] = strtotime($from ." 00:00:00");
1610 unset($_SESSION['from_date']);
1612 if(isset($to) && !empty($to))
1613 $_SESSION['to_date'] = strtotime($to ." 23:59:59");
1615 unset($_SESSION['to_date']);
1617 if(isset($searchfor_tag) && !empty($searchfor_tag)) {
1618 /* new search, reset the current selected tags */
1619 $_SESSION['selected_tags'] = Array();
1620 foreach($this->avail_tags as $tag) {
1621 if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
1622 array_push($_SESSION['selected_tags'], $tag);
1631 * updates sort order in session variable
1633 * this function is invoked by RPC and will sort the requested
1634 * sort order in the session variable.
1636 public function updateSortOrder($order)
1638 if(isset($this->sort_orders[$order])) {
1639 $_SESSION['sort_order'] = $order;
1643 return "unkown error";
1645 } // updateSortOrder()
1650 * this function rotates the image according the
1653 private function rotateImage($img, $degrees)
1655 if(function_exists("imagerotate")) {
1656 $img = imagerotate($img, $degrees, 0);
1658 function imagerotate($src_img, $angle)
1660 $src_x = imagesx($src_img);
1661 $src_y = imagesy($src_img);
1662 if ($angle == 180) {
1666 elseif ($src_x <= $src_y) {
1670 elseif ($src_x >= $src_y) {
1675 $rotate=imagecreatetruecolor($dest_x,$dest_y);
1676 imagealphablending($rotate, false);
1681 for ($y = 0; $y < ($src_y); $y++) {
1682 for ($x = 0; $x < ($src_x); $x++) {
1683 $color = imagecolorat($src_img, $x, $y);
1684 imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
1690 for ($y = 0; $y < ($src_y); $y++) {
1691 for ($x = 0; $x < ($src_x); $x++) {
1692 $color = imagecolorat($src_img, $x, $y);
1693 imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
1699 for ($y = 0; $y < ($src_y); $y++) {
1700 for ($x = 0; $x < ($src_x); $x++) {
1701 $color = imagecolorat($src_img, $x, $y);
1702 imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
1716 $img = imagerotate($img, $degrees);
1725 * returns flipped image
1727 * this function will return an either horizontal or
1728 * vertical flipped truecolor image.
1730 private function flipImage($image, $mode)
1732 $w = imagesx($image);
1733 $h = imagesy($image);
1734 $flipped = imagecreatetruecolor($w, $h);
1738 for ($y = 0; $y < $h; $y++) {
1739 imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
1743 for ($x = 0; $x < $w; $x++) {
1744 imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
1754 * return all assigned tags for the specified photo
1756 private function get_photo_tags($idx)
1758 $result = $this->db->db_query("
1761 INNER JOIN photo_tags pt
1763 WHERE pt.photo_id='". $idx ."'
1768 while($row = $this->db->db_fetch_object($result))
1769 $tags[$row['id']] = $row['name'];
1773 } // get_photo_tags()
1776 * create on-the-fly images with text within
1778 public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
1780 if (strlen($color) != 6)
1783 $int = hexdec($color);
1784 $h = imagefontheight($font);
1785 $fw = imagefontwidth($font);
1786 $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
1787 $lines = count($txt);
1788 $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
1789 $bg = imagecolorallocate($im, 255, 255, 255);
1790 $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
1793 foreach ($txt as $text) {
1794 $x = (($w - ($fw * strlen($text))) / 2);
1795 imagestring($im, $font, $x, $y, $text, $color);
1796 $y += ($h + $space);
1799 Header("Content-type: image/png");
1802 } // showTextImage()
1805 * check if all requirements are met
1807 private function check_requirements()
1809 if(!function_exists("imagecreatefromjpeg")) {
1810 print "PHP GD library extension is missing<br />\n";
1814 if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
1815 print "PHP SQLite3 library extension is missing<br />\n";
1819 /* Check for HTML_AJAX PEAR package, lent from Horde project */
1820 ini_set('track_errors', 1);
1821 @include_once 'HTML/AJAX/Server.php';
1822 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1823 print "PEAR HTML_AJAX package is missing<br />\n";
1826 @include_once 'Calendar/Calendar.php';
1827 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1828 print "PEAR Calendar package is missing<br />\n";
1831 @include_once 'Console/Getopt.php';
1832 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1833 print "PEAR Console_Getopt package is missing<br />\n";
1836 @include_once $this->cfg->smarty_path .'/libs/Smarty.class.php';
1837 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1838 print "Smarty template engine can not be found in ". $this->cfg->smarty_path ."/libs/Smarty.class.php<br />\n";
1841 ini_restore('track_errors');
1848 } // check_requirements()
1850 private function _debug($text)
1852 if($this->fromcmd) {
1859 * check if specified MIME type is supported
1861 public function checkifImageSupported($mime)
1863 if(in_array($mime, Array("image/jpeg", "image/png")))
1868 } // checkifImageSupported()
1870 public function _error($text)
1872 switch($this->cfg->logging) {
1875 print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
1876 print $text ."<br />\n";
1882 error_log($text, 3, $his->cfg->log_file);
1886 $this->runtime_error = true;
1891 * output calendard input fields
1893 private function get_calendar($mode)
1895 $year = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
1896 $month = isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
1897 $day = isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
1899 $output = "<input type=\"text\" size=\"3\" id=\"". $mode ."year\" value=\"". $year ."\"";
1900 if(!isset($_SESSION[$mode .'_date']))
1901 $output.= " disabled=\"disabled\"";
1903 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."month\" value=\"". $month ."\"";
1904 if(!isset($_SESSION[$mode .'_date']))
1905 $output.= " disabled=\"disabled\"";
1907 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."day\" value=\"". $day ."\"";
1908 if(!isset($_SESSION[$mode .'_date']))
1909 $output.= " disabled=\"disabled\"";
1917 * output calendar matrix
1919 public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
1921 if (!isset($year)) $year = date('Y');
1922 if (!isset($month)) $month = date('m');
1923 if (!isset($day)) $day = date('d');
1928 require_once CALENDAR_ROOT.'Month/Weekdays.php';
1929 require_once CALENDAR_ROOT.'Day.php';
1932 $month = new Calendar_Month_Weekdays($year,$month);
1935 $prevStamp = $month->prevMonth(true);
1936 $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
1937 $nextStamp = $month->nextMonth(true);
1938 $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
1940 $selectedDays = array (
1941 new Calendar_Day($year,$month,$day),
1942 new Calendar_Day($year,12,25),
1945 // Build the days in the month
1946 $month->build($selectedDays);
1948 $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
1949 $this->tmpl->assign('prev_month', $prev);
1950 $this->tmpl->assign('next_month', $next);
1952 while ( $day = $month->fetch() ) {
1954 if(!isset($matrix[$rows]))
1955 $matrix[$rows] = Array();
1959 $dayStamp = $day->thisDay(true);
1960 $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
1962 // isFirst() to find start of week
1963 if ( $day->isFirst() )
1966 if ( $day->isSelected() ) {
1967 $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
1968 } else if ( $day->isEmpty() ) {
1969 $string.= "<td> </td>\n";
1971 $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
1974 // isLast() to find end of week
1975 if ( $day->isLast() )
1976 $string.= "</tr>\n";
1978 $matrix[$rows][$cols] = $string;
1988 $this->tmpl->assign('matrix', $matrix);
1989 $this->tmpl->assign('rows', $rows);
1990 $this->tmpl->show("calendar.tpl");
1992 } // get_calendar_matrix()
1995 * output export page
1997 public function getExport($mode)
1999 $pictures = $this->getPhotoSelection();
2000 $current_tags = $this->getCurrentTags();
2002 foreach($pictures as $picture) {
2004 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2005 if($current_tags != "") {
2006 $orig_url.= "&tags=". $current_tags;
2008 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2009 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2012 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2017 // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
2018 print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
2022 // "[%pictureurl% %thumbnailurl%]"
2023 print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2026 case 'MoinMoinList':
2027 // " * [%pictureurl% %thumbnailurl%]"
2028 print " " . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2039 public function getRSSFeed()
2041 Header("Content-type: text/xml; charset=utf-8");
2042 print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2045 xmlns:media="http://search.yahoo.com/mrss/"
2046 xmlns:dc="http://purl.org/dc/elements/1.1/"
2049 <title>phpfspot</title>
2050 <description>phpfspot RSS feed</description>
2051 <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2052 <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2053 <generator>phpfspot</generator>
2056 $pictures = $this->getPhotoSelection();
2057 $current_tags = $this->getCurrentTags();
2059 foreach($pictures as $picture) {
2061 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2062 if($current_tags != "") {
2063 $orig_url.= "&tags=". $current_tags;
2065 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2066 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2069 $details = $this->get_photo_details($picture);
2071 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2072 $thumb_html = htmlspecialchars("
2073 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2075 ". $details['description']);
2077 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2079 /* get EXIF information if JPEG */
2080 if($details['mime'] == "image/jpeg") {
2081 $meta = $this->get_meta_informations($orig_path);
2084 $meta_date = isset($meta['FileDateTime']) ? $meta['FileDateTime'] : filemtime($orig_path);
2088 <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2089 <link><?php print htmlspecialchars($orig_url); ?></link>
2090 <guid><?php print htmlspecialchars($orig_url); ?></guid>
2091 <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $meta_date); ?></dc:date.Taken>
2093 <?php print $thumb_html; ?>
2095 <pubDate><?php print strftime("%a, %d %b %Y %T %z", $meta_date); ?></pubDate>
2110 * return all selected tags as one string
2112 private function getCurrentTags()
2115 if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2116 foreach($_SESSION['selected_tags'] as $tag)
2117 $current_tags.= $tag .",";
2118 $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2120 return $current_tags;
2122 } // getCurrentTags()
2125 * return the current photo
2127 public function getCurrentPhoto()
2129 if(isset($_SESSION['current_photo'])) {
2130 print $_SESSION['current_photo'];
2132 } // getCurrentPhoto()
2135 * tells the client browser what to do
2137 * this function is getting called via AJAX by the
2138 * client browsers. it will tell them what they have
2139 * to do next. This is necessary for directly jumping
2140 * into photo index or single photo view when the are
2141 * requested with specific URLs
2143 public function whatToDo()
2145 if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
2146 return "show_photo";
2148 elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
2149 return "showpi_tags";
2151 elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
2155 return "nothing special";
2160 * return the current process-user
2162 private function getuid()
2164 if($uid = posix_getuid()) {
2165 if($user = posix_getpwuid($uid)) {
2166 return $user['name'];
2175 * returns a select-dropdown box to select photo index sort parameters
2177 public function smarty_sort_select_list($params, &$smarty)
2181 foreach($this->sort_orders as $key => $value) {
2182 $output.= "<option value=\"". $key ."\"";
2183 if($key == $_SESSION['sort_order']) {
2184 $output.= " selected=\"selected\"";
2186 $output.= ">". $value ."</option>";
2191 } // smarty_sort_select_list()
2194 * returns the currently selected sort order
2196 private function get_sort_order()
2198 switch($_SESSION['sort_order']) {
2200 return " ORDER BY p.time ASC";
2203 return " ORDER BY p.time DESC";
2206 if($this->dbver < 9) {
2207 return " ORDER BY p.name ASC";
2210 return " ORDER BY basename(p.uri) ASC";
2214 if($this->dbver < 9) {
2215 return " ORDER BY p.name DESC";
2218 return " ORDER BY basename(p.uri) DESC";
2222 return " ORDER BY t.name ASC ,p.time ASC";
2225 return " ORDER BY t.name DESC ,p.time ASC";
2229 } // get_sort_order()
2232 * return the next to be shown slide show image
2234 * this function returns the URL of the next image
2235 * in the slideshow sequence.
2237 public function getNextSlideShowImage()
2239 $all_photos = $this->getPhotoSelection();
2241 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1)
2242 $_SESSION['slideshow_img'] = 0;
2244 $_SESSION['slideshow_img']++;
2246 return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2248 } // getNextSlideShowImage()
2251 * return the previous to be shown slide show image
2253 * this function returns the URL of the previous image
2254 * in the slideshow sequence.
2256 public function getPrevSlideShowImage()
2258 $all_photos = $this->getPhotoSelection();
2260 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
2261 $_SESSION['slideshow_img'] = 0;
2263 $_SESSION['slideshow_img']--;
2265 return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2267 } // getPrevSlideShowImage()
2269 public function resetSlideShow()
2271 if(isset($_SESSION['slideshow_img']))
2272 unset($_SESSION['slideshow_img']);
2274 } // resetSlideShow()
2279 * this function will get all photos from the fspot
2280 * database and randomly return ONE entry
2282 * saddly there is yet no sqlite3 function which returns
2283 * the bulk result in array, so we have to fill up our
2286 public function get_random_photo()
2290 $result = $this->db->db_query("
2295 while($row = $this->db->db_fetch_object($result)) {
2296 array_push($all, $row['id']);
2299 return $all[array_rand($all)];
2301 } // get_random_photo()
2304 * validates provided date
2306 * this function validates if the provided date
2307 * contains a valid date and will return true
2310 public function isValidDate($date_str)
2312 $timestamp = strtotime($date_str);
2314 if(is_numeric($timestamp))
2322 * timestamp to string conversion
2324 private function ts2str($timestamp)
2326 return strftime("%Y-%m-%d", $timestamp);
2329 private function extractTags($tags_str)
2331 $not_validated = split(',', $_GET['tags']);
2332 $validated = array();
2334 foreach($not_validated as $tag) {
2335 if(is_numeric($tag))
2336 array_push($validated, $tag);
2344 * returns the full path to a thumbnail
2346 public function get_thumb_path($width, $photo)
2348 $md5 = $this->getMD5($photo);
2349 $sub_path = substr($md5, 0, 2);
2350 return $this->cfg->thumb_path
2358 } // get_thumb_path()
2361 * returns server's virtual host name
2363 private function get_server_name()
2365 return $_SERVER['SERVER_NAME'];
2366 } // get_server_name()
2369 * returns type of webprotocol which is
2372 private function get_web_protocol()
2374 if(!isset($_SERVER['HTTPS']))
2378 } // get_web_protocol()
2381 * return url to this phpfspot installation
2383 private function get_phpfspot_url()
2385 return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
2386 } // get_phpfspot_url()
2389 * returns the number of photos which are tagged with $tag_id
2391 public function get_num_photos($tag_id)
2393 if($result = $this->db->db_fetchSingleRow("
2394 SELECT count(*) as number
2397 tag_id LIKE '". $tag_id ."'")) {
2399 return $result['number'];
2405 } // get_num_photos()
2408 * check file exists and is readable
2410 * returns true, if everything is ok, otherwise false
2411 * if $silent is not set, this function will output and
2414 private function check_readable($file, $silent = null)
2416 if(!file_exists($file)) {
2418 print "File \"". $file ."\" does not exist.\n";
2422 if(!is_readable($file)) {
2424 print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
2430 } // check_readable()
2433 * check if all needed indices are present
2435 * this function checks, if some needed indices are already
2436 * present, or if not, create them on the fly. they are
2437 * necessary to speed up some queries like that one look for
2438 * all tags, when show_tags is specified in the configuration.
2440 private function checkDbIndices()
2442 $result = $this->db->db_exec("
2443 CREATE INDEX IF NOT EXISTS
2450 } // checkDbIndices()
2453 * retrive F-Spot database version
2455 * this function will return the F-Spot database version number
2456 * It is stored within the sqlite3 database in the table meta
2458 public function getFspotDBVersion()
2460 if($result = $this->db->db_fetchSingleRow("
2461 SELECT data as version
2464 name LIKE 'F-Spot Database Version'
2466 return $result['version'];
2470 } // getFspotDBVersion()
2473 * parse the provided URI and will returned the
2476 public function parse_uri($uri, $mode)
2478 if(($components = parse_url($uri)) !== false) {
2482 return basename($components['path']);
2485 return dirname($components['path']);
2488 return $components['path'];
2498 * validate config options
2500 * this function checks if all necessary configuration options are
2501 * specified and set.
2503 private function check_config_options()
2505 if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
2506 $this->_error("Please set \$page_title in phpfspot_cfg");
2508 if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
2509 $this->_error("Please set \$base_path in phpfspot_cfg");
2511 if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
2512 $this->_error("Please set \$web_path in phpfspot_cfg");
2514 if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
2515 $this->_error("Please set \$thumb_path in phpfspot_cfg");
2517 if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
2518 $this->_error("Please set \$smarty_path in phpfspot_cfg");
2520 if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
2521 $this->_error("Please set \$fspot_db in phpfspot_cfg");
2523 if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
2524 $this->_error("Please set \$db_access in phpfspot_cfg");
2526 if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
2527 $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
2529 if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
2530 $this->_error("Please set \$thumb_width in phpfspot_cfg");
2532 if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
2533 $this->_error("Please set \$thumb_height in phpfspot_cfg");
2535 if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
2536 $this->_error("Please set \$photo_width in phpfspot_cfg");
2538 if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
2539 $this->_error("Please set \$mini_width in phpfspot_cfg");
2541 if(!isset($this->cfg->thumbs_per_page))
2542 $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
2544 if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
2545 $this->_error("Please set \$path_replace_from in phpfspot_cfg");
2547 if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
2548 $this->_error("Please set \$path_replace_to in phpfspot_cfg");
2550 if(!isset($this->cfg->hide_tags))
2551 $this->_error("Please set \$hide_tags in phpfspot_cfg");
2553 if(!isset($this->cfg->theme_name))
2554 $this->_error("Please set \$theme_name in phpfspot_cfg");
2556 if(!isset($this->cfg->logging))
2557 $this->_error("Please set \$logging in phpfspot_cfg");
2559 if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
2561 if(!isset($this->cfg->log_file))
2562 $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
2564 if(!is_writeable($this->cfg->log_file))
2565 $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
2569 /* check for pending slash on web_path */
2570 if(!preg_match("/\/$/", $this->cfg->web_path))
2571 $this->cfg->web_path.= "/";
2573 return $this->runtime_error;
2575 } // check_config_options()
2578 * cleanup phpfspot own database
2580 * When photos are getting delete from F-Spot, there will remain
2581 * remain some residues in phpfspot own database. This function
2582 * will try to wipe them out.
2584 public function cleanup_phpfspot_db()
2586 $to_delete = Array();
2588 $result = $this->cfg_db->db_query("
2591 ORDER BY img_idx ASC
2594 while($row = $this->cfg_db->db_fetch_object($result)) {
2595 if(!$this->db->db_fetchSingleRow("
2598 WHERE id='". $row['img_idx'] ."'")) {
2600 array_push($to_delete, $row['img_idx'], ',');
2604 print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
2606 $this->cfg_db->db_exec("
2608 WHERE img_idx IN (". implode($to_delete) .")
2611 } // cleanup_phpfspot_db()
2614 * return first image of the page, the $current photo
2617 * this function is used to find out the first photo of the
2618 * current page, in which the $current photo lies. this is
2619 * used to display the correct photo, when calling showPhotoIndex()
2622 private function getCurrentPage($current, $max)
2624 if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
2625 for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
2626 if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
2632 } // getCurrentPage()