diff --git a/src/email_templates/welcome.html b/src/email_templates/welcome.html new file mode 100644 index 00000000..feddab87 --- /dev/null +++ b/src/email_templates/welcome.html @@ -0,0 +1,7 @@ +{literal}{/literal} + +

Welcome {$name}!

+ +

This email is just an example of an email template.

+ +

Regards,

diff --git a/src/email_templates/welcome.subject b/src/email_templates/welcome.subject new file mode 100644 index 00000000..44121bd3 --- /dev/null +++ b/src/email_templates/welcome.subject @@ -0,0 +1 @@ +Welcome {$name}! diff --git a/src/includes/class/class.LSmail.php b/src/includes/class/class.LSmail.php index fc5cf9bc..dfe64dd9 100644 --- a/src/includes/class/class.LSmail.php +++ b/src/includes/class/class.LSmail.php @@ -20,7 +20,20 @@ ******************************************************************************/ -class LSmail { +class LSmail extends LSlog_staticLoggerClass { + + /** + * Array of templates directories where file have to be search + * @var array + */ + private static $template_directories = array('local', './'); + + /** + * Cache of loaded templates + * @see list_templates() + * @var array> + */ + private static $_templates = null; /* * Méthode chargeant les dépendances d'affichage @@ -81,4 +94,287 @@ class LSmail { } } + /** + * List exiting email templates + * @return array> + * [ + * '[name]' => [ + * 'subject' => '/path/to/name.subject' or null, + * 'html' => '/path/to/name.html' or null, + * 'txt' => '/path/to/name.txt' or null, + * ], + * [...] + * ] + */ + public static function list_templates() { + if (self :: $_templates) + return self :: $_templates; + self :: $_templates = []; + $expected_extensions = ['subject', 'html', 'txt']; + foreach(self :: $template_directories as $dir) { + $dir_path = realpath(LS_ROOT_DIR."/".$dir."/email_templates"); + if ($dir_path === false) + // Directory not found or not accessible + continue; + foreach (new DirectoryIterator($dir_path) as $fileInfo) { + if( + $fileInfo->isDot() + || !$fileInfo->isFile() + || !$fileInfo->isReadable() + || !in_array($fileInfo->getExtension(), $expected_extensions) + ) + continue; + $name = $fileInfo->getBasename(".".$fileInfo->getExtension()); + if (!array_key_exists($name, self :: $_templates)) { + self :: $_templates[$name] = []; + foreach($expected_extensions as $ext) self :: $_templates[$name][$ext] = null; + } + if (!self :: $_templates[$name][$fileInfo->getExtension()]) + self :: $_templates[$name][$fileInfo->getExtension()] = $fileInfo->getRealPath(); + } + } + return self :: $_templates; + } + + /** + * Send email from template + * @param string $tplname The email template name + * @param string $to The email recipient + * @param array $variables Variables to use to compute the template + * @param array|null $headers Email headers + * @param array|null $attachments Email attachments as an array with + * filepath as key and filename as value + * @return boolean True if the email was sent, false otherwise + */ + public static function send_mail_from_template( + $tplname, $to, $variables=null, $headers=null, $attachments=null + ) { + $templates = self :: list_templates(); + if (!array_key_exists($tplname, $templates)) { + LSerror :: addErrorCode('LSmail_01', $tplname); + return False; + } + + $tpl = $templates[$tplname]; + if (!$tpl['subject'] || !($tpl['txt'] || $tpl['html'])) { + LSerror :: addErrorCode('LSmail_02', $tplname); + return False; + } + + $smarty = new Smarty(); + + if (is_array($variables)) + array_map([$smarty, "assign"], array_keys($variables), array_values($variables)); + + try { + $subject = $smarty -> fetch("file:{$tpl['subject']}"); + // Multiple line from subject cause problem, trim it and only the first line + $subject = explode("\n", trim($subject))[0]; + + self :: log_debug( + "send_mail_from_template($tplname, ".implode("|", $to)."): ". + "subject compute from '{$tpl['subject']}'." + ); + if ($tpl['html']) { + $body = $smarty -> fetch("file:{$tpl['html']}"); + $html = true; + self :: log_debug( + "send_mail_from_template($tplname, ".implode("|", $to)."): ". + "HTML body compute from '{$tpl['html']}'." + ); + } + else { + $body = $smarty -> fetch("file:{$tpl['txt']}"); + $html = false; + self :: log_debug( + "send_mail_from_template($tplname, ".implode("|", $to)."): ". + "text body compute from '{$tpl['txt']}'." + ); + } + } + catch (Exception $e) { + self :: log_exception( + $e, getFData( + _("LSmail - An exception occured forging message from email template '%{template}'"), + $tplname + ), + false + ); + return false; + } + + return sendMail($to, $subject, $body, $headers, $attachments, "\n", "utf8", $html); + } + + /** + * CLI test_send_mail_template command + * + * @param array $command_args Command arguments : + * - Positional arguments : + * - template name + * - recipient + * - Optional arguments : + * - -V|--variable: template variable (format: variable=value) + * - -H|--header: header (format: header=value) + * - -a|--attachent: (format: /path/to/file.ext:filename or just /path/to/file.ext) + * - -bcc: BCC recipient(s) + * - -cc: CC recipient(s) + * + * @return boolean True on success, false otherwise + **/ + public static function cli_test_send_mail_template($command_args) { + $template = null; + $recipients = array(); + $variables = array(); + $headers = array(); + $attachments = array(); + for ($i=0; $i < count($command_args); $i++) { + LScli :: unquote_word($command_args[$i]); + if (in_array($command_args[$i], array('-V', '--variable'))) { + $i++; + LScli :: unquote_word($command_args[$i]); + $parts = explode('=', $command_args[$i]); + if (count($parts) != 2) + LScli :: usage('Invalid variable string ('.$command_args[$i].').'); + if (array_key_exists($parts[0], $variables)) + LScli :: usage('Variable "'.$parts[0].'" already specified.'); + $variables[$parts[0]] = $parts[1]; + } + elseif (in_array($command_args[$i], array('-H', '--header'))) { + $i++; + LScli :: unquote_word($command_args[$i]); + $parts = explode('=', $command_args[$i]); + if (count($parts) != 2) + LScli :: usage('Invalid header string ('.$command_args[$i].').'); + if (array_key_exists($parts[0], $headers)) + LScli :: usage('Header "'.$parts[0].'" already specified.'); + $headers[$parts[0]] = $parts[1]; + } + elseif (in_array($command_args[$i], array('-a', '--attachent'))) { + $i++; + LScli :: unquote_word($command_args[$i]); + $parts = explode(':', $command_args[$i]); + $path = $parts[0]; + if (!is_file($path)) + LScli :: usage('Invalid attachment "'.$command_args[$i].'": file not found.'); + $attachments[$path] = count($parts) >= 2?$parts[1]:basename($path); + } + elseif ($command_args[$i] == '--bcc') { + $i++; + LScli :: unquote_word($command_args[$i]); + if (!checkEmail($command_args[$i])) + LScli :: usage('Invalid BCC recipient "'.$command_args[$i].'".'); + $headers['BCC'] = isset($headers['BCC'])?ensureIsArray($headers['BCC']):[]; + $headers['BCC'][] = $command_args[$i]; + } + elseif ($command_args[$i] == '--cc') { + $i++; + LScli :: unquote_word($command_args[$i]); + if (!checkEmail($command_args[$i])) + LScli :: usage('Invalid CC recipient "'.$command_args[$i].'".'); + $headers['CC'] = isset($headers['CC'])?ensureIsArray($headers['CC']):[]; + $headers['CC'][] = $command_args[$i]; + } + else if (is_null($template)) { + $template = $command_args[$i]; + } + else if (checkEmail($command_args[$i])) { + $recipients[] = $command_args[$i]; + } + else + LScli :: usage('Invalid recipient "'.$command_args[$i].'".'); + } + + if (is_null($template) || empty($recipients)) + LScli :: usage('You must provide email template name and at least one recipient.'); + + return self :: send_mail_from_template( + $template, + $recipients, + $variables, + $headers, + $attachments + ); + } + + /** + * Args autocompleter for CLI test_send_mail_from_template command + * + * @param array $comp_words List of already typed words of the command + * @param int $comp_word_num The command word number to autocomplete + * @param string $comp_word The command word to autocomplete + * @param array $opts List of global available options + * + * @return array List of available options for the word to autocomplete + **/ + public static function cli_test_send_mail_from_template_autocompleter( + $comp_words, $comp_word_num, $comp_word, $opts + ) { + if (isset($comp_words[$comp_word_num-1])) + switch ($comp_words[$comp_word_num-1]) { + case '-v': + case '--variable': + case '-H': + case '--header': + case '-a': + case '--attachment': + case '--bcc': + case '--cc': + return array(); + break; + } + $opts = array_merge( + $opts, + array ( + '-v', '--variable', + '-H', '--header', + '-a', '--attachment', + '--bcc', '--cc', + ) + ); + return LScli :: autocomplete_opts($opts, $comp_word); + } + } + +/** + * Error Codes + */ +LSerror :: defineError('LSmail_01', +___("LSmail: Unknown template %{name}.") +); +LSerror :: defineError('LSmail_02', +___("LSmail: Template %{name} is incomplete.") +); + +// LScli +LScli :: add_command( + 'test_send_mail_template', + array('LSmail', 'cli_test_send_mail_template'), + 'Test to send an email template', + '[template] [-V var1=value] [recipient1] [recipient2]', + array( + ' - Positional arguments :', + ' - email template name', + ' - email recipient(s)', + '', + ' - Optional arguments :', + ' -V|--variable Template variable using format:', + ' variable_name=variable_value', + ' Multiple variables could be specified by using this', + ' optional argument multiple time.', + ' -H|--header Email header using format:', + ' header_name=header_value', + ' Multiple headers could be specified by using this', + ' optional argument multiple time.', + ' -a|--attachment Email attachment using format:', + ' /path/to/attachment.file[:filename]', + ' The filename is optional (default: using source filename).', + ' Multiple attachments could be specified by using this', + ' optional argument multiple time.', + ' --bcc Add Blind Carbon Copy (BCC) recipient(s)', + ' --cc Add Carbon Copy (CC) recipient(s)', + ), + false, // This command does not need LDAP connection + array('LSmail', 'cli_test_send_mail_from_template_autocompleter') +);