<?php
/* Thumbnail manager class used to create and cache thubnail images from JPG and GIF images files.  
*/

class ImageManager extends ImageEngine {
	
	var $aMetadata;
	var $oDbCall;
	var $WebBase;
	var $AuthGroup;
	var $ApiKeySecret;
	var $blacklist;
	var $ImageSizes;

	function __construct($oLog, $oDbCall, $AuthGroup, $ApiKeySecret, $sBaseDir, $sCacheDir, $sImageDir, $sWebBase = "") {
		
		$this->oDbCall = $oDbCall;
		ImageEngine::ImageEngine($oLog, $sBaseDir, $sCacheDir, $sImageDir);
		$this->WebBase = $sWebBase;
		$this->ApiKeySecret = $ApiKeySecret;
		$this->AuthGroup = $AuthGroup;
		$this->ImageSizes = array();
	}
	
	// Load folder blacklist
	function LoadBlacklist($blacklist) {
		$this->blacklist = $blacklist; 
		return true;
	}
	
	// Load image_sizes config data. 
	function LoadImageSizes($ImageSizes) {
		$this->ImageSizes = $ImageSizes;
		return true;
	}
	
	function GetImageSizes() {
		return $this->ImageSizes;
	}
	
	// Set the AuthGroup.
	// Caution - this should only used to temporarly eg When providing temp auth for shared galleries.
	function SetAuthGroup($AuthGroup) {
		$this->AuthGroup = $AuthGroup;
	}
	
	// Get all links for folder
	function GetFolderLinks($type_id) {
		return $this->oDbCall->GetLinks('image_folders', $type_id);
	}
	
	// Insert new link for folder. 
	function InsertFolderLink($type_id, $name, $url) {
		return $this->oDbCall->InsertLink('image_folders', $type_id, $name, $url);
	}
	
	// Update folder link. 
	function UpdateFolderLink($id, $type_id, $name, $url) {
		return $this->oDbCall->UpdateLink($id, 'image_folders', $type_id, $name, $url);
	}
	
	// Delete folder link.
	function DeleteFolderLink($id) {
		return $this->oDbCall->DeleteLink($id);
	}
	
	// Get all links for blog entry
	function GetBlogLinks($type_id) {
		return $this->oDbCall->GetLinks('travel_blog', $type_id);
	}
	
	// Insert new link for blog entry. 
	function InsertBlogLink($type_id, $name, $url) {
		return $this->oDbCall->InsertLink('travel_blog', $type_id, $name, $url);
	}
	
	// Update blog link. 
	function UpdateBlogLink($id, $type_id, $name, $url) {
		return $this->oDbCall->UpdateLink($id, 'travel_blog', $type_id, $name, $url);
	}
	
	// Delete blog entry link.
	function DeleteBlogLink($id) {
		return $this->oDbCall->DeleteLink($id);
	}
	
	// Delete all links for type_id.
	function DeleteFolderLinksForTypeId($type_id) {
		return $this->oDbCall->DeleteLinksForTypeId('image_folders', $type_id);
	}		
	
	// Get image filepath from image id.
	function GetImageFilepath($id) {
		return $this->oDbCall->GetImageFilepath($id);
	}
	
	// Get the folder ID for given Image ID. 
	function GetFolderID($id) {
		return $this->oDbCall->GetFolderID($id); 
	}
	
	// Set image folder title
	function SetImageFolderTitle($id, $title) {
		return $this->oDbCall->UpdateImageFoldersTitle($id, $title);
	}
	
	// Get all video config
	function GetVideoConfig() {
		return $this->oDbCall->GetVideoConfig();
	}
	
	function GetMediaFiletypes() {
		
		$filetypes = array();
		$media_config = $this->GetVideoConfig();

		foreach($media_config as $item) {
			$filetypes[] = $item['key'];
		}
		return $filetypes;			
	}
	
	// Insert new video config
	function InsertVideoConfig($key, $value, $type) {
		$this->oDbCall->InsertVideoConfig($key, $value, $type);
		return true;
	}
	
	// Update video config $key = $value
	function UpdateVideoConfig($key, $value) {
		$this->oDbCall->UpdateVideoConfig($key, $value);
		return true;
	}
	
	// Delete video config for $key
	function DeleteVideoConfig($key) {
		return $this->oDbCall->DeleteVideoConfig($key);
	}
	
	// Set image folder auth
	function SetImageFolderAuth($id, $auth) {
		
		// Set folder auth. 
		$this->oDbCall->UpdateImageFolderAuth($id, $auth);
		
		// Set auth for all images under folder.
		$this->oDbCall->UpdateFolderImagesAuth($id, $auth);
		return true;
	}
	
