<?php /*************************************************************************** * * phpfspot, presents your F-Spot photo collection in Web browsers. * * Copyright (c) by Andreas Unterkircher * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * ***************************************************************************/ require_once "phpfspot_cfg.php"; require_once "phpfspot_db.php"; /** * PHPFSPOT main class * * this class contains the most functions which will to the major * work for phpfspot. * * @package phpfspot */ class PHPFSPOT { /** * phpfspot configuration * @access public * @see PHPFSPOT_CFG() * @var PHPFSPOT_CFG */ var $cfg; /** * SQLite database handle to f-spot database * @see PHPFSPOT_DB() * @access public * @var PHPFSPOT_DB */ var $db; /** * SQLite database handle to phpfspot database * @see PHPFSPOT_DB() * @access public * @var PHPFSPOT_DB */ var $cfg_db; /** * Smarty template engine * @link http://smarty.php.net smarty.php.net * @see PHPFSPOT_TMPL() * @access public * @var PHPFSPOT_TMPL */ var $tmpl; /** * full tag - list * @access public * @var array */ var $tags; /** * list of available, not-selected, tags * @access public * @var array */ var $avail_tags; /** * true if runtime error occued * @access private * @var boolean */ private $runtime_error = false; /** * F-Spot database version * @access private * @var integer */ private $dbver; /** * class constructor ($cfg, $db, $cfg_db, $tmpl, $db_ver) * * this function will be called on class construct * and will check requirements, loads configuration, * open databases and start the user session */ public function __construct() { /** * register PHPFSPOT class global * * @global PHPFSPOT $GLOBALS['phpfspot'] * @name $phpfspot */ $GLOBALS['phpfspot'] =& $this; $this->cfg = new PHPFSPOT_CFG; /* verify config settings */ if($this->check_config_options()) { exit(1); } /* set application name and version information */ $this->cfg->product = "phpfspot"; $this->cfg->version = "1.7"; $this->cfg->db_version = 2; $this->sort_orders= array( 'date_asc' => 'Date ↑', 'date_desc' => 'Date ↓', 'name_asc' => 'Name ↑', 'name_desc' => 'Name ↓', 'tags_asc' => 'Tags ↑', 'tags_desc' => 'Tags ↓', ); /* Check necessary requirements */ if(!$this->check_requirements()) { exit(1); } /******* Opening F-Spot's sqlite database *********/ /* Check if database file exists and is readable */ if(!file_exists($this->cfg->fspot_db) || !is_readable($this->cfg->fspot_db)) { print "Error: ". $this->cfg->fspot_db ." does not exist or is not readable for user ". $this->getuid() .".\n"; exit(1); } /* Check if database file is writeable */ if(!is_writeable($this->cfg->fspot_db)) { print "Error: ". $this->cfg->fspot_db ." is not writeable for user ". $this->getuid() .".\n"; print "Please fix permissions so phpfspot can create indices within the F-Spot database to" ." speed up some database operations.\n"; exit(1); } /* open the database */ $this->db = new PHPFSPOT_DB($this, $this->cfg->fspot_db); /* change sqlite temp directory, if requested */ if(isset($this->cfg->sqlite_temp_dir)) { $this->db->db_exec(" PRAGMA temp_store_directory = '". $this->cfg->sqlite_temp_dir ."' "); } /* get F-Spot database version */ $this->dbver = $this->getFspotDBVersion(); if(!is_writeable($this->cfg->base_path ."/templates_c")) { print $this->cfg->base_path ."/templates_c: directory is not writeable for user ". $this->getuid() ."\n"; exit(1); } if(!is_writeable($this->cfg->thumb_path)) { print $this->cfg->thumb_path .": directory is not writeable for user ". $this->getuid() ."\n"; exit(1); } /******* Opening phpfspot's sqlite database *********/ /* Check if directory where the database file is stored is writeable */ if(!is_writeable(dirname($this->cfg->phpfspot_db))) { print "Error: ". dirname($this->cfg->phpfspot_db) .": directory is not writeable for user ". $this->getuid() .".\n"; print "Please fix permissions so phpfspot can create its own sqlite database to store some settings.\n"; exit(1); } /* Check if database file is writeable */ if(file_exists($this->cfg->phpfspot_db) && !is_writeable($this->cfg->phpfspot_db)) { print "Error: ". $this->cfg->phpfspot_db ." is not writeable for user ". $this->getuid() .".\n"; print "Please fix permissions so phpfspot can create its own sqlite database to store some settings.\n"; exit(1); } /* open the database */ $this->cfg_db = new PHPFSPOT_DB($this, $this->cfg->phpfspot_db); /* change sqlite temp directory, if requested */ if(isset($this->cfg->sqlite_temp_dir)) { $this->cfg_db->db_exec(" PRAGMA temp_store_directory = '". $this->cfg->sqlite_temp_dir ."' "); } /* Check if some tables need to be created */ $this->check_phpfspot_db(); /* overload Smarty class with our own template handler */ require_once "phpfspot_tmpl.php"; $this->tmpl = new PHPFSPOT_TMPL(); /* pre-set some template variables */ $this->tmpl->assign('web_path', $this->cfg->web_path); /* Starting with F-Spot 0.4.2, the rating-feature was available */ if($this->dbver > 10) { $this->tmpl->assign('has_rating', true); $this->sort_orders = array_merge($this->sort_orders, array( 'rate_asc' => 'Rate ↑', 'rate_desc' => 'Rate ↓', )); } /* check if all necessary indices exist */ $this->checkDbIndices(); /* if session is not yet started, do it now */ if(session_id() == "") session_start(); if(!isset($_SESSION['tag_condition'])) $_SESSION['tag_condition'] = 'or'; /* if sort-order has not been set yet, get the one specified in the config */ if(!isset($_SESSION['sort_order'])) $_SESSION['sort_order'] = $this->cfg->sort_order; if(!isset($_SESSION['searchfor_tag'])) $_SESSION['searchfor_tag'] = ''; // if begin_with is still set but thumbs_per_page is now 0, unset it if(isset($_SESSION['begin_with']) && $this->cfg->thumbs_per_page == 0) unset($_SESSION['begin_with']); // if user-friendly-url's are enabled, set also a flag for the template handler if($this->is_user_friendly_url()) { $this->tmpl->assign('user_friendly_url', 'true'); } } // __construct() public function __destruct() { } // __destruct() /** * show - generate html output * * this function can be called after the constructor has * prepared everyhing. it will load the index.tpl smarty * template. if necessary it will registere pre-selects * (photo index, photo, tag search, date search) into * users session. */ public function show() { $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']); $this->tmpl->assign('page_title', $this->cfg->page_title); $this->tmpl->assign('current_condition', $_SESSION['tag_condition']); $this->tmpl->assign('template_path', 'themes/'. $this->cfg->theme_name); /* parse URL */ if($this->is_user_friendly_url()) { $content = $this->parse_user_friendly_url($_SERVER['REQUEST_URI']); } if(isset($_GET['mode'])) { $_SESSION['start_action'] = $_GET['mode']; switch($_GET['mode']) { case 'showpi': if(isset($_GET['tags'])) { $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']); } if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) { $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00"); } if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) { $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59"); } break; case 'showp': if(isset($_GET['tags'])) { $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']); $_SESSION['start_action'] = 'showp'; } if(isset($_GET['id']) && is_numeric($_GET['id'])) { if($_SESSION['current_photo'] != $_GET['id']) unset($_SESSION['current_version']); $_SESSION['current_photo'] = $_GET['id']; $_SESSION['start_action'] = 'showp'; } if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) { $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00"); } if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) { $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59"); } break; case 'export': /* fetch export template */ print $this->tmpl->fetch("export.tpl"); /* no further execution necessary. */ return; break; case 'slideshow': /* fetch slideshow template */ print $this->tmpl->show("slideshow.tpl"); /* no further execution necessary. */ return; break; case 'rss': if(isset($_GET['tags'])) { $_SESSION['selected_tags'] = $this->extractTags($_GET['tags']); } if(isset($_GET['from_date']) && $this->isValidDate($_GET['from_date'])) { $_SESSION['from_date'] = strtotime($_GET['from_date'] ." 00:00:00"); } if(isset($_GET['to_date']) && $this->isValidDate($_GET['to_date'])) { $_SESSION['to_date'] = strtotime($_GET['to_date'] ." 23:59:59"); } $this->getRSSFeed(); return; break; } } /* if date-search variables are registered in the session, set the check for "consider date-range" in the html output */ if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) $this->tmpl->assign('date_search_enabled', true); /* if rate-search variables are registered in the session, set the check for "consider rate-range" in the html output */ if(isset($_SESSION['rate_from']) && isset($_SESSION['rate_to'])) { $this->tmpl->assign('rate_search_enabled', true); } $this->tmpl->register_function("sort_select_list", array(&$this, "smarty_sort_select_list"), false); $this->tmpl->assign('search_from_date', $this->get_date_text_field('from')); $this->tmpl->assign('search_to_date', $this->get_date_text_field('to')); $this->tmpl->assign('preset_selected_tags', $this->getSelectedTags()); $this->tmpl->assign('preset_available_tags', $this->getAvailableTags()); $this->tmpl->assign('rate_search', $this->get_rate_search()); /* if no site-content has been set yet... */ if(!isset($content)) { /* if tags are already selected, we can immediately display photo-index */ if((isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags']) && isset($_SESSION['start_action']) && $_SESSION['start_action'] != 'showp') || (isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi')) $this->tmpl->assign('initial_content', $this->showPhotoIndex()); else { /* if a photo is already selected, we can immediately display single-photo */ if(isset($_SESSION['current_photo']) && !empty($_SESSION['current_photo'])) $this->tmpl->assign('initial_content', $this->showPhoto($_SESSION['current_photo'])); else { /* ok, then let us show the welcome page... */ $this->tmpl->assign('initial_content', $this->tmpl->fetch('welcome.tpl')); } } } else $this->tmpl->assign('initial_content', $content); $this->tmpl->show("index.tpl"); } // show() /** * get_tags - grab all tags of f-spot's database * * this function will get all available tags from * the f-spot database and store them within two * arrays within this class for later usage. in * fact, if the user requests (hide_tags) it will * opt-out some of them. * * this function is getting called once by show() */ private function get_tags() { $this->avail_tags = Array(); $count = 0; /* if show_tags has been set in the configuration (only show photos which are tagged by these tags) they following will take care, that only these other tags are displayed where the photo is also tagged with one of show_tags. */ if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) { $query_str=" SELECT DISTINCT t1.id as id, t1.name as name FROM photo_tags pt1 INNER JOIN photo_tags pt2 ON pt1.photo_id=pt2.photo_id INNER JOIN tags t1 ON t1.id=pt1.tag_id INNER JOIN tags t2 ON t2.id=pt2.tag_id WHERE t2.name IN ('".implode("','",$this->cfg->show_tags)."') ORDER BY t1.sort_priority ASC"; $result = $this->db->db_query($query_str); } else { $result = $this->db->db_query(" SELECT id as id,name as name FROM tags ORDER BY sort_priority ASC "); } while($row = $this->db->db_fetch_object($result)) { $tag_id = $row['id']; $tag_name = $row['name']; /* if the user has specified to ignore this tag in phpfspot's configuration, ignore it here so it does not get added to the tag list. */ if(in_array($row['name'], $this->cfg->hide_tags)) continue; /* if you include the following if-clause and the user has specified to only show certain tags which are specified in phpfspot's configuration, ignore all others so they will not be added to the tag list. if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags) && !in_array($row['name'], $this->cfg->show_tags)) continue; */ $this->tags[$tag_id] = $tag_name; $this->avail_tags[$count] = $tag_id; $count++; } } // get_tags() /** * get all photo details from F-Spot database * * this function queries the F-Spot database for all available * details of the requested photo. It returns them as a object. * * Furthermore it takes care of the photo version to be requested. * If photo version is not yet, it queries information for the * original photo. * * @param integer $idx * @return object|null */ public function get_photo_details($idx, $version_idx = NULL) { /* ~ F-Spot version 0.3.x */ if($this->dbver < 9) { $query_str = " SELECT p.id as id, p.name as name, p.time as time, p.directory_path as directory_path, p.description as description FROM photos p "; } else { /* till F-Spot version 0.4.1 */ if($this->dbver < 11) { $query_str = " SELECT p.id as id, p.uri as uri, p.time as time, p.description as description FROM photos p "; } elseif($this->dbver < 17) { /* rating value got introduced */ $query_str = " SELECT p.id as id, p.uri as uri, p.time as time, p.description as description, p.rating as rating FROM photos p "; } else { /* path & filename now splited in base_uri & filename */ $query_str = " SELECT p.id as id, p.base_uri ||'/'|| p.filename as uri, p.time as time, p.description as description, p.rating as rating FROM photos p "; } } /* if show_tags is set, only return details of photos which are tagged with a tag that has been specified to be shown. */ if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) { $query_str.= " INNER JOIN photo_tags pt ON p.id=pt.photo_id INNER JOIN tags t ON pt.tag_id=t.id WHERE p.id='". $idx ."' AND t.name IN ('".implode("','",$this->cfg->show_tags)."')"; } else { $query_str.= " WHERE p.id='". $idx ."' "; } if(!$row = $this->db->db_fetchSingleRow($query_str)) return null; /* before F-Spot db version 9 there was no uri column but seperated fields for directory_path and name (= filename). */ if($this->dbver < 9) { $row['uri'] = "file://". $row['directory_path'] ."/". $row['name']; } /* starting with dbversion >= 17 we need to rawurldecode() uri */ elseif($this->dbver >= 17) { $row['uri'] = rawurldecode($row['uri']); } /* if version-idx has not yet been set, get the latest photo version */ if(!isset($version_idx) || !$this->is_valid_version($idx, $version_idx)) $version_idx = $this->get_latest_version($idx); /* if an alternative version has been requested. But we support this only for F-Spot database versions from v9. */ if($version_idx > 0 && $this->dbver >= 9) { if ($this->dbver < 17) { /* check for alternative versions */ if($version = $this->db->db_fetchSingleRow(" SELECT version_id, name, uri FROM photo_versions WHERE photo_id LIKE '". $idx ."' AND version_id LIKE '". $version_idx ."'")) { $row['name'] = $version['name']; $row['uri'] = $version['uri']; } } else { /* path & filename now splited in base_uri & filename */ if($version = $this->db->db_fetchSingleRow(" SELECT version_id, name, base_uri || '/'||filename as uri FROM photo_versions WHERE photo_id LIKE '". $idx ."' AND version_id LIKE '". $version_idx ."'")) { $row['name'] = $version['name']; $row['uri'] = rawurldecode($version['uri']); } } } return $row; } // get_photo_details() /** * returns aligned photo names * * this function returns aligned (length) names for a specific photo. * If the length of the name exceeds $limit the name will bei * shrinked (...). * * @param integer $idx * @param integer $limit * @return string|null */ public function getPhotoName($idx, $limit = 0) { if($details = $this->get_photo_details($idx)) { if($long_name = $this->parse_uri($details['uri'], 'filename')) { $name = $this->shrink_text($long_name, $limit); return $name; } } return null; } // getPhotoName() /** * get photo rating level * * this function will return the integer-based rating level of a * photo. This can only be done, if the F-Spot database is at a * specific version. If rating value can not be found, zero will * be returned indicating no rating value is available. * * @param integer idx * @return integer */ public function get_photo_rating($idx) { if($detail = $this->get_photo_details($idx)) { if(isset($detail['rating'])) return $detail['rating']; } return 0; } // get_photo_rating() /** * get rate-search bars * * this function will return the rating-bars for the search field. * * @return string */ public function get_rate_search() { $bar = ""; for($i = 1; $i <= 5; $i++) { $bar.= "<img id=\"rate_from_". $i ."\" src=\""; if(isset($_SESSION['rate_from']) && $i <= $_SESSION['rate_from']) $bar.= $this->cfg->web_path ."/resources/star.png"; else $bar.= $this->cfg->web_path ."/resources/empty_rate.png"; $bar.= "\" onmouseover=\"show_rate('from', ". $i .");\" onmouseout=\"reset_rate('from');\" onclick=\"set_rate('from', ". $i .")\" />"; } $bar.= "<br />\n"; for($i = 1; $i <= 5; $i++) { $bar.= "<img id=\"rate_to_". $i ."\" src=\""; if(isset($_SESSION['rate_to']) && $i <= $_SESSION['rate_to']) $bar.= $this->cfg->web_path ."/resources/star.png"; else $bar.= $this->cfg->web_path ."/resources/empty_rate.png"; $bar.= "\" onmouseover=\"show_rate('to', ". $i .");\" onmouseout=\"reset_rate('to');\" onclick=\"set_rate('to', ". $i .");\" />"; } return $bar; } // get_rate_search() /** * shrink text according provided limit * * If the length of the name exceeds $limit, text will be shortend * and inner content will be replaced with "...". * * @param string $ext * @param integer $limit * @return string */ private function shrink_text($text, $limit) { if($limit != 0 && strlen($text) > $limit) { $text = substr($text, 0, $limit-5) ."...". substr($text, -($limit-5)); } return $text; } // shrink_text(); /** * translate f-spoth photo path * * as the full-qualified path recorded in the f-spot database * is usally not the same as on the webserver, this function * will replace the path with that one specified in the cfg * @param string $path * @return string */ public function translate_path($path) { if($this->cfg->enable_replace_path == true) return str_replace( $this->cfg->path_replace_from, $this->cfg->path_replace_to, $path); return $path; } // translate_path /** * control HTML ouput for a single photo * * this function provides all the necessary information * for the single photo template. * @param integer photo */ public function showPhoto($photo) { /* get all photos from the current photo selection */ $all_photos = $this->getPhotoSelection(); $count = count($all_photos); for($i = 0; $i < $count; $i++) { // $get_next will be set, when the photo which has to // be displayed has been found - this means that the // next available is in fact the NEXT image (for the // navigation icons) if(isset($get_next)) { $next_img = $all_photos[$i]; break; } /* the next photo is our NEXT photo */ if($all_photos[$i] == $photo) { $get_next = 1; } else { $previous_img = $all_photos[$i]; } if($photo == $all_photos[$i]) { $current = $i; } } $details = $this->get_photo_details($photo); if(!$details) { print "error"; return; } $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath')); /* if current version is already set, use it */ if($this->get_current_version() !== false) $version = $this->get_current_version(); /* if version not set yet, we assume to display the latest version */ if(!isset($version) || !$this->is_valid_version($photo, $version)) $version = $this->get_latest_version($photo); $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo, $version); if(!file_exists($orig_path)) { $this->_error("Photo ". $orig_path ." does not exist!<br />\n"); return; } if(!is_readable($orig_path)) { $this->_error("Photo ". $orig_path ." is not readable for user ". $this->getuid() ."<br />\n"); return; } /* If the thumbnail doesn't exist yet, try to create it */ if(!file_exists($thumb_path)) { $this->gen_thumb($photo, true); $thumb_path = $this->get_thumb_path($this->cfg->photo_width, $photo, $version); } /* get mime-type, height and width from the original photo */ $info = getimagesize($orig_path); /* get EXIF information if JPEG */ if(isset($info['mime']) && $info['mime'] == "image/jpeg") { $meta = $this->get_meta_informations($orig_path); } /* If EXIF data are available, use them */ if(isset($meta['ExifImageWidth'])) { $meta_res = $meta['ExifImageWidth'] ."x". $meta['ExifImageLength']; } else { $meta_res = $info[0] ."x". $info[1]; } $meta_make = isset($meta['Make']) ? $meta['Make'] ." / ". $meta['Model'] : "n/a"; $meta_size = isset($meta['FileSize']) ? round($meta['FileSize']/1024, 1) ."kbyte" : "n/a"; $extern_link = "index.php?mode=showp&id=". $photo; $current_tags = $this->getCurrentTags(); if($current_tags != "") { $extern_link.= "&tags=". $current_tags; } if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) { $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']); } $this->tmpl->assign('extern_link', $extern_link); if(!file_exists($thumb_path)) { $this->_error("Can't open file ". $thumb_path ."\n"); return; } $info_thumb = getimagesize($thumb_path); $this->tmpl->assign('description', $details['description']); $this->tmpl->assign('image_name', $this->parse_uri($details['uri'], 'filename')); $this->tmpl->assign('image_rating', $this->get_photo_rating($photo)); $this->tmpl->assign('width', $info_thumb[0]); $this->tmpl->assign('height', $info_thumb[1]); $this->tmpl->assign('ExifMadeOn', strftime("%a %x %X", $details['time'])); $this->tmpl->assign('ExifMadeWith', $meta_make); $this->tmpl->assign('ExifOrigResolution', $meta_res); $this->tmpl->assign('ExifFileSize', $meta_size); if($this->is_user_friendly_url()) { $this->tmpl->assign('image_url', '/photo/'. $photo ."/". $this->cfg->photo_width .'/'. $version); $this->tmpl->assign('image_url_full', '/photo/'. $photo); } else { $this->tmpl->assign('image_url', 'phpfspot_img.php?idx='. $photo ."&width=". $this->cfg->photo_width ."&version=". $version); $this->tmpl->assign('image_url_full', 'phpfspot_img.php?idx='. $photo); } $this->tmpl->assign('image_filename', $this->parse_uri($details['uri'], 'filename')); $this->tmpl->assign('tags', $this->get_photo_tags($photo)); $this->tmpl->assign('current_page', $this->getCurrentPage($current, $count)); $this->tmpl->assign('current_img', $photo); if(isset($previous_img)) { $this->tmpl->assign('previous_url', "javascript:showPhoto(". $previous_img .");"); $this->tmpl->assign('prev_img', $previous_img); } if(isset($next_img)) { $this->tmpl->assign('next_url', "javascript:showPhoto(". $next_img .");"); $this->tmpl->assign('next_img', $next_img); } $this->tmpl->assign('mini_width', $this->cfg->mini_width); $this->tmpl->assign('photo_width', $this->cfg->photo_width); $this->tmpl->assign('photo_number', $i); $this->tmpl->assign('photo_count', count($all_photos)); $this->tmpl->assign('photo', $photo); $this->tmpl->assign('version', $version); /* if the photo as alternative versions, set a flag for the template */ if($this->get_photo_versions($photo)) $this->tmpl->assign('has_versions', true); $this->tmpl->register_function("photo_version_select_list", array(&$this, "smarty_photo_version_select_list"), false); return $this->tmpl->fetch("single_photo.tpl"); } // showPhoto() /** * all available tags and tag cloud * * this function outputs all available tags (time ordered) * and in addition output them as tag cloud (tags which have * many photos will appears more then others) */ public function getAvailableTags() { /* retrive tags from database */ $this->get_tags(); $output = ""; $result = $this->db->db_query(" SELECT tag_id as id, count(tag_id) as quantity FROM photo_tags INNER JOIN tags t ON t.id = tag_id GROUP BY tag_id ORDER BY t.name ASC "); $tags = Array(); while($row = $this->db->db_fetch_object($result)) { $tags[$row['id']] = $row['quantity']; } // change these font sizes if you will $max_size = 125; // max font size in % $min_size = 75; // min font size in % // color $max_sat = hexdec('cc'); $min_sat = hexdec('44'); // get the largest and smallest array values $max_qty = max(array_values($tags)); $min_qty = min(array_values($tags)); // find the range of values $spread = $max_qty - $min_qty; if (0 == $spread) { // we don't want to divide by zero $spread = 1; } // determine the font-size increment // this is the increase per tag quantity (times used) $step = ($max_size - $min_size)/($spread); $step_sat = ($max_sat - $min_sat)/($spread); // loop through our tag array foreach ($tags as $key => $value) { /* has the currently processed tag already been added to the selected tag list? if so, ignore it here... */ if(isset($_SESSION['selected_tags']) && in_array($key, $_SESSION['selected_tags'])) continue; // calculate CSS font-size // find the $value in excess of $min_qty // multiply by the font-size increment ($size) // and add the $min_size set above $size = $min_size + (($value - $min_qty) * $step); // uncomment if you want sizes in whole %: $size = ceil($size); $color = $min_sat + ($value - $min_qty) * $step_sat; $r = '44'; $g = dechex($color); $b = '88'; if(isset($this->tags[$key])) { if($this->is_user_friendly_url()) { $output.= "<a href=\"". $this->cfg->web_path ."/tag/". $key ."\" onclick=\"Tags('add', ". $key ."); return false;\" class=\"tag\" style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\" title=\"Tag ". $this->tags[$key] .", ". $tags[$key] ." picture(s)\">". $this->tags[$key] ."</a>, "; } else { $output.= "<a href=\"". $this->cfg->web_path ."/index.php?mode=showpi\" onclick=\"Tags('add', ". $key ."); return false;\" class=\"tag\" style=\"font-size: ". $size ."%; color: #". $r.$g.$b .";\" title=\"Tag ". $this->tags[$key] .", ". $tags[$key] ." picture(s)\">". $this->tags[$key] ."</a>, "; } } } $output = substr($output, 0, strlen($output)-2); return $output; } // getAvailableTags() /** * output all selected tags * * this function output all tags which have been selected * by the user. the selected tags are stored in the * session-variable $_SESSION['selected_tags'] * @return string */ public function getSelectedTags($type = 'link') { /* retrive tags from database */ $this->get_tags(); $output = ""; foreach($this->avail_tags as $tag) { // return all selected tags if(isset($_SESSION['selected_tags']) && in_array($tag, $_SESSION['selected_tags'])) { switch($type) { default: case 'link': $output.= "<a href=\"javascript:Tags('del', ". $tag .");\" class=\"tag\">". $this->tags[$tag] ."</a>, "; break; case 'img': $output.= " <div class=\"tagresulttag\"> <a href=\"javascript:Tags('del', ". $tag .");\" title=\"". $this->tags[$tag] ."\"> <img src=\"". $this->cfg->web_path ."/phpfspot_img.php?tagidx=". $tag ."\" /> </a> </div> "; break; } } } if($output != "") { $output = substr($output, 0, strlen($output)-2); return $output; } else { return "no tags selected"; } } // getSelectedTags() /** * add tag to users session variable * * this function will add the specified to users current * tag selection. if a date search has been made before * it will be now cleared * @return string */ public function addTag($tag) { if(!isset($_SESSION['selected_tags'])) $_SESSION['selected_tags'] = Array(); if(isset($_SESSION['searchfor_tag'])) unset($_SESSION['searchfor_tag']); // has the user requested to hide this tag, and still someone, // somehow tries to add it, don't allow this. if(!isset($this->cfg->hide_tags) && in_array($this->get_tag_name($tag), $this->cfg->hide_tags)) return "ok"; if(!in_array($tag, $_SESSION['selected_tags'])) array_push($_SESSION['selected_tags'], $tag); return "ok"; } // addTag() /** * remove tag to users session variable * * this function removes the specified tag from * users current tag selection * @param string $tag * @return string */ public function delTag($tag) { if(isset($_SESSION['searchfor_tag'])) unset($_SESSION['searchfor_tag']); if(isset($_SESSION['selected_tags'])) { $key = array_search($tag, $_SESSION['selected_tags']); unset($_SESSION['selected_tags'][$key]); sort($_SESSION['selected_tags']); } return "ok"; } // delTag() /** * reset tag selection * * if there is any tag selection, it will be * deleted now */ public function resetTags() { if(isset($_SESSION['selected_tags'])) unset($_SESSION['selected_tags']); } // resetTags() /** * returns the value for the autocomplete tag-search * @return string */ public function get_xml_tag_list() { if(!isset($_GET['search']) || !is_string($_GET['search'])) $_GET['search'] = ''; $length = 15; $i = 1; /* retrive tags from database */ $this->get_tags(); $matched_tags = Array(); header("Content-Type: text/xml"); $string = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"; $string.= "<results>\n"; foreach($this->avail_tags as $tag) { if(!empty($_GET['search']) && preg_match("/". $_GET['search'] ."/i", $this->tags[$tag]) && count($matched_tags) < $length) { $count = $this->get_num_photos($tag); if($count == 1) { $string.= " <rs id=\"". $i ."\" info=\"". $count ." photo\">". $this->tags[$tag] ."</rs>\n"; } else { $string.= " <rs id=\"". $i ."\" info=\"". $count ." photos\">". $this->tags[$tag] ."</rs>\n"; } $i++; } /* if we have collected enough items, break out */ if(count($matched_tags) >= $length) break; } $string.= "</results>\n"; return $string; } // get_xml_tag_list() /** * reset single photo * * if a specific photo was requested (external link) * unset the session variable now */ public function resetPhotoView() { if(isset($_SESSION['current_photo'])) unset($_SESSION['current_photo']); if(isset($_SESSION['current_version'])) unset($_SESSION['current_version']); } // resetPhotoView(); /** * reset tag search * * if any tag search has taken place, reset it now */ public function resetTagSearch() { if(isset($_SESSION['searchfor_tag'])) unset($_SESSION['searchfor_tag']); } // resetTagSearch() /** * reset name search * * if any name search has taken place, reset it now */ public function resetNameSearch() { if(isset($_SESSION['searchfor_name'])) unset($_SESSION['searchfor_name']); } // resetNameSearch() /** * reset date search * * if any date search has taken place, reset it now. */ public function resetDateSearch() { if(isset($_SESSION['from_date'])) unset($_SESSION['from_date']); if(isset($_SESSION['to_date'])) unset($_SESSION['to_date']); } // resetDateSearch(); /** * reset rate search * * if any rate search has taken place, reset it now. */ public function resetRateSearch() { if(isset($_SESSION['rate_from'])) unset($_SESSION['rate_from']); if(isset($_SESSION['rate_to'])) unset($_SESSION['rate_to']); } // resetRateSearch(); /** * return all photo according selection * * this function returns all photos based on * the tag-selection, tag- or date-search. * the tag-search also has to take care of AND * and OR conjunctions * @return array */ public function getPhotoSelection() { $matched_photos = Array(); $additional_where_cond = ""; if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) { $from_date = $_SESSION['from_date']; $to_date = $_SESSION['to_date']; $additional_where_cond.= " p.time>='". $from_date ."' AND p.time<='". $to_date ."' "; } if(isset($_SESSION['searchfor_name'])) { /* check for previous conditions. if so add 'AND' */ if(!empty($additional_where_cond)) { $additional_where_cond.= " AND "; } if($this->dbver < 9) { $additional_where_cond.= " ( p.name LIKE '%". $_SESSION['searchfor_name'] ."%' OR p.description LIKE '%". $_SESSION['searchfor_name'] ."%' ) "; } if($this->dbver < 17) { $additional_where_cond.= " ( basename(p.uri) LIKE '%". $_SESSION['searchfor_name'] ."%' OR p.description LIKE '%". $_SESSION['searchfor_name'] ."%' ) "; } else { $additional_where_cond.= " ( p.filename LIKE '%". $_SESSION['searchfor_name'] ."%' OR p.description LIKE '%". $_SESSION['searchfor_name'] ."%' ) "; } } /* limit result based on rate-search */ if(isset($_SESSION['rate_from']) && isset($_SESSION['rate_to'])) { if($this->dbver > 10) { /* check for previous conditions. if so add 'AND' */ if(!empty($additional_where_cond)) { $additional_where_cond.= " AND "; } $additional_where_cond.= " p.rating >= ". $_SESSION['rate_from'] ." AND p.rating <= ". $_SESSION['rate_to'] ." "; } } if(isset($_SESSION['sort_order'])) { $order_str = $this->get_sort_order(); } /* return a search result */ if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') { $query_str = " SELECT DISTINCT pt1.photo_id as photo_id FROM photo_tags pt1 INNER JOIN photo_tags pt2 ON pt1.photo_id=pt2.photo_id INNER JOIN tags t ON pt1.tag_id=t.id INNER JOIN photos p ON pt1.photo_id=p.id INNER JOIN tags t2 ON pt2.tag_id=t2.id WHERE t.name LIKE '%". $_SESSION['searchfor_tag'] ."%' "; if(!empty($additional_where_cond)) $query_str.= "AND ". $additional_where_cond ." "; if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) { $query_str.= "AND t2.name IN ('".implode("','",$this->cfg->show_tags)."')"; } if(isset($order_str)) $query_str.= $order_str; $result = $this->db->db_query($query_str); while($row = $this->db->db_fetch_object($result)) { array_push($matched_photos, $row['photo_id']); } return $matched_photos; } /* return according the selected tags */ if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) { $selected = ""; foreach($_SESSION['selected_tags'] as $tag) $selected.= $tag .","; $selected = substr($selected, 0, strlen($selected)-1); /* photo has to match at least on of the selected tags */ if($_SESSION['tag_condition'] == 'or') { $query_str = " SELECT DISTINCT pt1.photo_id as photo_id FROM photo_tags pt1 INNER JOIN photo_tags pt2 ON pt1.photo_id=pt2.photo_id INNER JOIN tags t ON pt2.tag_id=t.id INNER JOIN photos p ON pt1.photo_id=p.id WHERE pt1.tag_id IN (". $selected .") "; if(!empty($additional_where_cond)) $query_str.= "AND ". $additional_where_cond ." "; if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) { $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags)."')"; } if(isset($order_str)) $query_str.= $order_str; } /* photo has to match all selected tags */ elseif($_SESSION['tag_condition'] == 'and') { if(count($_SESSION['selected_tags']) >= 32) { print "A SQLite limit of 32 tables within a JOIN SELECT avoids to<br />\n"; print "evaluate your tag selection. Please remove some tags from your selection.\n"; return Array(); } /* Join together a table looking like pt1.photo_id pt1.tag_id pt2.photo_id pt2.tag_id ... so the query can quickly return all images matching the selected tags in an AND condition */ $query_str = " SELECT DISTINCT pt1.photo_id as photo_id FROM photo_tags pt1 "; if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) { $query_str.= " INNER JOIN tags t ON pt1.tag_id=t.id "; } for($i = 0; $i < count($_SESSION['selected_tags']); $i++) { $query_str.= " INNER JOIN photo_tags pt". ($i+2) ." ON pt1.photo_id=pt". ($i+2) .".photo_id "; } $query_str.= " INNER JOIN photos p ON pt1.photo_id=p.id "; $query_str.= "WHERE pt2.tag_id=". $_SESSION['selected_tags'][0]." "; for($i = 1; $i < count($_SESSION['selected_tags']); $i++) { $query_str.= " AND pt". ($i+2) .".tag_id=". $_SESSION['selected_tags'][$i] ." "; } if(!empty($additional_where_cond)) $query_str.= "AND ". $additional_where_cond; if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) { $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')"; } if(isset($order_str)) $query_str.= $order_str; } $result = $this->db->db_query($query_str); while($row = $this->db->db_fetch_object($result)) { array_push($matched_photos, $row['photo_id']); } return $matched_photos; } /* return all available photos */ $query_str = " SELECT DISTINCT p.id as id FROM photos p LEFT JOIN photo_tags pt ON p.id=pt.photo_id LEFT JOIN tags t ON pt.tag_id=t.id "; if(!empty($additional_where_cond)) $query_str.= "WHERE ". $additional_where_cond ." "; if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) { if(!empty($additional_where_cond)) $query_str.= "AND t.name IN ('".implode("','",$this->cfg->show_tags). "')"; else $query_str.= "WHERE t.name IN ('".implode("','",$this->cfg->show_tags). "')"; } if(isset($order_str)) $query_str.= $order_str; $result = $this->db->db_query($query_str); while($row = $this->db->db_fetch_object($result)) { array_push($matched_photos, $row['id']); } return $matched_photos; } // getPhotoSelection() /** * control HTML ouput for photo index * * this function provides all the necessary information * for the photo index template. * @return string */ public function showPhotoIndex() { $photos = $this->getPhotoSelection(); $current_tags = $this->getCurrentTags(); $count = count($photos); /* if all thumbnails should be shown on one page */ if(!isset($this->cfg->thumbs_per_page) || $this->cfg->thumbs_per_page == 0) { $begin_with = 0; $end_with = $count; } /* thumbnails should be splitted up in several pages */ elseif($this->cfg->thumbs_per_page > 0) { if(!isset($_SESSION['begin_with']) || $_SESSION['begin_with'] == 0) { $begin_with = 0; } else { $begin_with = $_SESSION['begin_with']; } $end_with = $begin_with + $this->cfg->thumbs_per_page; } $thumbs = 0; for($i = $begin_with; $i < $end_with; $i++) { if(!isset($photos[$i])) continue; /* on first run, initalize all used variables */ if($thumbs == 0) { $images = Array(); $images[$thumbs] = Array(); $img_height[$thumbs] = Array(); $img_width[$thumbs] = Array(); $img_id[$thumbs] = Array(); $img_name[$thumbs] = Array(); $img_fullname[$thumbs] = Array(); $img_title = Array(); $img_rating = Array(); } $images[$thumbs] = $photos[$i]; $img_id[$thumbs] = $i; $img_name[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 15)); $img_fullname[$thumbs] = htmlspecialchars($this->getPhotoName($photos[$i], 0)); $img_title[$thumbs] = "Click to view photo ". htmlspecialchars($this->getPhotoName($photos[$i], 0)); $img_rating[$thumbs] = $this->get_photo_rating($photos[$i]); /* get local path of the thumbnail image to be displayed */ $thumb_path = $this->get_thumb_path($this->cfg->thumb_width, $photos[$i], $this->get_latest_version($photos[$i])); /* if the image exist and is readable, extract some details */ if(file_exists($thumb_path) && is_readable($thumb_path)) { if($info = getimagesize($thumb_path) !== false) { $img_width[$thumbs] = $info[0]; $img_height[$thumbs] = $info[1]; } } $thumbs++; } if(isset($_SESSION['searchfor_tag']) && $_SESSION['searchfor_tag'] != '') $this->tmpl->assign('searchfor_tag', $_SESSION['searchfor_tag']); if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) { $this->tmpl->assign('from_date', $this->ts2str($_SESSION['from_date'])); $this->tmpl->assign('to_date', $this->ts2str($_SESSION['to_date'])); } if(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) { $this->tmpl->assign('tag_result', 1); } /* do we have to display the page selector ? */ if($this->cfg->thumbs_per_page != 0) { $page_select = ""; /* calculate the page switchers */ $previous_start = $begin_with - $this->cfg->thumbs_per_page; $next_start = $begin_with + $this->cfg->thumbs_per_page; if($begin_with != 0) $this->tmpl->assign("previous_url", "javascript:showPhotoIndex(". $previous_start .");"); if($end_with < $count) $this->tmpl->assign("next_url", "javascript:showPhotoIndex(". $next_start .");"); $photo_per_page = $this->cfg->thumbs_per_page; $last_page = ceil($count / $photo_per_page); /* get the current selected page */ if($begin_with == 0) { $current_page = 1; } else { $current_page = 0; for($i = $begin_with; $i >= 0; $i-=$photo_per_page) { $current_page++; } } $dotdot_made = 0; for($i = 1; $i <= $last_page; $i++) { if($current_page == $i) $style = "style=\"font-size: 125%; text-decoration: underline;\""; elseif($current_page-1 == $i || $current_page+1 == $i) $style = "style=\"font-size: 105%;\""; elseif(($current_page-5 >= $i) && ($i != 1) || ($current_page+5 <= $i) && ($i != $last_page)) $style = "style=\"font-size: 75%;\""; else $style = ""; $start_with = ($i*$photo_per_page)-$photo_per_page; if($this->is_user_friendly_url()) { $select = "<a href=\"". $this->cfg->web_path ."/tag/205/". $start_with ."\""; } else { $select = "<a href=\"". $this->cfg->web_path ."/index.php?mode=showpi tags=". $current_tags ." begin_with=". $begin_with ."\""; } $select.= " onclick=\"showPhotoIndex(". $start_with ."); return false;\""; if($style != "") $select.= $style; $select.= ">". $i ."</a> "; // until 9 pages we show the selector from 1-9 if($last_page <= 9) { $page_select.= $select; continue; } else { if($i == 1 /* first page */ || $i == $last_page /* last page */ || $i == $current_page /* current page */ || $i == ceil($last_page * 0.25) /* first quater */ || $i == ceil($last_page * 0.5) /* half */ || $i == ceil($last_page * 0.75) /* third quater */ || (in_array($i, array(1,2,3,4,5,6)) && $current_page <= 4) /* the first 6 */ || (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 */ || $i == $current_page-3 || $i == $current_page-2 || $i == $current_page-1 /* three before */ || $i == $current_page+3 || $i == $current_page+2 || $i == $current_page+1 /* three after */) { $page_select.= $select; $dotdot_made = 0; continue; } } if(!$dotdot_made) { $page_select.= "......... "; $dotdot_made = 1; } } /* only show the page selector if we have more then one page */ if($last_page > 1) $this->tmpl->assign('page_selector', $page_select); } $extern_link = "index.php?mode=showpi"; $rss_link = "index.php?mode=rss"; if($current_tags != "") { $extern_link.= "&tags=". $current_tags; $rss_link.= "&tags=". $current_tags; } if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) { $extern_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']); $rss_link.= "&from_date=". $this->ts2str($_SESSION['from_date']) ."&to_date=". $this->ts2str($_SESSION['to_date']); } $export_link = "index.php?mode=export"; $slideshow_link = "index.php?mode=slideshow"; $this->tmpl->assign('extern_link', $extern_link); $this->tmpl->assign('slideshow_link', $slideshow_link); $this->tmpl->assign('export_link', $export_link); $this->tmpl->assign('rss_link', $rss_link); $this->tmpl->assign('count', $count); $this->tmpl->assign('width', $this->cfg->thumb_width); $this->tmpl->assign('preview_width', $this->cfg->photo_width); $this->tmpl->assign('thumb_container_width', $this->cfg->thumb_width); $this->tmpl->assign('thumb_container_height', $this->cfg->thumb_height+20); $this->tmpl->assign('selected_tags', $this->getSelectedTags('img')); // +1 for for smarty's selection iteration $this->tmpl->assign('thumbs', $thumbs+1); if($thumbs > 0) { $this->tmpl->assign('images', $images); $this->tmpl->assign('img_width', $img_width); $this->tmpl->assign('img_height', $img_height); $this->tmpl->assign('img_id', $img_id); $this->tmpl->assign('img_name', $img_name); $this->tmpl->assign('img_fullname', $img_fullname); $this->tmpl->assign('img_title', $img_title); $this->tmpl->assign('img_rating', $img_rating); } $result = $this->tmpl->fetch("photo_index.tpl"); /* if we are returning to photo index from an photo-view, scroll the window to the last shown photo-thumbnail. after this, unset the last_photo session variable. */ if(isset($_SESSION['last_photo'])) { $result.= "<script language=\"JavaScript\">moveToThumb(". $_SESSION['last_photo'] .");</script>\n"; unset($_SESSION['last_photo']); } return $result; } // showPhotoIndex() /** * show credit template */ public function showCredits() { $this->tmpl->assign('version', $this->cfg->version); $this->tmpl->assign('product', $this->cfg->product); $this->tmpl->assign('db_version', $this->dbver); $this->tmpl->show("credits.tpl"); } // showCredits() /** * create thumbnails for the requested width * * this function creates image thumbnails of $orig_image * stored as $thumb_image. It will check if the image is * in a supported format, if necessary rotate the image * (based on EXIF orientation meta headers) and re-sizing. * @param string $orig_image * @param string $thumb_image * @param integer $width * @return boolean */ public function create_thumbnail($orig_image, $thumb_image, $width) { if(!file_exists($orig_image)) { return false; } $mime = $this->get_mime_info($orig_image); /* check if original photo is a support image type */ if(!$this->checkifImageSupported($mime)) return false; switch($mime) { case 'image/jpeg': $meta = $this->get_meta_informations($orig_image); $rotate = 0; $flip_hori = false; $flip_vert = false; if(isset($meta['Orientation'])) { switch($meta['Orientation']) { case 1: /* top, left */ /* nothing to do */ break; case 2: /* top, right */ $rotate = 0; $flip_hori = true; break; case 3: /* bottom, left */ $rotate = 180; break; case 4: /* bottom, right */ $flip_vert = true; break; case 5: /* left side, top */ $rotate = 270; $flip_vert = true; break; case 6: /* right side, top */ $rotate = 270; break; case 7: /* left side, bottom */ $rotate = 90; $flip_vert = true; break; case 8: /* right side, bottom */ $rotate = 90; break; } } $src_img = @imagecreatefromjpeg($orig_image); $handler = "gd"; break; case 'image/png': $src_img = @imagecreatefrompng($orig_image); $handler = "gd"; break; case 'image/x-portable-pixmap': $src_img = new Imagick($orig_image); $handler = "imagick"; break; } if(!isset($src_img) || empty($src_img)) { print "Can't load image from ". $orig_image ."\n"; return false; } switch($handler) { case 'gd': /* grabs the height and width */ $cur_width = imagesx($src_img); $cur_height = imagesy($src_img); // If requested width is more then the actual image width, // do not generate a thumbnail, instead safe the original // as thumbnail but with lower quality. But if the image // is to heigh too, then we still have to resize it. if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) { $result = imagejpeg($src_img, $thumb_image, 75); imagedestroy($src_img); return true; } break; case 'imagick': $cur_width = $src_img->getImageWidth(); $cur_height = $src_img->getImageHeight(); // If requested width is more then the actual image width, // do not generate a thumbnail, instead safe the original // as thumbnail but with lower quality. But if the image // is to heigh too, then we still have to resize it. if($width >= $cur_width && $cur_height < $this->cfg->thumb_height) { $src_img->setCompressionQuality(75); $src_img->setImageFormat('jpeg'); $src_img->writeImage($thumb_image); $src_img->clear(); $src_img->destroy(); return true; } break; } // If the image will be rotate because EXIF orientation said so // 'virtually rotate' the image for further calculations if($rotate == 90 || $rotate == 270) { $tmp = $cur_width; $cur_width = $cur_height; $cur_height = $tmp; } /* calculates aspect ratio */ $aspect_ratio = $cur_height / $cur_width; /* sets new size */ if($aspect_ratio < 1) { $new_w = $width; $new_h = abs($new_w * $aspect_ratio); } else { /* 'virtually' rotate the image and calculate it's ratio */ $tmp_w = $cur_height; $tmp_h = $cur_width; /* now get the ratio from the 'rotated' image */ $tmp_ratio = $tmp_h/$tmp_w; /* now calculate the new dimensions */ $tmp_w = $width; $tmp_h = abs($tmp_w * $tmp_ratio); // now that we know, how high they photo should be, if it // gets rotated, use this high to scale the image $new_h = $tmp_h; $new_w = abs($new_h / $aspect_ratio); // If the image will be rotate because EXIF orientation said so // now 'virtually rotate' back the image for the image manipulation if($rotate == 90 || $rotate == 270) { $tmp = $new_w; $new_w = $new_h; $new_h = $tmp; } } switch($handler) { case 'gd': /* creates new image of that size */ $dst_img = imagecreatetruecolor($new_w, $new_h); imagefill($dst_img, 0, 0, ImageColorAllocate($dst_img, 255, 255, 255)); /* copies resized portion of original image into new image */ imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, imagesx($src_img), imagesy($src_img)); /* needs the image to be flipped horizontal? */ if($flip_hori) { $this->_debug("(FLIP)"); $dst_img = $this->flipImage($dst_img, 'hori'); } /* needs the image to be flipped vertical? */ if($flip_vert) { $this->_debug("(FLIP)"); $dst_img = $this->flipImage($dst_img, 'vert'); } if($rotate) { $this->_debug("(ROTATE)"); $dst_img = $this->rotateImage($dst_img, $rotate); } /* write down new generated file */ $result = imagejpeg($dst_img, $thumb_image, 75); /* free your mind */ imagedestroy($dst_img); imagedestroy($src_img); if($result === false) { print "Can't write thumbnail ". $thumb_image ."\n"; return false; } return true; break; case 'imagick': $src_img->resizeImage($new_w, $new_h, Imagick::FILTER_LANCZOS, 1); /* needs the image to be flipped horizontal? */ if($flip_hori) { $this->_debug("(FLIP)"); $src_img->rotateImage(new ImagickPixel(), 90); $src_img->flipImage(); $src_img->rotateImage(new ImagickPixel(), -90); } /* needs the image to be flipped vertical? */ if($flip_vert) { $this->_debug("(FLIP)"); $src_img->flipImage(); } if($rotate) { $this->_debug("(ROTATE)"); $src_img->rotateImage(new ImagickPixel(), $rotate); } $src_img->setCompressionQuality(75); $src_img->setImageFormat('jpeg'); if(!$src_img->writeImage($thumb_image)) { print "Can't write thumbnail ". $thumb_image ."\n"; return false; } $src_img->clear(); $src_img->destroy(); return true; break; } } // create_thumbnail() /** * return all exif meta data from the file * @param string $file * @return array */ public function get_meta_informations($file) { return exif_read_data($file); } // get_meta_informations() /** * create phpfspot own sqlite database * * this function creates phpfspots own sqlite database * if it does not exist yet. this own is used to store * some necessary informations (md5 sum's, ...). */ public function check_phpfspot_db() { // if the config table doesn't exist yet, create it if(!$this->cfg_db->db_check_table_exists("images")) { $this->cfg_db->db_exec(" CREATE TABLE images ( img_idx int, img_version_idx int, img_md5 varchar(32), UNIQUE(img_idx, img_version_idx) ) "); } if(!$this->cfg_db->db_check_table_exists("meta")) { $this->cfg_db->db_exec(" CREATE TABLE meta ( meta_key varchar(255), meta_value varchar(255) ) "); /* db_version was added with phpfspot 1.7, before changes on the phpfspot database where not necessary. */ $this->cfg_db->db_exec(" INSERT INTO meta ( meta_key, meta_value ) VALUES ( 'phpfspot Database Version', '". $this->cfg->db_version ."' ) "); } /* if version <= 2 and column img_version_idx does not exist yet */ if($this->get_db_version() <= 2 && !$this->cfg_db->db_check_column_exists("images", "img_version_idx")) { if(!$this->cfg_db->db_start_transaction()) die("Can not start database transaction"); $result = $this->cfg_db->db_exec(" CREATE TEMPORARY TABLE images_temp ( img_idx int, img_version_idx int, img_md5 varchar(32), UNIQUE(img_idx, img_version_idx) ) "); if(!$result) { $this->cfg_db->db_rollback_transaction(); die("Upgrade failed - transaction rollback"); } $result = $this->cfg_db->db_exec(" INSERT INTO images_temp SELECT img_idx, 0, img_md5 FROM images "); if(!$result) { $this->cfg_db->db_rollback_transaction(); die("Upgrade failed - transaction rollback"); } $result = $this->cfg_db->db_exec(" DROP TABLE images "); if(!$result) { $this->cfg_db->db_rollback_transaction(); die("Upgrade failed - transaction rollback"); } $result = $this->cfg_db->db_exec(" CREATE TABLE images ( img_idx int, img_version_idx int, img_md5 varchar(32), UNIQUE(img_idx, img_version_idx) ) "); if(!$result) { $this->cfg_db->db_rollback_transaction(); die("Upgrade failed - transaction rollback"); } $result = $this->cfg_db->db_exec(" INSERT INTO images SELECT * FROM images_temp "); if(!$result) { $this->cfg_db->db_rollback_transaction(); die("Upgrade failed - transaction rollback"); } $result = $this->cfg_db->db_exec(" DROP TABLE images_temp "); if(!$result) { $this->cfg_db->db_rollback_transaction(); die("Upgrade failed - transaction rollback"); } if(!$this->cfg_db->db_commit_transaction()) die("Can not commit database transaction"); } } // check_phpfspot_db /** * generates thumbnails * * This function generates JPEG thumbnails from * provided F-Spot photo indize and its alternative * versions. * * 1. Check if all thumbnail generations (width) are already in place and * readable * 2. Check if the md5sum of the original file has changed * 3. Generate the thumbnails if needed * @param integer $idx * @param integer $force * @param boolean $overwrite */ public function gen_thumb($idx = 0, $force = 0, $overwrite = false) { $error = 0; $versions = Array(0); $resolutions = Array( $this->cfg->thumb_width, $this->cfg->photo_width, $this->cfg->mini_width, 30, ); if($alt_versions = $this->get_photo_versions($idx)) $versions = array_merge($versions, $alt_versions); foreach($versions as $version) { /* get details from F-Spot's database */ $details = $this->get_photo_details($idx, $version); /* calculate file MD5 sum */ $full_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath')); if(!file_exists($full_path)) { $this->_error("File ". $full_path ." does not exist"); return; } if(!is_readable($full_path)) { $this->_error("File ". $full_path ." is not readable for ". $this->getuid() ."\n"); return; } $this->_debug("Image [". $idx ."] ". $this->shrink_text($this->parse_uri($details['uri'], 'filename'), 20) ." Thumbnails:"); /* If Nikon NEF format, we need to treat it another way */ if(isset($this->cfg->dcraw_bin) && file_exists($this->cfg->dcraw_bin) && is_executable($this->cfg->dcraw_bin) && preg_match('/\.nef$/i', $details['uri'])) { $ppm_path = preg_replace('/\.nef$/i', '.ppm', $full_path); /* if PPM file does not exist, let dcraw convert it from NEF */ if(!file_exists($ppm_path)) { system($this->cfg->dcraw_bin ." -a ". $full_path); } /* for now we handle the PPM instead of the NEF */ $full_path = $ppm_path; } $file_md5 = md5_file($full_path); $changes = false; foreach($resolutions as $resolution) { $generate_it = false; $thumb_sub_path = substr($file_md5, 0, 2); $thumb_path = $this->cfg->thumb_path ."/". $thumb_sub_path ."/". $resolution ."_". $file_md5; /* if thumbnail-subdirectory does not exist yet, create it */ if(!file_exists(dirname($thumb_path))) { mkdir(dirname($thumb_path), 0755); } /* if the thumbnail file doesn't exist, create it */ if(!file_exists($thumb_path) || $force) { $generate_it = true; } elseif($file_md5 != $this->getMD5($idx, $version)) { $generate_it = true; } if($generate_it || $overwrite) { $this->_debug(" ". $resolution ."px"); if(!$this->create_thumbnail($full_path, $thumb_path, $resolution)) $error = 1; $changes = true; } } if(!$changes) { $this->_debug(" already exist"); } /* set the new/changed MD5 sum for the current photo */ if(!$error) { $this->setMD5($idx, $file_md5, $version); } $this->_debug("\n"); } } // gen_thumb() /** * returns stored md5 sum for a specific photo * * this function queries the phpfspot database for a stored MD5 * checksum of the specified photo. It also takes care of the * requested photo version - original or alternative photo. * * @param integer $idx * @return string|null */ public function getMD5($idx, $version_idx = 0) { $result = $this->cfg_db->db_query(" SELECT img_md5 FROM images WHERE img_idx='". $idx ."' AND img_version_idx='". $version_idx ."' "); if(!$result) return NULL; if($img = $this->cfg_db->db_fetch_object($result)) return $img['img_md5']; return NULL; } // getMD5() /** * set MD5 sum for the specific photo * @param integer $idx * @param string $md5 */ private function setMD5($idx, $md5, $version_idx = 0) { $result = $this->cfg_db->db_exec(" INSERT OR REPLACE INTO images ( img_idx, img_version_idx, img_md5 ) VALUES ( '". $idx ."', '". $version_idx ."', '". $md5 ."' ) "); } // setMD5() /** * store current tag condition * * this function stores the current tag condition * (AND or OR) in the users session variables * @param string $mode * @return string */ public function setTagCondition($mode) { $_SESSION['tag_condition'] = $mode; return "ok"; } // setTagCondition() /** * invoke tag & date search * * this function will return all matching tags and store * them in the session variable selected_tags. furthermore * it also handles the date search. * getPhotoSelection() will then only return the matching * photos. * @return string */ public function startSearch() { /* date search */ if(isset($_POST['date_from']) && $this->isValidDate($_POST['date_from'])) { $date_from = $_POST['date_from']; } if(isset($_POST['date_to']) && $this->isValidDate($_POST['date_to'])) { $date_to = $_POST['date_to']; } /* tag-name search */ if(isset($_POST['for_tag']) && is_string($_POST['for_tag'])) { $searchfor_tag = $_POST['for_tag']; $_SESSION['searchfor_tag'] = $_POST['for_tag']; } else { unset($_SESSION['searchfor_tag']); } /* file-name search */ if(isset($_POST['for_name']) && is_string($_POST['for_name'])) { $_SESSION['searchfor_name'] = $_POST['for_name']; } else { unset($_SESSION['searchfor_name']); } /* rate-search */ if(isset($_POST['rate_from']) && is_numeric($_POST['rate_from'])) { $_SESSION['rate_from'] = $_POST['rate_from']; if(isset($_POST['rate_to']) && is_numeric($_POST['rate_to'])) { $_SESSION['rate_to'] = $_POST['rate_to']; } } else { /* delete any previously set value */ unset($_SESSION['rate_to'], $_SESSION['rate_from']); } $this->get_tags(); if(isset($date_from) && !empty($date_from)) $_SESSION['from_date'] = strtotime($date_from ." 00:00:00"); else unset($_SESSION['from_date']); if(isset($date_to) && !empty($date_to)) $_SESSION['to_date'] = strtotime($date_to ." 23:59:59"); else unset($_SESSION['to_date']); if(isset($searchfor_tag) && !empty($searchfor_tag)) { /* new search, reset the current selected tags */ $_SESSION['selected_tags'] = Array(); foreach($this->avail_tags as $tag) { if(preg_match('/'. $searchfor_tag .'/i', $this->tags[$tag])) array_push($_SESSION['selected_tags'], $tag); } } return "ok"; } // startSearch() /** * updates sort order in session variable * * this function is invoked by RPC and will sort the requested * sort order in the session variable. * @param string $sort_order * @return string */ public function updateSortOrder($order) { if(isset($this->sort_orders[$order])) { $_SESSION['sort_order'] = $order; return "ok"; } return "unkown error"; } // updateSortOrder() /** * update photo version in session variable * * this function is invoked by RPC and will set the requested * photo version in the session variable. * @param string $photo_version * @return string */ public function update_photo_version($photo_idx, $photo_version) { if($this->is_valid_version($photo_idx, $photo_version)) { $_SESSION['current_version'] = $photo_version; return "ok"; } return "incorrect photo version provided"; } // update_photo_version() /** * rotate image * * this function rotates the image according the * specified angel. * @param string $img * @param integer $degress * @return image */ private function rotateImage($img, $degrees) { if(function_exists("imagerotate")) { $img = imagerotate($img, $degrees, 0); } else { function imagerotate($src_img, $angle) { $src_x = imagesx($src_img); $src_y = imagesy($src_img); if ($angle == 180) { $dest_x = $src_x; $dest_y = $src_y; } elseif ($src_x <= $src_y) { $dest_x = $src_y; $dest_y = $src_x; } elseif ($src_x >= $src_y) { $dest_x = $src_y; $dest_y = $src_x; } $rotate=imagecreatetruecolor($dest_x,$dest_y); imagealphablending($rotate, false); switch ($angle) { case 90: for ($y = 0; $y < ($src_y); $y++) { for ($x = 0; $x < ($src_x); $x++) { $color = imagecolorat($src_img, $x, $y); imagesetpixel($rotate, $dest_x - $y - 1, $x, $color); } } break; case 270: for ($y = 0; $y < ($src_y); $y++) { for ($x = 0; $x < ($src_x); $x++) { $color = imagecolorat($src_img, $x, $y); imagesetpixel($rotate, $y, $dest_y - $x - 1, $color); } } break; case 180: for ($y = 0; $y < ($src_y); $y++) { for ($x = 0; $x < ($src_x); $x++) { $color = imagecolorat($src_img, $x, $y); imagesetpixel($rotate, $dest_x - $x - 1, $dest_y - $y - 1, $color); } } break; default: $rotate = $src_img; break; }; return $rotate; } $img = imagerotate($img, $degrees); } return $img; } // rotateImage() /** * returns flipped image * * this function will return an either horizontal or * vertical flipped truecolor image. * @param string $image * @param string $mode * @return image */ private function flipImage($image, $mode) { $w = imagesx($image); $h = imagesy($image); $flipped = imagecreatetruecolor($w, $h); switch($mode) { case 'vert': for ($y = 0; $y < $h; $y++) { imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1); } break; case 'hori': for ($x = 0; $x < $w; $x++) { imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h); } break; } return $flipped; } // flipImage() /** * return all assigned tags for the specified photo * @param integer $idx * @return array */ private function get_photo_tags($idx) { $result = $this->db->db_query(" SELECT t.id as id, t.name as name FROM tags t INNER JOIN photo_tags pt ON t.id=pt.tag_id WHERE pt.photo_id='". $idx ."' "); $tags = Array(); while($row = $this->db->db_fetch_object($result)) { if(isset($this->cfg->hide_tags) && in_array($row['name'], $this->cfg->hide_tags)) continue; $tags[$row['id']] = $row['name']; } return $tags; } // get_photo_tags() /** * create on-the-fly images with text within * @param string $txt * @param string $color * @param integer $space * @param integer $font * @param integer $w */ public function showTextImage($txt, $color=000000, $space=4, $font=4, $w=300) { if (strlen($color) != 6) $color = 000000; $int = hexdec($color); $h = imagefontheight($font); $fw = imagefontwidth($font); $txt = explode("\n", wordwrap($txt, ($w / $fw), "\n")); $lines = count($txt); $im = imagecreate($w, (($h * $lines) + ($lines * $space))); $bg = imagecolorallocate($im, 255, 255, 255); $color = imagecolorallocate($im, 0xFF & ($int >> 0x10), 0xFF & ($int >> 0x8), 0xFF & $int); $y = 0; foreach ($txt as $text) { $x = (($w - ($fw * strlen($text))) / 2); imagestring($im, $font, $x, $y, $text, $color); $y += ($h + $space); } Header("Content-type: image/png"); ImagePng($im); } // showTextImage() /** * check if all requirements are met * @return boolean */ private function check_requirements() { if(!function_exists("imagecreatefromjpeg")) { print "PHP GD library extension is missing<br />\n"; $missing = true; } if($this->cfg->db_access == "native" && !function_exists("sqlite3_open")) { print "PHP SQLite3 library extension is missing<br />\n"; $missing = true; } if($this->cfg->db_access == "pdo") { if(array_search("sqlite", PDO::getAvailableDrivers()) === false) { print "PDO SQLite3 driver is missing<br />\n"; $missing = true; } } /* Check for HTML_AJAX PEAR package, lent from Horde project */ ini_set('track_errors', 1); @include_once 'HTML/AJAX/Server.php'; if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) { print "PEAR HTML_AJAX package is missing<br />\n"; $missing = true; } @include_once 'Calendar/Calendar.php'; if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) { print "PEAR Calendar package is missing<br />\n"; $missing = true; } @include_once 'Console/Getopt.php'; if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) { print "PEAR Console_Getopt package is missing<br />\n"; $missing = true; } @include_once 'Date.php'; if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) { print "PEAR Date package is missing<br />\n"; $missing = true; } @include_once $this->cfg->smarty_path .'/libs/Smarty.class.php'; if(isset($php_errormsg) && preg_match('/Failed opening.*for inclusion/i', $php_errormsg)) { print "Smarty template engine can not be found in ". $this->cfg->smarty_path ."/libs/Smarty.class.php<br />\n"; $missing = true; } ini_restore('track_errors'); if(isset($missing)) return false; return true; } // check_requirements() private function _debug($text) { if(isset($this->fromcmd)) { print $text; } } // _debug() /** * check if specified MIME type is supported * @param string $mime * @return boolean */ public function checkifImageSupported($mime) { $supported_types = Array( "image/jpeg", "image/png", "image/x-portable-pixmap", "image/tiff" ); if(in_array($mime, $supported_types)) return true; return false; } // checkifImageSupported() /** * output error text * @param string $text */ public function _error($text) { switch($this->cfg->logging) { default: case 'display': if(isset($this->fromcmd)) print $text ."\n"; else { print "<img src=\"resources/green_info.png\" alt=\"warning\" />\n"; print $text ."<br />\n"; } break; case 'errorlog': error_log($text); break; case 'logfile': error_log($text, 3, $this->cfg->log_file); break; } $this->runtime_error = true; } // _error() /** * get calendar input-text fields * * this function returns a text-field used for the data selection. * Either it will be filled with the current date or, if available, * filled with the date user entered previously. * * @param string $mode * @return string */ private function get_date_text_field($mode) { $date = isset($_SESSION[$mode .'_date']) ? date("Y", $_SESSION[$mode .'_date']) : date("Y"); $date.= "-"; $date.= isset($_SESSION[$mode .'_date']) ? date("m", $_SESSION[$mode .'_date']) : date("m"); $date.= "-"; $date.= isset($_SESSION[$mode .'_date']) ? date("d", $_SESSION[$mode .'_date']) : date("d"); $output = "<input type=\"text\" size=\"15\" id=\"date_". $mode ."\" value=\"". $date ."\""; if(!isset($_SESSION[$mode .'_date'])) $output.= " disabled=\"disabled\""; $output.= " />\n"; return $output; } // get_date_text_field() /** * output calendar matrix * @param integer $year * @param integer $month * @param integer $day */ public function get_calendar_matrix($userdate) { if(($userdate = strtotime($userdate)) === false) { $year = date('Y'); $month = date('m'); $day = date('d'); } else { $date = new Date(); $date->setDate($userdate); $year = $date->getYear(); $month = $date->getMonth(); $day = $date->getDay(); } $rows = 1; $cols = 1; $matrix = Array(); require_once CALENDAR_ROOT.'Month/Weekdays.php'; require_once CALENDAR_ROOT.'Day.php'; // Build the month $month_cal = new Calendar_Month_Weekdays($year,$month); // Create links $prevStamp = $month_cal->prevMonth(true); $prev = "javascript:setMonth(". date('Y',$prevStamp) .", ". date('n',$prevStamp) .", ". date('j',$prevStamp) .");"; $nextStamp = $month_cal->nextMonth(true); $next = "javascript:setMonth(". date('Y',$nextStamp) .", ". date('n',$nextStamp) .", ". date('j',$nextStamp) .");"; $selectedDays = array ( new Calendar_Day($year,$month,$day), ); // Build the days in the month $month_cal->build($selectedDays); $this->tmpl->assign('current_month', date('F Y',$month_cal->getTimeStamp())); $this->tmpl->assign('prev_month', $prev); $this->tmpl->assign('next_month', $next); while ( $day = $month_cal->fetch() ) { if(!isset($matrix[$rows])) $matrix[$rows] = Array(); $string = ""; $dayStamp = $day->thisDay(true); $link = "javascript:setCalendarDate('" . date('Y',$dayStamp) . "-" . date('m',$dayStamp) . "-" . date('d',$dayStamp) ."');"; // isFirst() to find start of week if ( $day->isFirst() ) $string.= "<tr>\n"; if ( $day->isSelected() ) { $string.= "<td class=\"selected\">".$day->thisDay()."</td>\n"; } else if ( $day->isEmpty() ) { $string.= "<td> </td>\n"; } else { $string.= "<td><a class=\"calendar\" href=\"".$link."\">".$day->thisDay()."</a></td>\n"; } // isLast() to find end of week if ( $day->isLast() ) $string.= "</tr>\n"; $matrix[$rows][$cols] = $string; $cols++; if($cols > 7) { $cols = 1; $rows++; } } $this->tmpl->assign('matrix', $matrix); $this->tmpl->assign('rows', $rows); $this->tmpl->show("calendar.tpl"); } // get_calendar_matrix() /** * output export page * @param string $mode */ public function getExport($mode) { $pictures = $this->getPhotoSelection(); $current_tags = $this->getCurrentTags(); foreach($pictures as $picture) { $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture; if($current_tags != "") { $orig_url.= "&tags=". $current_tags; } if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) { $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date']; } if($this->is_user_friendly_url()) { $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width; } else { $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width; } switch($mode) { case 'HTML': // <a href="%pictureurl%"><img src="%thumbnailurl%" ></a> print htmlspecialchars("<a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a>") ."<br />\n"; break; case 'MoinMoin': // "[%pictureurl% %thumbnailurl%]" print htmlspecialchars("[".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n"; break; case 'MoinMoinList': // " * [%pictureurl% %thumbnailurl%]" print " " . htmlspecialchars("* [".$orig_url." ".$thumb_url."&fake=1.jpg]") ."<br />\n"; break; } } } // getExport() /** * output RSS feed */ public function getRSSFeed() { Header("Content-type: text/xml; charset=utf-8"); print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; ?> <rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/" xmlns:dc="http://purl.org/dc/elements/1.1/" > <channel> <title>phpfspot</title> <description>phpfspot RSS feed</description> <link><?php print htmlspecialchars($this->get_phpfspot_url()); ?></link> <pubDate><?php print strftime("%a, %d %b %Y %T %z"); ?></pubDate> <generator>phpfspot</generator> <?php $pictures = $this->getPhotoSelection(); $current_tags = $this->getCurrentTags(); foreach($pictures as $picture) { $orig_url = $this->get_phpfspot_url() ."/index.php?mode=showp&id=". $picture; if($current_tags != "") { $orig_url.= "&tags=". $current_tags; } if(isset($_SESSION['from_date']) && isset($_SESSION['to_date'])) { $orig_url.= "&from_date=". $_SESSION['from_date'] ."&to_date=". $_SESSION['to_date']; } $details = $this->get_photo_details($picture); if($this->is_user_friendly_url()) { $thumb_url = $this->get_phpfspot_url() ."/photo/". $picture ."/". $this->cfg->thumb_width; } else { $thumb_url = $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $picture ."&width=". $this->cfg->thumb_width; } $thumb_html = htmlspecialchars(" <a href=\"". $orig_url ."\"><img src=\"". $thumb_url ."\" /></a> <br> ". $details['description']); $orig_path = $this->translate_path($this->parse_uri($details['uri'], 'fullpath')); /* get EXIF information if JPEG */ if(isset($details['mime']) && $details['mime'] == "image/jpeg") { $meta = $this->get_meta_informations($orig_path); } ?> <item> <title><?php print htmlspecialchars($this->parse_uri($details['uri'], 'filename')); ?></title> <link><?php print htmlspecialchars($orig_url); ?></link> <guid><?php print htmlspecialchars($orig_url); ?></guid> <dc:date.Taken><?php print strftime("%Y-%m-%dT%H:%M:%S+00:00", $details['time']); ?></dc:date.Taken> <description> <?php print $thumb_html; ?> </description> <pubDate><?php print strftime("%a, %d %b %Y %T %z", $details['time']); ?></pubDate> </item> <?php } ?> </channel> </rss> <?php } // getExport() /** * get all selected tags * * This function will return all selected tags as one string, seperated * by a comma. * @return array */ private function getCurrentTags() { $current_tags = ""; if(isset($_SESSION['selected_tags']) && $_SESSION['selected_tags'] != "") { foreach($_SESSION['selected_tags'] as $tag) $current_tags.= $tag .","; $current_tags = substr($current_tags, 0, strlen($current_tags)-1); } return $current_tags; } // getCurrentTags() /** * return the current photo */ public function get_current_photo() { if(isset($_SESSION['current_photo'])) { return $_SESSION['current_photo']; } return NULL; } // get_current_photo() /** * current selected photo version * * this function returns the current selected photo version * from the session variables. * * @return int */ public function get_current_version() { /* if current version is set, return it, if the photo really has that version */ if(isset($_SESSION['current_version']) && is_numeric($_SESSION['current_version'])) return $_SESSION['current_version']; return false; } // get_current_version() /** * returns latest available photo version * * this function returns the latested available version * for the requested photo. * * @return int */ public function get_latest_version($photo_idx) { /* try to get the lasted version for the current photo */ if($versions = $this->get_photo_versions($photo_idx)) return $versions[count($versions)-1]; /* if no alternative version were found, return original version */ return 0; } // get_current_version() /** * tells the client browser what to do * * this function is getting called via AJAX by the * client browsers. it will tell them what they have * to do next. This is necessary for directly jumping * into photo index or single photo view when the are * requested with specific URLs * @return string */ public function whatToDo() { if(isset($_SESSION['current_photo']) && $_SESSION['start_action'] == 'showp') { } elseif(isset($_SESSION['selected_tags']) && !empty($_SESSION['selected_tags'])) { return "showpi_tags"; } elseif(isset($_SESSION['start_action']) && $_SESSION['start_action'] == 'showpi') { return "showpi"; } } // whatToDo() /** * return the current process-user * @return string */ private function getuid() { if($uid = posix_getuid()) { if($user = posix_getpwuid($uid)) { return $user['name']; } } return 'n/a'; } // getuid() /** * photo version select list * * this function returns a HTML select list (drop down) * to select a alternative photo version of the original photo. * * @param array $params * @param smarty $smarty * @return string */ public function smarty_photo_version_select_list($params, &$smarty) { if(!isset($params['photo']) || !isset($params['current'])) return NULL; $output = "<option value=\"0\">Original</option>"; $versions = $this->get_photo_versions($params['photo']); foreach($versions as $version) { $output.= "<option value=\"". $version ."\""; if($version == $params['current']) { $output.= " selected=\"selected\""; } $output.= ">". $this->get_photo_version_name($params['photo'], $version) ."</option>"; } return $output; } // smarty_photo_version_select_list() /** * returns a select-dropdown box to select photo index sort parameters * @param array $params * @param smarty $smarty * @return string */ public function smarty_sort_select_list($params, &$smarty) { $output = ""; foreach($this->sort_orders as $key => $value) { $output.= "<option value=\"". $key ."\""; if($key == $_SESSION['sort_order']) { $output.= " selected=\"selected\""; } $output.= ">". $value ."</option>"; } return $output; } // smarty_sort_select_list() /** * returns the currently selected sort order * @return string */ private function get_sort_order() { switch($_SESSION['sort_order']) { case 'date_asc': return " ORDER BY p.time ASC"; break; case 'date_desc': return " ORDER BY p.time DESC"; break; case 'name_asc': if($this->dbver < 9) { return " ORDER BY p.name ASC"; } else { return " ORDER BY basename(p.uri) ASC"; } break; case 'name_desc': if($this->dbver < 9) { return " ORDER BY p.name DESC"; } else { return " ORDER BY basename(p.uri) DESC"; } break; case 'tags_asc': return " ORDER BY t.name ASC ,p.time ASC"; break; case 'tags_desc': return " ORDER BY t.name DESC ,p.time ASC"; break; case 'rate_asc': return " ORDER BY p.rating ASC, t.name ASC"; break; case 'rate_desc': return " ORDER BY p.rating DESC, t.name ASC"; break; } } // get_sort_order() /** * return the next to be shown slide show image * * this function returns the URL of the next image * in the slideshow sequence. * @return string */ public function getNextSlideShowImage() { $all_photos = $this->getPhotoSelection(); if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == count($all_photos)-1) $_SESSION['slideshow_img'] = 0; else $_SESSION['slideshow_img']++; if($this->is_user_friendly_url()) { return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width; } return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width; } // getNextSlideShowImage() /** * return the previous to be shown slide show image * * this function returns the URL of the previous image * in the slideshow sequence. * @return string */ public function getPrevSlideShowImage() { $all_photos = $this->getPhotoSelection(); if(!isset($_SESSION['slideshow_img']) || $_SESSION['slideshow_img'] == 0) $_SESSION['slideshow_img'] = 0; else $_SESSION['slideshow_img']--; if($this->is_user_friendly_url()) { return $this->get_phpfspot_url() ."/photo/". $all_photos[$_SESSION['slideshow_img']] ."/". $this->cfg->photo_width; } return $this->get_phpfspot_url() ."/phpfspot_img.php?idx=". $all_photos[$_SESSION['slideshow_img']] ."&width=". $this->cfg->photo_width; } // getPrevSlideShowImage() public function resetSlideShow() { if(isset($_SESSION['slideshow_img'])) unset($_SESSION['slideshow_img']); } // resetSlideShow() /** * get random photo * * this function will get all photos from the fspot * database and randomly return ONE entry * * saddly there is yet no sqlite3 function which returns * the bulk result in array, so we have to fill up our * own here. * @return array */ public function get_random_photo() { $all = Array(); $query_str = " SELECT p.id as id FROM photos p "; /* if show_tags is set, only return details for photos which are specified to be shown */ if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) { $query_str.= " INNER JOIN photo_tags pt ON p.id=pt.photo_id INNER JOIN tags t ON pt.tag_id=t.id WHERE t.name IN ('".implode("','",$this->cfg->show_tags)."')"; } $result = $this->db->db_query($query_str); while($row = $this->db->db_fetch_object($result)) { array_push($all, $row['id']); } return $all[array_rand($all)]; } // get_random_photo() /** * get random photo tag photo * * this function will get all photos tagged with the requested * tag from the fspot database and randomly return ONE entry * * saddly there is yet no sqlite3 function which returns * the bulk result in array, so we have to fill up our * own here. * @return array */ public function get_random_tag_photo($tagidx) { $all = Array(); if(isset($this->cfg->show_tags) && !empty($this->cfg->show_tags)) { $query_str.= " SELECT DISTINCT pt1.photo_id as id FROM photo_tags pt1 INNER JOIN photo_tags pt2 ON pt1.photo_id=pt2.photo_id INNER JOIN tags t1 ON t1.id=pt1.tag_id INNER JOIN tags t2 ON t2.id=pt2.tag_id WHERE pt1.tag_id LIKE '". $tagidx ."' AND t2.name IN ('".implode("','",$this->cfg->show_tags)."') ORDER BY t1.sort_priority ASC"; } else { $query_str = " SELECT p.id as id FROM photos p INNER JOIN photo_tags pt ON p.id=pt.photo_id WHERE pt.tag_id LIKE '". $tagidx ."'"; } $result = $this->db->db_query($query_str); while($row = $this->db->db_fetch_object($result)) { array_push($all, $row['id']); } return $all[array_rand($all)]; } // get_random_tag_photo() /** * validates provided date * * this function validates if the provided date * contains a valid date and will return true * if it is. * @param string $date_str * @return boolean */ public function isValidDate($date_str) { $timestamp = strtotime($date_str); if(is_numeric($timestamp)) return true; return false; } // isValidDate() /** * timestamp to string conversion * @param integer $timestamp * @return string */ private function ts2str($timestamp) { if(!empty($timestamp) && is_numeric($timestamp)) return strftime("%Y-%m-%d", $timestamp); } // ts2str() /** * extract tag-names from $_GET['tags'] * @param string $tags_str * @return string */ private function extractTags($tags_str) { $not_validated = split(',', $tags_str); $validated = array(); foreach($not_validated as $tag) { if(is_numeric($tag)) array_push($validated, $tag); } return $validated; } // extractTags() /** * returns the full path to a thumbnail * @param integer $width * @param integer $photo * @return string */ public function get_thumb_path($width, $photo_idx, $version_idx) { $md5 = $this->getMD5($photo_idx, $version_idx); $sub_path = substr($md5, 0, 2); return $this->cfg->thumb_path . "/" . $sub_path . "/" . $width . "_" . $md5; } // get_thumb_path() /** * returns server's virtual host name * @return string */ private function get_server_name() { return $_SERVER['SERVER_NAME']; } // get_server_name() /** * returns type of webprotocol which is currently used * @return string */ private function get_web_protocol() { if(!isset($_SERVER['HTTPS'])) return "http"; else return "https"; } // get_web_protocol() /** * return url to this phpfspot installation * @return string */ private function get_phpfspot_url() { return $this->get_web_protocol() ."://". $this->get_server_name() . $this->cfg->web_path; } // get_phpfspot_url() /** * returns the number of photos which are tagged with $tag_id * @param integer $tag_id * @return integer */ public function get_num_photos($tag_id) { if($result = $this->db->db_fetchSingleRow(" SELECT count(*) as number FROM photo_tags WHERE tag_id LIKE '". $tag_id ."'")) { return $result['number']; } return 0; } // get_num_photos() /** * check file exists and is readable * * returns true, if everything is ok, otherwise false * if $silent is not set, this function will output and * error message * @param string $file * @param boolean $silent * @return boolean */ private function check_readable($file, $silent = null) { if(!file_exists($file)) { if(!isset($silent)) print "File \"". $file ."\" does not exist.\n"; return false; } if(!is_readable($file)) { if(!isset($silent)) print "File \"". $file ."\" is not reachable for user ". $this->getuid() ."\n"; return false; } return true; } // check_readable() /** * check if all needed indices are present * * this function checks, if some needed indices are already * present, or if not, create them on the fly. they are * necessary to speed up some queries like that one look for * all tags, when show_tags is specified in the configuration. */ private function checkDbIndices() { $result = $this->db->db_exec(" CREATE INDEX IF NOT EXISTS phototag ON photo_tags (photo_id, tag_id) "); } // checkDbIndices() /** * retrive F-Spot database version * * this function will return the F-Spot database version number * It is stored within the sqlite3 database in the table meta * @return string|null */ public function getFspotDBVersion() { if($result = $this->db->db_fetchSingleRow(" SELECT data as version FROM meta WHERE name LIKE 'F-Spot Database Version' ")) return $result['version']; return null; } // getFspotDBVersion() /** * parse the provided URI and will returned the requested chunk * @param string $uri * @param string $mode * @return string */ public function parse_uri($uri, $mode) { if(($components = parse_url($uri)) === false) return $uri; switch($mode) { case 'filename': return basename($components['path']); break; case 'dirname': return dirname($components['path']); break; case 'fullpath': return $components['path']; break; default: $this->throwError("unknown mode ". $mode); break; } } // parse_uri() /** * validate config options * * this function checks if all necessary configuration options are * specified and set. * @return boolean */ private function check_config_options() { if(!isset($this->cfg->page_title) || $this->cfg->page_title == "") $this->_error("Please set \$page_title in phpfspot_cfg"); if(!isset($this->cfg->base_path) || $this->cfg->base_path == "") $this->_error("Please set \$base_path in phpfspot_cfg"); if(!isset($this->cfg->web_path) || $this->cfg->web_path == "") $this->_error("Please set \$web_path in phpfspot_cfg"); if(!isset($this->cfg->thumb_path) || $this->cfg->thumb_path == "") $this->_error("Please set \$thumb_path in phpfspot_cfg"); if(!isset($this->cfg->smarty_path) || $this->cfg->smarty_path == "") $this->_error("Please set \$smarty_path in phpfspot_cfg"); if(!isset($this->cfg->fspot_db) || $this->cfg->fspot_db == "") $this->_error("Please set \$fspot_db in phpfspot_cfg"); if(!isset($this->cfg->db_access) || $this->cfg->db_access == "") $this->_error("Please set \$db_access in phpfspot_cfg"); if(!isset($this->cfg->phpfspot_db) || $this->cfg->phpfspot_db == "") $this->_error("Please set \$phpfspot_db in phpfspot_cfg"); if(!isset($this->cfg->thumb_width) || $this->cfg->thumb_width == "") $this->_error("Please set \$thumb_width in phpfspot_cfg"); if(!isset($this->cfg->thumb_height) || $this->cfg->thumb_height == "") $this->_error("Please set \$thumb_height in phpfspot_cfg"); if(!isset($this->cfg->photo_width) || $this->cfg->photo_width == "") $this->_error("Please set \$photo_width in phpfspot_cfg"); if(!isset($this->cfg->mini_width) || $this->cfg->mini_width == "") $this->_error("Please set \$mini_width in phpfspot_cfg"); if(!isset($this->cfg->thumbs_per_page)) $this->_error("Please set \$thumbs_per_page in phpfspot_cfg"); if(!isset($this->cfg->enable_replace_path)) $this->_error("Please set \$enable_replace_path in phpfspot_cfg"); if($this->cfg->enable_replace_path == true) { if(!isset($this->cfg->path_replace_from) || $this->cfg->path_replace_from == "") $this->_error("Please set \$path_replace_from in phpfspot_cfg"); if(!isset($this->cfg->path_replace_to) || $this->cfg->path_replace_to == "") $this->_error("Please set \$path_replace_to in phpfspot_cfg"); } if(!isset($this->cfg->hide_tags)) $this->_error("Please set \$hide_tags in phpfspot_cfg"); if(!isset($this->cfg->theme_name)) $this->_error("Please set \$theme_name in phpfspot_cfg"); if(!isset($this->cfg->logging)) $this->_error("Please set \$logging in phpfspot_cfg"); if(isset($this->cfg->logging) && $this->cfg->logging == 'logfile') { if(!isset($this->cfg->log_file)) $this->_error("Please set \$log_file because you set logging = log_file in phpfspot_cfg"); if(!is_writeable($this->cfg->log_file)) $this->_error("The specified \$log_file ". $log_file ." is not writeable!"); } /* remove trailing slash, if set */ if($this->cfg->web_path == "/") $this->cfg->web_path = ""; elseif(preg_match('/\/$/', $this->cfg->web_path)) $this->cfg->web_path = preg_replace('/\/$/', '', $this->cfg->web_path); return $this->runtime_error; } // check_config_options() /** * cleanup phpfspot own database * * When photos are getting delete from F-Spot, there will remain * remain some residues in phpfspot own database. This function * will try to wipe them out. */ public function cleanup_phpfspot_db() { $to_delete = Array(); $result = $this->cfg_db->db_query(" SELECT img_idx as img_idx FROM images ORDER BY img_idx ASC "); while($row = $this->cfg_db->db_fetch_object($result)) { if(!$this->db->db_fetchSingleRow(" SELECT id as id FROM photos WHERE id='". $row['img_idx'] ."'")) { array_push($to_delete, $row['img_idx'], ','); } } print count($to_delete) ." unnecessary objects will be removed from phpfspot's database.\n"; $this->cfg_db->db_exec(" DELETE FROM images WHERE img_idx IN (". implode($to_delete) .") "); } // cleanup_phpfspot_db() /** * return first image of the page, the $current photo * lies in. * * this function is used to find out the first photo of the * current page, in which the $current photo lies. this is * used to display the correct photo, when calling showPhotoIndex() * from showImage() * @param integer $current * @param integer $max * @return integer */ private function getCurrentPage($current, $max) { if(isset($this->cfg->thumbs_per_page) && !empty($this->cfg->thumbs_per_page)) { for($page_start = 0; $page_start <= $max; $page_start+=$this->cfg->thumbs_per_page) { if($current >= $page_start && $current < ($page_start+$this->cfg->thumbs_per_page)) return $page_start; } } return 0; } // getCurrentPage() /** * return mime info * * this function tries to find out the correct mime-type * for the provided file. * @param string $file * @return string */ public function get_mime_info($file) { $details = getimagesize($file); /* if getimagesize() returns empty, try at least to find out the mime type. */ if(empty($details) && function_exists('mime_content_type')) { // mime_content_type is marked as deprecated in the documentation, // but is it really necessary to force users to install a PECL // extension? $details['mime'] = mime_content_type($file); } return $details['mime']; } // get_mime_info() /** * return tag-name by tag-idx * * this function returns the tag-name for the requested * tag specified by tag-idx. * @param integer $idx * @return string */ public function get_tag_name($idx) { if($result = $this->db->db_fetchSingleRow(" SELECT name as name FROM tags WHERE id LIKE '". $idx ."'")) { return $result['name']; } return 0; } // get_tag_name() /** * parse user friendly url which got rewritten by the websever * @param string $request_uri * @return string */ private function parse_user_friendly_url($request_uri) { if(preg_match('/\/photoview\/|\/photo\/|\/tag\//', $request_uri)) { $options = explode('/', $request_uri); switch($options[1]) { case 'photoview': if(is_numeric($options[2])) { $this->session_cleanup(); //unset($_SESSION['start_action']); //unset($_SESSION['selected_tags']); $_GET['mode'] = 'showp'; return $this->showPhoto($options[2]); } break; case 'photo': if(is_numeric($options[2])) { $width = NULL; $version = NULL; if(isset($options[3]) && is_numeric($options[3])) $width = $options[3]; if(isset($options[4]) && is_numeric($options[4])) $version = $options[4]; require_once "phpfspot_img.php"; $img = new PHPFSPOT_IMG; $img->showImg($options[2], $width, $version); } exit; break; case 'tag': if(is_numeric($options[2])) { $this->session_cleanup(); $_GET['tags'] = $options[2]; $_SESSION['selected_tags'] = Array($options[2]); if(isset($options[3]) && is_numeric($options[3])) $_SESSION['begin_with'] = $options[3]; return $this->showPhotoIndex(); } break; } } } // parse_user_friendly_url() /** * check if user-friendly-urls are enabled * * this function will return true, if the config option * $user_friendly_url has been set. Otherwise false. * @return boolean */ private function is_user_friendly_url() { if(isset($this->cfg->user_friendly_url) && $this->cfg->user_friendly_url) return true; return false; } // is_user_friendly_url() /** * session cleanup * * this function will cleanup user's session information */ private function session_cleanup() { unset($_SESSION['begin_with']); $this->resetDateSearch(); $this->resetPhotoView(); $this->resetTagSearch(); $this->resetNameSearch(); $this->resetDateSearch(); $this->resetTags(); } // session_cleanup() /** * get database version * * this function queries the meta table * and returns the current database version. * * @return integer */ public function get_db_version() { if($row = $this->cfg_db->db_fetchSingleRow(" SELECT meta_value as meta_value FROM meta WHERE meta_key LIKE 'phpfspot Database Version' ")) { return $row['meta_value']; } return 0; } // get_db_version() /** * get photo versions * * this function returns an array of all available * alterntaive versions of the provided photo id. * has alternative photo versions available * * @param int $idx * @return array */ public function get_photo_versions($idx) { $versions = Array(); $result = $this->db->db_query(" SELECT version_id FROM photo_versions WHERE photo_id LIKE '". $idx ."'"); while($row = $this->cfg_db->db_fetch_object($result)) { array_push($versions, $row['version_id']); } return $versions; } // get_photo_versions() /** * check for invalid version of photo * * this function validates the provided photo-id and version-id * * @param int $photo_idx * @param int $version_idx * @return bool */ public function is_valid_version($photo_idx, $version_idx) { /* the original version is always valid */ if($version_idx == 0) return true; if($versions = $this->get_photo_versions($photo_idx)) { if(in_array($version_idx, $versions)) return true; } return false; } // is_valid_version() /** * get photo version name * * this function returns the name of the version * identified by the photo-id and version-id. * * @param int $photo_idx * @param int $version_idx * @return string */ public function get_photo_version_name($photo_idx, $version_idx) { if($row = $this->db->db_fetchSingleRow(" SELECT name FROM photo_versions WHERE photo_id LIKE '". $photo_idx ."' AND version_id LIKE '". $version_idx ."'")) { return $row['name']; } return false; } // get_photo_version_name() /** */ public function is_valid_width($image_width) { if(in_array($image_width, Array($this->cfg->thumb_width, $this->cfg->photo_width, $this->cfg->mini_width, 30))) return true; return false; } // is_valid_width() } // class PHPFSPOT ?>