Hook : Edit email notifications

I’m new to Dolibarr and I’m looking to follow good practices in order to modify my site.

My first goal is to modify the email sent to the administrator when a ticket is created.

For this I created an “emailstemplates” module containing an “ActionsEmailsTemplates” class which overrides the “htdocs/public/ticket/create_ticket.php” file.

<?php
/* Copyright (C) 2022 Gaetan Vial <g.vial@medas.fr>
 *
 * 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 <https://www.gnu.org/licenses/>.
 */

/**
 * \file    emailstemplates/class/actions_emailstemplates.class.php
 * \ingroup emailstemplates
 * \brief   Example hook overload.
 *
 * Put detailed description here.
 */

/**
 * Class ActionsEmailsTemplates
 */
class ActionsEmailsTemplates
{
	/**
	 * @var DoliDB Database handler.
	 */
	public $db;

	/**
	 * @var string Error code (or message)
	 */
	public $error = '';

	/**
	 * @var array Errors
	 */
	public $errors = array();


	/**
	 * @var array Hook results. Propagated to $hookmanager->resArray for later reuse
	 */
	public $results = array();

	/**
	 * @var string String displayed by executeHook() immediately after return
	 */
	public $resprints;

	/**
	 * @var int		Priority of hook (50 is used if value is not defined)
	 */
	public $priority;


	/**
	 * Constructor
	 *
	 *  @param		DoliDB		$db      Database handler
	 */
	public function __construct($db)
	{
		$this->db = $db;
	}