	// Set image folder caption text formatting any http links.
	function SetImageFolderCaption($id, $caption) {
		
		// Search for and replace any links in text. 
		$modified_caption = preg_replace('/(?<!href=\")http:\/\/(www\.|)([\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:\/~\+#]*[\w\-\@?^=%&amp;\/~\+#])?)/', '<a href=\"$0\">$2</a>', $caption);
		//$modified_caption = $caption;
		return $this->oDbCall->UpdateImageFoldersCaption($id, $modified_caption);
	}
	
	// Update image title. 
	function SetImageTitle($id, $title) {
		return $this->WriteXmpTitle($id, $title);
	}
	
	// Update image auth. 
	function SetImageAuth($id, $auth) {
		return $this->oDbCall->UpdateImageAuth($id, $auth);
	}
	
	// Fetch list of unique regions in image_folders
	function GetUniqueRegions() {
		return $this->oDbCall->GetUniqueRegions($this->AuthGroup);
	}
	
	// Fetch list of unique countries in image_folders
	function GetUniqueCountry() {
		return $this->oDbCall->GetUniqueCountry($this->AuthGroup);
	}
	// Fetch image count for give folder ID. 
	function GetImageCount($id) {
		return $this->oDbCall->GetImageCount($id, $this->AuthGroup);
	}
	
	// Fetch filtered folders by passed in variables - year, region, country & keywords.
	function GetFilteredFolders($year, $region, $country, $keywords) {
		return $this->oDbCall->GetFilteredFolders($year, $region, $country, $keywords, $this->AuthGroup);
	}
	
	// Fetch all image_folders countries for given region. 
	function GetRegionCountries($region) {
		return $this->oDbCall->GetRegionCountries($region, $this->AuthGroup);
	}
	
	// Fetch keywords from image_folders. 
	function GetKeywords($set_name) {
		return $this->oDbCall->GetKeywords($set_name);
	}
	
	// Update keywords in image_folders.
	function UpdateKeywords($id, $keywords) {
		 return $this->oDbCall->UpdateKeywords($id, $keywords);
	}
	
	// Fetch keyword sets.
	function FetchKeywordSet($set_name) {
	
		// Fetch set name if exists
		$set = $this->oDbCall->GetKeywordSet($set_name);
		return $set;
		
	}
	
	// Get all keyword set names. 
	function GetKeywordSetNames() {
		return $this->oDbCall->GetKeywordSetNames();
	}
	
	// Update keyword sets or insert new set if doesn't exist. 
	function UpdateKeywordSet($set_name, $keywords) {
	
		// Fetch set name if exists
		if($set = $this->oDbCall->GetKeywordSet($set_name)) {
		
			// Update keyword set
			$this->oDbCall->UpdateKeywordSet($set_name, $keywords);
		} else {
			
			// Insert new keyword set. 
			$this->oDbCall->InsertKeywordSet($set_name, $keywords);
		}
		return true;
		
	}
	
	// Delete keyword set.
	function DeleteKeywordSet($set_name) {
	
		$this->oDbCall->DeleteKeywordSet($set_name);
		return true;
		
	}
	
	function GetImage($filename, $x, $y) {
		if(!$oImage = $this->GetCacheImage($filename, '', $x, $y)) {
			if(!$oImage = $this->ResizeImage($filename,$x, $y)) {
				return false; 
			}

			$this->CacheImage($oImage, $filename, '', $x, $y);
		}
		return $oImage;
	}
	
	function GetFullImage($filename) {
		if(!$oImage = $this->FetchFullImage($filename)) {
			return false; 
		}
		return $oImage;
	}
	
	function CacheDeleteAllFiletype($filetype, $assoc_ext) {

		// Run recursive delete for all files of this type (exact case)
		if(!$this->DeleteFiletypeRecursive($this->sBaseDir . $this->sCacheDir, $filetype, $assoc_ext)) {
			return false;
		}
		
		return true;
	}
	
	// Check if cached media s
	function CacheImageExists($filename) {
		
		foreach($this->GetImageSizes() as $size) {
			if(!$size['opac']) {$size['opac'] = '0';}
			if(!$size['border']) {$size['border'] = '0';}	
			if(!$size['mode']) {$size['mode'] = 'n';}
			list($arg1, $ext) = explode(".", $filename, 2);
			$cached_image = $arg1 . "_" . $size['mode'] . $size['x'] . "x" . $size['y'] . "x" . $size['opac'] . "x" . $size['border'] . "." . $ext;
			
			if(!$this->FileExist($this->sBaseDir . $this->sCacheDir . $cached_image, false, true)) {
				return false;
			}
		}
		
		return true;
	}
	
	function CacheVideoExists($filename) {
		
		if(!$this->FileExist($this->sBaseDir . $this->sCacheDir . $filename, false, true)) {
			return false;
		} 
		
		return true;
	}
	
