Location: PHPKode > projects > Gemibloo > Gemibloo1_0_alpha1/application/plugins/import/class.ImportMailbox.php
	* Import class (SINGLETON see http://en.wikipedia.org/wiki/Singleton_pattern)
	* This class allows image extraction from messages of a mailbox and the geoblog database update.
	* Messages can contain
	* -1 pic + 1 subject with coord -> imports location from subject
	* -1 pic + no subject -> imports location from pic (EXIF metadata) 
	* -No pic + 1 subject  with coord -> imports location from subject
	* -No pic + no Subject -> imports nothing!
	* -1 pic + subject is 'drop' ->  imports location from pic (EXIF metadata) but not pic 
	* Current restrictions are:
	*	-Only 1 pic is expected by message (in case of multi-attachments, only the first pic is imported)
	*	-ReplyTo is extracted only from the last no-read message found in the mailbox
	* @author Olivier G <hide@address.com>
	* @version 1.0
	* @license http://opensource.org/licenses/gpl-license.php GNU Public License
	* @link http://gemibloo.fr
	final class ImportMailbox extends PluginImport{	
		private static $_instance;
		private $_mbox = null;
		private $_reply_toaddress = null;
		* __construct
		private function __construct() {}

		* __destruct
		function __destruct() {
			if(FALSE === is_null($this->_mbox))
				@imap_close($this->_mbox, CL_EXPUNGE);
		* __clone
		private function __clone() {}
		* fGetInstance method
		public static function fGetInstance() 
			if (!isset(self::$_instance)) {
				$c = __CLASS__;
				self::$_instance = new $c;
			return self::$_instance;

		* fGetReplyTo method
		*  @return string email address to reply-to
		public function fGetReplyTo(){
			return $this->_reply_toaddress;

		* fImport method
		* This is the main function of the class. It throws ImportException typed exceptions if something goes wrong.
		public function fImport(){
			//Connect to mailbox
			if(FALSE === ($this->_mbox = imap_open(EMAIL_SERVER, EMAIL_USR, EMAIL_PWD))){
				Log::fGetInstance()->fLog(Log::$TYPE['error'] ,__CLASS__, 'Mailbox '.EMAIL_SERVER.' cannot be opened');			
				throw new ImportException("Mailbox cannot be opened.", ImportException::MAILBOX_REQUEST_FAILED);
			//Get all messages
			$mbox_search = imap_search ($this->_mbox, 'ALL');	
			if( (FALSE !== $mbox_search) && (0 != count($mbox_search)) ){
					Log::fGetInstance()->fLog(Log::$TYPE['info'] ,__CLASS__, 'New message found in '.EMAIL_SERVER);
				//Sort in reverse chronological order
				//Loop through messages
				foreach($mbox_search as $mbox_msg){
					$lat = 0.0;
					$lng = 0.0;
					$content = '';
					$filepath = '';
					$timestamp = time();
					$ratioHW = 	1.0;
					//Get current message header
					$msg_head = imap_headerinfo($this->_mbox, $mbox_msg);
					$this->_reply_toaddress = $msg_head->reply_to[0]->mailbox.'@'.$msg_head->reply_to[0]->host;				

					//Get current message subject
					$subject = trim($msg_head->subject);
					$isPicDropped = FALSE;
					if('drop' == strtolower($subject))
						$isPicDropped = TRUE;
					//Check from where loc must be extracted 	
					$isLocFromEXIF = TRUE;
					if( (0 != strlen($subject)) && (0 != strcmp(strtolower($subject), '(pas de sujet)') ) )
						$isLocFromEXIF = FALSE;
						//Extract loc from header string
						if(FALSE === ($coords = $this->_fGetLocationFromSubject($subject))){
								Log::fGetInstance()->fLog(Log::$TYPE['error'] ,__CLASS__, 'Location cannot be extracted from the mail subject');
								throw new ImportException("Location cannot be extracted from the mail subject.", ImportException::MESSAGE_REQUEST_FAILED);				

						$lat = $coords[0];
						$lng = $coords[1];
						$content = $coords[2];
						//and timestamp from message header
						$timestamp = strtotime ($msg_head->date);
					//Get message structure
					$msg_structure = imap_fetchstructure($this->_mbox,$mbox_msg);
					//Loop now through this structure
					foreach($msg_structure->parts as $part){
						//Is current section a plain text?
						if('PLAIN' == $part->subtype){
							//Extract this text
							$rawContent = imap_qprint(imap_fetchbody($this->_mbox, $mbox_msg,$i));
							$content = substr($rawContent,0, strlen($rawContent)-2).$content;
						//Is current section a jpeg picture?
						else if('JPEG' == $part->subtype){
							//Define a random filename
							$filename = md5(time()*$i*$mbox_msg).'.jpg';
							$filepath = dirname(__FILE__).'/../../../'.DATA_FOLDER.$filename;
							//Storespicture on file system
							if (!$handle = fopen($filepath, "w")){
								Log::fGetInstance()->fLog(Log::$TYPE['error'] ,__CLASS__, 'Image cannot be created on file system');
								throw new ImportException("Image cannot be created on file system.", ImportException::WRITING_FAILED);
							if (FALSE === fwrite($handle, base64_decode(imap_fetchbody($this->_mbox, $mbox_msg,$i)))){
								Log::fGetInstance()->fLog(Log::$TYPE['error'] ,__CLASS__, 'Image cannot be written on file system');								
								throw new ImportException("Image cannot be written on file system.", ImportException::WRITING_FAILED);
							//Extract usefull data from EXIF metadata (loc, timestamp)
							if(FALSE !== ($coords = $this->_fGetDataFromExif($filepath)))
								//Height /Width ratio
								$ratioHW = 	$coords[3];
									$lat = $coords[0];
									$lng = $coords[1];
									$timestamp = $coords[2];

								Log::fGetInstance()->fLog(Log::$TYPE['error'] ,__CLASS__, 'Coordinates cannot be extracted from the picture');																
								throw new ImportException("Coordinates cannot be extracted from the picture.", ImportException::MESSAGE_REQUEST_FAILED);				
							//Is current pic must be used in the blog entry?
								//Delete current image which was necessary only to get geodata
								$filename = '';
							break;//Only one pic is expected by image
						}//else if
					//Reverse geocode
					$placemark = $this->_fReverseGeocode($lat,$lng);
					//Inserts all extracted contents in database
					$this->_fInsert($lat, $lng, $content, $timestamp, $filename, $ratioHW, $placemark);
					$this->_newData = true;
					//Delete current message
					imap_delete($this->_mbox, $mbox_msg);
					Log::fGetInstance()->fLog(Log::$TYPE['info'] ,__CLASS__, 'All new messages deleted');								
				return TRUE;
				Log::fGetInstance()->fLog(Log::$TYPE['info'] ,__CLASS__, 'No message found in '.EMAIL_SERVER);
				return FALSE;
		//HELPER FUNCTIONS (all private)

		* _fGetLocationFromSubject method
		* @param string $aSubject subject of a message (should contain location either as address or coordinates)
		* @return array|FALSE If successfull coordinates as array of [lat, lng] extracted from the subject
		private function _fGetLocationFromSubject($aSubject){
			//location can be either a (lat,lng) pair or an address
			$coords = split(',', $aSubject);
			if(2 == count($coords)){
				$coords[0] = floatval(trim($coords[0]));
				$coords[1] = floatval(trim($coords[1]));
				if( ($coords[0]<90.0) && ($coords[0]>-90.0) && ($coords[1]<180.0) && ($coords[1]>-180.0)){
					//location is highly probably given as a (lat,lng) pair.
					$coords[] = '';
					return $coords;				
			//At this stage assumes loc is given as an address
			$coords = array();
			//try geocoding
			if(FALSE === ($csv = file_get_contents(GOOGLE_URL_GEOCODING.urlencode($aSubject))))
				return FALSE;
			$csvSplit = split(",", $csv);
			if (strcmp($status, $csvSplit[2]) == 0) {
				$coords[] = $csvSplit[2];//lat
				$coords[] = $csvSplit[3];//lng
				return FALSE;
			//current address will be appended to message body
			$coords[] = ' (@ '.$aSubject.')';
			return $coords;

		* _fGetDataFromExif method
		* @param string $aFilename path of the pic (on server file system) to analyse
		* @return array|FALSE If successfull coordinates as array of [lat, lng,timestamp, and height/width ratio] extracted from the subject
		private function _fGetDataFromExif($aFilename){
			//Extract EXIF structure
			if(FALSE === ($exif = @exif_read_data($aFilename, 0, true)))
				return FALSE;
			//Get file width
					$width = intval($exif['COMPUTED']['Width']);
					return FALSE;
				//Get file height
					$height = intval($exif['COMPUTED']['Height']);
					return FALSE;
				//...and computes pic size ratio		
				$ratio = $height / $width;
				return FALSE;
			//Get timestamp
			if(isset($exif['EXIF']) && isset($exif['EXIF']['DateTimeOriginal']))
					$timestamp = strtotime($exif['EXIF']['DateTimeOriginal']);
				return FALSE;
			//Get GPS data
				//Processe longitude first
					//Format extracted longitude
					$lng = $this->_fGetCoordinatesFromExif($exif['GPS']['GPSLongitude']);
					//Check longitude sign 
						if('W' == $exif['GPS']['GPSLongitudeRef'])
							$lng = -$lng;
						return FALSE;	
					return FALSE;
				//Processe latitude		
					//Format extracted latitude		
					$lat = $this->_fGetCoordinatesFromExif($exif['GPS']['GPSLatitude']);
					//Check latitude sign 
						if('S' == $exif['GPS']['GPSLatitudeRef'])
							$lat = -$lat;
						return FALSE;
					return FALSE;
				return FALSE;
			return array($lat, $lng, $timestamp, $ratio);		

		* _fGetCoordinatesFromExif method
		* @param array $aInput array of coordinates as [deg, mn, sec] (each item can be a quotient of numbers (See http://www.exif.org/Exif2-2.PDF for more explanations)
		* @return float|FALSE If successfull coordinates as decimal degrees
		private function _fGetCoordinatesFromExif($aInput){
			if(FALSE === $dd_f)
				$dd = $aInput[0];
				$denominator = floatval(substr($aInput[0],$dd_f+1));
					$dd= 0.0;
			if(FALSE === $mm_f)
				$mm= $aInput[1];
				$denominator = floatval(substr($aInput[1],$mm_f+1));
					$mm= 0.0;
			if(FALSE === $ss_f)
				$ss= $aInput[2];
				$denominator = floatval(substr($aInput[2],$ss_f+1));
					$ss= 0.0;
			//Return current coordinates converted in decimal degrees 
			return $dd + 0.0166666667 * $mm + 2.7777778e-4 * $ss;

		* _fReverseGeocode method
		* This method uses Curl library to connect to Google reverse geocoder
		* @param float $aLat latitude as decimal degrees
		* @param float $aLng longitude as decimal degrees
		* @return string location as 'city (country)'
		private function _fReverseGeocode($aLat, $aLng){
			$curl_handle = curl_init();
			curl_setopt($curl_handle, CURLOPT_CONNECTTIMEOUT, 2);
			curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, 1);	
			curl_setopt($curl_handle, CURLOPT_URL, GOOGLE_URL_REVERSEGEOCODING.$aLat.','.$aLng);	
			$buffer = curl_exec($curl_handle);
			if(FALSE !== ($rg = json_decode ($buffer, true))){
				if(200 !== $rg['Status']['code'])
					return '';
				$rg = $rg['Placemark'][0]['AddressDetails']['Country'];
				return $rg['AdministrativeArea']['SubAdministrativeArea']['Locality']['LocalityName'].' ('.$rg['CountryName'].')';
				return '';
Return current item: Gemibloo