From f0a3a5ce025a8470b34c8f44384d1cebaa9451dd Mon Sep 17 00:00:00 2001
From: Laurent Destailleur <eldy@destailleur.fr>
Date: Mon, 25 Jul 2016 21:50:41 +0200
Subject: [PATCH] NEW Mutualize mass action. So "Send by email" is also
 avavilable on orders.

---
 htdocs/commande/list.php                      | 277 ++++------
 htdocs/compta/facture.php                     |   2 +-
 .../facture/class/api_invoice.class.php       |   2 +-
 htdocs/compta/facture/class/facture.class.php |   6 +-
 htdocs/compta/facture/fiche-rec.php           |   4 +-
 htdocs/compta/facture/list.php                | 427 +--------------
 htdocs/core/actions_massactions.inc.php       | 499 ++++++++++++++++++
 htdocs/core/lib/files.lib.php                 |  12 +-
 htdocs/langs/en_US/mails.lang                 |   3 +-
 htdocs/paypal/lib/paypal.lib.php              |   2 +
 10 files changed, 647 insertions(+), 587 deletions(-)
 create mode 100644 htdocs/core/actions_massactions.inc.php

diff --git a/htdocs/commande/list.php b/htdocs/commande/list.php
index 0d1c2a1a1c7..6ded9b219ac 100644
--- a/htdocs/commande/list.php
+++ b/htdocs/commande/list.php
@@ -154,7 +154,7 @@ if (is_array($extrafields->attribute_label) && count($extrafields->attribute_lab
  */
 
 if (GETPOST('cancel')) { $action='list'; $massaction=''; }
-if (! GETPOST('confirmmassaction')) { $massaction=''; }
+if (! GETPOST('confirmmassaction') && $massaction != 'presend' && $massaction != 'confirm_presend') { $massaction=''; }
 
 $parameters=array('socid'=>$socid);
 $reshook=$hookmanager->executeHooks('doActions',$parameters,$object,$action);    // Note that $action and $object may have been modified by some hooks
@@ -195,175 +195,11 @@ if (GETPOST("button_removefilter_x") || GETPOST("button_removefilter.x") || GETP
 
 if (empty($reshook))
 {
-    // Mass actions. Controls on number of lines checked
-    $maxformassaction=1000;
-    if (! empty($massaction) && count($toselect) < 1)
-    {
-        $error++;
-        setEventMessages($langs->trans("NoLineChecked"), null, "warnings");
-    }
-    if (! $error && count($toselect) > $maxformassaction)
-    {
-        setEventMessages($langs->trans('TooManyRecordForMassAction',$maxformassaction), null, 'errors');
-        $error++;
-    }
-
-    // TODO Use a common inc.php file
-    if (! $error && $massaction == 'delete' && $user->rights->commande->supprimer)
-    {
-        $db->begin();
-
-        $objecttmp=new Commande($db);
-        $nbok = 0;
-        foreach($toselect as $toselectid)
-        {
-            $result=$objecttmp->fetch($toselectid);
-            if ($result > 0)
-            {
-                $result = $objecttmp->delete($user);
-                if ($result <= 0)
-                {
-                    setEventMessages($objecttmp->error, $objecttmp->errors, 'errors');
-                    $error++;
-                    break;
-                }
-                else $nbok++;
-            }
-            else
-            {
-                setEventMessages($objecttmp->error, $objecttmp->errors, 'errors');
-                $error++;
-                break;
-            }
-        }
-        
-        if (! $error)
-        {
-            if ($nbok > 1) setEventMessages($langs->trans("RecordsDeleted", $nbok), null, 'mesgs');
-            else setEventMessages($langs->trans("RecordDeleted", $nbok), null, 'mesgs');
-            $db->commit();
-        }
-        else
-        {
-            $db->rollback();
-        }
-        //var_dump($listofobjectthirdparties);exit;
-    }
-    
-    if (! $error && $massaction == "builddoc" && $user->rights->commande->lire && ! GETPOST('button_search'))
-    {
-        require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
-        require_once DOL_DOCUMENT_ROOT.'/core/lib/pdf.lib.php';
-        require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
-         
-        $objecttmp=new Commande($db);
-        $listofobjectid=array();
-        $listofobjectthirdparties=array();
-        $listofobjectref=array();
-        foreach($toselect as $toselectid)
-        {
-            $objecttmp=new Commande($db);	// must create new instance because instance is saved into $listofobjectref array for future use
-            $result=$objecttmp->fetch($toselectid);
-            if ($result > 0)
-            {
-                $listoinvoicesid[$toselectid]=$toselectid;
-                $thirdpartyid=$objecttmp->fk_soc?$objecttmp->fk_soc:$objecttmp->socid;
-                $listofobjectthirdparties[$thirdpartyid]=$thirdpartyid;
-                $listofobjectref[$toselectid]=$objecttmp->ref;
-            }
-        }
-    
-        $arrayofinclusion=array();
-        foreach($listofobjectref as $tmppdf) $arrayofinclusion[]=preg_quote($tmppdf.'.pdf','/');
-        $listoffiles = dol_dir_list($conf->commande->dir_output,'all',1,implode('|',$arrayofinclusion),'\.meta$|\.png','date',SORT_DESC,0,true);
-    
-        // build list of files with full path
-        $files = array();
-        foreach($listofobjectref as $basename)
-        {
-            foreach($listoffiles as $filefound)
-            {
-                if (strstr($filefound["name"],$basename))
-                {
-                    $files[] = $conf->commande->dir_output.'/'.$basename.'/'.$filefound["name"];
-                    break;
-                }
-            }
-        }
-    
-        // Define output language (Here it is not used because we do only merging existing PDF)
-        $outputlangs = $langs;
-        $newlang='';
-        if ($conf->global->MAIN_MULTILANGS && empty($newlang) && GETPOST('lang_id')) $newlang=GETPOST('lang_id');
-        if ($conf->global->MAIN_MULTILANGS && empty($newlang)) $newlang=$object->thirdparty->default_lang;
-        if (! empty($newlang))
-        {
-            $outputlangs = new Translate("",$conf);
-            $outputlangs->setDefaultLang($newlang);
-        }
-    
-        // Create empty PDF
-        $pdf=pdf_getInstance();
-        if (class_exists('TCPDF'))
-        {
-            $pdf->setPrintHeader(false);
-            $pdf->setPrintFooter(false);
-        }
-        $pdf->SetFont(pdf_getPDFFont($outputlangs));
-    
-        if (! empty($conf->global->MAIN_DISABLE_PDF_COMPRESSION)) $pdf->SetCompression(false);
-    
-        // Add all others
-        foreach($files as $file)
-        {
-            // Charge un document PDF depuis un fichier.
-            $pagecount = $pdf->setSourceFile($file);
-            for ($i = 1; $i <= $pagecount; $i++)
-            {
-                $tplidx = $pdf->importPage($i);
-                $s = $pdf->getTemplatesize($tplidx);
-                $pdf->AddPage($s['h'] > $s['w'] ? 'P' : 'L');
-                $pdf->useTemplate($tplidx);
-            }
-        }
-    
-        // Create output dir if not exists
-        dol_mkdir($diroutputmassaction);
-    
-        // Save merged file
-        $filename=strtolower(dol_sanitizeFileName($langs->transnoentities("Orders")));
-        if ($year) $filename.='_'.$year;
-        if ($month) $filename.='_'.$month;
-        if ($pagecount)
-        {
-            $now=dol_now();
-            $file=$diroutputmassaction.'/'.$filename.'_'.dol_print_date($now,'dayhourlog').'.pdf';
-            $pdf->Output($file,'F');
-            if (! empty($conf->global->MAIN_UMASK))
-                @chmod($file, octdec($conf->global->MAIN_UMASK));
-    
-                $langs->load("exports");
-                setEventMessages($langs->trans('FileSuccessfullyBuilt',$filename.'_'.dol_print_date($now,'dayhourlog')), null, 'mesgs');
-        }
-        else
-        {
-            setEventMessages($langs->trans('NoPDFAvailableForDocGenAmongChecked'), null, 'errors');
-        }
-    }
-    
-    // Remove file
-    if ($action == 'remove_file')
-    {
-        require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
-    
-        $langs->load("other");
-        $upload_dir = $diroutputmassaction;
-        $file = $upload_dir . '/' . GETPOST('file');
-        $ret=dol_delete_file($file);
-        if ($ret) setEventMessages($langs->trans("FileWasRemoved", GETPOST('file')), null, 'mesgs');
-        else setEventMessages($langs->trans("ErrorFailToDeleteFile", GETPOST('file')), null, 'errors');
-        $action='';
-    }    
+    $objectclass='Commande';
+    $permtoread = $user->rights->commande->lire;
+    $permtodelete = $user->rights->commande->supprimer;
+    $uploaddir = $conf->commande->dir_output;
+	include DOL_DOCUMENT_ROOT.'/core/actions_massactions.inc.php';
 }
 
 
@@ -564,7 +400,8 @@ if ($resql)
 	if ($search_total_ht != '') $param.='&search_total_ht='.$search_total_ht;
 	if ($search_total_vat != '') $param.='&search_total_vat='.$search_total_vat;
 	if ($search_total_ttc != '') $param.='&search_total_ttc='.$search_total_ttc;
-	if ($optioncss != '')       $param.='&optioncss='.$optioncss;
+    if ($show_files)            $param.='&show_files=' .$show_files;
+    if ($optioncss != '')       $param.='&optioncss='.$optioncss;
 	// Add $param from extra fields
 	foreach ($search_array_options as $key => $val)
 	{
@@ -593,6 +430,102 @@ if ($resql)
 
 	print_barre_liste($title, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, 'title_commercial.png', 0, '', '', $limit);
 	
+	if ($massaction == 'presend')
+	{
+	    $langs->load("mails");
+	
+	    if (! GETPOST('cancel'))
+	    {
+	        $objecttmp=new Commande($db);
+	        $listofselectedid=array();
+	        $listofselectedthirdparties=array();
+	        $listofselectedref=array();
+	        foreach($arrayofselected as $toselectid)
+	        {
+	            $result=$objecttmp->fetch($toselectid);
+	            if ($result > 0)
+	            {
+	                $listofselectedid[$toselectid]=$toselectid;
+	                $thirdpartyid=$objecttmp->fk_soc?$objecttmp->fk_soc:$objecttmp->socid;
+	                $listofselectedthirdparties[$thirdpartyid]=$thirdpartyid;
+	                $listofselectedref[$thirdpartyid][$toselectid]=$objecttmp->ref;
+	            }
+	        }
+	    }
+	
+	    print '<input type="hidden" name="massaction" value="confirm_presend">';
+	
+	    include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
+	    $formmail = new FormMail($db);
+	
+	    dol_fiche_head(null, '', '');
+	
+	    $topicmail="SendOrderRef";
+	    $modelmail="order_send";
+	
+	    // Cree l'objet formulaire mail
+	    include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
+	    $formmail = new FormMail($db);
+	    $formmail->withform=-1;
+	    $formmail->fromtype = 'user';
+	    $formmail->fromid   = $user->id;
+	    $formmail->fromname = $user->getFullName($langs);
+	    $formmail->frommail = $user->email;
+	    if (! empty($conf->global->MAIN_EMAIL_ADD_TRACK_ID) && ($conf->global->MAIN_EMAIL_ADD_TRACK_ID & 1))	// If bit 1 is set
+	    {
+	        $formmail->trackid='ord'.$object->id;
+	    }
+	    if (! empty($conf->global->MAIN_EMAIL_ADD_TRACK_ID) && ($conf->global->MAIN_EMAIL_ADD_TRACK_ID & 2))	// If bit 2 is set
+	    {
+	        include DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
+	        $formmail->frommail=dolAddEmailTrackId($formmail->frommail, 'ord'.$object->id);
+	    }
+	    $formmail->withfrom=1;
+	    $liste=$langs->trans("AllRecipientSelected");
+	    if (count($listofselectedthirdparties) == 1)
+	    {
+	        $liste=array();
+	        $thirdpartyid=array_shift($listofselectedthirdparties);
+	        $soc=new Societe($db);
+	        $soc->fetch($thirdpartyid);
+	        foreach ($soc->thirdparty_and_contact_email_array(1) as $key=>$value)
+	        {
+	            $liste[$key]=$value;
+	        }
+	        $formmail->withtoreadonly=0;
+	    }
+	    else
+	    {
+	        $formmail->withtoreadonly=1;
+	    }
+	    $formmail->withto=$liste;
+	    $formmail->withtofree=0;
+	    $formmail->withtocc=1;
+	    $formmail->withtoccc=$conf->global->MAIN_EMAIL_USECCC;
+	    $formmail->withtopic=$langs->transnoentities($topicmail, '__REF__', '__REFCLIENT__');
+	    $formmail->withfile=$langs->trans("OnlyPDFattachmentSupported");
+	    $formmail->withbody=1;
+	    $formmail->withdeliveryreceipt=1;
+	    $formmail->withcancel=1;
+	    // Tableau des substitutions
+	    $formmail->substit['__REF__']='__REF__';	// We want to keep the tag
+	    $formmail->substit['__SIGNATURE__']=$user->signature;
+	    $formmail->substit['__REFCLIENT__']='__REFCLIENT__';	// We want to keep the tag
+	    $formmail->substit['__PERSONALIZED__']='';
+	    $formmail->substit['__CONTACTCIVNAME__']='';
+	
+	    // Tableau des parametres complementaires du post
+	    $formmail->param['action']=$action;
+	    $formmail->param['models']=$modelmail;
+	    $formmail->param['models_id']=GETPOST('modelmailselected','int');
+	    $formmail->param['id']=join(',',$arrayofselected);
+	    //$formmail->param['returnurl']=$_SERVER["PHP_SELF"].'?id='.$object->id;
+	
+	    print $formmail->get_form();
+	
+	    dol_fiche_end();
+	}
+	
 	if ($sall)
     {
         foreach($fieldstosearchall as $key => $val) $fieldstosearchall[$key]=$langs->trans($val);
@@ -1173,7 +1106,7 @@ if ($resql)
         
         // Action column
         print '<td class="nowrap" align="center">';
-        if ($massactionbutton)
+        if ($massactionbutton || $massaction)   // If we are in select mode (massactionbutton defined) or if we have already selected and sent an action ($massaction) defined
         {
             $selected=0;
     		if (in_array($obj->rowid, $arrayofselected)) $selected=1;
diff --git a/htdocs/compta/facture.php b/htdocs/compta/facture.php
index b2b39e375d7..b6e7fc463e1 100644
--- a/htdocs/compta/facture.php
+++ b/htdocs/compta/facture.php
@@ -188,7 +188,7 @@ if (empty($reshook))
 			$qualified_for_stock_change = $object->hasProductsOrServices(1);
 		}
 
-		$result = $object->delete(0, 0, $idwarehouse);
+		$result = $object->delete($user, 0, $idwarehouse);
 		if ($result > 0) {
 			header('Location: ' . DOL_URL_ROOT . '/compta/facture/list.php');
 			exit();
diff --git a/htdocs/compta/facture/class/api_invoice.class.php b/htdocs/compta/facture/class/api_invoice.class.php
index 60098a38b9f..da2a6185053 100644
--- a/htdocs/compta/facture/class/api_invoice.class.php
+++ b/htdocs/compta/facture/class/api_invoice.class.php
@@ -262,7 +262,7 @@ class InvoiceApi extends DolibarrApi
 			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
 		}
         
-        if( !$this->invoice->delete($id))
+        if( !$this->invoice->delete(DolibarrApiAccess::$user))
         {
             throw new RestException(500);
         }
diff --git a/htdocs/compta/facture/class/facture.class.php b/htdocs/compta/facture/class/facture.class.php
index 3b1bb17bfa7..c88648900cc 100644
--- a/htdocs/compta/facture/class/facture.class.php
+++ b/htdocs/compta/facture/class/facture.class.php
@@ -1548,14 +1548,14 @@ class Facture extends CommonInvoice
 	/**
 	 *	Delete invoice
 	 *
-	 *	@param     	int		$rowid      	Id of invoice to delete. If empty, we delete current instance of invoice
+	 *	@param     	User	$user      	    User to delete.
 	 *	@param		int		$notrigger		1=Does not execute triggers, 0= execute triggers
 	 *	@param		int		$idwarehouse	Id warehouse to use for stock change.
 	 *	@return		int						<0 if KO, >0 if OK
 	 */
-	function delete($rowid=0, $notrigger=0, $idwarehouse=-1)
+	function delete($user, $notrigger=0, $idwarehouse=-1)
 	{
-		global $user,$langs,$conf;
+		global $langs,$conf;
 		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
 
 		if (empty($rowid)) $rowid=$this->id;
diff --git a/htdocs/compta/facture/fiche-rec.php b/htdocs/compta/facture/fiche-rec.php
index 198b08cb451..8eb77d30767 100644
--- a/htdocs/compta/facture/fiche-rec.php
+++ b/htdocs/compta/facture/fiche-rec.php
@@ -206,7 +206,7 @@ if ($action == 'add')
 		$result = $object->create($user, $oldinvoice->id);
 		if ($result > 0)
 		{
-			$result=$oldinvoice->delete(0, 1);
+			$result=$oldinvoice->delete($user, 1);
 			if ($result < 0)
 			{
 			    $error++;
@@ -242,7 +242,7 @@ if ($action == 'add')
 // Delete
 if ($action == 'delete' && $user->rights->facture->supprimer)
 {
-	$object->delete();
+	$object->delete($user);
 	header("Location: " . $_SERVER['PHP_SELF'] );
 	exit;
 }
diff --git a/htdocs/compta/facture/list.php b/htdocs/compta/facture/list.php
index 5bd8ecfe852..3e714c8da08 100644
--- a/htdocs/compta/facture/list.php
+++ b/htdocs/compta/facture/list.php
@@ -175,7 +175,8 @@ if (is_array($extrafields->attribute_label) && count($extrafields->attribute_lab
  */
 
 if (GETPOST('cancel')) { $action='list'; $massaction=''; }
-if (! GETPOST('confirmmassaction')) { $massaction=''; }
+if (! GETPOST('confirmmassaction') && $massaction != 'presend' && $massaction != 'confirm_presend') { $massaction=''; }
+
 
 $parameters=array('socid'=>$socid);
 $reshook=$hookmanager->executeHooks('doActions',$parameters,$object,$action);    // Note that $action and $object may have been modified by some hooks
@@ -218,408 +219,11 @@ if (GETPOST("button_removefilter_x") || GETPOST("button_removefilter") || GETPOS
 
 if (empty($reshook))
 {
-	// Mass actions. Controls on number of lines checked
-    $maxformassaction=1000;
-	if (! empty($massaction) && count($toselect) < 1)
-	{
-		$error++;
-		setEventMessages($langs->trans("NoLineChecked"), null, "warnings");
-	}
-	if (! $error && count($toselect) > $maxformassaction)
-	{
-	    setEventMessages($langs->trans('TooManyRecordForMassAction',$maxformassaction), null, 'errors');
-	    $error++;
-	}
-	
-	// TODO Use a common inc.php file
-	if (! $error && $massaction == 'confirm_presend')
-	{
-		$resaction = '';
-		$nbsent = 0;
-		$nbignored = 0;
-		$langs->load("mails");
-		include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
-		
-		if (!$error && !isset($user->email))
-		{
-			$error++;
-			setEventMessages($langs->trans("NoSenderEmailDefined"), null, 'warnings');
-		}
-
-		if (! $error)
-		{
-			$thirdparty=new Societe($db);
-			$objecttmp=new Facture($db);
-			$listofobjectid=array();
-			$listofobjectthirdparties=array();
-			$listofobjectref=array();
-			foreach($toselect as $toselectid)
-			{
-				$objecttmp=new Facture($db);	// must create new instance because instance is saved into $listofobjectref array for future use
-				$result=$objecttmp->fetch($toselectid);
-				if ($result > 0) 
-				{
-					$listoinvoicesid[$toselectid]=$toselectid;
-					$thirdpartyid=$objecttmp->fk_soc?$objecttmp->fk_soc:$objecttmp->socid;
-					$listofobjectthirdparties[$thirdpartyid]=$thirdpartyid;
-					$listofobjectref[$thirdpartyid][$toselectid]=$objecttmp;
-				}
-			}
-			//var_dump($listofobjectthirdparties);exit;
-			
-			foreach ($listofobjectthirdparties as $thirdpartyid)
-			{
-				$result = $thirdparty->fetch($thirdpartyid);
-				if ($result < 0) 
-				{
-					dol_print_error($db);
-					exit;
-				}
-				
-				// Define recipient $sendto and $sendtocc
-				if (trim($_POST['sendto']))
-				{
-					// Recipient is provided into free text
-					$sendto = trim($_POST['sendto']);
-					$sendtoid = 0;
-				}
-				elseif ($_POST['receiver'] != '-1')
-				{
-					// Recipient was provided from combo list
-					if ($_POST['receiver'] == 'thirdparty') // Id of third party
-					{
-						$sendto = $thirdparty->email;
-						$sendtoid = 0;
-					}
-					else	// Id du contact
-					{
-						$sendto = $thirdparty->contact_get_property((int) $_POST['receiver'],'email');
-						$sendtoid = $_POST['receiver'];
-					}
-				}
-				if (trim($_POST['sendtocc']))
-				{
-					$sendtocc = trim($_POST['sendtocc']);
-				}
-				elseif ($_POST['receivercc'] != '-1')
-				{
-					// Recipient was provided from combo list
-					if ($_POST['receivercc'] == 'thirdparty')	// Id of third party
-					{
-						$sendtocc = $thirdparty->email;
-					}
-					else	// Id du contact
-					{
-						$sendtocc = $thirdparty->contact_get_property((int) $_POST['receivercc'],'email');
-					}
-				}
-				
-				//var_dump($listofobjectref[$thirdpartyid]);	// Array of invoice for this thirdparty
-				
-				$attachedfiles=array('paths'=>array(), 'names'=>array(), 'mimes'=>array());
-				$listofqualifiedinvoice=array();
-				$listofqualifiedref=array();
-				foreach($listofobjectref[$thirdpartyid] as $objectid => $object)
-				{
-					//var_dump($object);
-					//var_dump($thirdpartyid.' - '.$objectid.' - '.$object->statut);
-					
-					if ($object->statut != Facture::STATUS_VALIDATED)
-					{
-						$nbignored++;
-						continue; // Payment done or started or canceled
-					}
-	
-					// Read document
-					// TODO Use future field $object->fullpathdoc to know where is stored default file
-					// TODO If not defined, use $object->modelpdf (or defaut invoice config) to know what is template to use to regenerate doc.
-					$filename=dol_sanitizeFileName($object->ref).'.pdf';
-					$filedir=$conf->facture->dir_output . '/' . dol_sanitizeFileName($object->ref);
-					$file = $filedir . '/' . $filename;
-					$mime = dol_mimetype($file);
-
-					if (dol_is_file($file))
-					{
-						if (empty($sendto)) 	// For the case, no recipient were set (multi thirdparties send)
-						{
-							$object->fetch_thirdparty();
-							$sendto = $object->thirdparty->email;
-						}
-	
-						if (empty($sendto)) 
-						{
-							//print "No recipient for thirdparty ".$object->thirdparty->name;
-							$nbignored++;
-							continue;
-						}
-	
-						if (dol_strlen($sendto))
-						{
-							// Create form object
-							$attachedfiles=array(
-									'paths'=>array_merge($attachedfiles['paths'],array($file)), 
-									'names'=>array_merge($attachedfiles['names'],array($filename)), 
-									'mimes'=>array_merge($attachedfiles['mimes'],array($mime))
-							);
-						}
-	
-						$listofqualifiedinvoice[$objectid]=$object;
-						$listofqualifiedref[$objectid]=$object->ref;
-					}
-					else
-					{  
-						$nbignored++;
-						$langs->load("other");
-						$resaction.='<div class="error">'.$langs->trans('ErrorCantReadFile',$file).'</div>';
-						dol_syslog('Failed to read file: '.$file, LOG_WARNING);
-						continue;
-					}
-					
-					//var_dump($listofqualifiedref);
-				}
-	
-				if (count($listofqualifiedinvoice) > 0)
-				{
-					$langs->load("commercial");
-					$from = $user->getFullName($langs) . ' <' . $user->email .'>';
-					$replyto = $from;
-					$subject = GETPOST('subject');
-					$message = GETPOST('message');
-					$sendtocc = GETPOST('sentocc');
-					$sendtobcc = (empty($conf->global->MAIN_MAIL_AUTOCOPY_INVOICE_TO)?'':$conf->global->MAIN_MAIL_AUTOCOPY_INVOICE_TO);
-		
-					$substitutionarray=array(
-						'__ID__' => join(', ',array_keys($listofqualifiedinvoice)),
-						'__EMAIL__' => $thirdparty->email,
-						'__CHECK_READ__' => '<img src="'.DOL_MAIN_URL_ROOT.'/public/emailing/mailing-read.php?tag='.$thirdparty->tag.'&securitykey='.urlencode($conf->global->MAILING_EMAIL_UNSUBSCRIBE_KEY).'" width="1" height="1" style="width:1px;height:1px" border="0"/>',
-						//'__LASTNAME__' => $obj2->lastname,
-						//'__FIRSTNAME__' => $obj2->firstname,
-						'__FACREF__' => join(', ',$listofqualifiedref),            // For backward compatibility
-					    '__REF__' => join(', ',$listofqualifiedref),
-						'__REFCLIENT__' => $thirdparty->name
-					);
-	
-					$subject=make_substitutions($subject, $substitutionarray);
-					$message=make_substitutions($message, $substitutionarray);
-	
-					$filepath = $attachedfiles['paths'];
-					$filename = $attachedfiles['names'];
-					$mimetype = $attachedfiles['mimes'];
-					
-					//var_dump($filepath);
-					
-					// Send mail
-					require_once(DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php');
-					$mailfile = new CMailFile($subject,$sendto,$from,$message,$filepath,$mimetype,$filename,$sendtocc,$sendtobcc,$deliveryreceipt,-1);
-					if ($mailfile->error)
-					{
-						$resaction.='<div class="error">'.$mailfile->error.'</div>';
-					}
-					else
-					{
-						$result=$mailfile->sendfile();
-						if ($result)
-						{
-							$resaction.=$langs->trans('MailSuccessfulySent',$mailfile->getValidAddress($from,2),$mailfile->getValidAddress($sendto,2)).'<br>';		// Must not contain "
-	
-							$error=0;
-	
-							// Insert logs into agenda
-							foreach($listofqualifiedinvoice as $invid => $object)
-							{
-								$actiontypecode='AC_FAC';
-								$actionmsg=$langs->transnoentities('MailSentBy').' '.$from.' '.$langs->transnoentities('To').' '.$sendto;
-								if ($message)
-								{
-									if ($sendtocc) $actionmsg = dol_concatdesc($actionmsg, $langs->transnoentities('Bcc') . ": " . $sendtocc);
-									$actionmsg = dol_concatdesc($actionmsg, $langs->transnoentities('MailTopic') . ": " . $subject);
-									$actionmsg = dol_concatdesc($actionmsg, $langs->transnoentities('TextUsedInTheMessageBody') . ":");
-									$actionmsg = dol_concatdesc($actionmsg, $message);
-								}
-								
-								// Initialisation donnees
-								$object->sendtoid		= 0;
-								$object->actiontypecode	= $actiontypecode;
-								$object->actionmsg		= $actionmsg;  // Long text
-								$object->actionmsg2		= $actionmsg2; // Short text
-								$object->fk_element		= $invid;
-								$object->elementtype	= $object->element;
-		
-								// Appel des triggers
-								include_once(DOL_DOCUMENT_ROOT . "/core/class/interfaces.class.php");
-								$interface=new Interfaces($db);
-								$result=$interface->run_triggers('BILL_SENTBYMAIL',$object,$user,$langs,$conf);
-								if ($result < 0) { $error++; $errors=$interface->errors; }
-								// Fin appel triggers
-		
-								if ($error)
-								{
-									setEventMessages($db->lasterror(), $errors, 'errors');
-									dol_syslog("Error in trigger BILL_SENTBYMAIL ".$db->lasterror(), LOG_ERR);
-								}
-								$nbsent++;
-							}
-						}
-						else
-						{
-							$langs->load("other");
-							if ($mailfile->error)
-							{
-								$resaction.=$langs->trans('ErrorFailedToSendMail',$from,$sendto);
-								$resaction.='<br><div class="error">'.$mailfile->error.'</div>';
-							}
-							else
-							{
-								$resaction.='<div class="warning">No mail sent. Feature is disabled by option MAIN_DISABLE_ALL_MAILS</div>';
-							}
-						}
-					}
-				}
-			}
-
-			$resaction.=($resaction?'<br>':$resaction);
-			$resaction.='<strong>'.$langs->trans("ResultOfMailSending").':</strong><br>'."\n";
-			$resaction.=$langs->trans("NbSelected").': '.count($toselect)."\n<br>";
-			$resaction.=$langs->trans("NbIgnored").': '.($nbignored?$nbignored:0)."\n<br>";
-			$resaction.=$langs->trans("NbSent").': '.($nbsent?$nbsent:0)."\n<br>";
-			
-			if ($nbsent)
-			{
-				$action='';	// Do not show form post if there was at least one successfull sent
-				setEventMessages($langs->trans("EMailSentToNRecipients", $nbsent.'/'.count($toselect)), null, 'mesgs');
-				setEventMessages($resaction, null, 'mesgs');
-			}
-			else
-			{
-				//setEventMessages($langs->trans("EMailSentToNRecipients", 0), null, 'warnings');  // May be object has no generated PDF file
-				setEventMessages($resaction, null, 'warnings');
-			}
-		}
-		
-		$action='list';
-		$massaction='';
-	}
-
-	if (! $error && $massaction == "builddoc" && $user->rights->facture->lire && ! GETPOST('button_search'))
-	{
-        require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
-        require_once DOL_DOCUMENT_ROOT.'/core/lib/pdf.lib.php';
-        require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
-         
-        $objecttmp=new Facture($db);
-        $listofobjectid=array();
-        $listofobjectthirdparties=array();
-        $listofobjectref=array();
-        foreach($toselect as $toselectid)
-        {
-            $objecttmp=new Facture($db);	// must create new instance because instance is saved into $listofobjectref array for future use
-            $result=$objecttmp->fetch($toselectid);
-            if ($result > 0)
-            {
-                $listoinvoicesid[$toselectid]=$toselectid;
-                $thirdpartyid=$objecttmp->fk_soc?$objecttmp->fk_soc:$objecttmp->socid;
-                $listofobjectthirdparties[$thirdpartyid]=$thirdpartyid;
-                $listofobjectref[$toselectid]=$objecttmp->ref;
-            }
-        }
-
-        $arrayofinclusion=array();
-        foreach($listofobjectref as $tmppdf) $arrayofinclusion[]=preg_quote($tmppdf.'.pdf','/');
-        $listoffiles = dol_dir_list($conf->facture->dir_output,'all',1,implode('|',$arrayofinclusion),'\.meta$|\.png','date',SORT_DESC,0,true);
-
-        // build list of files with full path
-        $files = array();
-        foreach($listofobjectref as $basename)
-        {
-            foreach($listoffiles as $filefound)
-            {
-                if (strstr($filefound["name"],$basename))
-                {
-                    $files[] = $conf->facture->dir_output.'/'.$basename.'/'.$filefound["name"];
-                    break;
-                }
-            }
-        }
-        
-        // Define output language (Here it is not used because we do only merging existing PDF)
-        $outputlangs = $langs;
-        $newlang='';
-        if ($conf->global->MAIN_MULTILANGS && empty($newlang) && GETPOST('lang_id')) $newlang=GETPOST('lang_id');
-        if ($conf->global->MAIN_MULTILANGS && empty($newlang)) $newlang=$object->thirdparty->default_lang;
-        if (! empty($newlang))
-        {
-            $outputlangs = new Translate("",$conf);
-            $outputlangs->setDefaultLang($newlang);
-        }
-
-        // Create empty PDF
-        $pdf=pdf_getInstance();
-        if (class_exists('TCPDF'))
-        {
-            $pdf->setPrintHeader(false);
-            $pdf->setPrintFooter(false);
-        }
-        $pdf->SetFont(pdf_getPDFFont($outputlangs));
-
-        if (! empty($conf->global->MAIN_DISABLE_PDF_COMPRESSION)) $pdf->SetCompression(false);
-
-        // Add all others
-        foreach($files as $file)
-        {
-            // Charge un document PDF depuis un fichier.
-            $pagecount = $pdf->setSourceFile($file);
-            for ($i = 1; $i <= $pagecount; $i++)
-            {
-                $tplidx = $pdf->importPage($i);
-                $s = $pdf->getTemplatesize($tplidx);
-                $pdf->AddPage($s['h'] > $s['w'] ? 'P' : 'L');
-                $pdf->useTemplate($tplidx);
-            }
-        }
-
-        // Create output dir if not exists
-        dol_mkdir($diroutputmassaction);
-
-        // Save merged file
-        $filename=strtolower(dol_sanitizeFileName($langs->transnoentities("Invoices")));
-        if ($filter=='paye:0')
-        {
-            if ($option=='late') $filename.='_'.strtolower(dol_sanitizeFileName($langs->transnoentities("Unpaid"))).'_'.strtolower(dol_sanitizeFileName($langs->transnoentities("Late")));
-            else $filename.='_'.strtolower(dol_sanitizeFileName($langs->transnoentities("Unpaid")));
-        }
-        if ($year) $filename.='_'.$year;
-        if ($month) $filename.='_'.$month;
-        if ($pagecount)
-        {
-            $now=dol_now();
-            $file=$diroutputmassaction.'/'.$filename.'_'.dol_print_date($now,'dayhourlog').'.pdf';
-            $pdf->Output($file,'F');
-            if (! empty($conf->global->MAIN_UMASK))
-                @chmod($file, octdec($conf->global->MAIN_UMASK));
-
-                $langs->load("exports");
-                setEventMessages($langs->trans('FileSuccessfullyBuilt',$filename.'_'.dol_print_date($now,'dayhourlog')), null, 'mesgs');
-        }
-        else
-        {
-            setEventMessages($langs->trans('NoPDFAvailableForDocGenAmongChecked'), null, 'errors');
-        }
-	}
-	
-	// Remove file
-	if ($action == 'remove_file')
-	{
-	    require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
-	
-	    $langs->load("other");
-	    $upload_dir = $diroutputmassaction;
-	    $file = $upload_dir . '/' . GETPOST('file');
-	    $ret=dol_delete_file($file);
-	    if ($ret) setEventMessages($langs->trans("FileWasRemoved", GETPOST('file')), null, 'mesgs');
-	    else setEventMessages($langs->trans("ErrorFailToDeleteFile", GETPOST('file')), null, 'errors');
-	    $action='';
-	}
-	
+	$objectclass='Facture';
+    $permtoread = $user->rights->facture->lire;
+	$permtodelete = $user->rights->facture->supprimer;
+	$uploaddir = $conf->facture->dir_output;
+	include DOL_DOCUMENT_ROOT.'/core/actions_massactions.inc.php';
 }
 
     
@@ -838,6 +442,18 @@ if ($resql)
 	    'presend'=>$langs->trans("SendByMail"),
 	    'builddoc'=>$langs->trans("PDFMerge")
 	);
+	if ($user->rights->facture->supprimer) 
+	{
+	    //if (! empty($conf->global->STOCK_CALCULATE_ON_BILL) || empty($conf->global->INVOICE_CAN_ALWAYS_BE_REMOVED))
+	    if (empty($conf->global->INVOICE_CAN_ALWAYS_BE_REMOVED))
+	    {
+	        // mass deletion never possible on invoices on such situation
+	    }
+	    else
+	    {
+	       $arrayofmassactions['delete']=$langs->trans("Delete");
+	    }
+	}
 	if ($massaction == 'presend') $arrayofmassactions=array();
 	$massactionbutton=$form->selectMassAction('', $arrayofmassactions);
     
@@ -942,6 +558,7 @@ if ($resql)
 		$formmail->param['models']=$modelmail;
 		$formmail->param['models_id']=GETPOST('modelmailselected','int');
 		$formmail->param['facid']=join(',',$arrayofselected);
+		// TODO We should use $formmail->param['id']=join(',',$arrayofselected);
 		//$formmail->param['returnurl']=$_SERVER["PHP_SELF"].'?id='.$object->id;
 
 		print $formmail->get_form();
@@ -1431,7 +1048,7 @@ if ($resql)
             
     		// Action column
             print '<td class="nowrap" align="center">';
-            if ($massactionbutton)
+            if ($massactionbutton || $massaction)   // If we are in select mode (massactionbutton defined) or if we have already selected and sent an action ($massaction) defined
             {
                 $selected=0;
         		if (in_array($obj->facid, $arrayofselected)) $selected=1;
@@ -1496,7 +1113,7 @@ if ($resql)
         $paramwithoutshowfiles=preg_replace('/show_files=1&?/','',$param);
         $title=$langs->trans("MassFilesArea").' <a href="'.$_SERVER["PHP_SELF"].'?'.$paramwithoutshowfiles.'">('.$langs->trans("Hide").')</a>';
         
-        print $formfile->showdocuments('massfilesarea_invoice','',$filedir,$urlsource,0,$delallowed,'',1,1,0,48,1,$param,$title,'');
+        print $formfile->showdocuments('massfilesarea_invoices','',$filedir,$urlsource,0,$delallowed,'',1,1,0,48,1,$param,$title,'');
     }
     else
     {
diff --git a/htdocs/core/actions_massactions.inc.php b/htdocs/core/actions_massactions.inc.php
new file mode 100644
index 00000000000..3c800f1f7fb
--- /dev/null
+++ b/htdocs/core/actions_massactions.inc.php
@@ -0,0 +1,499 @@
+<?php
+/* Copyright (C) 2015 Laurent Destailleur  <eldy@users.sourceforge.net>
+ *
+ * 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 3 of the License, or
+ * (at your option) 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, see <http://www.gnu.org/licenses/>.
+ * or see http://www.gnu.org/
+ */
+
+/**
+ *	\file			htdocs/core/actions_massactions.inc.php
+ *  \brief			Code for actions done with massaction button (send by email, merge pdf, delete, ...)
+ */
+
+
+// $massaction must be defined
+// $objectclass must be defined
+// $uploaddir (example $conf->projet->dir_output . "/";)
+// $toselect may be defined
+
+
+// Protection
+if (empty($objectclass) || empty($uploaddir)) 
+{
+    dol_print_error(null, 'include of actions_massactions.inc.php is done but var $massaction or $objectclass or $uploaddir was not defined');
+    exit;
+}
+
+
+// Mass actions. Controls on number of lines checked
+$maxformassaction=1000;
+if (! empty($massaction) && count($toselect) < 1)
+{
+    $error++;
+    setEventMessages($langs->trans("NoLineChecked"), null, "warnings");
+}
+if (! $error && count($toselect) > $maxformassaction)
+{
+    setEventMessages($langs->trans('TooManyRecordForMassAction',$maxformassaction), null, 'errors');
+    $error++;
+}
+
+
+if (! $error && $massaction == 'confirm_presend')
+{
+    $resaction = '';
+    $nbsent = 0;
+    $nbignored = 0;
+    $langs->load("mails");
+    include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
+
+    if (!$error && !isset($user->email))
+    {
+        $error++;
+        setEventMessages($langs->trans("NoSenderEmailDefined"), null, 'warnings');
+    }
+
+    if (! $error)
+    {
+        $thirdparty=new Societe($db);
+        $objecttmp=new $objectclass($db);
+        $listofobjectid=array();
+        $listofobjectthirdparties=array();
+        $listofobjectref=array();
+        foreach($toselect as $toselectid)
+        {
+            $objecttmp=new $objectclass($db);	// must create new instance because instance is saved into $listofobjectref array for future use
+            $result=$objecttmp->fetch($toselectid);
+            if ($result > 0)
+            {
+                $listoinvoicesid[$toselectid]=$toselectid;
+                $thirdpartyid=$objecttmp->fk_soc?$objecttmp->fk_soc:$objecttmp->socid;
+                $listofobjectthirdparties[$thirdpartyid]=$thirdpartyid;
+                $listofobjectref[$thirdpartyid][$toselectid]=$objecttmp;
+            }
+        }
+        //var_dump($listofobjectthirdparties);exit;
+        	
+        foreach ($listofobjectthirdparties as $thirdpartyid)
+        {
+            $result = $thirdparty->fetch($thirdpartyid);
+            if ($result < 0)
+            {
+                dol_print_error($db);
+                exit;
+            }
+
+            // Define recipient $sendto and $sendtocc
+            if (trim($_POST['sendto']))
+            {
+                // Recipient is provided into free text
+                $sendto = trim($_POST['sendto']);
+                $sendtoid = 0;
+            }
+            elseif ($_POST['receiver'] != '-1')
+            {
+                // Recipient was provided from combo list
+                if ($_POST['receiver'] == 'thirdparty') // Id of third party
+                {
+                    $sendto = $thirdparty->email;
+                    $sendtoid = 0;
+                }
+                else	// Id du contact
+                {
+                    $sendto = $thirdparty->contact_get_property((int) $_POST['receiver'],'email');
+                    $sendtoid = $_POST['receiver'];
+                }
+            }
+            if (trim($_POST['sendtocc']))
+            {
+                $sendtocc = trim($_POST['sendtocc']);
+            }
+            elseif ($_POST['receivercc'] != '-1')
+            {
+                // Recipient was provided from combo list
+                if ($_POST['receivercc'] == 'thirdparty')	// Id of third party
+                {
+                    $sendtocc = $thirdparty->email;
+                }
+                else	// Id du contact
+                {
+                    $sendtocc = $thirdparty->contact_get_property((int) $_POST['receivercc'],'email');
+                }
+            }
+
+            //var_dump($listofobjectref[$thirdpartyid]);	// Array of invoice for this thirdparty
+
+            $attachedfiles=array('paths'=>array(), 'names'=>array(), 'mimes'=>array());
+            $listofqualifiedinvoice=array();
+            $listofqualifiedref=array();
+            foreach($listofobjectref[$thirdpartyid] as $objectid => $object)
+            {
+                //var_dump($object);
+                //var_dump($thirdpartyid.' - '.$objectid.' - '.$object->statut);
+                	
+                if ($objectclass == 'Facture' && $object->statut != Facture::STATUS_VALIDATED)
+                {
+                    $nbignored++;
+                    $resaction.='<div class="error">'.$langs->trans('ErrorOnlyInvoiceValidatedCanBeSentInMassAction',$object->ref).'</div><br>';
+                    continue; // Payment done or started or canceled
+                }
+                if ($objectclass == 'Commande' && $object->statut == Commande::STATUS_DRAFT)
+                {
+                    $nbignored++;
+                    $resaction.='<div class="error">'.$langs->trans('ErrorOnlyOrderNotDraftCanBeSentInMassAction',$object->ref).'</div><br>';
+                    continue;
+                }
+                
+                // Read document
+                // TODO Use future field $object->fullpathdoc to know where is stored default file
+                // TODO If not defined, use $object->modelpdf (or defaut invoice config) to know what is template to use to regenerate doc.
+                $filename=dol_sanitizeFileName($object->ref).'.pdf';
+                $filedir=$uploaddir . '/' . dol_sanitizeFileName($object->ref);
+                $file = $filedir . '/' . $filename;
+                $mime = dol_mimetype($file);
+
+                if (dol_is_file($file))
+                {
+                    if (empty($sendto)) 	// For the case, no recipient were set (multi thirdparties send)
+                    {
+                        $object->fetch_thirdparty();
+                        $sendto = $object->thirdparty->email;
+                    }
+
+                    if (empty($sendto))
+                    {
+                        //print "No recipient for thirdparty ".$object->thirdparty->name;
+                        $nbignored++;
+                        continue;
+                    }
+
+                    if (dol_strlen($sendto))
+                    {
+                        // Create form object
+                        $attachedfiles=array(
+                            'paths'=>array_merge($attachedfiles['paths'],array($file)),
+                            'names'=>array_merge($attachedfiles['names'],array($filename)),
+                            'mimes'=>array_merge($attachedfiles['mimes'],array($mime))
+                        );
+                    }
+
+                    $listofqualifiedinvoice[$objectid]=$object;
+                    $listofqualifiedref[$objectid]=$object->ref;
+                }
+                else
+                {
+                    $nbignored++;
+                    $langs->load("errors");
+                    $resaction.='<div class="error">'.$langs->trans('ErrorCantReadFile',$file).'</div><br>';
+                    dol_syslog('Failed to read file: '.$file, LOG_WARNING);
+                    continue;
+                }
+                	
+                //var_dump($listofqualifiedref);
+            }
+
+            if (count($listofqualifiedinvoice) > 0)
+            {
+                $langs->load("commercial");
+                $from = $user->getFullName($langs) . ' <' . $user->email .'>';
+                $replyto = $from;
+                $subject = GETPOST('subject');
+                $message = GETPOST('message');
+                $sendtocc = GETPOST('sentocc');
+                $sendtobcc = (empty($conf->global->MAIN_MAIL_AUTOCOPY_INVOICE_TO)?'':$conf->global->MAIN_MAIL_AUTOCOPY_INVOICE_TO);
+
+                $substitutionarray=array(
+                    '__ID__' => join(', ',array_keys($listofqualifiedinvoice)),
+                    '__EMAIL__' => $thirdparty->email,
+                    '__CHECK_READ__' => '<img src="'.DOL_MAIN_URL_ROOT.'/public/emailing/mailing-read.php?tag='.$thirdparty->tag.'&securitykey='.urlencode($conf->global->MAILING_EMAIL_UNSUBSCRIBE_KEY).'" width="1" height="1" style="width:1px;height:1px" border="0"/>',
+                    '__FACREF__' => join(', ',$listofqualifiedref),            // For backward compatibility
+                    '__ORDERREF__' => join(', ',$listofqualifiedref),          // For backward compatibility
+                    '__PROPREF__' => join(', ',$listofqualifiedref),           // For backward compatibility
+                    '__REF__' => join(', ',$listofqualifiedref),
+                    '__REFCLIENT__' => $thirdparty->name
+                );
+
+                $subject=make_substitutions($subject, $substitutionarray);
+                $message=make_substitutions($message, $substitutionarray);
+
+                $filepath = $attachedfiles['paths'];
+                $filename = $attachedfiles['names'];
+                $mimetype = $attachedfiles['mimes'];
+                	
+                //var_dump($filepath);
+                	
+                // Send mail
+                require_once(DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php');
+                $mailfile = new CMailFile($subject,$sendto,$from,$message,$filepath,$mimetype,$filename,$sendtocc,$sendtobcc,$deliveryreceipt,-1);
+                if ($mailfile->error)
+                {
+                    $resaction.='<div class="error">'.$mailfile->error.'</div>';
+                }
+                else
+                {
+                    $result=$mailfile->sendfile();
+                    if ($result)
+                    {
+                        $resaction.=$langs->trans('MailSuccessfulySent',$mailfile->getValidAddress($from,2),$mailfile->getValidAddress($sendto,2)).'<br>';		// Must not contain "
+
+                        $error=0;
+
+                        // Insert logs into agenda
+                        foreach($listofqualifiedinvoice as $invid => $object)
+                        {
+                            if ($objectclass == 'Propale') $actiontypecode='AC_PROP';
+                            if ($objectclass == 'Commande') $actiontypecode='AC_COM';
+                            if ($objectclass == 'Facture') $actiontypecode='AC_FAC';
+                            if ($objectclass == 'Supplier_Proposal') $actiontypecode='AC_SUP_PRO';
+                            if ($objectclass == 'CommandeFournisseur') $actiontypecode='AC_SUP_ORD';
+                            if ($objectclass == 'FactureFournisseur') $actiontypecode='AC_SUP_INV';
+                            
+                            $actionmsg=$langs->transnoentities('MailSentBy').' '.$from.' '.$langs->transnoentities('To').' '.$sendto;
+                            if ($message)
+                            {
+                                if ($sendtocc) $actionmsg = dol_concatdesc($actionmsg, $langs->transnoentities('Bcc') . ": " . $sendtocc);
+                                $actionmsg = dol_concatdesc($actionmsg, $langs->transnoentities('MailTopic') . ": " . $subject);
+                                $actionmsg = dol_concatdesc($actionmsg, $langs->transnoentities('TextUsedInTheMessageBody') . ":");
+                                $actionmsg = dol_concatdesc($actionmsg, $message);
+                            }
+
+                            // Initialisation donnees
+                            $object->sendtoid		= 0;
+                            $object->actiontypecode	= $actiontypecode;
+                            $object->actionmsg		= $actionmsg;  // Long text
+                            $object->actionmsg2		= $actionmsg2; // Short text
+                            $object->fk_element		= $invid;
+                            $object->elementtype	= $object->element;
+
+                            // Appel des triggers
+                            include_once(DOL_DOCUMENT_ROOT . "/core/class/interfaces.class.php");
+                            $interface=new Interfaces($db);
+                            $result=$interface->run_triggers('BILL_SENTBYMAIL',$object,$user,$langs,$conf);
+                            if ($result < 0) { $error++; $errors=$interface->errors; }
+                            // Fin appel triggers
+
+                            if ($error)
+                            {
+                                setEventMessages($db->lasterror(), $errors, 'errors');
+                                dol_syslog("Error in trigger BILL_SENTBYMAIL ".$db->lasterror(), LOG_ERR);
+                            }
+                            $nbsent++;
+                        }
+                    }
+                    else
+                    {
+                        $langs->load("other");
+                        if ($mailfile->error)
+                        {
+                            $resaction.=$langs->trans('ErrorFailedToSendMail',$from,$sendto);
+                            $resaction.='<br><div class="error">'.$mailfile->error.'</div>';
+                        }
+                        else
+                        {
+                            $resaction.='<div class="warning">No mail sent. Feature is disabled by option MAIN_DISABLE_ALL_MAILS</div>';
+                        }
+                    }
+                }
+            }
+        }
+
+        $resaction.=($resaction?'<br>':$resaction);
+        $resaction.='<strong>'.$langs->trans("ResultOfMailSending").':</strong><br>'."\n";
+        $resaction.=$langs->trans("NbSelected").': '.count($toselect)."\n<br>";
+        $resaction.=$langs->trans("NbIgnored").': '.($nbignored?$nbignored:0)."\n<br>";
+        $resaction.=$langs->trans("NbSent").': '.($nbsent?$nbsent:0)."\n<br>";
+        	
+        if ($nbsent)
+        {
+            $action='';	// Do not show form post if there was at least one successfull sent
+            //setEventMessages($langs->trans("EMailSentToNRecipients", $nbsent.'/'.count($toselect)), null, 'mesgs');
+            setEventMessages($langs->trans("EMailSentForNElements", $nbsent.'/'.count($toselect)), null, 'mesgs');
+            setEventMessages($resaction, null, 'mesgs');
+        }
+        else
+        {
+            //setEventMessages($langs->trans("EMailSentToNRecipients", 0), null, 'warnings');  // May be object has no generated PDF file
+            setEventMessages($resaction, null, 'warnings');
+        }
+    }
+
+    $action='list';
+    $massaction='';
+}
+
+if (! $error && $massaction == "builddoc" && $permtoread && ! GETPOST('button_search'))
+{
+    require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
+    require_once DOL_DOCUMENT_ROOT.'/core/lib/pdf.lib.php';
+    require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
+     
+    $objecttmp=new $objectclass($db);
+    $listofobjectid=array();
+    $listofobjectthirdparties=array();
+    $listofobjectref=array();
+    foreach($toselect as $toselectid)
+    {
+        $objecttmp=new $objectclass($db);	// must create new instance because instance is saved into $listofobjectref array for future use
+        $result=$objecttmp->fetch($toselectid);
+        if ($result > 0)
+        {
+            $listoinvoicesid[$toselectid]=$toselectid;
+            $thirdpartyid=$objecttmp->fk_soc?$objecttmp->fk_soc:$objecttmp->socid;
+            $listofobjectthirdparties[$thirdpartyid]=$thirdpartyid;
+            $listofobjectref[$toselectid]=$objecttmp->ref;
+        }
+    }
+
+    $arrayofinclusion=array();
+    foreach($listofobjectref as $tmppdf) $arrayofinclusion[]=preg_quote($tmppdf.'.pdf','/');
+    $listoffiles = dol_dir_list($uploaddir,'all',1,implode('|',$arrayofinclusion),'\.meta$|\.png','date',SORT_DESC,0,true);
+
+    // build list of files with full path
+    $files = array();
+    foreach($listofobjectref as $basename)
+    {
+        foreach($listoffiles as $filefound)
+        {
+            if (strstr($filefound["name"],$basename))
+            {
+                $files[] = $uploaddir.'/'.$basename.'/'.$filefound["name"];
+                break;
+            }
+        }
+    }
+
+    // Define output language (Here it is not used because we do only merging existing PDF)
+    $outputlangs = $langs;
+    $newlang='';
+    if ($conf->global->MAIN_MULTILANGS && empty($newlang) && GETPOST('lang_id')) $newlang=GETPOST('lang_id');
+    if ($conf->global->MAIN_MULTILANGS && empty($newlang)) $newlang=$object->thirdparty->default_lang;
+    if (! empty($newlang))
+    {
+        $outputlangs = new Translate("",$conf);
+        $outputlangs->setDefaultLang($newlang);
+    }
+
+    // Create empty PDF
+    $pdf=pdf_getInstance();
+    if (class_exists('TCPDF'))
+    {
+        $pdf->setPrintHeader(false);
+        $pdf->setPrintFooter(false);
+    }
+    $pdf->SetFont(pdf_getPDFFont($outputlangs));
+
+    if (! empty($conf->global->MAIN_DISABLE_PDF_COMPRESSION)) $pdf->SetCompression(false);
+
+    // Add all others
+    foreach($files as $file)
+    {
+        // Charge un document PDF depuis un fichier.
+        $pagecount = $pdf->setSourceFile($file);
+        for ($i = 1; $i <= $pagecount; $i++)
+        {
+            $tplidx = $pdf->importPage($i);
+            $s = $pdf->getTemplatesize($tplidx);
+            $pdf->AddPage($s['h'] > $s['w'] ? 'P' : 'L');
+            $pdf->useTemplate($tplidx);
+        }
+    }
+
+    // Create output dir if not exists
+    dol_mkdir($diroutputmassaction);
+
+    // Save merged file
+    $filename=strtolower(dol_sanitizeFileName($langs->transnoentities("Invoices")));
+    if ($filter=='paye:0')
+    {
+        if ($option=='late') $filename.='_'.strtolower(dol_sanitizeFileName($langs->transnoentities("Unpaid"))).'_'.strtolower(dol_sanitizeFileName($langs->transnoentities("Late")));
+        else $filename.='_'.strtolower(dol_sanitizeFileName($langs->transnoentities("Unpaid")));
+    }
+    if ($year) $filename.='_'.$year;
+    if ($month) $filename.='_'.$month;
+    if ($pagecount)
+    {
+        $now=dol_now();
+        $file=$diroutputmassaction.'/'.$filename.'_'.dol_print_date($now,'dayhourlog').'.pdf';
+        $pdf->Output($file,'F');
+        if (! empty($conf->global->MAIN_UMASK))
+            @chmod($file, octdec($conf->global->MAIN_UMASK));
+
+            $langs->load("exports");
+            setEventMessages($langs->trans('FileSuccessfullyBuilt',$filename.'_'.dol_print_date($now,'dayhourlog')), null, 'mesgs');
+    }
+    else
+    {
+        setEventMessages($langs->trans('NoPDFAvailableForDocGenAmongChecked'), null, 'errors');
+    }
+}
+
+// Remove a file from massaction area
+if ($action == 'remove_file')
+{
+    require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
+
+    $langs->load("other");
+    $upload_dir = $diroutputmassaction;
+    $file = $upload_dir . '/' . GETPOST('file');
+    $ret=dol_delete_file($file);
+    if ($ret) setEventMessages($langs->trans("FileWasRemoved", GETPOST('file')), null, 'mesgs');
+    else setEventMessages($langs->trans("ErrorFailToDeleteFile", GETPOST('file')), null, 'errors');
+    $action='';
+}
+
+// Delete records
+if (! $error && $massaction == 'delete' && $permtodelete)
+{
+    $db->begin();
+
+    $objecttmp=new $objectclass($db);
+    $nbok = 0;
+    foreach($toselect as $toselectid)
+    {
+        $result=$objecttmp->fetch($toselectid);
+        if ($result > 0)
+        {
+            $result = $objecttmp->delete($user);
+            if ($result <= 0)
+            {
+                setEventMessages($objecttmp->error, $objecttmp->errors, 'errors');
+                $error++;
+                break;
+            }
+            else $nbok++;
+        }
+        else
+        {
+            setEventMessages($objecttmp->error, $objecttmp->errors, 'errors');
+            $error++;
+            break;
+        }
+    }
+
+    if (! $error)
+    {
+        if ($nbok > 1) setEventMessages($langs->trans("RecordsDeleted", $nbok), null, 'mesgs');
+        else setEventMessages($langs->trans("RecordDeleted", $nbok), null, 'mesgs');
+        $db->commit();
+    }
+    else
+    {
+        $db->rollback();
+    }
+    //var_dump($listofobjectthirdparties);exit;
+}
+
+
+
+
diff --git a/htdocs/core/lib/files.lib.php b/htdocs/core/lib/files.lib.php
index e8cb4545507..6e44f4754b9 100644
--- a/htdocs/core/lib/files.lib.php
+++ b/htdocs/core/lib/files.lib.php
@@ -1990,7 +1990,7 @@ function dol_check_secure_access_document($modulepart,$original_file,$entity,$fu
 		$original_file=$conf->facture->dir_output.'/'.$original_file;
 		$sqlprotectagainstexternals = "SELECT fk_soc as fk_soc FROM ".MAIN_DB_PREFIX."facture WHERE ref='".$db->escape($refname)."' AND entity=".$conf->entity;
 	}
-	else if ($modulepart == 'massfilesarea_facture')
+	else if ($modulepart == 'massfilesarea_invoices')
 	{
 		if ($fuser->rights->facture->lire || preg_match('/^specimen/i',$original_file))
 		{
@@ -1998,7 +1998,15 @@ function dol_check_secure_access_document($modulepart,$original_file,$entity,$fu
 		}
 		$original_file=$conf->facture->dir_output.'/temp/massgeneration/'.$user->id.'/'.$original_file;
 	}
-
+	else if ($modulepart == 'massfilesarea_orders')
+	{
+	    if ($fuser->rights->commande->lire || preg_match('/^specimen/i',$original_file))
+	    {
+	        $accessallowed=1;
+	    }
+	    $original_file=$conf->commande->dir_output.'/temp/massgeneration/'.$user->id.'/'.$original_file;
+	}
+	
 	// Wrapping pour les fiches intervention
 	else if ($modulepart == 'ficheinter')
 	{
diff --git a/htdocs/langs/en_US/mails.lang b/htdocs/langs/en_US/mails.lang
index 6c1e41c9d1c..104d5d3e657 100644
--- a/htdocs/langs/en_US/mails.lang
+++ b/htdocs/langs/en_US/mails.lang
@@ -67,8 +67,9 @@ MailingStatusRead=Read
 YourMailUnsubcribeOK=The email <b>%s</b>  is correctly unsubcribe from mailing list
 ActivateCheckReadKey=Key used to encrypt URL used for "Read Receipt" and "Unsubcribe" feature
 EMailSentToNRecipients=EMail sent to %s recipients.
+EMailSentForNElements=EMail sent for %s elements.
 XTargetsAdded=<b>%s</b> recipients added into target list
-OnlyPDFattachmentSupported=If the PDF document was already generated for the invoice, it will be attached to email. If not, no email will be sent (also, note that only pdf invoice are supported as attachment in mass sending in this version).
+OnlyPDFattachmentSupported=If the PDF document was already generated for the object to send, it will be attached to email. If not, no email will be sent (also, note that only pdf documents are supported as attachment in mass sending in this version).
 AllRecipientSelected=All thirdparties selected and if an email is set.
 ResultOfMailSending=Result of mass EMail sending
 NbSelected=Nb selected
diff --git a/htdocs/paypal/lib/paypal.lib.php b/htdocs/paypal/lib/paypal.lib.php
index b51086b8e07..6587e25ecb1 100644
--- a/htdocs/paypal/lib/paypal.lib.php
+++ b/htdocs/paypal/lib/paypal.lib.php
@@ -228,6 +228,8 @@ function getPaypalPaymentUrl($mode,$type,$ref='',$amount='9.99',$freetag='your_f
 {
 	global $conf;
 
+	$ref=str_replace(' ','',$ref);
+	
     if ($type == 'free')
     {
 	    $out=DOL_MAIN_URL_ROOT.'/public/paypal/newpayment.php?amount='.($mode?'<font color="#666666">':'').$amount.($mode?'</font>':'').'&tag='.($mode?'<font color="#666666">':'').$freetag.($mode?'</font>':'');
-- 
GitLab