	// GetVideo returns full filesystem path for video. 
	// Checks cache first, if doesn't exist goes to source and attempts any conversions and copies to cache. 
	function GetVideo($filename, $still_image_x, $still_image_y) {
	
		// Check if video exists in cache
		if(!$path = $this->GetCacheVideo($filename)) {
			
			$this->LOG->LogWarn("Get original Video - " . $filename);
			// Fetch the source video
			if(!$path = $this->GetSourceVideo($filename)) {
				$this->LOG->LogWarn("Failed to get Video - " . $filename);
				return false;
			}
			
			// Cache video, and covert from older .MOV types to modern Mp4.
			if(!$path = $this->CacheVideo($filename, $still_image_x, $still_image_y)) {
				$this->LOG->LogWarn("Failed to cache Video - " . $filename);
				return false;
			}
			return $path;
		}
		$this->LOG->LogInfo("Get cache Video - " . $path);
		return $path;
	}
	
	// fetch Video poster image PNG. 
	function GetVideoPoster($filename, $still_image_x, $still_image_y) {
		
		// Check if poster exists in cache.
		// If not then cache a new poster image and video into cache. 
		if(!$poster = $this->GetCachePoster($filename)) {
			
			$this->LOG->LogInfo("Get original Video publish new poster and cache Video - " . $filename);
			
			// Cache video, and covert from older .MOV types to modern Mp4 Nad publish fresh poster PNG still if doesn't exist. 
			if(!$poster = $this->CacheVideo($filename, $still_image_x, $still_image_y)) {
				$this->LOG->LogWarn("Failed to cache Video - " . $filename);
				return false;
			}
		}
		
		$this->LOG->LogInfo("Get cache Video poster - " . $poster);
		return $poster;
	}
			
	// Multimode resize image. 
	//
	// Mode n - Simply resize image with no regard for aspect. Returns image to given Width and Height. 
	// Mode h - Resize image to given Height maintaining Width aspect.
	//          Crop image width if image wider than given Width after aspect resizing.
	//          Pass in Width of 0 or false for no width cropping. 
	// Mode w - Resize image to given Width maintaining Height aspect.
	//   	    Crop image height if image higher than given Height after aspect resizing. 
	//          Pass in Height of 0 or false for no height cropping.
	// Mode s - Special resize an inteligent combination of mode W and H. 
	//			If width greater than height - resize to given width keeping height aspect with cropping and image filler, if required
	//			If height greater than width - resize to given height keeping width aspect with cropping and image filler, if required 
	//
	// Additional Options:
	// 			- Pass in Image_Filler or Image_Background to maintain image width or height after resize by filling with a given colour
	//   		  or background image repeated. Set Image_Filler with hexdecimal RGB colour code (eg ffaa55).
	//   		  Set Image_Background with given image URL.
	// 			- Pass in opac (0 - 100) for image transparency. Merges iages with a white background to give transparency effect.
	//			- Pass in border to add a image border (border equals size in pixels). Also pass in border_colour in hexadecial RGB colour code (eg ffaa55). 
	//			  Border colour defaults to white (ffffff).
	
