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";
36 * phpfspot configuration
44 * SQLite database handle to f-spot database
52 * SQLite database handle to phpfspot database
60 * Smarty template engine
61 * @link http://smarty.php.net smarty.php.net
62 * @see PHPFSPOT_TMPL()
76 * list of available, not-selected, tags
83 * true if runtime error occued
87 private $runtime_error = false;
90 * F-Spot database version
97 * class constructor ($cfg, $db, $cfg_db, $tmpl, $db_ver)
99 * this function will be called on class construct
100 * and will check requirements, loads configuration,
101 * open databases and start the user session
103 public function __construct()
106 * register PHPFSPOT class global
108 * @global PHPFSPOT $GLOBALS['phpfspot']
111 $GLOBALS['phpfspot'] =& $this;
113 $this->cfg = new PHPFSPOT_CFG;
115 /* verify config settings */
116 if($this->check_config_options()) {
120 /* set application name and version information */
121 $this->cfg->product = "phpfspot";
122 $this->cfg->version = "1.4";
124 $this->sort_orders= array(
125 'date_asc' => 'Date ↑',
126 'date_desc' => 'Date ↓',
127 'name_asc' => 'Name ↑',
128 'name_desc' => 'Name ↓',
129 'tags_asc' => 'Tags ↑',
130 'tags_desc' => 'Tags ↓',
133 /* Check necessary requirements */
134 if(!$this->check_requirements()) {
138 /******* Opening F-Spot's sqlite database *********/
140 /* Check if database file is writeable */
141 if(!is_writeable($this->cfg->fspot_db)) {
142 print $this->cfg->fspot_db ." is not writeable for user ". $this->getuid() ."\n";
146 /* open the database */
147 $this->db = new PHPFSPOT_DB($this, $this->cfg->fspot_db);
149 /* change sqlite temp directory, if requested */
150 if(isset($this->cfg->sqlite_temp_dir)) {
153 temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
157 $this->dbver = $this->getFspotDBVersion();
159 if(!is_writeable($this->cfg->base_path ."/templates_c")) {
160 print $this->cfg->base_path ."/templates_c: directory is not writeable for user ". $this->getuid() ."\n";
164 if(!is_writeable($this->cfg->thumb_path)) {
165 print $this->cfg->thumb_path .": directory is not writeable for user ". $this->getuid() ."\n";
169 /******* Opening phpfspot's sqlite database *********/
171 /* Check if directory where the database file is stored is writeable */
172 if(!is_writeable(dirname($this->cfg->phpfspot_db))) {
173 print dirname($this->cfg->phpfspot_db) .": directory is not writeable for user ". $this->getuid() ."\n";
177 /* Check if database file is writeable */
178 if(!is_writeable($this->cfg->phpfspot_db)) {
179 print $this->cfg->phpfspot_db ." is not writeable for user ". $this->getuid() ."\n";
183 /* open the database */
184 $this->cfg_db = new PHPFSPOT_DB($this, $this->cfg->phpfspot_db);
186 /* change sqlite temp directory, if requested */
187 if(isset($this->cfg->sqlite_temp_dir)) {
188 $this->cfg_db->db_exec("
190 temp_store_directory = '". $this->cfg->sqlite_temp_dir ."'
194 /* Check if some tables need to be created */
195 $this->check_config_table();
197 /* overload Smarty class with our own template handler */
198 require_once "phpfspot_tmpl.php";
199 $this->tmpl = new PHPFSPOT_TMPL();
201 /* check if all necessary indices exist */
202 $this->checkDbIndices();
204 /* if session is not yet started, do it now */
205 if(session_id() == "")
208 if(!isset($_SESSION['tag_condition']))
209 $_SESSION['tag_condition'] = 'or';
211 if(!isset($_SESSION['sort_order']))
212 $_SESSION['sort_order'] = 'date_desc';
214 if(!isset($_SESSION['searchfor_tag']))
215 $_SESSION['searchfor_tag'] = '';
217 // if begin_with is still set but thumbs_per_page is now 0, unset it
218 if(isset($_SESSION['begin_with']) && $this->cfg->thumbs_per_page == 0)
219 unset($_SESSION['begin_with']);
223 public function __destruct()
229 * show - generate html output
231 * this function can be called after the constructor has
232 * prepared everyhing. it will load the index.tpl smarty
233 * template. if necessary it will registere pre-selects
234 * (photo index, photo, tag search, date search) into
237 public function show()
239 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
240 $this->tmpl->assign('page_title', $this->cfg->page_title);
241 $this->tmpl->assign('current_condition', $_SESSION['tag_condition']);
242 $this->tmpl->assign('template_path', 'themes/'. $this->cfg->theme_name);
244 if(isset($_GET['mode'])) {
246 $_SESSION['start_action'] = $_GET['mode'];
248 switch($_GET['mode']) {
250 if(isset($_GET['tags'])) {
251 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
253 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
254 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
256 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
257 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
261 if(isset($_GET['tags'])) {
262 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
263 $_SESSION['start_action'] = 'showp';
265 if(isset($_GET['id']) && is_numeric($_GET['id'])) {
266 $_SESSION['current_photo'] = $_GET['id'];
267 $_SESSION['start_action'] = 'showp';
269 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
270 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
272 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
273 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
277 $this->tmpl->show("export.tpl");
281 $this->tmpl->show("slideshow.tpl");
285 if(isset($_GET['tags'])) {
286 $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']);
288 if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) {
289 $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00");
291 if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) {
292 $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59");
300 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date']))
301 $this->tmpl->assign('date_search_enabled', true);
303 $this->tmpl->register_function("sort_select_list", array(&$this, "smarty_sort_select_list"), false);
304 $this->tmpl->assign('from_date', $this->get_calendar('from'));
305 $this->tmpl->assign('to_date', $this->get_calendar('to'));
306 $this->tmpl->assign('content_page', 'welcome.tpl');
307 $this->tmpl->show("index.tpl");
312 * get_tags - grab all tags of f-spot's database
314 * this function will get all available tags from
315 * the f-spot database and store them within two
316 * arrays within this class for later usage. in
317 * fact, if the user requests (hide_tags) it will
318 * opt-out some of them.
320 * this function is getting called once by show()
322 private function get_tags()
324 $this->avail_tags = Array();
327 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
330 DISTINCT t1.id as id, t1.name as name
333 INNER JOIN photo_tags
334 pt2 ON pt1.photo_id=pt2.photo_id
340 t2.name IN ('".implode("','",$this->cfg->show_tags)."')
342 t1.sort_priority ASC";
344 $result = $this->db->db_query($query_str);
348 $result = $this->db->db_query("
351 ORDER BY sort_priority ASC
355 while($row = $this->db->db_fetch_object($result)) {
357 $tag_id = $row['id'];
358 $tag_name = $row['name'];
360 /* if the user has specified to ignore this tag in phpfspot's
361 configuration, ignore it here so it does not get added to
364 if(in_array($row['name'], $this->cfg->hide_tags))
367 /* if you include the following if-clause and the user has specified
368 to only show certain tags which are specified in phpfspot's
369 configuration, ignore all others so they will not be added to the
371 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags) &&
372 !in_array($row['name'], $this->cfg->show_tags))
376 $this->tags[$tag_id] = $tag_name;
377 $this->avail_tags[$count] = $tag_id;
385 * extract all photo details
387 * retrieve all available details from f-spot's
388 * database and return them as object
389 * @param integer $idx
390 * @return object|null
392 public function get_photo_details($idx)
394 if($this->dbver < 9) {
396 SELECT p.id, p.name, p.time, p.directory_path, p.description
402 SELECT p.id, p.uri, p.time, p.description
407 /* if show_tags is set, only return details for photos which
408 are specified to be shown
410 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
412 INNER JOIN photo_tags pt
416 WHERE p.id='". $idx ."'
417 AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
421 WHERE p.id='". $idx ."'
425 if($result = $this->db->db_query($query_str)) {
427 $row = $this->db->db_fetch_object($result);
429 if($this->dbver < 9) {
430 $row['uri'] = "file://". $row['directory_path'] ."/". $row['name'];
439 } // get_photo_details
442 * returns aligned photo names
444 * this function returns aligned (length) names for
445 * an specific photo. If the length of the name exceeds
446 * $limit the name will be shrinked (...)
447 * @param integer $idx
448 * @param integer $limit
449 * @return string|null
451 public function getPhotoName($idx, $limit = 0)
453 if($details = $this->get_photo_details($idx)) {
454 if($long_name = $this->parse_uri($details['uri'], 'filename')) {
455 $name = $this->shrink_text($long_name, $limit);
465 * shrink text according provided limit
467 * If the length of the name exceeds $limit the
468 * text will be shortend and some content in between
469 * will be replaced with "..."
471 * @param integer $limit
474 private function shrink_text($text, $limit)
476 if($limit != 0 && strlen($text) > $limit) {
477 $text = substr($text, 0, $limit-5) ."...". substr($text, -($limit-5));
485 * translate f-spoth photo path
487 * as the full-qualified path recorded in the f-spot database
488 * is usally not the same as on the webserver, this function
489 * will replace the path with that one specified in the cfg
490 * @param string $path
493 public function translate_path($path)
495 return str_replace($this->cfg->path_replace_from, $this->cfg->path_replace_to, $path);
500 * control HTML ouput for a single photo
502 * this function provides all the necessary information
503 * for the single photo template.
504 * @param integer photo
506 public function showPhoto($photo)
508 /* get all photos from the current photo selection */
509 $all_photos = $this->getPhotoSelection();
510 $count = count($all_photos);
512 for($i = 0; $i < $count; $i++) {
514 // $get_next will be set, when the photo which has to
515 // be displayed has been found - this means that the
516 // next available is in fact the NEXT image (for the
518 if(isset($get_next)) {
519 $next_img = $all_photos[$i];
523 /* the next photo is our NEXT photo */
524 if($all_photos[$i] == $photo) {
528 $previous_img = $all_photos[$i];
531 if($photo == $all_photos[$i]) {
536 $details = $this->get_photo_details($photo);
543 $orig_path = $this->translate_path($this->parse_uri($details['uri']));
544 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
546 if(!file_exists($orig_path)) {
547 $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
551 if(!is_readable($orig_path)) {
552 $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
556 /* If the thumbnail doesn't exist yet, try to create it */
557 if(!file_exists($thumb_path)) {
558 $this->gen_thumb($photo, true);
559 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
562 /* get mime-type, height and width from the original photo */
563 $info = getimagesize($orig_path);
565 /* get EXIF information if JPEG */
566 if($info['mime'] == "image/jpeg") {
567 $meta = $this->get_meta_informations($orig_path);
570 /* If EXIF data are available, use them */
571 if(isset($meta['ExifImageWidth'])) {
572 $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
574 $meta_res = $info[0] ."x". $info[1];
577 $meta_date = isset($meta['FileDateTime']) ? strftime("%a %x %X", $meta['FileDateTime']) : "n/a";
578 $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
579 $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
581 $extern_link = "index.php?mode=showp&id=". $photo;
582 $current_tags = $this->getCurrentTags();
583 if($current_tags != "") {
584 $extern_link.= "&tags=". $current_tags;
586 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
587 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
590 $this->tmpl->assign('extern_link', $extern_link);
592 if(!file_exists($thumb_path)) {
593 $this->_error("Can't open file ". $thumb_path ."\n");
597 $info_thumb = getimagesize($thumb_path);
599 $this->tmpl->assign('description', $details['description']);
600 $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
602 $this->tmpl->assign('width', $info_thumb[0]);
603 $this->tmpl->assign('height', $info_thumb[1]);
604 $this->tmpl->assign('ExifMadeOn', $meta_date);
605 $this->tmpl->assign('ExifMadeWith', $meta_make);
606 $this->tmpl->assign('ExifOrigResolution', $meta_res);
607 $this->tmpl->assign('ExifFileSize', $meta_size);
609 $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&width=". $this->cfg->photo_width);
610 $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
611 $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
613 $this->tmpl->assign('tags', $this->get_photo_tags($photo));
614 $this->tmpl->assign('current_page', $this->getCurrentPage($current, $count));
615 $this->tmpl->assign('current_img', $photo);
618 $this->tmpl->assign('previous_url', "javascript:showImage(". $previous_img .");");
619 $this->tmpl->assign('prev_img', $previous_img);
623 $this->tmpl->assign('next_url', "javascript:showImage(". $next_img .");");
624 $this->tmpl->assign('next_img', $next_img);
626 $this->tmpl->assign('mini_width', $this->cfg->mini_width);
627 $this->tmpl->assign('photo_width', $this->cfg->photo_width);
628 $this->tmpl->assign('photo_number', $i);
629 $this->tmpl->assign('photo_count', count($all_photos));
631 $this->tmpl->show("single_photo.tpl");
636 * all available tags and tag cloud
638 * this function outputs all available tags (time ordered)
639 * and in addition output them as tag cloud (tags which have
640 * many photos will appears more then others)
642 public function getAvailableTags()
644 /* retrive tags from database */
649 $result = $this->db->db_query("
650 SELECT tag_id as id, count(tag_id) as quantity
660 while($row = $this->db->db_fetch_object($result)) {
661 $tags[$row['id']] = $row['quantity'];
664 // change these font sizes if you will
665 $max_size = 125; // max font size in %
666 $min_size = 75; // min font size in %
669 $max_sat = hexdec('cc');
670 $min_sat = hexdec('44');
672 // get the largest and smallest array values
673 $max_qty = max(array_values($tags));
674 $min_qty = min(array_values($tags));
676 // find the range of values
677 $spread = $max_qty - $min_qty;
678 if (0 == $spread) { // we don't want to divide by zero
682 // determine the font-size increment
683 // this is the increase per tag quantity (times used)
684 $step = ($max_size - $min_size)/($spread);
685 $step_sat = ($max_sat - $min_sat)/($spread);
687 // loop through our tag array
688 foreach ($tags as $key => $value) {
690 if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
693 // calculate CSS font-size
694 // find the $value in excess of $min_qty
695 // multiply by the font-size increment ($size)
696 // and add the $min_size set above
697 $size = $min_size + (($value - $min_qty) * $step);
698 // uncomment if you want sizes in whole %:
701 $color = $min_sat + ($value - $min_qty) * $step_sat;
707 if(isset($this->tags[$key])) {
708 $output.= "<a href=\"javascript:Tags('add', ". $key .");\" class=\"tag\" style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\">". $this->tags[$key] ."</a>, ";
713 $output = substr($output, 0, strlen($output)-2);
716 } // getAvailableTags()
719 * output all selected tags
721 * this function output all tags which have been selected
722 * by the user. the selected tags are stored in the
723 * session-variable $_SESSION['selected_tags']
726 public function getSelectedTags()
728 /* retrive tags from database */
733 foreach($this->avail_tags as $tag)
735 // return all selected tags
736 if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
737 $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
742 $output = substr($output, 0, strlen($output)-2);
746 return "no tags selected";
749 } // getSelectedTags()
752 * add tag to users session variable
754 * this function will add the specified to users current
755 * tag selection. if a date search has been made before
756 * it will be now cleared
759 public function addTag($tag)
761 if(!isset($_SESSION['selected_tags']))
762 $_SESSION['selected_tags'] = Array();
764 if(isset($_SESSION['searchfor_tag']))
765 unset($_SESSION['searchfor_tag']);
767 if(!in_array($tag, $_SESSION['selected_tags']))
768 array_push($_SESSION['selected_tags'], $tag);
776 * remove tag to users session variable
778 * this function removes the specified tag from
779 * users current tag selection
783 public function delTag($tag)
785 if(isset($_SESSION['searchfor_tag']))
786 unset($_SESSION['searchfor_tag']);
788 if(isset($_SESSION['selected_tags'])) {
789 $key = array_search($tag, $_SESSION['selected_tags']);
790 unset($_SESSION['selected_tags'][$key]);
791 sort($_SESSION['selected_tags']);
799 * reset tag selection
801 * if there is any tag selection, it will be
804 public function resetTags()
806 if(isset($_SESSION['selected_tags']))
807 unset($_SESSION['selected_tags']);
812 * returns the value for the autocomplet tag-search
815 public function get_xml_tag_list()
817 if(!isset($_GET['search']) || !is_string($_GET['search']))
818 $_GET['search'] = '';
823 /* retrive tags from database */
826 $matched_tags = Array();
828 header("Content-Type: text/xml");
830 $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
831 $string.= "<results>\n";
833 foreach($this->avail_tags as $tag)
835 if(!empty($_GET['search']) &&
836 preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) &&
837 count($matched_tags) < $length) {
839 $count = $this->get_num_photos($tag);
842 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n";
845 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n";
851 /* if we have collected enough items, break out */
852 if(count($matched_tags) >= $length)
856 $string.= "</results>\n";
860 } // get_xml_tag_list()
866 * if a specific photo was requested (external link)
867 * unset the session variable now
869 public function resetPhotoView()
871 if(isset($_SESSION['current_photo']))
872 unset($_SESSION['current_photo']);
874 } // resetPhotoView();
879 * if any tag search has taken place, reset it now
881 public function resetTagSearch()
883 if(isset($_SESSION['searchfor_tag']))
884 unset($_SESSION['searchfor_tag']);
886 } // resetTagSearch()
891 * if any name search has taken place, reset it now
893 public function resetNameSearch()
895 if(isset($_SESSION['searchfor_name']))
896 unset($_SESSION['searchfor_name']);
898 } // resetNameSearch()
903 * if any date search has taken place, reset
906 public function resetDateSearch()
908 if(isset($_SESSION['from_date']))
909 unset($_SESSION['from_date']);
910 if(isset($_SESSION['to_date']))
911 unset($_SESSION['to_date']);
913 } // resetDateSearch();
916 * return all photo according selection
918 * this function returns all photos based on
919 * the tag-selection, tag- or date-search.
920 * the tag-search also has to take care of AND
921 * and OR conjunctions
924 public function getPhotoSelection()
926 $matched_photos = Array();
927 $additional_where_cond = "";
929 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
930 $from_date = $_SESSION['from_date'];
931 $to_date = $_SESSION['to_date'];
932 $additional_where_cond.= "
933 p.time>='". $from_date ."'
935 p.time<='". $to_date ."'
939 if(isset($_SESSION['searchfor_name'])) {
940 if($this->dbver < 9) {
941 $additional_where_cond.= "
943 p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
945 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
950 $additional_where_cond.= "
952 basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
954 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
960 if(isset($_SESSION['sort_order'])) {
961 $order_str = $this->get_sort_order();
964 /* return a search result */
965 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
967 SELECT DISTINCT pt1.photo_id
969 INNER JOIN photo_tags pt2
970 ON pt1.photo_id=pt2.photo_id
977 WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
979 if(isset($additional_where_cond) && !empty($additional_where_cond))
980 $query_str.= "AND ". $additional_where_cond ." ";
982 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
983 $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
986 if(isset($order_str))
987 $query_str.= $order_str;
989 $result = $this->db->db_query($query_str);
990 while($row = $this->db->db_fetch_object($result)) {
991 array_push($matched_photos, $row['photo_id']);
993 return $matched_photos;
996 /* return according the selected tags */
997 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
999 foreach($_SESSION['selected_tags'] as $tag)
1000 $selected.= $tag .",";
1001 $selected = substr($selected, 0, strlen($selected)-1);
1003 /* photo has to match at least on of the selected tags */
1004 if($_SESSION['tag_condition'] == 'or') {
1006 SELECT DISTINCT pt1.photo_id
1008 INNER JOIN photo_tags pt2
1009 ON pt1.photo_id=pt2.photo_id
1013 ON pt1.photo_id=p.id
1014 WHERE pt1.tag_id IN (". $selected .")
1016 if(isset($additional_where_cond) && !empty($additional_where_cond))
1017 $query_str.= "AND ". $additional_where_cond ." ";
1019 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1020 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
1023 if(isset($order_str))
1024 $query_str.= $order_str;
1026 /* photo has to match all selected tags */
1027 elseif($_SESSION['tag_condition'] == 'and') {
1029 if(count($_SESSION['selected_tags']) >= 32) {
1030 print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
1031 print "evaluate your tag selection. Please remove some tags from your selection.\n";
1035 /* Join together a table looking like
1037 pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
1039 so the query can quickly return all images matching the
1040 selected tags in an AND condition
1045 SELECT DISTINCT pt1.photo_id
1049 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1056 for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
1058 INNER JOIN photo_tags pt". ($i+2) ."
1059 ON pt1.photo_id=pt". ($i+2) .".photo_id
1064 ON pt1.photo_id=p.id
1066 $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
1067 for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
1069 AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
1072 if(isset($additional_where_cond) && !empty($additional_where_cond))
1073 $query_str.= "AND ". $additional_where_cond;
1075 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1076 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1079 if(isset($order_str))
1080 $query_str.= $order_str;
1084 $result = $this->db->db_query($query_str);
1085 while($row = $this->db->db_fetch_object($result)) {
1086 array_push($matched_photos, $row['photo_id']);
1088 return $matched_photos;
1091 /* return all available photos */
1093 SELECT DISTINCT p.id
1095 LEFT JOIN photo_tags pt
1101 if(isset($additional_where_cond) && !empty($additional_where_cond))
1102 $query_str.= "WHERE ". $additional_where_cond ." ";
1104 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1105 if(isset($additional_where_cond) && !empty($additional_where_cond))
1106 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1108 $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1111 if(isset($order_str))
1112 $query_str.= $order_str;
1114 $result = $this->db->db_query($query_str);
1115 while($row = $this->db->db_fetch_object($result)) {
1116 array_push($matched_photos, $row['id']);
1118 return $matched_photos;
1120 } // getPhotoSelection()
1123 * control HTML ouput for photo index
1125 * this function provides all the necessary information
1126 * for the photo index template.
1128 public function showPhotoIndex()
1130 $photos = $this->getPhotoSelection();
1132 $count = count($photos);
1134 /* if all thumbnails should be shown on one page */
1135 if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
1139 /* thumbnails should be splitted up in several pages */
1140 elseif($this->cfg->thumbs_per_page > 0) {
1142 if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
1146 $begin_with = $_SESSION['begin_with'];
1149 $end_with = $begin_with + $this->cfg->thumbs_per_page;
1153 $images[$thumbs] = Array();
1154 $img_height[$thumbs] = Array();
1155 $img_width[$thumbs] = Array();
1156 $img_id[$thumbs] = Array();
1157 $img_name[$thumbs] = Array();
1158 $img_fullname[$thumbs] = Array();
1159 $img_title = Array();
1161 for($i = $begin_with; $i < $end_with; $i++) {
1163 if(isset($photos[$i])) {
1165 $images[$thumbs] = $photos[$i];
1166 $img_id[$thumbs] = $i;
1167 $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
1168 $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0));
1169 $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
1171 $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i]);
1173 if(file_exists($thumb_path)) {
1174 $info = getimagesize($thumb_path);
1175 $img_width[$thumbs] = $info[0];
1176 $img_height[$thumbs] = $info[1];
1182 // +1 for for smarty's selection iteration
1185 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1186 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1188 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1189 $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1190 $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1193 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1194 $this->tmpl->assign('tag_result', 1);
1197 /* do we have to display the page selector ? */
1198 if($this->cfg->thumbs_per_page != 0) {
1202 /* calculate the page switchers */
1203 $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1204 $next_start = $begin_with + $this->cfg->thumbs_per_page;
1206 if($begin_with != 0)
1207 $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");");
1208 if($end_with < $count)
1209 $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");");
1211 $photo_per_page = $this->cfg->thumbs_per_page;
1212 $last_page = ceil($count / $photo_per_page);
1214 /* get the current selected page */
1215 if($begin_with == 0) {
1219 for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1226 for($i = 1; $i <= $last_page; $i++) {
1228 if($current_page == $i)
1229 $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1230 elseif($current_page-1 == $i || $current_page+1 == $i)
1231 $style = "style=\"font-size: 105%;\"";
1232 elseif(($current_page-5 >= $i) && ($i != 1) ||
1233 ($current_page+5 <= $i) && ($i != $last_page))
1234 $style = "style=\"font-size: 75%;\"";
1238 $select = "<a href=\"javascript:showPhotoIndex(". (($i*$photo_per_page)-$photo_per_page) .");\"";
1241 $select.= ">". $i ."</a> ";
1243 // until 9 pages we show the selector from 1-9
1244 if($last_page <= 9) {
1245 $page_select.= $select;
1248 if($i == 1 /* first page */ ||
1249 $i == $last_page /* last page */ ||
1250 $i == $current_page /* current page */ ||
1251 $i == ceil($last_page * 0.25) /* first quater */ ||
1252 $i == ceil($last_page * 0.5) /* half */ ||
1253 $i == ceil($last_page * 0.75) /* third quater */ ||
1254 (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1255 (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 */ ||
1256 $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1257 $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1259 $page_select.= $select;
1267 $page_select.= "......... ";
1272 /* only show the page selector if we have more then one page */
1274 $this->tmpl->assign('page_selector', $page_select);
1278 $current_tags = $this->getCurrentTags();
1279 $extern_link = "index.php?mode=showpi";
1280 $rss_link = "index.php?mode=rss";
1281 if($current_tags != "") {
1282 $extern_link.= "&tags=". $current_tags;
1283 $rss_link.= "&tags=". $current_tags;
1285 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1286 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1287 $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1290 $export_link = "index.php?mode=export";
1291 $slideshow_link = "index.php?mode=slideshow";
1293 $this->tmpl->assign('extern_link', $extern_link);
1294 $this->tmpl->assign('slideshow_link', $slideshow_link);
1295 $this->tmpl->assign('export_link', $export_link);
1296 $this->tmpl->assign('rss_link', $rss_link);
1297 $this->tmpl->assign('count', $count);
1298 $this->tmpl->assign('width', $this->cfg->thumb_width);
1299 $this->tmpl->assign('preview_width', $this->cfg->photo_width);
1300 $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1301 $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1302 $this->tmpl->assign('images', $images);
1303 $this->tmpl->assign('img_width', $img_width);
1304 $this->tmpl->assign('img_height', $img_height);
1305 $this->tmpl->assign('img_id', $img_id);
1306 $this->tmpl->assign('img_name', $img_name);
1307 $this->tmpl->assign('img_fullname', $img_fullname);
1308 $this->tmpl->assign('img_title', $img_title);
1309 $this->tmpl->assign('thumbs', $thumbs);
1311 $this->tmpl->show("photo_index.tpl");
1313 /* if we are returning to photo index from an photo-view,
1314 scroll the window to the last shown photo-thumbnail.
1315 after this, unset the last_photo session variable.
1317 if(isset($_SESSION['last_photo'])) {
1318 print "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n";
1319 unset($_SESSION['last_photo']);
1322 } // showPhotoIndex()
1325 * show credit template
1327 public function showCredits()
1329 $this->tmpl->assign('version', $this->cfg->version);
1330 $this->tmpl->assign('product', $this->cfg->product);
1331 $this->tmpl->assign('db_version', $this->dbver);
1332 $this->tmpl->show("credits.tpl");
1337 * create_thumbnails for the requested width
1339 * this function creates image thumbnails of $orig_image
1340 * stored as $thumb_image. It will check if the image is
1341 * in a supported format, if necessary rotate the image
1342 * (based on EXIF orientation meta headers) and re-sizing.
1343 * @param string $orig_image
1344 * @param string $thumb_image
1345 * @param integer $width
1348 public function create_thumbnail($orig_image, $thumb_image, $width)
1350 if(!file_exists($orig_image)) {
1354 $details = getimagesize($orig_image);
1356 /* check if original photo is a support image type */
1357 if(!$this->checkifImageSupported($details['mime']))
1360 switch($details['mime']) {
1364 $meta = $this->get_meta_informations($orig_image);
1370 switch($meta['Orientation']) {
1371 case 1: /* top, left */
1372 /* nothing to do */ break;
1373 case 2: /* top, right */
1374 $rotate = 0; $flip_hori = true; break;
1375 case 3: /* bottom, left */
1376 $rotate = 180; break;
1377 case 4: /* bottom, right */
1378 $flip_vert = true; break;
1379 case 5: /* left side, top */
1380 $rotate = 90; $flip_vert = true; break;
1381 case 6: /* right side, top */
1382 $rotate = 90; break;
1383 case 7: /* left side, bottom */
1384 $rotate = 270; $flip_vert = true; break;
1385 case 8: /* right side, bottom */
1386 $rotate = 270; break;
1389 $src_img = @imagecreatefromjpeg($orig_image);
1394 $src_img = @imagecreatefrompng($orig_image);
1400 print "Can't load image from ". $orig_image ."\n";
1404 /* grabs the height and width */
1405 $cur_width = imagesx($src_img);
1406 $cur_height = imagesy($src_img);
1408 // If requested width is more then the actual image width,
1409 // do not generate a thumbnail, instead safe the original
1410 // as thumbnail but with lower quality. But if the image
1411 // is to heigh too, then we still have to resize it.
1412 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1413 $result = imagejpeg($src_img, $thumb_image, 75);
1414 imagedestroy($src_img);
1418 // If the image will be rotate because EXIF orientation said so
1419 // 'virtually rotate' the image for further calculations
1420 if($rotate == 90 || $rotate == 270) {
1422 $cur_width = $cur_height;
1426 /* calculates aspect ratio */
1427 $aspect_ratio = $cur_height / $cur_width;
1430 if($aspect_ratio < 1) {
1432 $new_h = abs($new_w * $aspect_ratio);
1434 /* 'virtually' rotate the image and calculate it's ratio */
1435 $tmp_w = $cur_height;
1436 $tmp_h = $cur_width;
1437 /* now get the ratio from the 'rotated' image */
1438 $tmp_ratio = $tmp_h/$tmp_w;
1439 /* now calculate the new dimensions */
1441 $tmp_h = abs($tmp_w * $tmp_ratio);
1443 // now that we know, how high they photo should be, if it
1444 // gets rotated, use this high to scale the image
1446 $new_w = abs($new_h / $aspect_ratio);
1448 // If the image will be rotate because EXIF orientation said so
1449 // now 'virtually rotate' back the image for the image manipulation
1450 if($rotate == 90 || $rotate == 270) {
1457 /* creates new image of that size */
1458 $dst_img = imagecreatetruecolor($new_w, $new_h);
1460 imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1462 /* copies resized portion of original image into new image */
1463 imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1465 /* needs the image to be flipped horizontal? */
1467 $this->_debug("(FLIP)");
1468 $dst_img = $this->flipImage($dst_img, 'hori');
1470 /* needs the image to be flipped vertical? */
1472 $this->_debug("(FLIP)");
1473 $dst_img = $this->flipImage($dst_img, 'vert');
1477 $this->_debug("(ROTATE)");
1478 $dst_img = $this->rotateImage($dst_img, $rotate);
1481 /* write down new generated file */
1482 $result = imagejpeg($dst_img, $thumb_image, 75);
1484 /* free your mind */
1485 imagedestroy($dst_img);
1486 imagedestroy($src_img);
1488 if($result === false) {
1489 print "Can't write thumbnail ". $thumb_image ."\n";
1495 } // create_thumbnail()
1498 * return all exif meta data from the file
1499 * @param string $file
1502 public function get_meta_informations($file)
1504 return exif_read_data($file);
1506 } // get_meta_informations()
1509 * create phpfspot own sqlite database
1511 * this function creates phpfspots own sqlite database
1512 * if it does not exist yet. this own is used to store
1513 * some necessary informations (md5 sum's, ...).
1515 public function check_config_table()
1517 // if the config table doesn't exist yet, create it
1518 if(!$this->cfg_db->db_check_table_exists("images")) {
1519 $this->cfg_db->db_exec("
1520 CREATE TABLE images (
1521 img_idx int primary key,
1527 } // check_config_table
1530 * Generates a thumbnail from photo idx
1532 * This function will generate JPEG thumbnails from provided F-Spot photo
1535 * 1. Check if all thumbnail generations (width) are already in place and
1537 * 2. Check if the md5sum of the original file has changed
1538 * 3. Generate the thumbnails if needed
1539 * @param integer $idx
1540 * @param integer $force
1541 * @param boolean $overwrite
1543 public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
1547 $resolutions = Array(
1548 $this->cfg->thumb_width,
1549 $this->cfg->photo_width,
1550 $this->cfg->mini_width,
1553 /* get details from F-Spot's database */
1554 $details = $this->get_photo_details($idx);
1556 /* calculate file MD5 sum */
1557 $full_path = $this->translate_path($this->parse_uri($details['uri']));
1559 if(!file_exists($full_path)) {
1560 $this->_error("File ". $full_path ." does not exist\n");
1564 if(!is_readable($full_path)) {
1565 $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
1569 $file_md5 = md5_file($full_path);
1571 $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
1575 foreach($resolutions as $resolution) {
1577 $generate_it = false;
1579 $thumb_sub_path = substr($file_md5, 0, 2);
1580 $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
1582 /* if thumbnail-subdirectory does not exist yet, create it */
1583 if(!file_exists(dirname($thumb_path))) {
1584 mkdir(dirname($thumb_path), 0755);
1587 /* if the thumbnail file doesn't exist, create it */
1588 if(!file_exists($thumb_path)) {
1589 $generate_it = true;
1591 /* if the file hasn't changed there is no need to regen the thumb */
1592 elseif($file_md5 != $this->getMD5($idx) || $force) {
1593 $generate_it = true;
1596 if($generate_it || $overwrite) {
1598 $this->_debug(" ". $resolution ."px");
1599 if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1607 $this->_debug(" already exist");
1610 /* set the new/changed MD5 sum for the current photo */
1612 $this->setMD5($idx, $file_md5);
1615 $this->_debug("\n");
1620 * returns stored md5 sum for a specific photo
1622 * this function queries the phpfspot database for a
1623 * stored MD5 checksum of the specified photo
1624 * @param integer $idx
1625 * @return string|null
1627 public function getMD5($idx)
1629 $result = $this->cfg_db->db_query("
1632 WHERE img_idx='". $idx ."'
1638 $img = $this->cfg_db->db_fetch_object($result);
1639 return $img['img_md5'];
1644 * set MD5 sum for the specific photo
1645 * @param integer $idx
1646 * @param string $md5
1648 private function setMD5($idx, $md5)
1650 $result = $this->cfg_db->db_exec("
1651 REPLACE INTO images (img_idx, img_md5)
1652 VALUES ('". $idx ."', '". $md5 ."')
1658 * store current tag condition
1660 * this function stores the current tag condition
1661 * (AND or OR) in the users session variables
1662 * @param string $mode
1665 public function setTagCondition($mode)
1667 $_SESSION['tag_condition'] = $mode;
1671 } // setTagCondition()
1674 * invoke tag & date search
1676 * this function will return all matching tags and store
1677 * them in the session variable selected_tags. furthermore
1678 * it also handles the date search.
1679 * getPhotoSelection() will then only return the matching
1683 public function startSearch()
1685 if(isset($_POST['from']) && $this->isValidDate($_POST['from'])) {
1686 $from = $_POST['from'];
1688 if(isset($_POST['to']) && $this->isValidDate($_POST['to'])) {
1692 if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
1693 $searchfor_tag = $_POST['for_tag'];
1694 $_SESSION['searchfor_tag'] = $_POST['for_tag'];
1697 if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
1698 $searchfor_name = $_POST['for_name'];
1699 $_SESSION['searchfor_name'] = $_POST['for_name'];
1704 if(isset($from) && !empty($from))
1705 $_SESSION['from_date'] = strtotime($from ." 00:00:00");
1707 unset($_SESSION['from_date']);
1709 if(isset($to) && !empty($to))
1710 $_SESSION['to_date'] = strtotime($to ." 23:59:59");
1712 unset($_SESSION['to_date']);
1714 if(isset($searchfor_tag) && !empty($searchfor_tag)) {
1715 /* new search, reset the current selected tags */
1716 $_SESSION['selected_tags'] = Array();
1717 foreach($this->avail_tags as $tag) {
1718 if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
1719 array_push($_SESSION['selected_tags'], $tag);
1728 * updates sort order in session variable
1730 * this function is invoked by RPC and will sort the requested
1731 * sort order in the session variable.
1732 * @param string $sort_order
1735 public function updateSortOrder($order)
1737 if(isset($this->sort_orders[$order])) {
1738 $_SESSION['sort_order'] = $order;
1742 return "unkown error";
1744 } // updateSortOrder()
1749 * this function rotates the image according the
1751 * @param string $img
1752 * @param integer $degress
1755 private function rotateImage($img, $degrees)
1757 if(function_exists("imagerotate")) {
1758 $img = imagerotate($img, $degrees, 0);
1760 function imagerotate($src_img, $angle)
1762 $src_x = imagesx($src_img);
1763 $src_y = imagesy($src_img);
1764 if ($angle == 180) {
1768 elseif ($src_x <= $src_y) {
1772 elseif ($src_x >= $src_y) {
1777 $rotate=imagecreatetruecolor($dest_x,$dest_y);
1778 imagealphablending($rotate, false);
1783 for ($y = 0; $y < ($src_y); $y++) {
1784 for ($x = 0; $x < ($src_x); $x++) {
1785 $color = imagecolorat($src_img, $x, $y);
1786 imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
1792 for ($y = 0; $y < ($src_y); $y++) {
1793 for ($x = 0; $x < ($src_x); $x++) {
1794 $color = imagecolorat($src_img, $x, $y);
1795 imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
1801 for ($y = 0; $y < ($src_y); $y++) {
1802 for ($x = 0; $x < ($src_x); $x++) {
1803 $color = imagecolorat($src_img, $x, $y);
1804 imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
1818 $img = imagerotate($img, $degrees);
1827 * returns flipped image
1829 * this function will return an either horizontal or
1830 * vertical flipped truecolor image.
1831 * @param string $image
1832 * @param string $mode
1835 private function flipImage($image, $mode)
1837 $w = imagesx($image);
1838 $h = imagesy($image);
1839 $flipped = imagecreatetruecolor($w, $h);
1843 for ($y = 0; $y < $h; $y++) {
1844 imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
1848 for ($x = 0; $x < $w; $x++) {
1849 imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
1859 * return all assigned tags for the specified photo
1860 * @param integer $idx
1863 private function get_photo_tags($idx)
1865 $result = $this->db->db_query("
1868 INNER JOIN photo_tags pt
1870 WHERE pt.photo_id='". $idx ."'
1875 while($row = $this->db->db_fetch_object($result))
1876 $tags[$row['id']] = $row['name'];
1880 } // get_photo_tags()
1883 * create on-the-fly images with text within
1884 * @param string $txt
1885 * @param string $color
1886 * @param integer $space
1887 * @param integer $font
1890 public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
1892 if (strlen($color) != 6)
1895 $int = hexdec($color);
1896 $h = imagefontheight($font);
1897 $fw = imagefontwidth($font);
1898 $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
1899 $lines = count($txt);
1900 $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
1901 $bg = imagecolorallocate($im, 255, 255, 255);
1902 $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
1905 foreach ($txt as $text) {
1906 $x = (($w - ($fw * strlen($text))) / 2);
1907 imagestring($im, $font, $x, $y, $text, $color);
1908 $y += ($h + $space);
1911 Header("Content-type: image/png");
1914 } // showTextImage()
1917 * check if all requirements are met
1920 private function check_requirements()
1922 if(!function_exists("imagecreatefromjpeg")) {
1923 print "PHP GD library extension is missing<br />\n";
1927 if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
1928 print "PHP SQLite3 library extension is missing<br />\n";
1932 /* Check for HTML_AJAX PEAR package, lent from Horde project */
1933 ini_set('track_errors', 1);
1934 @include_once 'HTML/AJAX/Server.php';
1935 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1936 print "PEAR HTML_AJAX package is missing<br />\n";
1939 @include_once 'Calendar/Calendar.php';
1940 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1941 print "PEAR Calendar package is missing<br />\n";
1944 @include_once 'Console/Getopt.php';
1945 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1946 print "PEAR Console_Getopt package is missing<br />\n";
1949 @include_once $this->cfg->smarty_path .'/libs/Smarty.class.php';
1950 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1951 print "Smarty template engine can not be found in ". $this->cfg->smarty_path ."/libs/Smarty.class.php<br />\n";
1954 ini_restore('track_errors');
1961 } // check_requirements()
1963 private function _debug($text)
1965 if($this->fromcmd) {
1972 * check if specified MIME type is supported
1973 * @param string $mime
1976 public function checkifImageSupported($mime)
1978 if(in_array($mime, Array("image/jpeg", "image/png")))
1983 } // checkifImageSupported()
1987 * @param string $text
1989 public function _error($text)
1991 switch($this->cfg->logging) {
1994 print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
1995 print $text ."<br />\n";
2001 error_log($text, 3, $his->cfg->log_file);
2005 $this->runtime_error = true;
2010 * output calendard input fields
2011 * @param string $mode
2014 private function get_calendar($mode)
2016 $year = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
2017 $month = isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
2018 $day = isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
2020 $output = "<input type=\"text\" size=\"3\" id=\"". $mode ."year\" value=\"". $year ."\"";
2021 if(!isset($_SESSION[$mode .'_date']))
2022 $output.= " disabled=\"disabled\"";
2024 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."month\" value=\"". $month ."\"";
2025 if(!isset($_SESSION[$mode .'_date']))
2026 $output.= " disabled=\"disabled\"";
2028 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."day\" value=\"". $day ."\"";
2029 if(!isset($_SESSION[$mode .'_date']))
2030 $output.= " disabled=\"disabled\"";
2038 * output calendar matrix
2039 * @param integer $year
2040 * @param integer $month
2041 * @param integer $day
2043 public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
2045 if (!isset($year)) $year = date('Y');
2046 if (!isset($month)) $month = date('m');
2047 if (!isset($day)) $day = date('d');
2052 require_once CALENDAR_ROOT.'Month/Weekdays.php';
2053 require_once CALENDAR_ROOT.'Day.php';
2056 $month = new Calendar_Month_Weekdays($year,$month);
2059 $prevStamp = $month->prevMonth(true);
2060 $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
2061 $nextStamp = $month->nextMonth(true);
2062 $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
2064 $selectedDays = array (
2065 new Calendar_Day($year,$month,$day),
2066 new Calendar_Day($year,12,25),
2069 // Build the days in the month
2070 $month->build($selectedDays);
2072 $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
2073 $this->tmpl->assign('prev_month', $prev);
2074 $this->tmpl->assign('next_month', $next);
2076 while ( $day = $month->fetch() ) {
2078 if(!isset($matrix[$rows]))
2079 $matrix[$rows] = Array();
2083 $dayStamp = $day->thisDay(true);
2084 $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
2086 // isFirst() to find start of week
2087 if ( $day->isFirst() )
2090 if ( $day->isSelected() ) {
2091 $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
2092 } else if ( $day->isEmpty() ) {
2093 $string.= "<td> </td>\n";
2095 $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
2098 // isLast() to find end of week
2099 if ( $day->isLast() )
2100 $string.= "</tr>\n";
2102 $matrix[$rows][$cols] = $string;
2112 $this->tmpl->assign('matrix', $matrix);
2113 $this->tmpl->assign('rows', $rows);
2114 $this->tmpl->show("calendar.tpl");
2116 } // get_calendar_matrix()
2119 * output export page
2120 * @param string $mode
2122 public function getExport($mode)
2124 $pictures = $this->getPhotoSelection();
2125 $current_tags = $this->getCurrentTags();
2127 foreach($pictures as $picture) {
2129 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2130 if($current_tags != "") {
2131 $orig_url.= "&tags=". $current_tags;
2133 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2134 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2137 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2142 // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
2143 print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
2147 // "[%pictureurl% %thumbnailurl%]"
2148 print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2151 case 'MoinMoinList':
2152 // " * [%pictureurl% %thumbnailurl%]"
2153 print " " . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2164 public function getRSSFeed()
2166 Header("Content-type: text/xml; charset=utf-8");
2167 print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2170 xmlns:media="http://search.yahoo.com/mrss/"
2171 xmlns:dc="http://purl.org/dc/elements/1.1/"
2174 <title>phpfspot</title>
2175 <description>phpfspot RSS feed</description>
2176 <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2177 <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2178 <generator>phpfspot</generator>
2181 $pictures = $this->getPhotoSelection();
2182 $current_tags = $this->getCurrentTags();
2184 foreach($pictures as $picture) {
2186 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2187 if($current_tags != "") {
2188 $orig_url.= "&tags=". $current_tags;
2190 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2191 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2194 $details = $this->get_photo_details($picture);
2196 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2197 $thumb_html = htmlspecialchars("
2198 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2200 ". $details['description']);
2202 $orig_path = $this->translate_path($this->parse_uri($details['uri']));
2204 /* get EXIF information if JPEG */
2205 if($details['mime'] == "image/jpeg") {
2206 $meta = $this->get_meta_informations($orig_path);
2209 $meta_date = isset($meta['FileDateTime']) ? $meta['FileDateTime'] : filemtime($orig_path);
2213 <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2214 <link><?php print htmlspecialchars($orig_url); ?></link>
2215 <guid><?php print htmlspecialchars($orig_url); ?></guid>
2216 <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $meta_date); ?></dc:date.Taken>
2218 <?php print $thumb_html; ?>
2220 <pubDate><?php print strftime("%a, %d %b %Y %T %z", $meta_date); ?></pubDate>
2235 * return all selected tags as one string
2238 private function getCurrentTags()
2241 if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2242 foreach($_SESSION['selected_tags'] as $tag)
2243 $current_tags.= $tag .",";
2244 $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2246 return $current_tags;
2248 } // getCurrentTags()
2251 * return the current photo
2253 public function getCurrentPhoto()
2255 if(isset($_SESSION['current_photo'])) {
2256 print $_SESSION['current_photo'];
2258 } // getCurrentPhoto()
2261 * tells the client browser what to do
2263 * this function is getting called via AJAX by the
2264 * client browsers. it will tell them what they have
2265 * to do next. This is necessary for directly jumping
2266 * into photo index or single photo view when the are
2267 * requested with specific URLs
2270 public function whatToDo()
2272 if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
2273 return "show_photo";
2275 elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
2276 return "showpi_tags";
2278 elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
2282 return "nothing special";
2287 * return the current process-user
2290 private function getuid()
2292 if($uid = posix_getuid()) {
2293 if($user = posix_getpwuid($uid)) {
2294 return $user['name'];
2303 * returns a select-dropdown box to select photo index sort parameters
2304 * @param array $params
2305 * @param smarty $smarty
2308 public function smarty_sort_select_list($params, &$smarty)
2312 foreach($this->sort_orders as $key => $value) {
2313 $output.= "<option value=\"". $key ."\"";
2314 if($key == $_SESSION['sort_order']) {
2315 $output.= " selected=\"selected\"";
2317 $output.= ">". $value ."</option>";
2322 } // smarty_sort_select_list()
2325 * returns the currently selected sort order
2328 private function get_sort_order()
2330 switch($_SESSION['sort_order']) {
2332 return " ORDER BY p.time ASC";
2335 return " ORDER BY p.time DESC";
2338 if($this->dbver < 9) {
2339 return " ORDER BY p.name ASC";
2342 return " ORDER BY basename(p.uri) ASC";
2346 if($this->dbver < 9) {
2347 return " ORDER BY p.name DESC";
2350 return " ORDER BY basename(p.uri) DESC";
2354 return " ORDER BY t.name ASC ,p.time ASC";
2357 return " ORDER BY t.name DESC ,p.time ASC";
2361 } // get_sort_order()
2364 * return the next to be shown slide show image
2366 * this function returns the URL of the next image
2367 * in the slideshow sequence.
2370 public function getNextSlideShowImage()
2372 $all_photos = $this->getPhotoSelection();
2374 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1)
2375 $_SESSION['slideshow_img'] = 0;
2377 $_SESSION['slideshow_img']++;
2379 return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2381 } // getNextSlideShowImage()
2384 * return the previous to be shown slide show image
2386 * this function returns the URL of the previous image
2387 * in the slideshow sequence.
2390 public function getPrevSlideShowImage()
2392 $all_photos = $this->getPhotoSelection();
2394 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
2395 $_SESSION['slideshow_img'] = 0;
2397 $_SESSION['slideshow_img']--;
2399 return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2401 } // getPrevSlideShowImage()
2403 public function resetSlideShow()
2405 if(isset($_SESSION['slideshow_img']))
2406 unset($_SESSION['slideshow_img']);
2408 } // resetSlideShow()
2413 * this function will get all photos from the fspot
2414 * database and randomly return ONE entry
2416 * saddly there is yet no sqlite3 function which returns
2417 * the bulk result in array, so we have to fill up our
2421 public function get_random_photo()
2425 $result = $this->db->db_query("
2430 while($row = $this->db->db_fetch_object($result)) {
2431 array_push($all, $row['id']);
2434 return $all[array_rand($all)];
2436 } // get_random_photo()
2439 * validates provided date
2441 * this function validates if the provided date
2442 * contains a valid date and will return true
2444 * @param string $date_str
2447 public function isValidDate($date_str)
2449 $timestamp = strtotime($date_str);
2451 if(is_numeric($timestamp))
2459 * timestamp to string conversion
2460 * @param integer $timestamp
2463 private function ts2str($timestamp)
2465 return strftime("%Y-%m-%d", $timestamp);
2469 * extract tag-names from $_GET['tags']
2470 * @param string $tags_str
2473 private function extractTags($tags_str)
2475 $not_validated = split(',', $tags_str);
2476 $validated = array();
2478 foreach($not_validated as $tag) {
2479 if(is_numeric($tag))
2480 array_push($validated, $tag);
2488 * returns the full path to a thumbnail
2489 * @param integer $width
2490 * @param integer $photo
2493 public function get_thumb_path($width, $photo)
2495 $md5 = $this->getMD5($photo);
2496 $sub_path = substr($md5, 0, 2);
2497 return $this->cfg->thumb_path
2505 } // get_thumb_path()
2508 * returns server's virtual host name
2511 private function get_server_name()
2513 return $_SERVER['SERVER_NAME'];
2514 } // get_server_name()
2517 * returns type of webprotocol which is currently used
2520 private function get_web_protocol()
2522 if(!isset($_SERVER['HTTPS']))
2526 } // get_web_protocol()
2529 * return url to this phpfspot installation
2532 private function get_phpfspot_url()
2534 return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
2535 } // get_phpfspot_url()
2538 * returns the number of photos which are tagged with $tag_id
2539 * @param integer $tag_id
2542 public function get_num_photos($tag_id)
2544 if($result = $this->db->db_fetchSingleRow("
2545 SELECT count(*) as number
2548 tag_id LIKE '". $tag_id ."'")) {
2550 return $result['number'];
2556 } // get_num_photos()
2559 * check file exists and is readable
2561 * returns true, if everything is ok, otherwise false
2562 * if $silent is not set, this function will output and
2564 * @param string $file
2565 * @param boolean $silent
2568 private function check_readable($file, $silent = null)
2570 if(!file_exists($file)) {
2572 print "File \"". $file ."\" does not exist.\n";
2576 if(!is_readable($file)) {
2578 print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
2584 } // check_readable()
2587 * check if all needed indices are present
2589 * this function checks, if some needed indices are already
2590 * present, or if not, create them on the fly. they are
2591 * necessary to speed up some queries like that one look for
2592 * all tags, when show_tags is specified in the configuration.
2594 private function checkDbIndices()
2596 $result = $this->db->db_exec("
2597 CREATE INDEX IF NOT EXISTS
2604 } // checkDbIndices()
2607 * retrive F-Spot database version
2609 * this function will return the F-Spot database version number
2610 * It is stored within the sqlite3 database in the table meta
2611 * @return string|null
2613 public function getFspotDBVersion()
2615 if($result = $this->db->db_fetchSingleRow("
2616 SELECT data as version
2619 name LIKE 'F-Spot Database Version'
2621 return $result['version'];
2625 } // getFspotDBVersion()
2628 * parse the provided URI and will returned the requested chunk
2629 * @param string $uri
2630 * @param string $mode
2633 public function parse_uri($uri, $mode)
2635 if(($components = parse_url($uri)) !== false) {
2639 return basename($components['path']);
2642 return dirname($components['path']);
2645 return $components['path'];
2655 * validate config options
2657 * this function checks if all necessary configuration options are
2658 * specified and set.
2661 private function check_config_options()
2663 if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
2664 $this->_error("Please set \$page_title in phpfspot_cfg");
2666 if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
2667 $this->_error("Please set \$base_path in phpfspot_cfg");
2669 if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
2670 $this->_error("Please set \$web_path in phpfspot_cfg");
2672 if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
2673 $this->_error("Please set \$thumb_path in phpfspot_cfg");
2675 if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
2676 $this->_error("Please set \$smarty_path in phpfspot_cfg");
2678 if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
2679 $this->_error("Please set \$fspot_db in phpfspot_cfg");
2681 if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
2682 $this->_error("Please set \$db_access in phpfspot_cfg");
2684 if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
2685 $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
2687 if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
2688 $this->_error("Please set \$thumb_width in phpfspot_cfg");
2690 if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
2691 $this->_error("Please set \$thumb_height in phpfspot_cfg");
2693 if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
2694 $this->_error("Please set \$photo_width in phpfspot_cfg");
2696 if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
2697 $this->_error("Please set \$mini_width in phpfspot_cfg");
2699 if(!isset($this->cfg->thumbs_per_page))
2700 $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
2702 if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
2703 $this->_error("Please set \$path_replace_from in phpfspot_cfg");
2705 if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
2706 $this->_error("Please set \$path_replace_to in phpfspot_cfg");
2708 if(!isset($this->cfg->hide_tags))
2709 $this->_error("Please set \$hide_tags in phpfspot_cfg");
2711 if(!isset($this->cfg->theme_name))
2712 $this->_error("Please set \$theme_name in phpfspot_cfg");
2714 if(!isset($this->cfg->logging))
2715 $this->_error("Please set \$logging in phpfspot_cfg");
2717 if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
2719 if(!isset($this->cfg->log_file))
2720 $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
2722 if(!is_writeable($this->cfg->log_file))
2723 $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
2727 /* check for pending slash on web_path */
2728 if(!preg_match("/\/$/", $this->cfg->web_path))
2729 $this->cfg->web_path.= "/";
2731 return $this->runtime_error;
2733 } // check_config_options()
2736 * cleanup phpfspot own database
2738 * When photos are getting delete from F-Spot, there will remain
2739 * remain some residues in phpfspot own database. This function
2740 * will try to wipe them out.
2742 public function cleanup_phpfspot_db()
2744 $to_delete = Array();
2746 $result = $this->cfg_db->db_query("
2749 ORDER BY img_idx ASC
2752 while($row = $this->cfg_db->db_fetch_object($result)) {
2753 if(!$this->db->db_fetchSingleRow("
2756 WHERE id='". $row['img_idx'] ."'")) {
2758 array_push($to_delete, $row['img_idx'], ',');
2762 print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
2764 $this->cfg_db->db_exec("
2766 WHERE img_idx IN (". implode($to_delete) .")
2769 } // cleanup_phpfspot_db()
2772 * return first image of the page, the $current photo
2775 * this function is used to find out the first photo of the
2776 * current page, in which the $current photo lies. this is
2777 * used to display the correct photo, when calling showPhotoIndex()
2779 * @param integer $current
2780 * @param integer $max
2783 private function getCurrentPage($current, $max)
2785 if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
2786 for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
2787 if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
2793 } // getCurrentPage()