* * @return boolean true if this LSaddon is fully supported, false otherwise */ function LSaddon_mail_support() { $retval=true; // Lib dependencies (check/load) if (!class_exists('Mail')) { if(!LSsession::includeFile(PEAR_MAIL, true)) { LSerror :: addErrorCode('MAIL_SUPPORT_01'); $retval=false; } } if (!class_exists('Mail_mime')) { if(!LSsession::includeFile(PEAR_MAIL_MIME, true)) { LSerror :: addErrorCode('MAIL_SUPPORT_02'); $retval=false; } } if (!$retval) return false; $GLOBALS['MAIL_LOGGER'] = LSlog :: get_logger('LSaddon_mail'); LScli :: add_command( 'test_send_mail', 'cli_test_send_mail', 'Send a test email', "[-s subject] [-b body] [-H] [recipient1] [...]", array ( " -s/--subject The test email subject (optional)", " -b/--body The test email body (optional)", " -H/--HTML Enable HTML email body mode (optional)", " --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)", " recipients The test email recipient(s) (required).", ), false, // This command does not need LDAP connection 'cli_test_send_mail_autocompleter' ); // Handle mail templates stuff LScli :: add_command( 'test_send_mail_template', '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 'cli_test_send_mail_from_template_autocompleter' ); if ( isset($GLOBALS['MAIL_TEMPLATES_EDITOR_VIEW_ACCESS']) && is_array($GLOBALS['MAIL_TEMPLATES_EDITOR_VIEW_ACCESS']) && $GLOBALS['MAIL_TEMPLATES_EDITOR_VIEW_ACCESS'] && list_mail_templates() ) { if (!class_exists('\Html2Text\Html2Text')) { if(!LSsession::includeFile(HTML2TEXT, true)) { LSerror :: addErrorCode('MAIL_SUPPORT_03'); $retval = false; } } if ($retval) LSsession :: registerLSaddonView( 'mail', 'templates', _('Email templates'), 'email_templates_view', $GLOBALS['MAIL_TEMPLATES_EDITOR_VIEW_ACCESS'] ); } return $retval; } /** * Send an email * * @param string|array $to Email recipient(s) * @param string $subject Email subject * @param string $msg Email body * @param array|null $headers Email headers * @param array|null $attachments Email attachments as an array with * filepath as key and filename as value * @param string|null $eol End of line string (default : \n) * @param string|null $encoding Email encoding (default: utf8) * @param boolean $html Set to true to send an HTML email (default: false) * * @author Benjamin Renard * * @return boolean true si MAIL est pleinement supporté, false sinon */ function sendMail($to, $subject, $msg, $headers=null, $attachments=null, $eol=null, $encoding=null, $html=false) { global $MAIL_SEND_PARAMS, $MAIL_HEARDERS, $MAIL_CATCH_ALL; $mail_obj = Mail::factory(MAIL_SEND_METHOD, (isset($MAIL_SEND_PARAMS)?$MAIL_SEND_PARAMS:null)); $logger = LSlog :: get_logger('LSaddon_mail'); if (!$headers) $headers = array(); if (isset($MAIL_HEARDERS) && is_array($MAIL_HEARDERS)) { $headers = array_merge($headers,$MAIL_HEARDERS); } $logger -> trace( 'Mail catch all: '.( isset($MAIL_CATCH_ALL) && $MAIL_CATCH_ALL? varDump($MAIL_CATCH_ALL):'not set') ); if (isset($MAIL_CATCH_ALL) && $MAIL_CATCH_ALL) { $logger -> debug( 'Mail catch to '. (is_array($MAIL_CATCH_ALL)?implode(',', $MAIL_CATCH_ALL):$MAIL_CATCH_ALL) ); $msg .= sprintf( ( $html? _("

Mail initialy intended for %s.

"): _("\n\n\nMail initialy intended for %s.") ), (is_array($to)?implode(',', $to):$to)); $to = ( is_array($MAIL_CATCH_ALL)? implode(',', $MAIL_CATCH_ALL):$MAIL_CATCH_ALL ); } if (isset($headers['From'])) { $from = $headers['From']; unset($headers['From']); } else { $from = LSsession :: getEmailSender(); } $headers["To"] = $to; $to = ensureIsArray($to); foreach(array_keys($headers) as $header) { if(in_array(strtoupper($header), array('BCC', 'CC'))) { $headers[$header] = ensureIsArray($headers[$header]); if (isset($MAIL_CATCH_ALL) && $MAIL_CATCH_ALL) { $logger -> debug("Mail catched: remove $header header"); $msg .= sprintf( ( $html? _("

%s: %s

"): _("\n%s: %s") ), strtoupper($header), implode(', ', $headers[$header]) ); unset($headers[$header]); continue; } $to = array_merge($to, $headers[$header]); } } if (!$encoding) $encoding = "utf8"; $mime = new Mail_mime( array( 'eol' => ($eol?$eol:"\n"), ($html?'html_charset':'text_charset') => $encoding, 'head_charset' => $encoding, ) ); if ($from) $mime->setFrom($from); if ($subject) $mime->setSubject($subject); if ($html) $mime->setHTMLBody($msg); else $mime->setTXTBody($msg); if (is_array($attachments) && !empty($attachments)) { $finfo = new finfo(FILEINFO_MIME_TYPE); foreach ($attachments as $file => $filename) { $mime->addAttachment($file, $finfo->file($file), $filename); } } $body = $mime->get(); $headers = $mime->headers($headers); $ret = $mail_obj -> send($to, $headers, $body); if ($ret instanceof PEAR_Error) { LSerror :: addErrorCode('MAIL_01'); LSerror :: addErrorCode('MAIL_00', $ret -> getMessage()); return false; } return true; } /** * List email templates directories * @return array */ function list_mail_templates_directories() { if (isset($GLOBALS['MAIL_TEMPLATES_DIRECTORIES_CACHE'])) return $GLOBALS['MAIL_TEMPLATES_DIRECTORIES_CACHE']; if ( !isset($GLOBALS['MAIL_TEMPLATES_DIRECTORIES']) || !is_array($GLOBALS['MAIL_TEMPLATES_DIRECTORIES']) ) return []; $GLOBALS['MAIL_TEMPLATES_DIRECTORIES_CACHE'] = []; foreach($GLOBALS['MAIL_TEMPLATES_DIRECTORIES'] as $directory) { $path = new SplFileInfo( substr($directory, 0,1) == '/'? $directory: LS_ROOT_DIR."/".$directory ); if ($path && $path->isDir()) $GLOBALS['MAIL_TEMPLATES_DIRECTORIES_CACHE'][] = $path; else $GLOBALS['MAIL_LOGGER'] -> warning( "list_mail_templates_directories(): directory {$directory} does not exists." ); } $GLOBALS['MAIL_LOGGER'] -> debug( "list_mail_templates_directories(): directories = ". implode(', ', $GLOBALS['MAIL_TEMPLATES_DIRECTORIES_CACHE']) ); return $GLOBALS['MAIL_TEMPLATES_DIRECTORIES_CACHE']; } /** * 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, * ], * [...] * ] */ function list_mail_templates() { if (isset($GLOBALS['MAIL_TEMPLATES']) && $GLOBALS['MAIL_TEMPLATES']) return $GLOBALS['MAIL_TEMPLATES']; $GLOBALS['MAIL_TEMPLATES'] = []; $expected_extensions = ['subject', 'html', 'txt']; foreach(list_mail_templates_directories() as $directory) { foreach (new DirectoryIterator($directory) 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, $GLOBALS['MAIL_TEMPLATES'])) { $GLOBALS['MAIL_TEMPLATES'][$name] = []; foreach($expected_extensions as $ext) $GLOBALS['MAIL_TEMPLATES'][$name][$ext] = null; } if (!$GLOBALS['MAIL_TEMPLATES'][$name][$fileInfo->getExtension()]) $GLOBALS['MAIL_TEMPLATES'][$name][$fileInfo->getExtension()] = $fileInfo->getRealPath(); } } return $GLOBALS['MAIL_TEMPLATES']; } /** * Search for writable path to save change of a template file * @param string $template The template name * @param string $extension The template file extension (subject, html or txt) * @return string|false The path of writable template file if found, false otherwise */ function get_mail_template_saved_path($template, $extension) { $templates = list_mail_templates(); if (!array_key_exists($template, $templates)) return false; $found = false; $first_writable = false; foreach(list_mail_templates_directories() as $directory) { $file_path = new SplFileInfo("$directory/$template.$extension"); if ($file_path->isFile()) { // File exist, check is writable if ($file_path->isWritable()) return $file_path->getRealPath(); // If we don't find previously a writable file, trigger an error if (!$first_writable) { $GLOBALS['MAIL_LOGGER'] -> error( "get_mail_template_saved_path($template, $extension): file '{$file_path->getRealPath()}' ". "is not writable, can't saved this template file." ); return false; } continue; } else if (!$first_writable && $directory->isWritable()) { $first_writable = strval($file_path); } } // No existing writable file found if ($first_writable) return $first_writable; $GLOBALS['MAIL_LOGGER'] -> error( "get_mail_template_saved_path($template, $extension): ". "no writable path found, can't saved this template file." ); return false; } /** * 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 */ function sendMailFromTemplate( $tplname, $to, $variables=null, $headers=null, $attachments=null ) { $templates = list_mail_templates(); if (!array_key_exists($tplname, $templates)) { LSerror :: addErrorCode('MAIL_02', $tplname); return False; } $tpl = $templates[$tplname]; if (!$tpl['subject'] || !($tpl['txt'] || $tpl['html'])) { LSerror :: addErrorCode('MAIL_03', $tplname); return False; } $smarty = new Smarty(); $smarty -> setCompileDir(LS_TMP_DIR_PATH); 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]; $GLOBALS['MAIL_LOGGER'] -> debug( "sendMailFromTemplate($tplname, ".implode("|", $to)."): ". "subject compute from '{$tpl['subject']}'." ); if ($tpl['html']) { $message = $smarty -> fetch("file:{$tpl['html']}"); $html = true; $GLOBALS['MAIL_LOGGER'] -> debug( "sendMailFromTemplate($tplname, ".implode("|", $to)."): ". "HTML content compute from '{$tpl['html']}'." ); } else { $message = $smarty -> fetch("file:{$tpl['txt']}"); $html = false; $GLOBALS['MAIL_LOGGER'] -> debug( "sendMailFromTemplate($tplname, ".implode("|", $to)."): ". "text content compute from '{$tpl['txt']}'." ); } } catch (Exception $e) { $GLOBALS['MAIL_LOGGER'] -> exception( $e, getFData( _("An exception occured forging message from email template '%{template}'"), $tplname ), false ); return false; } return sendMail($to, $subject, $message, $headers, $attachments, "\n", "utf8", $html); } /** * Email templates management view * @return void */ function email_templates_view() { $template = isset($_REQUEST['name'])?$_REQUEST['name']:null; $templates = []; foreach(list_mail_templates() as $name => $tpl) { if ($template && $template != $name) continue; $templates[$name] = [ 'name' => $name, 'subject' => $tpl['subject']?file_get_contents($tpl['subject']):null, 'html' => $tpl['html']?file_get_contents($tpl['html']):null, 'txt' => $tpl['txt']?file_get_contents($tpl['txt']):null, ]; if ($template) continue; if ($templates[$name]['html']) { $Html2Text = new \Html2Text\Html2Text($templates[$name]['html']); $templates[$name]['html'] = substr($Html2Text->getText(), 0, 70)."..."; } if ($templates[$name]['txt']) { $templates[$name]['txt'] = substr($templates[$name]['txt'], 0, 70)."..."; } } if ($template) { if (!array_key_exists($template, $templates)) { LSurl::redirect("addon/mail/templates"); } LStemplate :: assign('pagetitle', getFData(_('Email template: %{name}'), $template)); $tab = isset($_REQUEST['tab'])?$_REQUEST['tab']:'subject'; $path = false; switch ($tab) { case 'subject': $path = get_mail_template_saved_path($template, $tab); if (array_key_exists('subject', $_POST)) { if (!$path) LSerror :: addErrorCode('MAIL_04'); elseif (file_put_contents($path, $_POST['subject']) !== false) { LSsession :: addInfo(_("Your changes have been saved.")); LSurl::redirect("addon/mail/templates?name=".urlencode($template)."&tab=$tab"); } else { LSerror :: addErrorCode('MAIL_05'); $tpl['subject'] = $_POST['subject']; } } break; case 'html': $path = get_mail_template_saved_path($template, $tab); if (array_key_exists('html', $_POST)) { if (!$path) LSerror :: addErrorCode('MAIL_04'); elseif (file_put_contents($path, $_POST['html']) !== false) { LSsession :: addInfo(_("Your changes have been saved.")); LSurl::redirect("addon/mail/templates?name=".urlencode($template)."&tab=$tab"); } else { LSerror :: addErrorCode('MAIL_05'); $tpl['html'] = $_POST['html']; } } LStemplate :: addLibJSscript('tinymce/js/tinymce/tinymce.min.js'); LStemplate :: addJSscript('email_templates.js'); break; case 'txt': $path = get_mail_template_saved_path($template, $tab); if (array_key_exists('txt', $_POST)) { if (!$path) LSerror :: addErrorCode('MAIL_04'); elseif (file_put_contents($path, $_POST['txt']) !== false) { LSsession :: addInfo(_("Your changes have been saved.")); LSurl::redirect("addon/mail/templates?name=".urlencode($template)."&tab=$tab"); } else { LSerror :: addErrorCode('MAIL_05'); $tpl['txt'] = $_POST['txt']; } } break; default: LSurl::redirect("addon/mail/templates?name=".urlencode($template)); } LStemplate :: assign('template', $templates[$template]); LStemplate :: assign('tab', $tab); LStemplate :: assign('writable', boolval($path)); $LSview_actions = array(); $LSview_actions['return'] = array ( 'label' => _('Go back'), 'url' => 'addon/mail/templates', 'action' => 'view' ); LStemplate :: assign('LSview_actions', $LSview_actions); LSsession :: setTemplate('email_template.tpl'); } else { LStemplate :: assign('pagetitle', _('Email templates')); LStemplate :: assign('templates', $templates); LSsession :: setTemplate('email_templates.tpl'); } LStemplate :: addCssFile('email_templates.css'); } if (php_sapi_name() != 'cli') return true; /** * CLI command to send a test email * @param array $command_args Command arguments * @return bool */ function cli_test_send_mail($command_args) { $recipients = array(); $subject = "Test email"; $body = "This is a test message."; $html = false; $headers = array(); $attachments = array(); for ($i=0; $i < count($command_args); $i++) { switch ($command_args[$i]) { case '--subject': case '-s': $i++; if (!isset($command_args[$i])) LScli :: usage("You must provide the email subject after -s/--subject parameter."); $subject = $command_args[$i]; if (!$subject) LScli :: usage("Invalid subject provided."); break; case '--body': case '-b': $i++; if (!isset($command_args[$i])) LScli :: usage("You must provide the email body after -b/--body parameter."); $body = $command_args[$i]; if (!$body) LScli :: usage("Invalid body provided."); break; case '--html': case '-H': $html = true; break; case '--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]; break; case '-a': case '--attachment': $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); break; case '--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]; break; case '--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]; break; default: if (checkEmail($command_args[$i])) $recipients[] = $command_args[$i]; else LScli :: usage("Invalid parameter '".$command_args[$i]."'."); } } if (!$recipients) LScli :: usage("You must provide as least one email recipient as positional parameter"); $logger = LSlog :: get_logger('LSaddon_mail'); if (!sendMail($recipients, $subject, $body, $headers, $attachments, null, null, $html)) { $logger -> error("Fail to send test email sent to '".implode(', ', $recipients)."'."); return false; } $logger -> info("Test email sent to '".implode(', ', $recipients)."'."); return true; } /** * Args autocompleter for CLI test_send_mail 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 **/ function cli_test_send_mail_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 '-s': case '--subject': case '-b': case '--body': case '--header': case '-a': case '--attachment': case '--bcc': case '--cc': return array(); break; } $opts = array_merge( $opts, array ( '-s', '--subject', '-b', '--body', '-H', '--html', '--header', '-a', '--attachment', '--bcc', '--cc', ) ); return LScli :: autocomplete_opts($opts, $comp_word); } /** * 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 **/ 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 sendMailFromTemplate( $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 **/ 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); }