	function GetResizeImage($filename, $mode = "n", $width = false, $height = false, $image_filler = false, $image_background = false, $opac = false, $border = false, $border_colour = false, $cache = false) {
	
		// Check for cached version and return if exists. 
		if(!$oImage = $this->GetCacheImage($filename, $mode, $width, $height, $opac, $border)) {
			
			// Build the full image path. 
			if($cache) {
				$path = $this->sBaseDir . $this->sCacheDir . $filename;
			} else {
				$path = $this->sImageDir . $filename;
			}
			
			// Fetch image source object from given filename and obtain original width and height.
			if(!$oSource = $this->GetImageObject($path)) {
				return false;
			}
			
			(list($source_width,$source_height) = @getimagesize($path));	
			
			// Mode switch, defaults to N (Normal resize).
			switch ($mode) {
    			case "h":
    		
    			// Work out width to maintain image ratio to resized height. 
    			$new_width = $source_width * ($height / $source_height);
			    if(!$oImage = $this->ResizeImage($oSource, $source_width, $source_height, $new_width, $height)) {
        			return false;
        		}
        		
        		
    			
        		// Crop image width if greater than passed in Width. 
        		if($width && $new_width > $width) {
        		    if(!$oImage = $this->CropImage($oImage, $new_width, $height, $width, $height)) {
        				return false;
        			}
        		}
    			
   				if($image_filler && $new_width < $width) {
   				
   					// If a colour filler is selected and the new width is smaller than passed in width
   					// then call the colour filler function which will pad the image left and right with 
   					// given colour.  
        			if(!$oImage = $this->ImageColourFiller($oImage, $new_width, $height, $width, $height, $image_filler)) {
        				return false;
        			}
        		}

        		if($image_background && $new_width < $width) {
        			
        			// If image background URL passed and the new width is smaller than passed in width
        			// then call the image background function to pad the image with given background image repeated. 
			        if(!$oImage = $this->ImageBackgroundFiller($oImage, $new_width, $height, $width, $height, $image_background)) {
        				return false;
        			}
				}	
        		break;
    			case "w":
    			
			    // Work out height to maintain image ratio to resized width. 
    			$new_height = $source_height * ($width / $source_width);
			    if(!$oImage = $this->ResizeImage($oSource, $source_width, $source_height, $width, $new_height)) {
        			return false;
        		}
    			
        		// Crop image height is greater than passed in Height. 
        		if($height && $new_height > $height) {
        		    if(!$oImage = $this->CropImage($oImage, $width, $new_height, $width, $height)) {
        				return false;
        			}
        		}
    			
   				if($image_filler && $new_height < $height) {
   				
   					// If a colour filler is selected and the new height is smaller than passed in height
   					// then call the colour filler function which will pad the image top and bottom with 
   					// given colour.  
        			if(!$oImage = $this->ImageColourFiller($oImage, $width, $new_height, $width, $height, $image_filler)) {
        				return false;
        			}
        		}
        		
        		if($image_background && $new_height < $height) {
        			
        			// If image background URL passed and the new height is smaller than passed in height
        			// then call the image background function to pad the image with given background image repeated. 
			        if(!$oImage = $this->ImageBackgroundFiller($oImage, $width, $new_height, $width, $height, $image_background)) {
        				return false;
        			}
				}	
        		
        		break;
    			case "s":
    			if($source_width > $source_height) {
    				
    				// Work out height to maintain image ratio to resized width. 
    				$new_height = $source_height * ($width / $source_width);
			    	if(!$oImage = $this->ResizeImage($oSource, $source_width, $source_height, $width, $new_height)) {
        				return false;
        			}
    			
        			// Crop image width if new height is larger than passed in Height. 
        			if($new_height > $height) {
        		   		if(!$oImage = $this->CropImage($oImage, $width, $new_height, $width, $height)) {
        					return false;
        				}
        			}
    			
   					if($image_filler && $new_height < $height) {
   						if(!$oImage = $this->ImageColourFiller($oImage, $width, $new_height, $width, $height, $image_filler)) {
        					return false;
        				}
        			}
        		
        			if($image_background && $new_height < $height) {
        				if(!$oImage = $this->ImageBackgroundFiller($oImage, $width, $new_height, $width, $height, $image_background)) {
        					return false;
        				}
					}
					
					
    			} else {
    				
    				// Work out width to maintain image ratio to rezied height. 
    				$new_width = $source_width * ($height / $source_height);
    				$new_width = intval($new_width);
    				$width = intval($width);
			    	if(!$oImage = $this->ResizeImage($oSource, $source_width, $source_height, $new_width, $height)) {
        				return false;
        			}
    			
        			// Crop image width if Width is passed in and new width is larger than passed in Width. 
        			if($new_width > $width) {
        		    	if(!$oImage = $this->CropImage($oImage, $new_width, $height, $width, $height)) {
        					return false;
        				}
        			}
    				
   					if($image_filler && $new_width < $width) {
   						if(!$oImage = $this->ImageColourFiller($oImage, $new_width, $height, $width, $height, $image_filler)) {
        					return false;
        				}
        			}
        		
        			if($image_background && $new_width < $width) {
        				if(!$oImage = $this->ImageBackgroundFiller($oImage, $new_width, $height, $width, $height, $image_background)) {
        					return false;
        				}
					}	
					
    			}
        		
        		break;
        		default:
        			if(!$oImage = $this->ResizeImage($oSource, $source_width, $source_height, $width, $height)) {
        				return false;
        			}
        		break;
			}
			
			if($opac) {
				if(!$oImage = $this->ImageOpacity($oImage, $width, $height, $opac)) {
        			return false;
        		}
			}

			if($border) {
				if(!$oImage = $this->ImageBorder($oImage, $width, $height, $border, $border_colour)) {
        			return false;
        		}
			}
			$this->LOG->LogWarn("Get original image - " . $filename);
			if(!$cache) $this->CacheImage($oImage, $filename, $mode, $width, $height, $opac, $border);
			imagedestroy($oSource);
		}
		
		$this->LOG->LogInfo("Get cache image - " . $filename);
		return $oImage;
	}
	
	function ImageExists($img) {
		if(!$img) {
			return false;
		}
		return $this->ImageFileExists($img);
	}
	
	// GetVideoMetadata
	function GetVideoMetadata($filename, $folder_id) {
	
		// Check video exists and hasn't been modified since the db got updated last.
		$metadata = $this->oDbCall->GetImageMetadata($filename);
		if(!$file_modified = $this->GetImageFileModified($filename)) {
			if($this->ImageFolderUp()) {
				$this->DeleteImageFromDb($filename);
				$this->ImageFolderCleanup($filename);
				return false;
			} 
		}
		$filesize = $this->GetImageFilesize($filename);
		if($file_modified > $metadata['modified']) {
			$metadata = $this->WriteVideoDataToDb($metadata, $filename, $file_modified, $filesize, $folder_id);
		}
		return $metadata;
	}
	
