Mail2Ticket PièceJointes & images

Contribuer au projet de part vos connaissances: PHP, HTML, CSS, SQL, JS ....
Répondre
ThomasLR
Gsup LEVEL 0
Messages : 4
Enregistré le : mar. 21 avr. 2015 12:41

Hello,

Du coup j'ai supprimé mon précédent message dans le forum BugTracking. (la version est désormais obsolète)

Ci-dessous une version très grandement modifié de Mail2Ticket qui permet de récupérer les pièces jointes et les images (et les affiche convenablement sur le ticket dans Gestsup.

Fonctionne avec les emails envoyés depuis Outlook et désormais Gmail.
L'encodage des noms de PJ semble différent suivant le mailbrowser qui envoie le mail.
Pour les images j'ai modifié le code et désormais tout devrait bien s'afficher quelque soit l'expéditeur.

Sinon, pour le fonctionnement, on trouve l'email en question sur le serveur, on parcours l'ensemble des parts de l'email de façon récursive, et suivant le type de la parts on la traite différemment (corps du texte / pièce jointes / images).

Exemple d'un email récupéré avec ce code :

Image

Code : Tout sélectionner

<?php
//error_reporting(E_ALL);

$count=0;

//connexion script with database parameters
require "connect.php";

//define current time
$datetime = date("Y-m-d H:i:s");

//load parameters table
$qparameters = mysql_query("SELECT * FROM `tparameters`"); 
$rparameters= mysql_fetch_array($qparameters);

//hostname building
$hostname = '{'.$rparameters['imap_server'].':'.$rparameters['imap_port'].'}'.$rparameters['imap_inbox'].'';


//connect to inbox

//-----Default Case 
//$inbox = imap_open($hostname,$rparameters['imap_user'],$rparameters['imap_password']) or die('Impossible de se connecter au serveur de Messagerie: ' . imap_last_error());
//-----Exchange2010
$inbox = imap_open('{'.$rparameters['imap_server'].':'.$rparameters['imap_port'].'/imap/novalidate-cert}'.$rparameters['imap_inbox'].'', $rparameters['imap_user'], $rparameters['imap_password'],NULL,1,array('DISABLE_AUTHENTICATOR'=>'GSSAPI')) or die('Impossible de se connecter au serveur de Messagerie: ' . imap_last_error());




//display header
echo'
<html lang="fr">
	<head>
		<meta charset="UTF-8" />
	</head>
';

if($inbox) 
{ 
	//grab mail
	echo "<u></u>Connexion à la boite au lettre en cours: <font color=green>ok</font><br /><br />";
	$emails = imap_search($inbox,'ALL');
	 //if emails are returned
    if($emails) {
		//for every email...
		foreach($emails as $email_number) {
			
			//get next new ticket id
			$query = mysql_query("SELECT MAX(id) FROM tincidents");
			$row=mysql_fetch_array($query);
			$tid=$row[0]+1;
			
			//get information specific to this email		
			$overview = imap_fetch_overview($inbox, $email_number, 0);
			$seen=$overview[0]->seen;
			//if message is not read
			if ($seen==0)
			//if($email_number==12)
			{			
				
				$count=$count+1;
				//get mail data
								
				$emailMessage = new EmailMessage($inbox, $email_number,"upload/".$tid."/");
				$emailMessage->fetch();
				
				//$tes = $emailMessage->bodyHTML;
				//echo "$tes";
				//$te = $emailMessage->bodyPlain;
				//echo "$te";

				if($emailMessage->EmailStructure == "parts"){
					// IN CASE OF PARTS EMAIL STRUCT
					
					$message =  $emailMessage->bodyHTML;
					// Bug Correction 05/11 : getting the code inside the body balise only
					// Otherwise the message displayed on the wysywig editor is updated to text 
					$bodyPos=0;
					$bodyPos=strpos($message,"<body"); 
					$message=substr($message,$bodyPos); 
					$bodyPos=strpos($message,">"); 
					$message=substr($message,$bodyPos+1); 
					$bodyPos=strpos($message,"</body>"); 
					$message=substr($message,0,$bodyPos-1); 
					//special char convert
					$message=quoted_printable_decode($message);
					$message = str_replace("'","''",$message);
				
				}
				else{
					// IN CASE OF SIMPLE EMAIL STRUCT
					$message = $emailMessage->bodyPlain;
					$message = base64_decode($message);
					
				}				
					
				//Escape special char to SQL query
				//$message=mysql_real_escape_string($message); //no more needed
					
				$header = imap_headerinfo($inbox, $email_number);
				$subject = $overview[0] -> subject;
				$from = $header->from[0]->mailbox . "@" . $header->from[0]->host;
				$subject=mb_decode_mimeheader($subject);
				$subject = str_replace('_', ' ', $subject);

				$subject=mysql_real_escape_string($subject);
			
				//find gestsup userid from mail address
				$query= mysql_query("SELECT id FROM `tusers` where mail='$from' ");
				$row=mysql_fetch_array($query);
				if($row[0])
				{
					$user_id=$row[0];
				} else {
					$user_id='';
					$message='De '.$from.':\n'.$message;	
				}
				
				//attachment for the query
				$aCount=$emailMessage->attachmentsCount;
				$aArray=$emailMessage->attachments;
				$fields="";
				$values="";
				
				for($z = 0; $z < $aCount; $z++) {
							$y=$z+1;
							$fields .= "img".$y.",";
							$values .= "'".$aArray[$z]['filename']."',";
						}
						
				//echo "START";
				//echo "$message";
				//echo "END";
				
				// create ticket
				$query= "INSERT INTO tincidents 
				(user,technician,title,description,date_create,".$fields."techread,state,criticality,disable,place) 
				VALUES  ('$user_id','0','$subject','$message','$datetime',".$values."'0','5','4','0','1')";
				$exec = mysql_query($query) or die('Erreur SQL !<br />'.mysql_error());
				//echo $query;
			
				echo "&nbsp> Import du message $count: $subject: <font color=green>ok</font><br />";
			}
		}
	}
	
	imap_close($inbox);
	echo "<br />Total: Récupération de $count messages depuis <b>$rparameters[imap_server]</b> depuis le port <b>$rparameters[imap_port]</b><br />";

} else {
	echo "Erreur de connexion IMAP";
}
echo '</html>';

exit;

class EmailMessage {
	protected $connection;
	protected $messageNumber;
	protected $imagePathFolder;
	public $bodyHTML = '';
	public $bodyPlain = '';
	public $EmailStructure = ''; //simple or parts
	public $attachments=array();
	public $attachmentsCount=0;
	public $imageNumber=0;
	public $imgsrc = array(); //array because you can have more than 1 image

	public function __construct($connection, $messageNumber, $imagePathFolder) {
		$this->connection = $connection;
		$this->messageNumber = $messageNumber;		
		$this->imagePathFolder = $imagePathFolder;	//exemple "upload/image/"	
		
		//folder creation if it don't exist
		if (!file_exists($imagePathFolder)) {
			mkdir($imagePathFolder, 0777);
		}
		
	}

	public function fetch() {
		//look for all the email parts and extract the body
		$structure = @imap_fetchstructure($this->connection, $this->messageNumber);
		if(!$structure) {
			return false;
		}
		else {
			
			if(property_exists ( $structure , "parts" )){
				//Parts exist
				$this->EmailStructure = "parts";
				$this->RecursiveFetch($structure->parts);
				
				//For debug HELP ONLY
				//$this->printobject($structure);
			}
			else {
				//parts do not exist (it means no attachments & simple body)
				// simply check the body
				$this->EmailStructure = "simple";
				$this->bodyPlain = imap_body($this->connection, $this->messageNumber);
				
				//For debug HELP ONLY
				//$this->printobject($structure);
			}
			return true;
		}
	}
	
	
	// PRINTARRAY & PRINT OBJECT are used for debug only
	//use $this->printobject($structure);
	//to print the structure of an email
	
	public function printarray($array){
	  while(list($key,$value) = each($array)){
		if(is_array($value)){
		  echo $key."(array):<blockquote>";
		  $this->printarray($value);
		  echo "</blockquote>";
		}elseif(is_object($value)){
		  echo $key."(object):<blockquote>";
		  $this->printobject($value);
		  echo "</blockquote>";
		}else{
		  echo $key."==>".$value."<br />";
		}
	  }
	}
	public function printobject($obj){
	  $array = get_object_vars($obj);
	  $this->printarray($array);
	}

	
	public function RecursiveFetch($messageParts, $prefix = '', $index = 1, $fullPrefix = true) {

		foreach($messageParts as $part) {
		
			$partNumber = $prefix.$index;
			
			switch($part->type) {
				//===================== CHECKING FOR THE BODY MESSAGE ====================
				//Please check imap_fetchstructure on php.net for more details about type
				case 0: 
					
					if($part->subtype == 'PLAIN') {
						//------- Case PLAIN TEXT --------
						$txt = $this->getPart($partNumber, $part->encoding);
						$this->bodyPlain .= $txt;
					}
					else {	
					
						//------- Case HTML --------
						$txt = $this->getPart($partNumber, $part->encoding);
						
						//update all img src since the one we get means nothing (and target nothing)
						//the src is updated here and the image download/save below ()check type==5 for image)
						
						//the src for exchange is still starting by cid:imagexxx where xxx is the image number
						//this is followed by an id.
						//the objective is to update the src to target the image we will download
						$pos= strpos($txt,"cid:"); 
						
						$remainHTML = $txt; //the remaining html code we have to check
						$imagecount=0;
						
						while ($pos){
							$this->imgsrc[$imagecount] = array(
										'src' => '',
										'filename' => '');
							$remainHTML = substr($remainHTML,$pos);
							$temp = str_pad($imagecount, 3, "0", STR_PAD_LEFT);  
							$this->imgsrc[$imagecount]['filename'] = "TemporaryImageName".$temp; //substr($remainHTML,4,strpos($remainHTML,'"')-4); //isolating the image name
							$this->imgsrc[$imagecount]['src'] = substr($remainHTML,0,strpos($remainHTML,'"')); //isolating the image src from the actual html body
							$imagecount = $imagecount+1;
							$remainHTML = substr($remainHTML,1);
							$pos = strpos($remainHTML,"cid:");
						}
						
						//replacing all img src by the temporaryimagename
						//you can change the $imagePathFolder property
						for($i = 0; $i < $imagecount; $i++) {
							$txt = str_replace($this->imgsrc[$i]['src'],$this->imagePathFolder.$this->imgsrc[$i]['filename'],$txt);
						}
						
						$this->bodyHTML .= $txt;
					}
					break;

				//MULTIPART - depend of the mail structure, it vary depending on the library used (apple, gmail, exchange ? ?)
				//sometimes the body can be found on part 1. sometimes it is on a subpart 1.1 - 1.2 etc...
				//that's why we need the recursive function.
				case 1:
					if(isset($part->parts)) {
						if($fullPrefix) {
							$this->RecursiveFetch($part->parts, $prefix.$index.'.');
						}
						else {
							$this->RecursiveFetch($part->parts, $prefix);
						}
					}		
					break;
				
				//===================== ATTACHED EMAIL =========================
				case 2:
					//probably used when an email is attached to the email you are fetching.
					break;
				
				//===================== SAVE IMAGES OR ATTACHMENTS TO THE SPECIFIED DIRECTORY ====================
				default:
					//All attachments
					// 3 : application
					// 4 : audio
					// 5 : image (attached or paste)
					// 6 : video
					// 7 : other
					
					
					$filename="";
					$filename = $this->getFilenameFromPart($part);
					
					//echo "$filename";
					
					//If the part is an attachment and not a paste image
			        if (isset($part->disposition)){ 
						if ($part->disposition == 'attachment'){ 
										
							
							$this->attachments[$this->attachmentsCount] = array(
										'filename' => $filename);
							$this->attachmentsCount = $this->attachmentsCount+1;
						} 
						else {
							//if the part is a paste image
							$this->bodyHTML = str_replace($this->imgsrc[$this->imageNumber]['filename'],$filename,$this->bodyHTML);
							$this->imageNumber = $this->imageNumber+1;
						}
					} 			
					
					//saving all images / attachments
					$attachment = $this->getPart($partNumber, $part->encoding);
										
					//echo "$this->imagePathFolder";
					//echo "$filename";
					//echo "$this->imagePathFolder" + "$filename";
					
					file_put_contents($this->imagePathFolder.$filename,$attachment); 
					break;
			}	
			$index++;
		}
	}
	
	function getPart($partNumber, $encoding) {

		$data = imap_fetchbody($this->connection, $this->messageNumber, $partNumber);
		switch($encoding) {
			case 0: return $data; // 7BIT
			case 1: return $data; // 8BIT
			case 2: return $data; // BINARY
			case 3: return base64_decode($data); // BASE64
			case 4: return quoted_printable_decode($data); // QUOTED_PRINTABLE
			case 5: return $data; // OTHER		
		}
	}
	
	function getFilenameFromPart($part) {

		$filename = '';
		
		if($part->ifdparameters) {
			foreach($part->dparameters as $object) {
				if(strtolower($object->attribute) == 'filename') {
					$filename = $object->value;
					//decodage UTF8- useful for gmail emails
					
					//echo "$filename";
					
					$filename = imap_utf8($filename);
					$filename = utf8_decode($filename);
					$filename = str_replace("?","",$filename);
				}
			}
		}

		if(!$filename && $part->ifparameters) {
			foreach($part->parameters as $object) {
				if(strtolower($object->attribute) == 'name') {
					$filename = $object->value;
					//decodage UTF8- useful for gmail emails
					$filename = imap_utf8($filename);
					$filename = utf8_decode($filename);
					$filename = str_replace("?","",$filename);
				}
			}
		}

		return $filename;

	}

}
?>
N'hésitez pas si vous avez des questions :D
@+++
Modifié en dernier par ThomasLR le lun. 11 janv. 2016 12:22, modifié 1 fois.
of_course
Gsup LEVEL 0
Messages : 5
Enregistré le : mer. 3 juin 2015 11:23

Hello,
J'aimerai appliquer vos modifications, je suis néophyte pouvez-vous me dire quel fichier modifier pour appliquer votre code s'il vous plait?
ThomasLR
Gsup LEVEL 0
Messages : 4
Enregistré le : mar. 21 avr. 2015 12:41

Hello,

J'ai updaté le code de mon post original en corrigeant quelques bugs.
Notamment certaines images mal enregistrée.
Egalement un bug apparaissant dans certains cas lors de la création en rafale de plusieurs incidents.

Sinon désolé pour la réponse ultra tardive... mais pour utiliser ce code il faut que tu remplaces le fichier mail2ticket.php par un nouveau dans lequel tu copies / colles le code que j'ai fourni et c'est tout.
Ou alors tu édites mail2ticket.php et tu remplace tout par mon code.

@++

Edit : Par défaut les pièces jointes / images sont enregistrées dans www/upload/numduticket.
Pour changer ça il faut modifier la ligne :
$emailMessage = new EmailMessage($inbox, $email_number,"upload/".$tid."/");

L'idéal serait d'inclure un paramètre à régler directement depuis l'interface d'admin de gestsup mais il faudrait modifier la base + d'autre fichier PHP. Ce qui est gênant en cas de montée de version...

La seul le fichier mail2ticket.php est à changer en cas d'update de version.
Avatar du membre
Flox
Administrateur du site
Messages : 9030
Enregistré le : jeu. 21 juin 2012 19:00

Bonjour,

merci pour votre travail mais je pense que le code sera incompatible avec la monté en version 3.1.X puisque nous sommes passer en PDO.

Pouvez vous me donner les lignes à modifier que je regarde pour intégrer cela dans la prochaine version.

voir de faire les modification directement dans le fichier en version:

Code : Tout sélectionner

# @Update : 26/11/2015
# @Version : 3.1.1
Merci
GestSup: 3.2.47 | Debian: 12 | Apache: 2.4.58 | MariaDB: 11.3.2 | PHP: 8.3.6 | https://doc.gestsup.fr/
ouioui2821
Gsup LEVEL 1
Messages : 22
Enregistré le : mar. 17 juin 2014 11:57

Je serai très intéressé par cette modification, pouvez vous nous en dire plus et surtout si possible la rendre compatible avec les versions après 3.1.7.

Merci d'avance
GestSup: 3.1.39
Dan
Gsup LEVEL 1
Messages : 19
Enregistré le : lun. 16 mai 2016 17:11

Bonjour,

Est-ce possible d'intégrer la solution que j'ai proposée sur ce poste viewtopic.php?f=1&t=3086
et continuer son développement ?

Merci,
Dan
Version 3.2.24 (Version 3.2 avec patch 24)
Répondre