diff --git a/htdocs/admin/contract.php b/htdocs/admin/contract.php index 98c72ac68dc..26212242049 100644 --- a/htdocs/admin/contract.php +++ b/htdocs/admin/contract.php @@ -152,9 +152,18 @@ if ($action == 'updateMask') { } else { setEventMessages($langs->trans("Error"), null, 'errors'); } +} elseif ($action == "allowonlinesign") { + if (!dolibarr_set_const($db, "CONTRACT_ALLOW_ONLINESIGN", $value, 0, 'int', $conf->entity)) { + $error++; + } +} elseif ($action == "allowexternaldownload") { + if (!dolibarr_set_const($db, "CONTRACT_ALLOW_EXTERNAL_DOWNLOAD", $value, 0, 'int', $conf->entity)) { + $error++; + } } + /* * View */ @@ -469,6 +478,37 @@ print $form->selectyesno("activate_hideClosedServiceByDefault", (!empty($conf->g print ''; print ''; +// Allow online signing +print ''; +print ''.$langs->trans("AllowOnlineSign").''; +print ''; +if ($conf->global->CONTRACT_ALLOW_ONLINESIGN) { + print ''; + print img_picto($langs->trans("Activited"), 'switch_on'); + print ''; +} else { + print ''; + print img_picto($langs->trans("Disabled"), 'switch_off'); + print ''; +} +print ''; +print ''; + +// Allow external download +print ''; +print ''.$langs->trans("AllowExternalDownload").''; +print ''; +if ($conf->global->CONTRACT_ALLOW_EXTERNAL_DOWNLOAD) { + print ''; + print img_picto($langs->trans("Activited"), 'switch_on'); + print ''; +} else { + print ''; + print img_picto($langs->trans("Disabled"), 'switch_off'); + print ''; +} +print ''; +print ''; print ''; print $form->buttonsSaveCancel("Save", ''); diff --git a/htdocs/contrat/card.php b/htdocs/contrat/card.php index de45d433f71..d77cc8afdab 100644 --- a/htdocs/contrat/card.php +++ b/htdocs/contrat/card.php @@ -2207,11 +2207,10 @@ if ($action == 'create') { $somethingshown = $form->showLinkedObjectBlock($object, $linktoelem); // Show online signature link - $useonlinesignature = 1; - - if ($object->statut != Contrat::STATUS_DRAFT && $useonlinesignature) { + if ($object->statut != Contrat::STATUS_DRAFT && $conf->global->CONTRACT_ALLOW_ONLINESIGN) { print '
'; require_once DOL_DOCUMENT_ROOT.'/core/lib/signature.lib.php'; + print showOnlineSignatureUrl('contract', $object->ref).'
'; } diff --git a/htdocs/contrat/class/contrat.class.php b/htdocs/contrat/class/contrat.class.php index f3b617a251a..2a8d1b815a7 100644 --- a/htdocs/contrat/class/contrat.class.php +++ b/htdocs/contrat/class/contrat.class.php @@ -663,7 +663,7 @@ class Contrat extends CommonObject $sql .= " fk_user_author,"; $sql .= " fk_projet as fk_project,"; $sql .= " fk_commercial_signature, fk_commercial_suivi,"; - $sql .= " note_private, note_public, model_pdf, extraparams"; + $sql .= " note_private, note_public, model_pdf, last_main_doc, extraparams"; $sql .= " FROM ".MAIN_DB_PREFIX."contrat"; if (!$id) { $sql .= " WHERE entity IN (".getEntity('contract').")"; @@ -717,7 +717,7 @@ class Contrat extends CommonObject $this->socid = $obj->fk_soc; $this->fk_soc = $obj->fk_soc; - + $this->last_main_doc = $obj->last_main_doc; $this->extraparams = (array) json_decode($obj->extraparams, true); $this->db->free($resql); diff --git a/htdocs/core/ajax/onlineSign.php b/htdocs/core/ajax/onlineSign.php index 53eaf69aea1..9966b9c6093 100644 --- a/htdocs/core/ajax/onlineSign.php +++ b/htdocs/core/ajax/onlineSign.php @@ -224,6 +224,95 @@ if ($action == "importSignature") { $response = "error sql"; } } + } elseif ($mode == 'contract') { + require_once DOL_DOCUMENT_ROOT.'/contrat/class/contrat.class.php'; + require_once DOL_DOCUMENT_ROOT.'/core/lib/pdf.lib.php'; + $object = new Contrat($db); + $object->fetch(0, $ref); + + $upload_dir = !empty($conf->contrat->multidir_output[$object->entity])?$conf->contrat->multidir_output[$object->entity]:$conf->contrat->dir_output; + $upload_dir .= '/'.dol_sanitizeFileName($object->ref).'/'; + + $date = dol_print_date(dol_now(), "%Y%m%d%H%M%S"); + $filename = "signatures/".$date."_signature.png"; + if (!is_dir($upload_dir."signatures/")) { + if (!dol_mkdir($upload_dir."signatures/")) { + $response ="Error mkdir. Failed to create dir ".$upload_dir."signatures/"; + $error++; + } + } + + if (!$error) { + $return = file_put_contents($upload_dir.$filename, $data); + if ($return == false) { + $error++; + $response = 'Error file_put_content: failed to create signature file.'; + } + } + + if (!$error) { + // Defined modele of doc + $last_main_doc_file = $object->last_main_doc; + $directdownloadlink = $object->getLastMainDocLink('contrat'); // url to download the $object->last_main_doc + if (preg_match('/\.pdf/i', $last_main_doc_file)) { + // TODO Use the $last_main_doc_file to defined the $newpdffilename and $sourcefile + $newpdffilename = $upload_dir.$ref."_signed-".$date.".pdf"; + $sourcefile = $upload_dir.$ref.".pdf"; + + if (dol_is_file($sourcefile)) { + // We build the new PDF + $pdf = pdf_getInstance(); + if (class_exists('TCPDF')) { + $pdf->setPrintHeader(false); + $pdf->setPrintFooter(false); + } + $pdf->SetFont(pdf_getPDFFont($langs)); + + if (getDolGlobalString('MAIN_DISABLE_PDF_COMPRESSION')) { + $pdf->SetCompression(false); + } + + + //$pdf->Open(); + $pagecount = $pdf->setSourceFile($sourcefile); // original PDF + $s = array(); // Array with size of each page. Exemple array(w'=>210, 'h'=>297); + for ($i=1; $i<($pagecount+1); $i++) { + try { + $tppl = $pdf->importPage($i); + $s = $pdf->getTemplatesize($tppl); + $pdf->AddPage($s['h'] > $s['w'] ? 'P' : 'L'); + $pdf->useTemplate($tppl); + } catch (Exception $e) { + dol_syslog("Error when manipulating some PDF by onlineSign: ".$e->getMessage(), LOG_ERR); + $response = $e->getMessage(); + $error++; + } + } + + // A signature image file is 720 x 180 (ratio 1/4) but we use only the size into PDF + // TODO Get position of box from PDF template + $xforimgstart = 5; + $yforimgstart = (empty($s['h']) ? 240 : $s['h'] - 65); + $wforimg = $s['w']/2 - $xforimgstart; + + $pdf->Image($upload_dir.$filename, $xforimgstart, $yforimgstart, $wforimg, round($wforimg / 4)); + //$pdf->Close(); + $pdf->Output($newpdffilename, "F"); + + // Index the new file and update the last_main_doc property of object. + $object->indexFile($newpdffilename, 1); + } + if (!$error) { + $response = "success"; + } + } elseif (preg_match('/\.odt/i', $last_main_doc_file)) { + // Adding signature on .ODT not yet supported + // TODO + } else { + // Document format not supported to insert online signature. + // We should just create an image file with the signature. + } + } } } else { $error++; diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php index 7f30878d566..34b8ade962c 100644 --- a/htdocs/core/class/commonobject.class.php +++ b/htdocs/core/class/commonobject.class.php @@ -5608,7 +5608,7 @@ abstract class CommonObject if ($this->element == 'product' && !empty($conf->global->PRODUCT_ALLOW_EXTERNAL_DOWNLOAD)) { $setsharekey = true; } - if ($this->element == 'contrat' && !empty($conf->global->CONTRACT_ALLOW_EXTERNAL_DOWNLOAD)) { + if ($this->element == 'contrat' && !empty($conf->global->CONTRACT_ALLOW_EXTERNAL_DOWNLOAD) && !empty($conf->global->CONTRACT_ALLOW_ONLINESIGN)) { $setsharekey = true; } if ($this->element == 'supplier_proposal' && !empty($conf->global->SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD)) { diff --git a/htdocs/core/lib/signature.lib.php b/htdocs/core/lib/signature.lib.php index d2f0c64f463..8ac2bbfe100 100644 --- a/htdocs/core/lib/signature.lib.php +++ b/htdocs/core/lib/signature.lib.php @@ -78,7 +78,6 @@ function getOnlineSignatureUrl($mode, $type, $ref = '', $localorexternal = 1) if ($type == 'proposal') { $securekeyseed = isset($conf->global->PROPOSAL_ONLINE_SIGNATURE_SECURITY_TOKEN) ? $conf->global->PROPOSAL_ONLINE_SIGNATURE_SECURITY_TOKEN : ''; - $out = $urltouse.'/public/onlinesign/newonlinesign.php?source=proposal&ref='.($mode ? '' : ''); if ($mode == 1) { $out .= 'proposal_ref'; @@ -116,6 +115,21 @@ function getOnlineSignatureUrl($mode, $type, $ref = '', $localorexternal = 1) $out .= '&hashp='.$hashp; } }*/ + } elseif ($type == 'contract') { + $securekeyseed = isset($conf->global->CONTRACT_ONLINE_SIGNATURE_SECURITY_TOKEN) ? $conf->global->CONTRACT_ONLINE_SIGNATURE_SECURITY_TOKEN : ''; + $out = $urltouse.'/public/onlinesign/newonlinesign.php?source=contract&ref='.($mode ? '' : ''); + if ($mode == 1) { + $out .= 'contract_ref'; + } + if ($mode == 0) { + $out .= urlencode($ref); + } + $out .= ($mode ? '' : ''); + if ($mode == 1) { + $out .= "hash('".$securekeyseed."' + '".$type."' + contract_ref)"; + } else { + $out .= '&securekey='.dol_hash($securekeyseed.$type.$ref.(!isModEnabled('multicompany') ? '' : $object->entity), '0'); + } } // For multicompany diff --git a/htdocs/langs/en_US/commercial.lang b/htdocs/langs/en_US/commercial.lang index 21d282cd794..9978118f763 100644 --- a/htdocs/langs/en_US/commercial.lang +++ b/htdocs/langs/en_US/commercial.lang @@ -74,8 +74,12 @@ StatusProsp=Prospect status DraftPropals=Draft commercial proposals NoLimit=No limit ToOfferALinkForOnlineSignature=Link for online signature -WelcomeOnOnlineSignaturePage=Welcome to the page to accept commercial proposals from %s -ThisScreenAllowsYouToSignDocFrom=This screen allow you to accept and sign, or refuse, a quote/commercial proposal -ThisIsInformationOnDocumentToSign=This is information on document to accept or refuse +WelcomeOnOnlineSignaturePageProposal=Welcome to the page to accept commercial proposals from %s +WelcomeOnOnlineSignaturePageContract=Welcome to %s Contract PDF Signing Page +ThisScreenAllowsYouToSignDocFromProposal=This screen allow you to accept and sign, or refuse, a quote/commercial proposal +ThisScreenAllowsYouToSignDocFromContract=This screen allow you to sign contract on PDF format online. +ThisIsInformationOnDocumentToSignProposal=This is information on document to accept or refuse +ThisIsInformationOnDocumentToSignContract=This is information on contract to sign SignatureProposalRef=Signature of quote/commercial proposal %s +SignatureContractRef=Signature of contract %s FeatureOnlineSignDisabled=Feature for online signing disabled or document generated before the feature was enabled diff --git a/htdocs/langs/en_US/contracts.lang b/htdocs/langs/en_US/contracts.lang index ab94a63bcc3..f0db53f3ddd 100644 --- a/htdocs/langs/en_US/contracts.lang +++ b/htdocs/langs/en_US/contracts.lang @@ -101,6 +101,8 @@ TypeContact_contrat_external_BILLING=Billing customer contact TypeContact_contrat_external_CUSTOMER=Follow-up customer contact TypeContact_contrat_external_SALESREPSIGN=Signing contract customer contact HideClosedServiceByDefault=Hide closed services by default +AllowOnlineSign=Allow online signing +AllowExternalDownload=Allow external download ShowClosedServices=Show Closed Services HideClosedServices=Hide Closed Services UserStartingService=User starting service diff --git a/htdocs/langs/en_US/propal.lang b/htdocs/langs/en_US/propal.lang index aa5ef73f7dc..d07d6d2efba 100644 --- a/htdocs/langs/en_US/propal.lang +++ b/htdocs/langs/en_US/propal.lang @@ -103,11 +103,13 @@ IdProposal=Proposal ID IdProduct=Product ID LineBuyPriceHT=Buy Price Amount net of tax for line SignPropal=Accept proposal +SignContract=Sign contract RefusePropal=Refuse proposal Sign=Sign NoSign=Refuse PropalAlreadySigned=Proposal already accepted PropalAlreadyRefused=Proposal already refused PropalSigned=Proposal accepted +ContractSigned=Contract signed PropalRefused=Proposal refused ConfirmRefusePropal=Are you sure you want to refuse this commercial proposal? diff --git a/htdocs/langs/fr_FR/commercial.lang b/htdocs/langs/fr_FR/commercial.lang index d663185936b..84bb1364386 100644 --- a/htdocs/langs/fr_FR/commercial.lang +++ b/htdocs/langs/fr_FR/commercial.lang @@ -74,8 +74,12 @@ StatusProsp=Status prospection DraftPropals=Propositions brouillons NoLimit=Pas de limite ToOfferALinkForOnlineSignature=Lien pour signature en ligne -WelcomeOnOnlineSignaturePage=Bienvenue sur la page pour accepter les propositions commerciales de %s -ThisScreenAllowsYouToSignDocFrom=Cet écran vous permet d'accepter et signer en ligne, ou de refuser, le devis ou la proposition commerciale -ThisIsInformationOnDocumentToSign=Voici les informations sur le document à accepter ou refuser +WelcomeOnOnlineSignaturePageProposal=Bienvenue sur la page pour accepter les propositions commerciales de %s +WelcomeOnOnlineSignaturePageContract=Bienvenue sur la page de signature en ligne de contrats au format PDF de %s +ThisScreenAllowsYouToSignDocFromProposal=Cet écran vous permet d'accepter et signer en ligne, ou de refuser, le devis ou la proposition commerciale +ThisScreenAllowsYouToSignDocFromContract=Cet écran vous permet de signer en ligne des contrats au format PDF. +ThisIsInformationOnDocumentToSignProposal=Voici les informations sur le document à accepter ou refuser +ThisIsInformationOnDocumentToSignContract=Voici les informations sur le contrat a signer SignatureProposalRef=Signature du devis ou proposition commerciale %s +SignatureContractRef=Signature du contrat %s FeatureOnlineSignDisabled=Fonctionnalité pour la signature en ligne désactivée ou document généré avant l'activation de la fonctionnalité diff --git a/htdocs/langs/fr_FR/contracts.lang b/htdocs/langs/fr_FR/contracts.lang index 8d89487c5cf..bb7bf18da67 100644 --- a/htdocs/langs/fr_FR/contracts.lang +++ b/htdocs/langs/fr_FR/contracts.lang @@ -100,5 +100,7 @@ TypeContact_contrat_external_BILLING=Contact client facturation contrat TypeContact_contrat_external_CUSTOMER=Contact client suivi contrat TypeContact_contrat_external_SALESREPSIGN=Contact client signataire contrat HideClosedServiceByDefault=Masquer les services fermés par défaut +AllowOnlineSign=Autoriser la signature en ligne +AllowExternalDownload=Autoriser le téléchargement externe ShowClosedServices=Afficher les services fermés HideClosedServices=Masquer les services fermés diff --git a/htdocs/langs/fr_FR/propal.lang b/htdocs/langs/fr_FR/propal.lang index 41483196a39..4c44d0691c3 100644 --- a/htdocs/langs/fr_FR/propal.lang +++ b/htdocs/langs/fr_FR/propal.lang @@ -47,7 +47,7 @@ SendPropalByMail=Envoyer proposition commerciale par email DatePropal=Date de proposition DateEndPropal=Date de fin de validité ValidityDuration=Durée de validité -SetAcceptedRefused=Accepter/Refuser +SetAcceptedRefused=Accepter/Refuser ErrorPropalNotFound=Propale %s inexistante AddToDraftProposals=Ajouter à proposition brouillon NoDraftProposals=Pas de propositions brouillons @@ -103,11 +103,13 @@ IdProposal=ID de la proposition commerciale IdProduct=ID produit LineBuyPriceHT=Prix d'achat HT de la ligne SignPropal=Accepter la proposition +SignContract=Signer le contrat RefusePropal=Refuser la proposition Sign=Signer NoSign=Mettre à Non signé PropalAlreadySigned=Proposition déjà acceptée PropalAlreadyRefused=Proposition déjà refusée PropalSigned=Proposition acceptée +ContractSigned=Contrat signé PropalRefused=Proposition refusée ConfirmRefusePropal=Êtes-vous sûr de vouloir refuser cette proposition ? diff --git a/htdocs/public/onlinesign/newonlinesign.php b/htdocs/public/onlinesign/newonlinesign.php index 76b26b09a3b..578bd1410b4 100644 --- a/htdocs/public/onlinesign/newonlinesign.php +++ b/htdocs/public/onlinesign/newonlinesign.php @@ -79,6 +79,7 @@ $suffix = GETPOST("suffix", 'aZ09'); $source = GETPOST("source", 'alpha'); $ref = $REF = GETPOST("ref", 'alpha'); + if (empty($source)) { $source = 'proposal'; } @@ -128,8 +129,9 @@ if (!$action) { $securekeyseed = ''; if ($source == 'proposal') { $securekeyseed = getDolGlobalString('PROPOSAL_ONLINE_SIGNATURE_SECURITY_TOKEN'); +} elseif ($source == 'contract') { + $securekeyseed = getDolGlobalString('CONTRACT_ONLINE_SIGNATURE_SECURITY_TOKEN'); } - if (!dol_verifyHash($securekeyseed.$type.$ref.(isModEnabled('multicompany') ? $entity : ''), $SECUREKEY, '0')) { httponly_accessforbidden('Bad value for securitykey. Value provided '.dol_escape_htmltag($SECUREKEY).' does not match expected value for ref='.dol_escape_htmltag($ref), 403, 1); } @@ -138,6 +140,10 @@ if ($source == 'proposal') { require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php'; $object = new Propal($db); $result= $object->fetch(0, $ref, '', $entity); +} elseif ($source == 'contract') { + require_once DOL_DOCUMENT_ROOT.'/contrat/class/contrat.class.php'; + $object = new Contrat($db); + $result= $object->fetch(0, $ref); } else { httponly_accessforbidden($langs->trans('ErrorBadParameters')." - Bad value for source", 400, 1); } @@ -278,16 +284,24 @@ if (!empty($conf->global->ONLINE_SIGN_NEWFORM_TEXT)) { $text = '
'.$text.'
'."\n"; } if (empty($text)) { - $text .= '
'.$langs->trans("WelcomeOnOnlineSignaturePage", $mysoc->name).''."\n"; - $text .= ''.$langs->trans("ThisScreenAllowsYouToSignDocFrom", $creditor).'

'."\n"; + if ($source == 'proposal') { + $text .= '
'.$langs->trans("WelcomeOnOnlineSignaturePageProposal", $mysoc->name).''."\n"; + $text .= ''.$langs->trans("ThisScreenAllowsYouToSignDocFromProposal", $creditor).'

'."\n"; + } elseif ($source == 'contract') { + $text .= '
'.$langs->trans("WelcomeOnOnlineSignaturePageContract", $mysoc->name).''."\n"; + $text .= ''.$langs->trans("ThisScreenAllowsYouToSignDocFromContract", $creditor).'

'."\n"; + } } print $text; // Output payment summary form print ''; print ''; -print ''."\n"; - +if ($source == 'proposal') { + print ''."\n"; +} elseif ($source == 'contract') { + print ''."\n"; +} $found = false; $error = 0; @@ -370,8 +384,53 @@ if ($source == 'proposal') { print ''; print ''; print ''."\n"; -} +} elseif ($source == 'contract') { // Signature on contract + $found = true; + $langs->load("contract"); + $result = $object->fetch_thirdparty($object->socid); + + // Proposer + print ''."\n"; + + // Target + print ''."\n"; + + // Object + $text = ''.$langs->trans("SignatureContractRef", $object->ref).''; + print ''."\n"; +} if (!$found && !$mesg) { @@ -436,7 +495,7 @@ if ($action == "dosign" && empty($cancel)) { success: function(response) { if(response == "success"){ console.log("Success on saving signature"); - window.location.replace("'.$_SERVER["PHP_SELF"].'?ref='.urlencode($ref).'&message=signed&securekey='.urlencode($SECUREKEY).(isModEnabled('multicompany')?'&entity='.$entity:'').'"); + window.location.replace("'.$_SERVER["PHP_SELF"].'?ref='.urlencode($ref).'&source='.urlencode($source).'&message=signed&securekey='.urlencode($SECUREKEY).(isModEnabled('multicompany')?'&entity='.$entity:'').'"); }else{ console.error(response); } @@ -474,6 +533,12 @@ if ($action == "dosign" && empty($cancel)) { print ''; print ''; } + } elseif ($source == 'contract') { + if ($message == 'signed') { + print ''.$langs->trans("ContractSigned").''; + } else { + print ''; + } } } print ''."\n";
'.$langs->trans("ThisIsInformationOnDocumentToSign").' :
'.$langs->trans("ThisIsInformationOnDocumentToSignProposal").' :
'.$langs->trans("ThisIsInformationOnDocumentToSignContract").' :
'.$langs->trans("Proposer"); + print ''; + print img_picto('', 'company', 'class="pictofixedwidth"'); + print ''.$creditor.''; + print ''; + print '
'.$langs->trans("ThirdParty"); + print ''; + print img_picto('', 'company', 'class="pictofixedwidth"'); + print ''.$object->thirdparty->name.''; + print '
'.$langs->trans("Designation"); + print ''.$text; + + $last_main_doc_file = $object->last_main_doc; + + if (empty($last_main_doc_file) || !dol_is_file(DOL_DATA_ROOT.'/'.$object->last_main_doc)) { + // It seems document has never been generated, or was generated and then deleted. + // So we try to regenerate it with its default template. + $defaulttemplate = ''; // We force the use an empty string instead of $object->model_pdf to be sure to use a "main" default template and not the last one used. + $object->generateDocument($defaulttemplate, $langs); + } + + $directdownloadlink = $object->getLastMainDocLink('contract'); + if ($directdownloadlink) { + print '
'; + print img_mime($object->last_main_doc, ''); + print $langs->trans("DownloadDocument").''; + } + + + print ''; + print ''; + print '