	// GetJpegImageMetadata. 
	function GetJpegImageMetadata($sFilename, $folder_id) {

		// Check image exists and hasn't been modified since the db got updated last.
		$metadata = $this->oDbCall->GetImageMetadata($sFilename);

		if(!$file_modified = $this->GetImageFileModified($sFilename)) {
		
			// Check to see if image filesystem is up delete image data from db and clean up folders.
			if($this->ImageFolderUp()) {
				$this->DeleteImageFromDb($sFilename);
				$this->ImageFolderCleanup($sFilename);
				return false;
			} 
		}

		$filesize = $this->GetImageFilesize($sFilename);
		//print "<br>FILE SIZE: " . $filesize;
		//print "<br>DB_MODIFIED: " . $metadata['modified'];
		//print "<br>FILE_MODIFIED: " . $file_modified;
		//print "<br>FILE_MODIFIED: " . $sFilename . "<br>";

		if($file_modified > $metadata['modified']) {

			//print $file_modified . " = " . $metadata['modified'];
			$metadata = $this->WriteImageDataToDb($metadata, $sFilename, $file_modified, $filesize, $folder_id);
		}
		
		/* EXAMPLE: Full capabilities.
		$this->aMetadata[$sFilename] = $this->GetAppMetaSegments($sFilename);
		$aData = $this->GetExifData($sFilename);
		$aData2 = $this->GetXMPData($sFilename);
		$aData3 = $this->GetPhotoshopIRBData($sFilename); 
		 */
		
		return $metadata;
	}
	
	// WriteXmpTitle writes a new Title tag to the image XMP metadata.
	// Also updates the Database with new title to keep in sync with image.
	function WriteXmpTitle($id, $title) {
		
		// First fetch the image info from DB given photo id. 
		if(!$metadata = $this->oDbCall->GetImageMetadataFromID($id, $this->AuthGroup)) {
				return false;
		}
		
		// Only if the title has changed do we want to proceed.
		if($metadata['title'] != $title) {

			$this->WriteXMPDataToImage($metadata['filepath'], $title);
		
			$file_modified = $this->GetImageFileModified($metadata['filepath']);
			if(!$this->oDbCall->UpdateImageTitle($id, $title, $file_modified)) {
				return false;
			}
		}
		
		return true;
	}
	
	// Write new or updates the folder set geolocation settings. 
	// This method will also write geolocation data to all images within this set who 
	// don't have any location data set already. 
	function WriteFolderGelocation($folder_id, $loc_string, $lat, $lng, $alt, $country, $region) {
		
		// Fetch folder metadata
		if(!$metadata = $this->oDbCall->GetFolderData($folder_id, $this->AuthGroup)) {
				return false;
		}
		
		// Update folder geolocation data in DB
		if(!$this->oDbCall->UpdateFolderGeolocation($folder_id, $loc_string, $lat, $lng, $alt, $country, $region)) {
				return false;
		}
		
		// Update each folder image that has no geolocation (GPS) data set already.
		//
		// Fetch all images in folder set.
		if(!$folder_images = $this->oDbCall->GetFolderImagesGelocation($folder_id, $this->AuthGroup)) {
			return false;
		}
		
		// Loop through all images for folder checking for blank geolocation data. 
		// update all images without geolocation data. 
		// Leave any location strings allready set in images, otehrwise overwrite with new 
		// reverse geocode lookup string. 

		foreach($folder_images as $image) {
			
			if($image['type'] != "vid") {
			 
				// If no GPS (lat, long) data already set then update image with new GPS info. 
				// Updates image Exif GPS sub IFD info and the DB.
				if(!$image['latitude'] && !$image['longitude']) {
					
					// Write geoolocation data to image Exif info and update database.
					if(!$this->WriteExifGeolocation($image['id'], $lat, $lng, $alt)) {
						//return false;
					}
					if(!$this->oDbCall->UpdateImageGeolocation($image['id'], $lat, $lng, $alt)) {
						return false;
					}					
				}	
				
				// Check if we have a location string set already and maintain. 
				// We don't want to overwrite a manualy entered location string, even if no lat and lng set.
				if(!$image['location'] && $loc_string) {			

					// Update JPEG XMP metadata with new location. 
					if(!$this->WriteXMPDataToImage($image['filepath'], null, $loc_string)) {
						return false;
					}
					if(!$this->oDbCall->UpdateImageLocation($image['id'], $loc_string)) {
						return false;
					}	
				}
			
				// Update each image with passed in Country and Region i.e. this is only for database and not Exif info. 
				if(!$this->oDbCall->UpdateImageCountryRegion($image['id'], $country, $region)) {
					return false;
				}
			}	
			
		}

		return true; 
		
	}
	