	/**
	 * Overloading the doActions function : replacing the parent's function with the one below
	 *
	 * @param   array           $parameters     Hook metadatas (context, etc...)
	 * @param   CommonObject    $object         The object to process (an invoice if you are in invoice module, a propale in propale's module, etc...)
	 * @param   string          $action         Current action (if set). Generally create or edit or null
	 * @param   HookManager     $hookmanager    Hook manager propagated to allow calling another hook
	 * @return  int                             < 0 on error, 0 on success, 1 to replace standard code
	 */
	public function doActions($parameters, &$object, &$action, $hookmanager)
	{
		global $conf, $user, $langs;

		$error = 0; // Error counter

		/* print_r($parameters); print_r($object); echo "action: " . $action; */
		if (in_array($parameters['currentcontext'], array('publicnewticketcard'))) {	    // do something only for the context 'somecontext1' or 'somecontext2'
			
			if (!defined('NOTOKENRENEWAL')) {
				define('NOTOKENRENEWAL', '1');
			}
			if (!defined('NOREQUIREMENU')) {
				define('NOREQUIREMENU', '1');
			}
			if (!defined('NOREQUIREHTML')) {
				define('NOREQUIREHTML', '1');
			}
			if (!defined('NOLOGIN')) {
				define("NOLOGIN", 1); // This means this output page does not require to be logged.
			}
			if (!defined('NOIPCHECK')) {
				define('NOIPCHECK', '1'); // Do not check IP defined into conf $dolibarr_main_restrict_ip
			}
			if (!defined('NOBROWSERNOTIF')) {
				define('NOBROWSERNOTIF', '1');
			}
			
			// For MultiCompany module.
			// Do not use GETPOST here, function is not defined and define must be done before including main.inc.php
			$entity = (!empty($_GET['entity']) ? (int) $_GET['entity'] : (!empty($_POST['entity']) ? (int) $_POST['entity'] : 1));
			if (is_numeric($entity)) {
				define("DOLENTITY", $entity);
			}

			//var_dump('../../../main.inc.php'); die;
			
			require_once DOL_DOCUMENT_ROOT.'/main.inc.php';
			require_once DOL_DOCUMENT_ROOT.'/ticket/class/actions_ticket.class.php';
			require_once DOL_DOCUMENT_ROOT.'/core/class/html.formticket.class.php';
			require_once DOL_DOCUMENT_ROOT.'/core/lib/ticket.lib.php';
			require_once DOL_DOCUMENT_ROOT.'/core/lib/security.lib.php';
			require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
			require_once DOL_DOCUMENT_ROOT.'/core/lib/payments.lib.php';
			require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
			require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';
			require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
			
			
			// Load translation files required by the page
			$langs->loadLangs(array('companies', 'other', 'mails', 'ticket'));
			
			// Get parameters
			$id = GETPOST('id', 'int');
			$msg_id = GETPOST('msg_id', 'int');
			
			$action = GETPOST('action', 'aZ09');
			$cancel = GETPOST('cancel', 'aZ09');
			
			$backtopage = '';
			
			// Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context
			$hookmanager->initHooks(array('publicnewticketcard', 'globalcard'));
			
			$object = new Ticket($db);
			$extrafields = new ExtraFields($db);
			$contacts = array();
			$with_contact = null;
			if (!empty($conf->global->TICKET_CREATE_THIRD_PARTY_WITH_CONTACT_IF_NOT_EXIST)) {
				$with_contact = new Contact($db);
			}
			//$extrafields->fetch_name_optionals_label($object->table_element); 
			
			if (empty($conf->ticket->enabled)) {
				accessforbidden('', 0, 0, 1);
			}

			if ($cancel) {
				$backtopage = DOL_URL_ROOT.'/public/ticket/index.php';
		
				header("Location: ".$backtopage);
				exit;
			}
		
			if (GETPOST('addfile', 'alpha') && !GETPOST('save', 'alpha')) {
				////$res = $object->fetch('','',GETPOST('track_id'));
				////if($res > 0)
				////{
				include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
		
				// Set tmp directory TODO Use a dedicated directory for temp mails files
				$vardir = $conf->ticket->dir_output;
				$upload_dir_tmp = $vardir.'/temp/'.session_id();
				if (!dol_is_dir($upload_dir_tmp)) {
					dol_mkdir($upload_dir_tmp);
				}
		
				dol_add_file_process($upload_dir_tmp, 0, 0, 'addedfile', '', null, '', 0);
				$action = 'create_ticket';
				////}
			}
		
			// Remove file
			if (GETPOST('removedfile', 'alpha') && !GETPOST('save', 'alpha')) {
				include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
		
				// Set tmp directory
				$vardir = $conf->ticket->dir_output.'/';
				$upload_dir_tmp = $vardir.'/temp/'.session_id();
		
				// TODO Delete only files that was uploaded from email form
				dol_remove_file_process(GETPOST('removedfile'), 0, 0);
				$action = 'create_ticket';
			}
		
			if ($action == 'create_ticket' && GETPOST('save', 'alpha')) {
				$error = 0;
				$origin_email = GETPOST('email', 'alpha');
				if (empty($origin_email)) {
					$error++;
					array_push($object->errors, $langs->trans("ErrorFieldRequired", $langs->transnoentities("Email")));
					$action = '';
				} else {
					// Search company saved with email
					$searched_companies = $object->searchSocidByEmail($origin_email, '0');
		
					// Chercher un contact existant avec cette adresse email
					// Le premier contact trouvé est utilisé pour déterminer le contact suivi
					$contacts = $object->searchContactByEmail($origin_email);
		
					// Option to require email exists to create ticket
					if (!empty($conf->global->TICKET_EMAIL_MUST_EXISTS) && !$contacts[0]->socid) {
						$error++;
						array_push($object->errors, $langs->trans("ErrorEmailMustExistToCreateTicket"));
						$action = '';
					}
				}
		
				$contact_lastname = '';
				$contact_firstname = '';
				$company_name = '';
				$contact_phone = '';
				if ($with_contact) {
					// set linked contact to add in form
					if (is_array($contacts) && count($contacts) == 1) {
						$with_contact = current($contacts);
					}
		
					// check mandatory fields on contact
					$contact_lastname = trim(GETPOST('contact_lastname', 'alphanohtml'));
					$contact_firstname = trim(GETPOST('contact_firstname', 'alphanohtml'));
					$company_name = trim(GETPOST('company_name', 'alphanohtml'));
					$contact_phone = trim(GETPOST('contact_phone', 'alphanohtml'));
					if (!($with_contact->id > 0)) {
						// check lastname
						if (empty($contact_lastname)) {
							$error++;
							array_push($object->errors, $langs->trans('ErrorFieldRequired', $langs->transnoentities('Lastname')));
							$action = '';
						}
						// check firstname
						if (empty($contact_firstname)) {
							$error++;
							array_push($object->errors, $langs->trans('ErrorFieldRequired', $langs->transnoentities('Firstname')));
							$action = '';
						}
					}
				}
		
				if (!GETPOST("subject", "restricthtml")) {
					$error++;
					array_push($object->errors, $langs->trans("ErrorFieldRequired", $langs->transnoentities("Subject")));
					$action = '';
				}
				if (!GETPOST("message", "restricthtml")) {
					$error++;
					array_push($object->errors, $langs->trans("ErrorFieldRequired", $langs->transnoentities("Message")));
					$action = '';
				}
		
				// Check email address
				if (!empty($origin_email) && !isValidEmail($origin_email)) {
					$error++;
					array_push($object->errors, $langs->trans("ErrorBadEmailAddress", $langs->transnoentities("email")));
					$action = '';
				}
		
				// Check Captcha code if is enabled
				if (!empty($conf->global->MAIN_SECURITY_ENABLECAPTCHA_TICKET)) {
					$sessionkey = 'dol_antispam_value';
					$ok = (array_key_exists($sessionkey, $_SESSION) === true && (strtolower($_SESSION[$sessionkey]) === strtolower(GETPOST('code', 'restricthtml'))));
					if (!$ok) {
						$error++;
						array_push($object->errors, $langs->trans("ErrorBadValueForCode"));
						$action = '';
					}
				}
		
				if (!$error) {
					$object->db->begin();
		
					$object->track_id = generate_random_id(16);
		
					$object->subject = GETPOST("subject", "restricthtml");
					$object->message = GETPOST("message", "restricthtml");
					$object->origin_email = $origin_email;
		
					$object->type_code = GETPOST("type_code", 'aZ09');
					$object->category_code = GETPOST("category_code", 'aZ09');
					$object->severity_code = GETPOST("severity_code", 'aZ09');
		
					if (!is_object($user)) {
						$user = new User($db);
					}
		
					// create third-party with contact
					$usertoassign = 0;
					if ($with_contact && !($with_contact->id > 0)) {
						$company = new Societe($db);
						if (!empty($company_name)) {
							$company->name = $company_name;
						} else {
							$company->particulier = 1;
							$company->name = dolGetFirstLastname($contact_firstname, $contact_lastname);
						}
						$result = $company->create($user);
						if ($result < 0) {
							$error++;
							$errors = ($company->error ? array($company->error) : $company->errors);
							array_push($object->errors, $errors);
							$action = 'create_ticket';
						}
		
						// create contact and link to this new company
						if (!$error) {
							$with_contact->email = $origin_email;
							$with_contact->lastname = $contact_lastname;
							$with_contact->firstname = $contact_firstname;
							$with_contact->socid = $company->id;
							$with_contact->phone_pro = $contact_phone;
							$result = $with_contact->create($user);
							if ($result < 0) {
								$error++;
								$errors = ($with_contact->error ? array($with_contact->error) : $with_contact->errors);
								array_push($object->errors, $errors);
								$action = 'create_ticket';
							} else {
								$contacts = array($with_contact);
							}
						}
					}
		
					if (is_array($searched_companies)) {
						$object->fk_soc = $searched_companies[0]->id;
					}
		
					if (is_array($contacts) and count($contacts) > 0) {
						$object->fk_soc = $contacts[0]->socid;
						$usertoassign = $contacts[0]->id;
					}
		
					//$ret = $extrafields->setOptionalsFromPost(null, $object);
		
					// Generate new ref
					$object->ref = $object->getDefaultRef();
		
					$object->context['disableticketemail'] = 1; // Disable emails sent by ticket trigger when creation is done from this page, emails are already sent later
		
					$id = $object->create($user);
					if ($id <= 0) {
						$error++;
						$errors = ($object->error ? array($object->error) : $object->errors);
						array_push($object->errors, $object->error ? array($object->error) : $object->errors);
						$action = 'create_ticket';
					}
		
					if (!$error && $id > 0) {
						if ($usertoassign > 0) {
							$object->add_contact($usertoassign, "SUPPORTCLI", 'external', 0);
						}
					}
		
					if (!$error) {
						$object->db->commit();
						$action = "infos_success";
					} else {
						$object->db->rollback();
						setEventMessages($object->error, $object->errors, 'errors');
						$action = 'create_ticket';
					}
		
					if (!$error) {
						$res = $object->fetch($id);
						if ($res) {
							// Create form object
							include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
							include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
							$formmail = new FormMail($db);
		
							// Init to avoid errors
							$filepath = array();
							$filename = array();
							$mimetype = array();
		
							$attachedfiles = $formmail->get_attached_files();
							$filepath = $attachedfiles['paths'];
							$filename = $attachedfiles['names'];
							$mimetype = $attachedfiles['mimes'];
		
							// Send email to customer
		
							$subject = '['.$conf->global->MAIN_INFO_SOCIETE_NOM.'] '.$langs->transnoentities('TicketNewEmailSubject', $object->ref, $object->track_id);
							$message  = ($conf->global->TICKET_MESSAGE_MAIL_NEW ? $conf->global->TICKET_MESSAGE_MAIL_NEW : $langs->transnoentities('TicketNewEmailBody')).'<br><br>';
							$message .= $langs->transnoentities('TicketNewEmailBodyInfosTicket').'<br>';
		
							$url_public_ticket = ($conf->global->TICKET_URL_PUBLIC_INTERFACE ? $conf->global->TICKET_URL_PUBLIC_INTERFACE.'/view.php' : dol_buildpath('/public/ticket/view.php', 2)).'?track_id='.$object->track_id;
							$infos_new_ticket = $langs->transnoentities('TicketNewEmailBodyInfosTrackId', '<a href="'.$url_public_ticket.'" rel="nofollow noopener">'.$object->track_id.'</a>').'<br>';
							$infos_new_ticket .= $langs->transnoentities('TicketNewEmailBodyInfosTrackUrl').'<br><br>';
		
							$message .= $infos_new_ticket;
							$message .= getDolGlobalString('TICKET_MESSAGE_MAIL_SIGNATURE', $langs->transnoentities('TicketMessageMailSignatureText', $mysoc->name));
		
							$sendto = GETPOST('email', 'alpha');
		
							$from = $conf->global->MAIN_INFO_SOCIETE_NOM.' <'.getDolGlobalString('TICKET_NOTIFICATION_EMAIL_FROM').'>';
							$replyto = $from;
							$sendtocc = '';
							$deliveryreceipt = 0;
		
							if (!empty($conf->global->TICKET_DISABLE_MAIL_AUTOCOPY_TO)) {
								$old_MAIN_MAIL_AUTOCOPY_TO = $conf->global->MAIN_MAIL_AUTOCOPY_TO;
								$conf->global->MAIN_MAIL_AUTOCOPY_TO = '';
							}
							include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
							$mailfile = new CMailFile($subject, $sendto, $from, $message, $filepath, $mimetype, $filename, $sendtocc, '', $deliveryreceipt, -1, '', '', 'tic'.$object->id, '', 'ticket');
							if ($mailfile->error || $mailfile->errors) {
								setEventMessages($mailfile->error, $mailfile->errors, 'errors');
							} else {
								$result = $mailfile->sendfile();
							}
							if (!empty($conf->global->TICKET_DISABLE_MAIL_AUTOCOPY_TO)) {
								$conf->global->MAIN_MAIL_AUTOCOPY_TO = $old_MAIN_MAIL_AUTOCOPY_TO;
							}
		
							// Send email to TICKET_NOTIFICATION_EMAIL_TO
							$sendto = $conf->global->TICKET_NOTIFICATION_EMAIL_TO;
							if ($sendto) {
								$subject = '['.$conf->global->MAIN_INFO_SOCIETE_NOM.'] '.$langs->transnoentities('TicketNewEmailSubjectAdmin', $object->ref, $object->track_id);
								$message_admin = $langs->transnoentities('TicketNewEmailBodyAdmin', $object->track_id).'<br><br>';
								$message_admin .= '<ul><li>'.$langs->trans('Title').' : '.$object->subject.'</li>';
								$message_admin .= '<li>le hook du module est bien utilisé pour modifier ce mail</li>';
								$message_admin .= '<li>'.$langs->trans('Type').' : '.$object->type_label.'</li>';
								$message_admin .= '<li>'.$langs->trans('Category').' : '.$object->category_label.'</li>';
								$message_admin .= '<li>'.$langs->trans('Severity').' : '.$object->severity_label.'</li>';
								$message_admin .= '<li>'.$langs->trans('From').' : '.$object->origin_email.'</li>';
								// Extrafields
								//$extrafields->fetch_name_optionals_label($object->table_element);
								/*if (is_array($object->array_options) && count($object->array_options) > 0) {
									foreach ($object->array_options as $key => $value) {
										$key = substr($key, 8); // remove "options_"
										$message_admin .= '<li>'.$langs->trans($extrafields->attributes[$object->table_element]['label'][$key]).' : '.$extrafields->showOutputField($key, $value, '', $object->table_element).'</li>';
									}
								}*/
								$message_admin .= '</ul>';
		
								$message_admin .= '<p>'.$langs->trans('Message').' : <br>'.$object->message.'</p>';
								$message_admin .= '<p><a href="'.dol_buildpath('/ticket/card.php', 2).'?track_id='.$object->track_id.'" rel="nofollow noopener">'.$langs->trans('SeeThisTicketIntomanagementInterface').'</a></p>';
		
								$from = $conf->global->MAIN_INFO_SOCIETE_NOM.' <'.$conf->global->TICKET_NOTIFICATION_EMAIL_FROM.'>';
								$replyto = $from;
		
								if (!empty($conf->global->TICKET_DISABLE_MAIL_AUTOCOPY_TO)) {
									$old_MAIN_MAIL_AUTOCOPY_TO = $conf->global->MAIN_MAIL_AUTOCOPY_TO;
									$conf->global->MAIN_MAIL_AUTOCOPY_TO = '';
								}
								include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
								$mailfile = new CMailFile($subject, $sendto, $from, $message_admin, $filepath, $mimetype, $filename, $sendtocc, '', $deliveryreceipt, -1, '', '', 'tic'.$object->id, '', 'ticket');
								if ($mailfile->error || $mailfile->errors) {
									setEventMessages($mailfile->error, $mailfile->errors, 'errors');
								} else {
									$result = $mailfile->sendfile();
								}
								if (!empty($conf->global->TICKET_DISABLE_MAIL_AUTOCOPY_TO)) {
									$conf->global->MAIN_MAIL_AUTOCOPY_TO = $old_MAIN_MAIL_AUTOCOPY_TO;
								}
							}
						}
		
						// Copy files into ticket directory
						$destdir = $conf->ticket->dir_output.'/'.$object->ref;
						if (!dol_is_dir($destdir)) {
							dol_mkdir($destdir);
						}
						foreach ($filename as $i => $val) {
							dol_move($filepath[$i], $destdir.'/'.$filename[$i], 0, 1);
							$formmail->remove_attached_files($i);
						}
		
						//setEventMessages($langs->trans('YourTicketSuccessfullySaved'), null, 'mesgs');
		
						// Make a redirect to avoid to have ticket submitted twice if we make back
						$messagetoshow = $langs->trans('MesgInfosPublicTicketCreatedWithTrackId', '{s1}', '{s2}');
						$messagetoshow = str_replace(array('{s1}', '{s2}'), array('<strong>'.$object->track_id.'</strong>', '<strong>'.$object->ref.'</strong>'), $messagetoshow);
						setEventMessages($messagetoshow, null, 'warnings');
						setEventMessages($langs->trans('PleaseRememberThisId'), null, 'warnings');
						header("Location: index.php".(!empty($entity) && !empty($conf->multicompany->enabled)?'?entity='.$entity:''));
						exit;
					}
				} else {
					setEventMessages($object->error, $object->errors, 'errors');
				}
			}

		}

		if (!$error) {
			$this->results = array('myreturn' => 999);
			$this->resprints = 'A text to show';
			return 0; // or return 1 to replace standard code
		} else {
			$this->errors[] = 'Error message';
			return -1;
		}
	}

}

I have two problems:

How do I get accurate information about my errors? Because my server or Dolibarr logs do not allow me to know exactly on which line my error occurs.

Why do I get a 500 error when I uncomment the line
$extrafields->fetch_name_optionals_label($object->table_element);
although “htdocs/core/class/extrafields.class.php” is included?

Thanks a lot for your help !

Hi @Gaetan

Have you tried this by putting it at the begging of the page which you are calling or executing?

ini_set(‘display_errors’, 1);
ini_set(‘display_startup_errors’, 1);
error_reporting(E_ALL);

If not then just put at the top your calling page after <?php then see what error message you are getting.

Great ! Thank you Saikatkoley ! :grinning:

Hi @Gaetan

You are welcome. :smiley: