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
491 * @param integer $width
494 public function translate_path($path, $width = 0)
496 return str_replace($this->cfg->path_replace_from, $this->cfg->path_replace_to, $path);
501 * control HTML ouput for a single photo
503 * this function provides all the necessary information
504 * for the single photo template.
505 * @param integer photo
507 public function showPhoto($photo)
509 /* get all photos from the current photo selection */
510 $all_photos = $this->getPhotoSelection();
511 $count = count($all_photos);
513 for($i = 0; $i < $count; $i++) {
515 // $get_next will be set, when the photo which has to
516 // be displayed has been found - this means that the
517 // next available is in fact the NEXT image (for the
519 if(isset($get_next)) {
520 $next_img = $all_photos[$i];
524 /* the next photo is our NEXT photo */
525 if($all_photos[$i] == $photo) {
529 $previous_img = $all_photos[$i];
532 if($photo == $all_photos[$i]) {
537 $details = $this->get_photo_details($photo);
544 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
545 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
547 if(!file_exists($orig_path)) {
548 $this->_error("Photo ". $orig_path ." does not exist!<br />\n");
552 if(!is_readable($orig_path)) {
553 $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n");
557 /* If the thumbnail doesn't exist yet, try to create it */
558 if(!file_exists($thumb_path)) {
559 $this->gen_thumb($photo, true);
560 $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo);
563 /* get mime-type, height and width from the original photo */
564 $info = getimagesize($orig_path);
566 /* get EXIF information if JPEG */
567 if($info['mime'] == "image/jpeg") {
568 $meta = $this->get_meta_informations($orig_path);
571 /* If EXIF data are available, use them */
572 if(isset($meta['ExifImageWidth'])) {
573 $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength'];
575 $meta_res = $info[0] ."x". $info[1];
578 $meta_date = isset($meta['FileDateTime']) ? strftime("%a %x %X", $meta['FileDateTime']) : "n/a";
579 $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a";
580 $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a";
582 $extern_link = "index.php?mode=showp&id=". $photo;
583 $current_tags = $this->getCurrentTags();
584 if($current_tags != "") {
585 $extern_link.= "&tags=". $current_tags;
587 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
588 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
591 $this->tmpl->assign('extern_link', $extern_link);
593 if(!file_exists($thumb_path)) {
594 $this->_error("Can't open file ". $thumb_path ."\n");
598 $info_thumb = getimagesize($thumb_path);
600 $this->tmpl->assign('description', $details['description']);
601 $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename'));
603 $this->tmpl->assign('width', $info_thumb[0]);
604 $this->tmpl->assign('height', $info_thumb[1]);
605 $this->tmpl->assign('ExifMadeOn', $meta_date);
606 $this->tmpl->assign('ExifMadeWith', $meta_make);
607 $this->tmpl->assign('ExifOrigResolution', $meta_res);
608 $this->tmpl->assign('ExifFileSize', $meta_size);
610 $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&width=". $this->cfg->photo_width);
611 $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo);
612 $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename'));
614 $this->tmpl->assign('tags', $this->get_photo_tags($photo));
615 $this->tmpl->assign('current_page', $this->getCurrentPage($current, $count));
616 $this->tmpl->assign('current_img', $photo);
619 $this->tmpl->assign('previous_url', "javascript:showImage(". $previous_img .");");
620 $this->tmpl->assign('prev_img', $previous_img);
624 $this->tmpl->assign('next_url', "javascript:showImage(". $next_img .");");
625 $this->tmpl->assign('next_img', $next_img);
627 $this->tmpl->assign('mini_width', $this->cfg->mini_width);
628 $this->tmpl->assign('photo_width', $this->cfg->photo_width);
629 $this->tmpl->assign('photo_number', $i);
630 $this->tmpl->assign('photo_count', count($all_photos));
632 $this->tmpl->show("single_photo.tpl");
637 * all available tags and tag cloud
639 * this function outputs all available tags (time ordered)
640 * and in addition output them as tag cloud (tags which have
641 * many photos will appears more then others)
643 public function getAvailableTags()
645 /* retrive tags from database */
650 $result = $this->db->db_query("
651 SELECT tag_id as id, count(tag_id) as quantity
661 while($row = $this->db->db_fetch_object($result)) {
662 $tags[$row['id']] = $row['quantity'];
665 // change these font sizes if you will
666 $max_size = 125; // max font size in %
667 $min_size = 75; // min font size in %
670 $max_sat = hexdec('cc');
671 $min_sat = hexdec('44');
673 // get the largest and smallest array values
674 $max_qty = max(array_values($tags));
675 $min_qty = min(array_values($tags));
677 // find the range of values
678 $spread = $max_qty - $min_qty;
679 if (0 == $spread) { // we don't want to divide by zero
683 // determine the font-size increment
684 // this is the increase per tag quantity (times used)
685 $step = ($max_size - $min_size)/($spread);
686 $step_sat = ($max_sat - $min_sat)/($spread);
688 // loop through our tag array
689 foreach ($tags as $key => $value) {
691 if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags']))
694 // calculate CSS font-size
695 // find the $value in excess of $min_qty
696 // multiply by the font-size increment ($size)
697 // and add the $min_size set above
698 $size = $min_size + (($value - $min_qty) * $step);
699 // uncomment if you want sizes in whole %:
702 $color = $min_sat + ($value - $min_qty) * $step_sat;
708 if(isset($this->tags[$key])) {
709 $output.= "<a href=\"javascript:Tags('add', ". $key .");\" class=\"tag\" style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\">". $this->tags[$key] ."</a>, ";
714 $output = substr($output, 0, strlen($output)-2);
717 } // getAvailableTags()
720 * output all selected tags
722 * this function output all tags which have been selected
723 * by the user. the selected tags are stored in the
724 * session-variable $_SESSION['selected_tags']
727 public function getSelectedTags()
729 /* retrive tags from database */
734 foreach($this->avail_tags as $tag)
736 // return all selected tags
737 if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) {
738 $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, ";
743 $output = substr($output, 0, strlen($output)-2);
747 return "no tags selected";
750 } // getSelectedTags()
753 * add tag to users session variable
755 * this function will add the specified to users current
756 * tag selection. if a date search has been made before
757 * it will be now cleared
760 public function addTag($tag)
762 if(!isset($_SESSION['selected_tags']))
763 $_SESSION['selected_tags'] = Array();
765 if(isset($_SESSION['searchfor_tag']))
766 unset($_SESSION['searchfor_tag']);
768 if(!in_array($tag, $_SESSION['selected_tags']))
769 array_push($_SESSION['selected_tags'], $tag);
777 * remove tag to users session variable
779 * this function removes the specified tag from
780 * users current tag selection
784 public function delTag($tag)
786 if(isset($_SESSION['searchfor_tag']))
787 unset($_SESSION['searchfor_tag']);
789 if(isset($_SESSION['selected_tags'])) {
790 $key = array_search($tag, $_SESSION['selected_tags']);
791 unset($_SESSION['selected_tags'][$key]);
792 sort($_SESSION['selected_tags']);
800 * reset tag selection
802 * if there is any tag selection, it will be
805 public function resetTags()
807 if(isset($_SESSION['selected_tags']))
808 unset($_SESSION['selected_tags']);
813 * returns the value for the autocomplet tag-search
816 public function get_xml_tag_list()
818 if(!isset($_GET['search']) || !is_string($_GET['search']))
819 $_GET['search'] = '';
824 /* retrive tags from database */
827 $matched_tags = Array();
829 header("Content-Type: text/xml");
831 $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
832 $string.= "<results>\n";
834 foreach($this->avail_tags as $tag)
836 if(!empty($_GET['search']) &&
837 preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) &&
838 count($matched_tags) < $length) {
840 $count = $this->get_num_photos($tag);
843 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n";
846 $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n";
852 /* if we have collected enough items, break out */
853 if(count($matched_tags) >= $length)
857 $string.= "</results>\n";
861 } // get_xml_tag_list()
867 * if a specific photo was requested (external link)
868 * unset the session variable now
870 public function resetPhotoView()
872 if(isset($_SESSION['current_photo']))
873 unset($_SESSION['current_photo']);
875 } // resetPhotoView();
880 * if any tag search has taken place, reset it now
882 public function resetTagSearch()
884 if(isset($_SESSION['searchfor_tag']))
885 unset($_SESSION['searchfor_tag']);
887 } // resetTagSearch()
892 * if any name search has taken place, reset it now
894 public function resetNameSearch()
896 if(isset($_SESSION['searchfor_name']))
897 unset($_SESSION['searchfor_name']);
899 } // resetNameSearch()
904 * if any date search has taken place, reset
907 public function resetDateSearch()
909 if(isset($_SESSION['from_date']))
910 unset($_SESSION['from_date']);
911 if(isset($_SESSION['to_date']))
912 unset($_SESSION['to_date']);
914 } // resetDateSearch();
917 * return all photo according selection
919 * this function returns all photos based on
920 * the tag-selection, tag- or date-search.
921 * the tag-search also has to take care of AND
922 * and OR conjunctions
925 public function getPhotoSelection()
927 $matched_photos = Array();
928 $additional_where_cond = "";
930 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
931 $from_date = $_SESSION['from_date'];
932 $to_date = $_SESSION['to_date'];
933 $additional_where_cond.= "
934 p.time>='". $from_date ."'
936 p.time<='". $to_date ."'
940 if(isset($_SESSION['searchfor_name'])) {
941 if($this->dbver < 9) {
942 $additional_where_cond.= "
944 p.name LIKE '%". $_SESSION['searchfor_name'] ."%'
946 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
951 $additional_where_cond.= "
953 basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%'
955 p.description LIKE '%". $_SESSION['searchfor_name'] ."%'
961 if(isset($_SESSION['sort_order'])) {
962 $order_str = $this->get_sort_order();
965 /* return a search result */
966 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') {
968 SELECT DISTINCT pt1.photo_id
970 INNER JOIN photo_tags pt2
971 ON pt1.photo_id=pt2.photo_id
978 WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' ";
980 if(isset($additional_where_cond) && !empty($additional_where_cond))
981 $query_str.= "AND ". $additional_where_cond ." ";
983 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
984 $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')";
987 if(isset($order_str))
988 $query_str.= $order_str;
990 $result = $this->db->db_query($query_str);
991 while($row = $this->db->db_fetch_object($result)) {
992 array_push($matched_photos, $row['photo_id']);
994 return $matched_photos;
997 /* return according the selected tags */
998 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1000 foreach($_SESSION['selected_tags'] as $tag)
1001 $selected.= $tag .",";
1002 $selected = substr($selected, 0, strlen($selected)-1);
1004 /* photo has to match at least on of the selected tags */
1005 if($_SESSION['tag_condition'] == 'or') {
1007 SELECT DISTINCT pt1.photo_id
1009 INNER JOIN photo_tags pt2
1010 ON pt1.photo_id=pt2.photo_id
1014 ON pt1.photo_id=p.id
1015 WHERE pt1.tag_id IN (". $selected .")
1017 if(isset($additional_where_cond) && !empty($additional_where_cond))
1018 $query_str.= "AND ". $additional_where_cond ." ";
1020 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1021 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')";
1024 if(isset($order_str))
1025 $query_str.= $order_str;
1027 /* photo has to match all selected tags */
1028 elseif($_SESSION['tag_condition'] == 'and') {
1030 if(count($_SESSION['selected_tags']) >= 32) {
1031 print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n";
1032 print "evaluate your tag selection. Please remove some tags from your selection.\n";
1036 /* Join together a table looking like
1038 pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ...
1040 so the query can quickly return all images matching the
1041 selected tags in an AND condition
1046 SELECT DISTINCT pt1.photo_id
1050 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1057 for($i = 0; $i < count($_SESSION['selected_tags']); $i++) {
1059 INNER JOIN photo_tags pt". ($i+2) ."
1060 ON pt1.photo_id=pt". ($i+2) .".photo_id
1065 ON pt1.photo_id=p.id
1067 $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." ";
1068 for($i = 1; $i < count($_SESSION['selected_tags']); $i++) {
1070 AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ."
1073 if(isset($additional_where_cond) && !empty($additional_where_cond))
1074 $query_str.= "AND ". $additional_where_cond;
1076 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1077 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1080 if(isset($order_str))
1081 $query_str.= $order_str;
1085 $result = $this->db->db_query($query_str);
1086 while($row = $this->db->db_fetch_object($result)) {
1087 array_push($matched_photos, $row['photo_id']);
1089 return $matched_photos;
1092 /* return all available photos */
1094 SELECT DISTINCT p.id
1096 LEFT JOIN photo_tags pt
1102 if(isset($additional_where_cond) && !empty($additional_where_cond))
1103 $query_str.= "WHERE ". $additional_where_cond ." ";
1105 if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) {
1106 if(isset($additional_where_cond) && !empty($additional_where_cond))
1107 $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1109 $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')";
1112 if(isset($order_str))
1113 $query_str.= $order_str;
1115 $result = $this->db->db_query($query_str);
1116 while($row = $this->db->db_fetch_object($result)) {
1117 array_push($matched_photos, $row['id']);
1119 return $matched_photos;
1121 } // getPhotoSelection()
1124 * control HTML ouput for photo index
1126 * this function provides all the necessary information
1127 * for the photo index template.
1129 public function showPhotoIndex()
1131 $photos = $this->getPhotoSelection();
1133 $count = count($photos);
1135 /* if all thumbnails should be shown on one page */
1136 if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) {
1140 /* thumbnails should be splitted up in several pages */
1141 elseif($this->cfg->thumbs_per_page > 0) {
1143 if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) {
1147 $begin_with = $_SESSION['begin_with'];
1150 $end_with = $begin_with + $this->cfg->thumbs_per_page;
1154 $images[$thumbs] = Array();
1155 $img_height[$thumbs] = Array();
1156 $img_width[$thumbs] = Array();
1157 $img_id[$thumbs] = Array();
1158 $img_name[$thumbs] = Array();
1159 $img_fullname[$thumbs] = Array();
1160 $img_title = Array();
1162 for($i = $begin_with; $i < $end_with; $i++) {
1164 if(isset($photos[$i])) {
1166 $images[$thumbs] = $photos[$i];
1167 $img_id[$thumbs] = $i;
1168 $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15));
1169 $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0));
1170 $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0));
1172 $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i]);
1174 if(file_exists($thumb_path)) {
1175 $info = getimagesize($thumb_path);
1176 $img_width[$thumbs] = $info[0];
1177 $img_height[$thumbs] = $info[1];
1183 // +1 for for smarty's selection iteration
1186 if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '')
1187 $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']);
1189 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1190 $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date']));
1191 $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date']));
1194 if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
1195 $this->tmpl->assign('tag_result', 1);
1198 /* do we have to display the page selector ? */
1199 if($this->cfg->thumbs_per_page != 0) {
1203 /* calculate the page switchers */
1204 $previous_start = $begin_with - $this->cfg->thumbs_per_page;
1205 $next_start = $begin_with + $this->cfg->thumbs_per_page;
1207 if($begin_with != 0)
1208 $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");");
1209 if($end_with < $count)
1210 $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");");
1212 $photo_per_page = $this->cfg->thumbs_per_page;
1213 $last_page = ceil($count / $photo_per_page);
1215 /* get the current selected page */
1216 if($begin_with == 0) {
1220 for($i = $begin_with; $i >= 0; $i-=$photo_per_page) {
1227 for($i = 1; $i <= $last_page; $i++) {
1229 if($current_page == $i)
1230 $style = "style=\"font-size: 125%; text-decoration: underline;\"";
1231 elseif($current_page-1 == $i || $current_page+1 == $i)
1232 $style = "style=\"font-size: 105%;\"";
1233 elseif(($current_page-5 >= $i) && ($i != 1) ||
1234 ($current_page+5 <= $i) && ($i != $last_page))
1235 $style = "style=\"font-size: 75%;\"";
1239 $select = "<a href=\"javascript:showPhotoIndex(". (($i*$photo_per_page)-$photo_per_page) .");\"";
1242 $select.= ">". $i ."</a> ";
1244 // until 9 pages we show the selector from 1-9
1245 if($last_page <= 9) {
1246 $page_select.= $select;
1249 if($i == 1 /* first page */ ||
1250 $i == $last_page /* last page */ ||
1251 $i == $current_page /* current page */ ||
1252 $i == ceil($last_page * 0.25) /* first quater */ ||
1253 $i == ceil($last_page * 0.5) /* half */ ||
1254 $i == ceil($last_page * 0.75) /* third quater */ ||
1255 (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ ||
1256 (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 */ ||
1257 $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ ||
1258 $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) {
1260 $page_select.= $select;
1268 $page_select.= "......... ";
1273 /* only show the page selector if we have more then one page */
1275 $this->tmpl->assign('page_selector', $page_select);
1279 $current_tags = $this->getCurrentTags();
1280 $extern_link = "index.php?mode=showpi";
1281 $rss_link = "index.php?mode=rss";
1282 if($current_tags != "") {
1283 $extern_link.= "&tags=". $current_tags;
1284 $rss_link.= "&tags=". $current_tags;
1286 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
1287 $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1288 $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']);
1291 $export_link = "index.php?mode=export";
1292 $slideshow_link = "index.php?mode=slideshow";
1294 $this->tmpl->assign('extern_link', $extern_link);
1295 $this->tmpl->assign('slideshow_link', $slideshow_link);
1296 $this->tmpl->assign('export_link', $export_link);
1297 $this->tmpl->assign('rss_link', $rss_link);
1298 $this->tmpl->assign('count', $count);
1299 $this->tmpl->assign('width', $this->cfg->thumb_width);
1300 $this->tmpl->assign('preview_width', $this->cfg->photo_width);
1301 $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width);
1302 $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20);
1303 $this->tmpl->assign('images', $images);
1304 $this->tmpl->assign('img_width', $img_width);
1305 $this->tmpl->assign('img_height', $img_height);
1306 $this->tmpl->assign('img_id', $img_id);
1307 $this->tmpl->assign('img_name', $img_name);
1308 $this->tmpl->assign('img_fullname', $img_fullname);
1309 $this->tmpl->assign('img_title', $img_title);
1310 $this->tmpl->assign('thumbs', $thumbs);
1312 $this->tmpl->show("photo_index.tpl");
1314 /* if we are returning to photo index from an photo-view,
1315 scroll the window to the last shown photo-thumbnail.
1316 after this, unset the last_photo session variable.
1318 if(isset($_SESSION['last_photo'])) {
1319 print "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n";
1320 unset($_SESSION['last_photo']);
1323 } // showPhotoIndex()
1326 * show credit template
1328 public function showCredits()
1330 $this->tmpl->assign('version', $this->cfg->version);
1331 $this->tmpl->assign('product', $this->cfg->product);
1332 $this->tmpl->assign('db_version', $this->dbver);
1333 $this->tmpl->show("credits.tpl");
1338 * create_thumbnails for the requested width
1340 * this function creates image thumbnails of $orig_image
1341 * stored as $thumb_image. It will check if the image is
1342 * in a supported format, if necessary rotate the image
1343 * (based on EXIF orientation meta headers) and re-sizing.
1344 * @param string $orig_image
1345 * @param string $thumb_image
1346 * @param integer $width
1349 public function create_thumbnail($orig_image, $thumb_image, $width)
1351 if(!file_exists($orig_image)) {
1355 $details = getimagesize($orig_image);
1357 /* check if original photo is a support image type */
1358 if(!$this->checkifImageSupported($details['mime']))
1361 switch($details['mime']) {
1365 $meta = $this->get_meta_informations($orig_image);
1371 switch($meta['Orientation']) {
1372 case 1: /* top, left */
1373 /* nothing to do */ break;
1374 case 2: /* top, right */
1375 $rotate = 0; $flip_hori = true; break;
1376 case 3: /* bottom, left */
1377 $rotate = 180; break;
1378 case 4: /* bottom, right */
1379 $flip_vert = true; break;
1380 case 5: /* left side, top */
1381 $rotate = 90; $flip_vert = true; break;
1382 case 6: /* right side, top */
1383 $rotate = 90; break;
1384 case 7: /* left side, bottom */
1385 $rotate = 270; $flip_vert = true; break;
1386 case 8: /* right side, bottom */
1387 $rotate = 270; break;
1390 $src_img = @imagecreatefromjpeg($orig_image);
1395 $src_img = @imagecreatefrompng($orig_image);
1401 print "Can't load image from ". $orig_image ."\n";
1405 /* grabs the height and width */
1406 $cur_width = imagesx($src_img);
1407 $cur_height = imagesy($src_img);
1409 // If requested width is more then the actual image width,
1410 // do not generate a thumbnail, instead safe the original
1411 // as thumbnail but with lower quality. But if the image
1412 // is to heigh too, then we still have to resize it.
1413 if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) {
1414 $result = imagejpeg($src_img, $thumb_image, 75);
1415 imagedestroy($src_img);
1419 // If the image will be rotate because EXIF orientation said so
1420 // 'virtually rotate' the image for further calculations
1421 if($rotate == 90 || $rotate == 270) {
1423 $cur_width = $cur_height;
1427 /* calculates aspect ratio */
1428 $aspect_ratio = $cur_height / $cur_width;
1431 if($aspect_ratio < 1) {
1433 $new_h = abs($new_w * $aspect_ratio);
1435 /* 'virtually' rotate the image and calculate it's ratio */
1436 $tmp_w = $cur_height;
1437 $tmp_h = $cur_width;
1438 /* now get the ratio from the 'rotated' image */
1439 $tmp_ratio = $tmp_h/$tmp_w;
1440 /* now calculate the new dimensions */
1442 $tmp_h = abs($tmp_w * $tmp_ratio);
1444 // now that we know, how high they photo should be, if it
1445 // gets rotated, use this high to scale the image
1447 $new_w = abs($new_h / $aspect_ratio);
1449 // If the image will be rotate because EXIF orientation said so
1450 // now 'virtually rotate' back the image for the image manipulation
1451 if($rotate == 90 || $rotate == 270) {
1458 /* creates new image of that size */
1459 $dst_img = imagecreatetruecolor($new_w, $new_h);
1461 imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255));
1463 /* copies resized portion of original image into new image */
1464 imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img));
1466 /* needs the image to be flipped horizontal? */
1468 $this->_debug("(FLIP)");
1469 $dst_img = $this->flipImage($dst_img, 'hori');
1471 /* needs the image to be flipped vertical? */
1473 $this->_debug("(FLIP)");
1474 $dst_img = $this->flipImage($dst_img, 'vert');
1478 $this->_debug("(ROTATE)");
1479 $dst_img = $this->rotateImage($dst_img, $rotate);
1482 /* write down new generated file */
1483 $result = imagejpeg($dst_img, $thumb_image, 75);
1485 /* free your mind */
1486 imagedestroy($dst_img);
1487 imagedestroy($src_img);
1489 if($result === false) {
1490 print "Can't write thumbnail ". $thumb_image ."\n";
1496 } // create_thumbnail()
1499 * return all exif meta data from the file
1500 * @param string $file
1503 public function get_meta_informations($file)
1505 return exif_read_data($file);
1507 } // get_meta_informations()
1510 * create phpfspot own sqlite database
1512 * this function creates phpfspots own sqlite database
1513 * if it does not exist yet. this own is used to store
1514 * some necessary informations (md5 sum's, ...).
1516 public function check_config_table()
1518 // if the config table doesn't exist yet, create it
1519 if(!$this->cfg_db->db_check_table_exists("images")) {
1520 $this->cfg_db->db_exec("
1521 CREATE TABLE images (
1522 img_idx int primary key,
1528 } // check_config_table
1531 * Generates a thumbnail from photo idx
1533 * This function will generate JPEG thumbnails from provided F-Spot photo
1536 * 1. Check if all thumbnail generations (width) are already in place and
1538 * 2. Check if the md5sum of the original file has changed
1539 * 3. Generate the thumbnails if needed
1540 * @param integer $idx
1541 * @param integer $force
1542 * @param boolean $overwrite
1544 public function gen_thumb($idx = 0, $force = 0, $overwrite = false)
1548 $resolutions = Array(
1549 $this->cfg->thumb_width,
1550 $this->cfg->photo_width,
1551 $this->cfg->mini_width,
1554 /* get details from F-Spot's database */
1555 $details = $this->get_photo_details($idx);
1557 /* calculate file MD5 sum */
1558 $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
1560 if(!file_exists($full_path)) {
1561 $this->_error("File ". $full_path ." does not exist\n");
1565 if(!is_readable($full_path)) {
1566 $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n");
1570 $file_md5 = md5_file($full_path);
1572 $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:");
1576 foreach($resolutions as $resolution) {
1578 $generate_it = false;
1580 $thumb_sub_path = substr($file_md5, 0, 2);
1581 $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5;
1583 /* if thumbnail-subdirectory does not exist yet, create it */
1584 if(!file_exists(dirname($thumb_path))) {
1585 mkdir(dirname($thumb_path), 0755);
1588 /* if the thumbnail file doesn't exist, create it */
1589 if(!file_exists($thumb_path)) {
1590 $generate_it = true;
1592 /* if the file hasn't changed there is no need to regen the thumb */
1593 elseif($file_md5 != $this->getMD5($idx) || $force) {
1594 $generate_it = true;
1597 if($generate_it || $overwrite) {
1599 $this->_debug(" ". $resolution ."px");
1600 if(!$this->create_thumbnail($full_path, $thumb_path, $resolution))
1608 $this->_debug(" already exist");
1611 /* set the new/changed MD5 sum for the current photo */
1613 $this->setMD5($idx, $file_md5);
1616 $this->_debug("\n");
1621 * returns stored md5 sum for a specific photo
1623 * this function queries the phpfspot database for a
1624 * stored MD5 checksum of the specified photo
1625 * @param integer $idx
1626 * @return string|null
1628 public function getMD5($idx)
1630 $result = $this->cfg_db->db_query("
1633 WHERE img_idx='". $idx ."'
1639 $img = $this->cfg_db->db_fetch_object($result);
1640 return $img['img_md5'];
1645 * set MD5 sum for the specific photo
1646 * @param integer $idx
1647 * @param string $md5
1649 private function setMD5($idx, $md5)
1651 $result = $this->cfg_db->db_exec("
1652 REPLACE INTO images (img_idx, img_md5)
1653 VALUES ('". $idx ."', '". $md5 ."')
1659 * store current tag condition
1661 * this function stores the current tag condition
1662 * (AND or OR) in the users session variables
1663 * @param string $mode
1666 public function setTagCondition($mode)
1668 $_SESSION['tag_condition'] = $mode;
1672 } // setTagCondition()
1675 * invoke tag & date search
1677 * this function will return all matching tags and store
1678 * them in the session variable selected_tags. furthermore
1679 * it also handles the date search.
1680 * getPhotoSelection() will then only return the matching
1684 public function startSearch()
1686 if(isset($_POST['from']) && $this->isValidDate($_POST['from'])) {
1687 $from = $_POST['from'];
1689 if(isset($_POST['to']) && $this->isValidDate($_POST['to'])) {
1693 if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) {
1694 $searchfor_tag = $_POST['for_tag'];
1695 $_SESSION['searchfor_tag'] = $_POST['for_tag'];
1698 if(isset($_POST['for_name']) && is_string($_POST['for_name'])) {
1699 $searchfor_name = $_POST['for_name'];
1700 $_SESSION['searchfor_name'] = $_POST['for_name'];
1705 if(isset($from) && !empty($from))
1706 $_SESSION['from_date'] = strtotime($from ." 00:00:00");
1708 unset($_SESSION['from_date']);
1710 if(isset($to) && !empty($to))
1711 $_SESSION['to_date'] = strtotime($to ." 23:59:59");
1713 unset($_SESSION['to_date']);
1715 if(isset($searchfor_tag) && !empty($searchfor_tag)) {
1716 /* new search, reset the current selected tags */
1717 $_SESSION['selected_tags'] = Array();
1718 foreach($this->avail_tags as $tag) {
1719 if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag]))
1720 array_push($_SESSION['selected_tags'], $tag);
1729 * updates sort order in session variable
1731 * this function is invoked by RPC and will sort the requested
1732 * sort order in the session variable.
1733 * @param string $sort_order
1736 public function updateSortOrder($order)
1738 if(isset($this->sort_orders[$order])) {
1739 $_SESSION['sort_order'] = $order;
1743 return "unkown error";
1745 } // updateSortOrder()
1750 * this function rotates the image according the
1752 * @param string $img
1753 * @param integer $degress
1756 private function rotateImage($img, $degrees)
1758 if(function_exists("imagerotate")) {
1759 $img = imagerotate($img, $degrees, 0);
1761 function imagerotate($src_img, $angle)
1763 $src_x = imagesx($src_img);
1764 $src_y = imagesy($src_img);
1765 if ($angle == 180) {
1769 elseif ($src_x <= $src_y) {
1773 elseif ($src_x >= $src_y) {
1778 $rotate=imagecreatetruecolor($dest_x,$dest_y);
1779 imagealphablending($rotate, false);
1784 for ($y = 0; $y < ($src_y); $y++) {
1785 for ($x = 0; $x < ($src_x); $x++) {
1786 $color = imagecolorat($src_img, $x, $y);
1787 imagesetpixel($rotate, $dest_x - $y - 1, $x, $color);
1793 for ($y = 0; $y < ($src_y); $y++) {
1794 for ($x = 0; $x < ($src_x); $x++) {
1795 $color = imagecolorat($src_img, $x, $y);
1796 imagesetpixel($rotate, $y, $dest_y - $x - 1, $color);
1802 for ($y = 0; $y < ($src_y); $y++) {
1803 for ($x = 0; $x < ($src_x); $x++) {
1804 $color = imagecolorat($src_img, $x, $y);
1805 imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color);
1819 $img = imagerotate($img, $degrees);
1828 * returns flipped image
1830 * this function will return an either horizontal or
1831 * vertical flipped truecolor image.
1832 * @param string $image
1833 * @param string $mode
1836 private function flipImage($image, $mode)
1838 $w = imagesx($image);
1839 $h = imagesy($image);
1840 $flipped = imagecreatetruecolor($w, $h);
1844 for ($y = 0; $y < $h; $y++) {
1845 imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
1849 for ($x = 0; $x < $w; $x++) {
1850 imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
1860 * return all assigned tags for the specified photo
1861 * @param integer $idx
1864 private function get_photo_tags($idx)
1866 $result = $this->db->db_query("
1869 INNER JOIN photo_tags pt
1871 WHERE pt.photo_id='". $idx ."'
1876 while($row = $this->db->db_fetch_object($result))
1877 $tags[$row['id']] = $row['name'];
1881 } // get_photo_tags()
1884 * create on-the-fly images with text within
1885 * @param string $txt
1886 * @param string $color
1887 * @param integer $space
1888 * @param integer $font
1891 public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300)
1893 if (strlen($color) != 6)
1896 $int = hexdec($color);
1897 $h = imagefontheight($font);
1898 $fw = imagefontwidth($font);
1899 $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n"));
1900 $lines = count($txt);
1901 $im = imagecreate($w, (($h * $lines) + ($lines * $space)));
1902 $bg = imagecolorallocate($im, 255, 255, 255);
1903 $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int);
1906 foreach ($txt as $text) {
1907 $x = (($w - ($fw * strlen($text))) / 2);
1908 imagestring($im, $font, $x, $y, $text, $color);
1909 $y += ($h + $space);
1912 Header("Content-type: image/png");
1915 } // showTextImage()
1918 * check if all requirements are met
1921 private function check_requirements()
1923 if(!function_exists("imagecreatefromjpeg")) {
1924 print "PHP GD library extension is missing<br />\n";
1928 if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) {
1929 print "PHP SQLite3 library extension is missing<br />\n";
1933 /* Check for HTML_AJAX PEAR package, lent from Horde project */
1934 ini_set('track_errors', 1);
1935 @include_once 'HTML/AJAX/Server.php';
1936 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1937 print "PEAR HTML_AJAX package is missing<br />\n";
1940 @include_once 'Calendar/Calendar.php';
1941 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1942 print "PEAR Calendar package is missing<br />\n";
1945 @include_once 'Console/Getopt.php';
1946 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1947 print "PEAR Console_Getopt package is missing<br />\n";
1950 @include_once $this->cfg->smarty_path .'/libs/Smarty.class.php';
1951 if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) {
1952 print "Smarty template engine can not be found in ". $this->cfg->smarty_path ."/libs/Smarty.class.php<br />\n";
1955 ini_restore('track_errors');
1962 } // check_requirements()
1964 private function _debug($text)
1966 if($this->fromcmd) {
1973 * check if specified MIME type is supported
1974 * @param string $mime
1977 public function checkifImageSupported($mime)
1979 if(in_array($mime, Array("image/jpeg", "image/png")))
1984 } // checkifImageSupported()
1988 * @param string $text
1990 public function _error($text)
1992 switch($this->cfg->logging) {
1995 print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n";
1996 print $text ."<br />\n";
2002 error_log($text, 3, $his->cfg->log_file);
2006 $this->runtime_error = true;
2011 * output calendard input fields
2012 * @param string $mode
2015 private function get_calendar($mode)
2017 $year = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y");
2018 $month = isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m");
2019 $day = isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d");
2021 $output = "<input type=\"text\" size=\"3\" id=\"". $mode ."year\" value=\"". $year ."\"";
2022 if(!isset($_SESSION[$mode .'_date']))
2023 $output.= " disabled=\"disabled\"";
2025 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."month\" value=\"". $month ."\"";
2026 if(!isset($_SESSION[$mode .'_date']))
2027 $output.= " disabled=\"disabled\"";
2029 $output.= "<input type=\"text\" size=\"1\" id=\"". $mode ."day\" value=\"". $day ."\"";
2030 if(!isset($_SESSION[$mode .'_date']))
2031 $output.= " disabled=\"disabled\"";
2039 * output calendar matrix
2040 * @param integer $year
2041 * @param integer $month
2042 * @param integer $day
2044 public function get_calendar_matrix($year = 0, $month = 0, $day = 0)
2046 if (!isset($year)) $year = date('Y');
2047 if (!isset($month)) $month = date('m');
2048 if (!isset($day)) $day = date('d');
2053 require_once CALENDAR_ROOT.'Month/Weekdays.php';
2054 require_once CALENDAR_ROOT.'Day.php';
2057 $month = new Calendar_Month_Weekdays($year,$month);
2060 $prevStamp = $month->prevMonth(true);
2061 $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");";
2062 $nextStamp = $month->nextMonth(true);
2063 $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");";
2065 $selectedDays = array (
2066 new Calendar_Day($year,$month,$day),
2067 new Calendar_Day($year,12,25),
2070 // Build the days in the month
2071 $month->build($selectedDays);
2073 $this->tmpl->assign('current_month', date('F Y',$month->getTimeStamp()));
2074 $this->tmpl->assign('prev_month', $prev);
2075 $this->tmpl->assign('next_month', $next);
2077 while ( $day = $month->fetch() ) {
2079 if(!isset($matrix[$rows]))
2080 $matrix[$rows] = Array();
2084 $dayStamp = $day->thisDay(true);
2085 $link = "javascript:setCalendarDate(". date('Y',$dayStamp) .", ". date('n',$dayStamp).", ". date('j',$dayStamp) .");";
2087 // isFirst() to find start of week
2088 if ( $day->isFirst() )
2091 if ( $day->isSelected() ) {
2092 $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n";
2093 } else if ( $day->isEmpty() ) {
2094 $string.= "<td> </td>\n";
2096 $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n";
2099 // isLast() to find end of week
2100 if ( $day->isLast() )
2101 $string.= "</tr>\n";
2103 $matrix[$rows][$cols] = $string;
2113 $this->tmpl->assign('matrix', $matrix);
2114 $this->tmpl->assign('rows', $rows);
2115 $this->tmpl->show("calendar.tpl");
2117 } // get_calendar_matrix()
2120 * output export page
2121 * @param string $mode
2123 public function getExport($mode)
2125 $pictures = $this->getPhotoSelection();
2126 $current_tags = $this->getCurrentTags();
2128 foreach($pictures as $picture) {
2130 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2131 if($current_tags != "") {
2132 $orig_url.= "&tags=". $current_tags;
2134 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2135 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2138 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2143 // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a>
2144 print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n";
2148 // "[%pictureurl% %thumbnailurl%]"
2149 print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2152 case 'MoinMoinList':
2153 // " * [%pictureurl% %thumbnailurl%]"
2154 print " " . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n";
2165 public function getRSSFeed()
2167 Header("Content-type: text/xml; charset=utf-8");
2168 print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2171 xmlns:media="http://search.yahoo.com/mrss/"
2172 xmlns:dc="http://purl.org/dc/elements/1.1/"
2175 <title>phpfspot</title>
2176 <description>phpfspot RSS feed</description>
2177 <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link>
2178 <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate>
2179 <generator>phpfspot</generator>
2182 $pictures = $this->getPhotoSelection();
2183 $current_tags = $this->getCurrentTags();
2185 foreach($pictures as $picture) {
2187 $orig_url = $this->get_phpfspot_url() ."index.php?mode=showp&id=". $picture;
2188 if($current_tags != "") {
2189 $orig_url.= "&tags=". $current_tags;
2191 if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) {
2192 $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date'];
2195 $details = $this->get_photo_details($picture);
2197 $thumb_url = $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width;
2198 $thumb_html = htmlspecialchars("
2199 <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>
2201 ". $details['description']);
2203 $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath'));
2205 /* get EXIF information if JPEG */
2206 if($details['mime'] == "image/jpeg") {
2207 $meta = $this->get_meta_informations($orig_path);
2210 $meta_date = isset($meta['FileDateTime']) ? $meta['FileDateTime'] : filemtime($orig_path);
2214 <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title>
2215 <link><?php print htmlspecialchars($orig_url); ?></link>
2216 <guid><?php print htmlspecialchars($orig_url); ?></guid>
2217 <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $meta_date); ?></dc:date.Taken>
2219 <?php print $thumb_html; ?>
2221 <pubDate><?php print strftime("%a, %d %b %Y %T %z", $meta_date); ?></pubDate>
2236 * return all selected tags as one string
2239 private function getCurrentTags()
2242 if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") {
2243 foreach($_SESSION['selected_tags'] as $tag)
2244 $current_tags.= $tag .",";
2245 $current_tags = substr($current_tags, 0, strlen($current_tags)-1);
2247 return $current_tags;
2249 } // getCurrentTags()
2252 * return the current photo
2254 public function getCurrentPhoto()
2256 if(isset($_SESSION['current_photo'])) {
2257 print $_SESSION['current_photo'];
2259 } // getCurrentPhoto()
2262 * tells the client browser what to do
2264 * this function is getting called via AJAX by the
2265 * client browsers. it will tell them what they have
2266 * to do next. This is necessary for directly jumping
2267 * into photo index or single photo view when the are
2268 * requested with specific URLs
2271 public function whatToDo()
2273 if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') {
2274 return "show_photo";
2276 elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) {
2277 return "showpi_tags";
2279 elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') {
2283 return "nothing special";
2288 * return the current process-user
2291 private function getuid()
2293 if($uid = posix_getuid()) {
2294 if($user = posix_getpwuid($uid)) {
2295 return $user['name'];
2304 * returns a select-dropdown box to select photo index sort parameters
2305 * @param array $params
2306 * @param smarty $smarty
2309 public function smarty_sort_select_list($params, &$smarty)
2313 foreach($this->sort_orders as $key => $value) {
2314 $output.= "<option value=\"". $key ."\"";
2315 if($key == $_SESSION['sort_order']) {
2316 $output.= " selected=\"selected\"";
2318 $output.= ">". $value ."</option>";
2323 } // smarty_sort_select_list()
2326 * returns the currently selected sort order
2329 private function get_sort_order()
2331 switch($_SESSION['sort_order']) {
2333 return " ORDER BY p.time ASC";
2336 return " ORDER BY p.time DESC";
2339 if($this->dbver < 9) {
2340 return " ORDER BY p.name ASC";
2343 return " ORDER BY basename(p.uri) ASC";
2347 if($this->dbver < 9) {
2348 return " ORDER BY p.name DESC";
2351 return " ORDER BY basename(p.uri) DESC";
2355 return " ORDER BY t.name ASC ,p.time ASC";
2358 return " ORDER BY t.name DESC ,p.time ASC";
2362 } // get_sort_order()
2365 * return the next to be shown slide show image
2367 * this function returns the URL of the next image
2368 * in the slideshow sequence.
2371 public function getNextSlideShowImage()
2373 $all_photos = $this->getPhotoSelection();
2375 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1)
2376 $_SESSION['slideshow_img'] = 0;
2378 $_SESSION['slideshow_img']++;
2380 return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2382 } // getNextSlideShowImage()
2385 * return the previous to be shown slide show image
2387 * this function returns the URL of the previous image
2388 * in the slideshow sequence.
2391 public function getPrevSlideShowImage()
2393 $all_photos = $this->getPhotoSelection();
2395 if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0)
2396 $_SESSION['slideshow_img'] = 0;
2398 $_SESSION['slideshow_img']--;
2400 return $this->get_phpfspot_url() ."phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width;
2402 } // getPrevSlideShowImage()
2404 public function resetSlideShow()
2406 if(isset($_SESSION['slideshow_img']))
2407 unset($_SESSION['slideshow_img']);
2409 } // resetSlideShow()
2414 * this function will get all photos from the fspot
2415 * database and randomly return ONE entry
2417 * saddly there is yet no sqlite3 function which returns
2418 * the bulk result in array, so we have to fill up our
2422 public function get_random_photo()
2426 $result = $this->db->db_query("
2431 while($row = $this->db->db_fetch_object($result)) {
2432 array_push($all, $row['id']);
2435 return $all[array_rand($all)];
2437 } // get_random_photo()
2440 * validates provided date
2442 * this function validates if the provided date
2443 * contains a valid date and will return true
2445 * @param string $date_str
2448 public function isValidDate($date_str)
2450 $timestamp = strtotime($date_str);
2452 if(is_numeric($timestamp))
2460 * timestamp to string conversion
2461 * @param integer $timestamp
2464 private function ts2str($timestamp)
2466 return strftime("%Y-%m-%d", $timestamp);
2470 * extract tag-names from $_GET['tags']
2471 * @param string $tags_str
2474 private function extractTags($tags_str)
2476 $not_validated = split(',', $tags_str);
2477 $validated = array();
2479 foreach($not_validated as $tag) {
2480 if(is_numeric($tag))
2481 array_push($validated, $tag);
2489 * returns the full path to a thumbnail
2490 * @param integer $width
2491 * @param integer $photo
2494 public function get_thumb_path($width, $photo)
2496 $md5 = $this->getMD5($photo);
2497 $sub_path = substr($md5, 0, 2);
2498 return $this->cfg->thumb_path
2506 } // get_thumb_path()
2509 * returns server's virtual host name
2512 private function get_server_name()
2514 return $_SERVER['SERVER_NAME'];
2515 } // get_server_name()
2518 * returns type of webprotocol which is currently used
2521 private function get_web_protocol()
2523 if(!isset($_SERVER['HTTPS']))
2527 } // get_web_protocol()
2530 * return url to this phpfspot installation
2533 private function get_phpfspot_url()
2535 return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path;
2536 } // get_phpfspot_url()
2539 * returns the number of photos which are tagged with $tag_id
2540 * @param integer $tag_id
2543 public function get_num_photos($tag_id)
2545 if($result = $this->db->db_fetchSingleRow("
2546 SELECT count(*) as number
2549 tag_id LIKE '". $tag_id ."'")) {
2551 return $result['number'];
2557 } // get_num_photos()
2560 * check file exists and is readable
2562 * returns true, if everything is ok, otherwise false
2563 * if $silent is not set, this function will output and
2565 * @param string $file
2566 * @param boolean $silent
2569 private function check_readable($file, $silent = null)
2571 if(!file_exists($file)) {
2573 print "File \"". $file ."\" does not exist.\n";
2577 if(!is_readable($file)) {
2579 print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n";
2585 } // check_readable()
2588 * check if all needed indices are present
2590 * this function checks, if some needed indices are already
2591 * present, or if not, create them on the fly. they are
2592 * necessary to speed up some queries like that one look for
2593 * all tags, when show_tags is specified in the configuration.
2595 private function checkDbIndices()
2597 $result = $this->db->db_exec("
2598 CREATE INDEX IF NOT EXISTS
2605 } // checkDbIndices()
2608 * retrive F-Spot database version
2610 * this function will return the F-Spot database version number
2611 * It is stored within the sqlite3 database in the table meta
2612 * @return string|null
2614 public function getFspotDBVersion()
2616 if($result = $this->db->db_fetchSingleRow("
2617 SELECT data as version
2620 name LIKE 'F-Spot Database Version'
2622 return $result['version'];
2626 } // getFspotDBVersion()
2629 * parse the provided URI and will returned the requested chunk
2630 * @param string $uri
2631 * @param string $mode
2634 public function parse_uri($uri, $mode)
2636 if(($components = parse_url($uri)) !== false) {
2640 return basename($components['path']);
2643 return dirname($components['path']);
2646 return $components['path'];
2656 * validate config options
2658 * this function checks if all necessary configuration options are
2659 * specified and set.
2662 private function check_config_options()
2664 if(!isset($this->cfg->page_title) || $this->cfg->page_title == "")
2665 $this->_error("Please set \$page_title in phpfspot_cfg");
2667 if(!isset($this->cfg->base_path) || $this->cfg->base_path == "")
2668 $this->_error("Please set \$base_path in phpfspot_cfg");
2670 if(!isset($this->cfg->web_path) || $this->cfg->web_path == "")
2671 $this->_error("Please set \$web_path in phpfspot_cfg");
2673 if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "")
2674 $this->_error("Please set \$thumb_path in phpfspot_cfg");
2676 if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "")
2677 $this->_error("Please set \$smarty_path in phpfspot_cfg");
2679 if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "")
2680 $this->_error("Please set \$fspot_db in phpfspot_cfg");
2682 if(!isset($this->cfg->db_access) || $this->cfg->db_access == "")
2683 $this->_error("Please set \$db_access in phpfspot_cfg");
2685 if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "")
2686 $this->_error("Please set \$phpfspot_db in phpfspot_cfg");
2688 if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "")
2689 $this->_error("Please set \$thumb_width in phpfspot_cfg");
2691 if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "")
2692 $this->_error("Please set \$thumb_height in phpfspot_cfg");
2694 if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "")
2695 $this->_error("Please set \$photo_width in phpfspot_cfg");
2697 if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "")
2698 $this->_error("Please set \$mini_width in phpfspot_cfg");
2700 if(!isset($this->cfg->thumbs_per_page))
2701 $this->_error("Please set \$thumbs_per_page in phpfspot_cfg");
2703 if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "")
2704 $this->_error("Please set \$path_replace_from in phpfspot_cfg");
2706 if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "")
2707 $this->_error("Please set \$path_replace_to in phpfspot_cfg");
2709 if(!isset($this->cfg->hide_tags))
2710 $this->_error("Please set \$hide_tags in phpfspot_cfg");
2712 if(!isset($this->cfg->theme_name))
2713 $this->_error("Please set \$theme_name in phpfspot_cfg");
2715 if(!isset($this->cfg->logging))
2716 $this->_error("Please set \$logging in phpfspot_cfg");
2718 if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') {
2720 if(!isset($this->cfg->log_file))
2721 $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg");
2723 if(!is_writeable($this->cfg->log_file))
2724 $this->_error("The specified \$log_file ". $log_file ." is not writeable!");
2728 /* check for pending slash on web_path */
2729 if(!preg_match("/\/$/", $this->cfg->web_path))
2730 $this->cfg->web_path.= "/";
2732 return $this->runtime_error;
2734 } // check_config_options()
2737 * cleanup phpfspot own database
2739 * When photos are getting delete from F-Spot, there will remain
2740 * remain some residues in phpfspot own database. This function
2741 * will try to wipe them out.
2743 public function cleanup_phpfspot_db()
2745 $to_delete = Array();
2747 $result = $this->cfg_db->db_query("
2750 ORDER BY img_idx ASC
2753 while($row = $this->cfg_db->db_fetch_object($result)) {
2754 if(!$this->db->db_fetchSingleRow("
2757 WHERE id='". $row['img_idx'] ."'")) {
2759 array_push($to_delete, $row['img_idx'], ',');
2763 print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n";
2765 $this->cfg_db->db_exec("
2767 WHERE img_idx IN (". implode($to_delete) .")
2770 } // cleanup_phpfspot_db()
2773 * return first image of the page, the $current photo
2776 * this function is used to find out the first photo of the
2777 * current page, in which the $current photo lies. this is
2778 * used to display the correct photo, when calling showPhotoIndex()
2780 * @param integer $current
2781 * @param integer $max
2784 private function getCurrentPage($current, $max)
2786 if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) {
2787 for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) {
2788 if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page))
2794 } // getCurrentPage()