	// Clear All Locations from folder and all images contained within - both from DB and JPEG Exif.
	function ClearAllLocations($folder_id) {
	
		// Clear DB folder location
		if(!$this->oDbCall->UpdateImageFoldersLocation($folder_id, "")) {
			return false;
		}
		
		// Loop through each image in folder and clear location field
		// Clear DB image location and JPEG XMP location info.
		if(!$folder_images = $this->oDbCall->GetFolderImagesGelocation($folder_id)) {
			return false;
		}
		
		foreach($folder_images as $image) {
			// Clear DB image location field.
			if(!$this->oDbCall->UpdateImageLocation($image['id'], "")) {
				return false;
			}

			// Clear Jpeg XMP metadata for location field
			if(!$this->WriteXMPDataToImage($image['filepath'], $title = "", $location = "CLEARNOW")) {
				return false;
			}
		}
		return true;
	}
				
	// Get map icons for all folder sets. 
	// Returns folder map icon info for displaying on Google Maps. 
	function GetMapIconsAllFolders() {
		
		$all_folders = $this->GetAllFolders();
		
		$zindex = 0;
		foreach($all_folders as $set) {
			if($set['longitude']) {
				$tmp = array (
							'id' => $set['id'],
							'title' => $set['title'],
							'latitude' => $set['latitude'],
							'longitude' => $set['longitude'],
							'altitude' => $set['altitude'],
							'zindex' => $zindex,
							'filepath' => $set['filepath']
				);
				$data[] = $tmp;
				$zindex++;
			}
		}
	
		return $data;
	}
	
	// Get map icons for all images within a folder (id) 
	// Returns image map icon info for displaying on Google Maps. 
	function GetMapIconsImages($id) {
		
		$data = array();
		$images = $this->GetImagesForFolder($id);
		$folder = $this->GetFolder($id);
		$zindex = 0;
		foreach($images as $img) {
			$tmp = array();
			$tmp['id'] = $img['id'];
			$tmp['title'] = $img['title'];
			$tmp['filepath'] = $img['filepath'];
			$tmp['zindex'] = $zindex;
			
			if($img['longitude'] & $img['latitude']) {
				$tmp['longitude'] = $img['longitude'];
				$tmp['latitude'] = $img['latitude'];
				$tmp['altitude'] = $img['altitude'];
				
			} else {
				$tmp['longitude'] = $folder['longitude'];
				$tmp['latitude'] = $folder['latitude'];
				$tmp['altitude'] = $folder['altitude'];
			}
				
			$data[] = $tmp;
			$zindex++;
		}
		return $data;
	}
	
	// Get image metadata straight out of DB. Provide image ID and full metadata returned if 
	// image exists in DB, otherwise return false. 
	function GetImageMetadataFromDB($id) {
		if(!$metadata = $this->oDbCall->GetImageMetadataFromID($id, $this->AuthGroup)) {
				return false;
		}
		
		// Cludge! 
		// Check and change given strings from Exif datal 
		//
		//
		if($metadata['make'] == 'NIKON CORPORATION') {$metadata['make'] = '';}
		
		return $metadata;
	}	
	
	// Fetch all folders in date order. 
	function GetAllFolders($filecheck = false) {	
		$folders = array();
		
		// fetch all years
		$years = $this->GetYearFolders($filecheck);
		

		// Loop through each year and fetch folders adding to total array.
		foreach($years as $year) {
			$ft = $this->GetFoldersForYear($year, $filecheck);
			foreach($ft as $f) {
				$folders[] = $f;
			}
		}
		return $folders;
	}
	
	function GetYearFolders($filecheck = false) {
		
	
		// Pull all years out of image_folders db table. 
		$db_years = $this->oDbCall->GetDistinctFolderYears($this->AuthGroup);
		if($filecheck & !$this->ImageFolderUp()) {
			$this->LOG->LogError("Image filesystem is down and file checking is enabled!.", E_USER_ERROR);
			return false;
		}
		
		// If $filecheck is true (check filesystem) and image filesystem up then check top level folder for removed / add years. 
		// Run checks for delete and added years against database and update as necessary. 
		if($filecheck & $this->ImageFolderUp()) {

			// Get year folders under image filesystem root
			$fs_years_raw = $this->GetFoldersTopLevel();
			$fs_years = array();
			
			// Check for blacklisted folder in array $this->blacklist. We want to ignore any folder names in that list.
			if($this->blacklist) { 
				foreach($fs_years_raw as $folder) {
					if(!in_array($folder, $this->blacklist)) {
						$fs_years[] = $folder;
					}
				}
			}
			
			// Loop through database years and delete where doesn't exist on filesystem.
			foreach($db_years as $year) {
				if(!in_array($year, $fs_years)) {
					$this->oDbCall->DeleteFolderYear($year);
					$this->DeleteCacheFolder($year);
				}
			}
			rsort($fs_years);
			return $fs_years;
		}
		rsort($db_years);
		return $db_years;
	}
	
	function GetFoldersForYear($year, $filecheck = false) {
		
		// Pull all folders under this year from image_folders. 
		$db_folders = $this->oDbCall->GetFoldersForYear($year, $this->AuthGroup);
		
		if($filecheck & !$this->ImageFolderUp()) {
			$this->LOG->LogError("Image filesystem is down and file checking is enabled!.", E_USER_ERROR);
			return false;
		}
		// If we want to check filesystem and it's up then check second level folder for removed / add folders. 
		// Run checks for delete and added folders against database and update as necessary. 
		if($filecheck & $this->ImageFolderUp()) {
			
			// Get folders for this year under image filesystem root
			$fs_folders = $this->GetFoldersSecondLevel($year);
			
			// Collape filesystem array into list of all filepaths found in year folder. 
			// We do this to make the next search function more efficent when it checks to see if the dir exits.
			$fs_folder_paths = array(); 
			foreach($fs_folders as $fs_folder) {
				$fs_folder_paths[] = $fs_folder['folder_path'];
			}
			
			// Collape DB array into list of all filepaths found in table.
			// We do this to make the next search function more efficent when looking for missing folders later.
			$db_folder_paths = array(); 
			foreach($db_folders as $db_folder) {
				$db_folder_paths[] = $db_folder['folder_path'];
			}
			
			// Loop through database folders for this year and delete where doesn't exist on filesystem.
			$db_folders_refresh = false;
			foreach($db_folders as $db_folder) {
				if(!in_array($db_folder['folder_path'], $fs_folder_paths)) {
					$this->oDbCall->DeleteFolder($db_folder['folder_path']);
					$this->DeleteCacheFolder($db_folder['folder_path']);
				}
			}
			
			// Check for folders not in DB and create new image_folders 
			// record, along with adding to return folders array. 
			foreach($fs_folders as $fs_folder){
				if(!in_array($fs_folder['folder_path'], $db_folder_paths)) {
					$this->CreateImageFolder($fs_folder['folder_path']);
				}
			}
			// Refresh folder data from DB.
			$db_folders = $this->oDbCall->GetFoldersForYear($year, $this->AuthGroup);
		}
		return $db_folders;
	}
		
	function GetImagesForFolder($folder_id, $folder_path = "", $filecheck = false) {
		
		$images = array();
		$folder_files = array();
		$filetypes = $this->GetMediaFiletypes();
		
		if($filecheck & !$this->ImageFolderUp()) {
			$this->LOG->LogError("Image filesystem is down and file checking is enabled!.", E_USER_ERROR);
			return false;
		}
		
		if($filecheck & $this->ImageFolderUp()) {

			if(!$folder_path) {
				$fd = $this->GetFolder($folder_id);
				$folder_path = $fd['folder_path'];
			}
			
			$raw_files = $this->GetFileList($folder_path, $this->sImageDir, true);
			
			// Filter out all non image and movie files. 
			foreach($raw_files as $img_file) {
				list($arg1, $ext) = explode(".", $img_file, 2);
				$ext = strtolower($ext);
				if(in_array($ext, $filetypes)) {
					$folder_files[] = $img_file;
				}
			}
			
			// Check and delete removed image files from database and cache.
			$db_images = $this->oDbCall->GetFolderImages($folder_id, $this->AuthGroup);
			$delete_images_trigger = false;
			foreach($db_images as $img) {
				if(!in_array($img['filepath'], $folder_files)) {
					$this->DeleteImageFromDb($img['filepath']);
					$this->ImageFolderCleanup($img['filepath']);
					$delete_images_trigger = true;
				}
			}
			
			// Delete cached image
			if($delete_images_trigger) {
				$this->DeleteCacheImages($folder_path);
			}
			
			// Fetch all media config data.
			$media_config = $this->GetVideoConfig();
			
			foreach($folder_files as $img_file) {
				
				list($arg1, $ext) = explode(".", $img_file, 2);
				$ext = strtolower($ext);
				
				reset($media_config);
				foreach($media_config as $item) {
					
					if($item['key'] == $ext & $item['type'] == "Image") {
						
						// Special cases for each image media type - look to abstract this out, similar to Video fetch. 
						// Fetch and write to db Jpeg image information. 
						if($ext == "jpg") {
   		   					$this->GetJpegImageMetadata($img_file, $folder_id);
				
 		  	   			// CAN'T handle GIFs yet.
		   	   			} elseif($ext == "gif") {
		   	   			// Do nothing.				
	
		      			// CAN'T handle PNGs yet.
		      			} elseif($ext == "png") {
  		    				// Do nothing.
		      			}
		      			
					} elseif($item['key'] == $ext & $item['type'] == "Video") {
											
						// Fetch video information and write to DB. 
	      				$this->GetVideoMetadata($img_file, $folder_id);
					}
				}		
			}
		}
			
		// Fetch all the image for this folder from database and return ID, File Path and Title (for ALT tag).
		$images = $this->oDbCall->GetFolderImages($folder_id, $this->AuthGroup);
		return $images;
	}
	
	function GetImagesForFolderLimited($folder_id, $folder_path = "") {

		// Fetch first 20 images only (excluding videos).
		$images = $this->oDbCall->GetFolderImagesLimited($folder_id, $this->AuthGroup);
		return $images;
	}
	
	// Fetch the SET folder image for this folder. Only return filepath for image. 
	function GetFolderSetImage($folder_id) {
		$data = array();
		if(!$data = $this->oDbCall->GetFolderSetImage($folder_id)) {
			return false;
		}
		return $this->GetImageMetadataFromDB($data['id']);
	}
	
	// Update the Set image for this image set. 
	function UpdateFolderSetImage($folder_id, $set_image) {
		$data = array();
		if(!$data = $this->oDbCall->UpdateFolderSetImage($folder_id, $set_image)) {
			return false;
		}
		return;
	}
	
	// Update image folder mode. 
	// Mode is used to set the display type of the gallery info for the set. 
	function UpdateImageFolderMode($folder_id, $mode) {
		return $this->oDbCall->UpdateFolderMode($folder_id, $mode); 
	}
	
	// Update image folder date. 
	function UpdateImageFolderDate($folder_id, $date) {
		if(!$this->oDbCall->UpdateFolderDate($folder_id, $date)) {
			return false;
		}
		
		// Update all images (image_date) under this folder with new date. 
		if(!$this->oDbCall->UpdateImageDates($folder_id, $date)) {
			return false;
		}
	}
	
	function GetFolder($id) {
		
		$data = array();
		
		// Fetch folder data, return false if doesn't exist.
		if(!$data = $this->oDbCall->GetFolderData($id, $this->AuthGroup)) {
			return false;
		}
		return $data;
	}
	
	// Fetch limited folder data, used for site wide search result sets. 
	function GetFolderLimited($id) {
		if(!$data = $this->oDbCall->GetFolderDataLimited($id)) {
			return false;
		}
		return $data;
	}
	
	// Fetch the image position data within the folder set. Provide at least image ID and folder ID if available 
	// to return a data array containing the next and previous image ID in set. Returns false if no previous or next. 
	function GetImagePositionData($pid, $fid) {
		
		$img_list = array();
		$img_pos = array();
		
		// Fetch all images for this foldder ID (fid)
		$img_list = $this->oDbCall->GetImageListIDs($fid);
		$key = array_search($pid, $img_list);
		$img_pos['current'] = $img_list[$key];
		$img_pos['disp_current'] = $key + 1;
		$img_pos['previous'] = $img_list[$key - 1]; 
		$img_pos['next'] = $img_list[$key + 1];  
		$img_pos['count'] = count($img_list);
		return $img_pos;
	}
	
	// Simply fetch a folders title form given FID (Folder ID). 
	function GetFolderTitle($fid) {
		
		return $this->oDbCall->GetFolderTitle($fid);
	}
	
	// Get hashed secret request key for image call. 
	function GetRequestKey($time_offset = false) {
		if(!$time_offset) {$time_offset = 60;}
		$time = (time() + $time_offset);
		$hash = hash_hmac('ripemd128', $time , $this->ApiKeySecret);
		return "t=" . rawurlencode($time) . "&ha=" . rawurlencode($hash);
	}
	
	// Get hashed secret gallery share hash. 
	// Use time and Set ID as salt. 
	function GetGalleryShareKey($id, $role, $folder_auth, $time_offset = false) {
		// Default time_offset to 3 months. 
		//if(!$time_offset) {$time_offset = 7776000;}
		//$time = (time() + $time_offset);
		$hash = hash_hmac('ripemd128', $id . substr($role, 0, 2), $this->ApiKeySecret);
		return rawurlencode($hash);
	}
	
	// Check hashed URL secrets for authorisation.
	function CheckApiKey($time, $hash) {
		$now = time();
		$myhash = hash_hmac('ripemd128', $time, $this->ApiKeySecret);
		if($myhash == $hash && $now <= $time) {
			return true;
		}
		return false;
	}
	
	// Check hashed secret gallery share.
	function CheckGalleryShareKey($id, $role, $hash) {
		$now = time();
		$myhash = hash_hmac('ripemd128', $id . $role, $this->ApiKeySecret);
		if($myhash == $hash) {
			return true;
		}
		return false;
	}
	
	// Function providing Javascript encodeURICompnent equivalence. 
	function encodeURIComponent($str) {
    	$revert = array('%21'=>'!', '%2A'=>'*', '%27'=>"'", '%28'=>'(', '%29'=>')');
    	return strtr(rawurlencode($str), $revert);
	}
	
	// Image and blogs
	//
	// Add blog ID to image set - image_folders. 
	function AddBlogId($fid, $bid) {
		if(!$data = $this->oDbCall->UpdateFolderBlogId($fid, $bid)) {
			return false;
		}
		return true;
	}
	
	// Get blog data for folder id.
	function GetFolderBlog($fid) {
		if(!$bid = $this->oDbCall->GetFolderBlogId($fid)) {
			return false;
		}
		return $bid;
	}
}
?>
