Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' into phpunit

* master: (29 commits)
  some edge case checking in search result highlighting
  Resolve empty page ID to configured start page
  Added more detail error code for unauthorized calls in xmlrpc interface.
  changed internal Mailer members from private to protected
  made it possible to disable HTML mails in the config
  removed commented code left from the quoted_printable days
  add missing table tags for HTML diff mails
  fixed subscriber mail signatures
  use real HRs in HTML mails
  Add various headers to the mails FS#2247. pull request #83 closed
  removed footer image from HTML mails
  use inlinestyles for diffs in HTML mails
  fixed signature stripping
  fixed mailprefix handling
  fixed missing replacement for HTML notify mails
  allow image embeds in HTML mail templates
  Added HTML wrapper for mails
  allow non-txt extensions when accessing locales
  added Mailer class to autoloader
  Replaced mail_send calls with new Mailer class
  ...
  • Loading branch information...
commit 73d2d14ce0b465ec7117c8dc6ab39a59f6ed7b5a 2 parents f64856c + 8a803ca
@splitbrain authored
View
1  conf/dokuwiki.php
@@ -110,6 +110,7 @@
$conf['registernotify'] = ''; //send info about newly registered users to this email (leave blank for nobody)
$conf['mailfrom'] = ''; //use this email when sending mails
$conf['mailprefix'] = ''; //use this as prefix of outgoing mails
+$conf['htmlmail'] = 1; //send HTML multipart mails
/* Syndication Settings */
$conf['sitemap'] = 0; //Create a google sitemap? How often? In days.
View
61 inc/DifferenceEngine.php
@@ -818,6 +818,39 @@ function _changed($orig, $closing) {
}
}
+/**
+ * Utilityclass for styling HTML formatted diffs
+ *
+ * Depends on global var $DIFF_INLINESTYLES, if true some minimal predefined
+ * inline styles are used. Useful for HTML mails and RSS feeds
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+class HTMLDiff {
+ /**
+ * Holds the style names and basic CSS
+ */
+ static public $styles = array(
+ 'diff-addedline' => 'background-color: #ddffdd;',
+ 'diff-deletedline' => 'background-color: #ffdddd;',
+ 'diff-context' => 'background-color: #f5f5f5;',
+ 'diff-mark' => 'color: #ff0000;',
+ );
+
+ /**
+ * Return a class or style parameter
+ */
+ static function css($classname){
+ global $DIFF_INLINESTYLES;
+
+ if($DIFF_INLINESTYLES){
+ if(!isset(self::$styles[$classname])) return '';
+ return 'style="'.self::$styles[$classname].'"';
+ }else{
+ return 'class="'.$classname.'"';
+ }
+ }
+}
/**
* Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3
@@ -838,11 +871,11 @@ function __construct() {
function _flushGroup($new_tag) {
if ($this->_group !== '') {
if ($this->_tag == 'mark')
- $this->_line .= '<strong>'.$this->_group.'</strong>';
+ $this->_line .= '<strong '.HTMLDiff::css('diff-mark').'>'.$this->_group.'</strong>';
elseif ($this->_tag == 'add')
- $this->_line .= '<span class="diff-addedline">'.$this->_group.'</span>';
+ $this->_line .= '<span '.HTMLDiff::css('diff-addedline').'>'.$this->_group.'</span>';
elseif ($this->_tag == 'del')
- $this->_line .= '<span class="diff-deletedline"><del>'.$this->_group.'</del></span>';
+ $this->_line .= '<span '.HTMLDiff::css('diff-deletedline').'><del>'.$this->_group.'</del></span>';
else
$this->_line .= $this->_group;
}
@@ -1020,8 +1053,8 @@ function _block_header($xbeg, $xlen, $ybeg, $ylen) {
global $lang;
$l1 = $lang['line'].' '.$xbeg;
$l2 = $lang['line'].' '.$ybeg;
- $r = '<tr><td class="diff-blockheader" colspan="2">'.$l1.":</td>\n".
- '<td class="diff-blockheader" colspan="2">'.$l2.":</td>\n".
+ $r = '<tr><td '.HTMLDiff::css('diff-blockheader').' colspan="2">'.$l1.":</td>\n".
+ '<td '.HTMLDiff::css('diff-blockheader').' colspan="2">'.$l2.":</td>\n".
"</tr>\n";
return $r;
}
@@ -1037,11 +1070,11 @@ function _lines($lines, $prefix=' ', $color="white") {
}
function addedLine($line) {
- return '<td>+</td><td class="diff-addedline">' . $line.'</td>';
+ return '<td>+</td><td '.HTMLDiff::css('diff-addedline').'>' . $line.'</td>';
}
function deletedLine($line) {
- return '<td>-</td><td class="diff-deletedline">' . $line.'</td>';
+ return '<td>-</td><td '.HTMLDiff::css('diff-deletedline').'>' . $line.'</td>';
}
function emptyLine() {
@@ -1049,7 +1082,7 @@ function emptyLine() {
}
function contextLine($line) {
- return '<td> </td><td class="diff-context">'.$line.'</td>';
+ return '<td> </td><td '.HTMLDiff::css('diff-context').'>'.$line.'</td>';
}
function _added($lines) {
@@ -1115,9 +1148,9 @@ function _block_header($xbeg, $xlen, $ybeg, $ylen) {
$xbeg .= "," . $xlen;
if ($ylen != 1)
$ybeg .= "," . $ylen;
- $r = '<tr><td colspan="'.$this->colspan.'" class="diff-blockheader">@@ '.$lang['line']." -$xbeg +$ybeg @@";
- $r .= ' <span class="diff-deletedline"><del>'.$lang['deleted'].'</del></span>';
- $r .= ' <span class="diff-addedline">'.$lang['created'].'</span>';
+ $r = '<tr><td colspan="'.$this->colspan.'" '.HTMLDiff::css('diff-blockheader').'>@@ '.$lang['line']." -$xbeg +$ybeg @@";
+ $r .= ' <span '.HTMLDiff::css('diff-deletedline').'><del>'.$lang['deleted'].'</del></span>';
+ $r .= ' <span '.HTMLDiff::css('diff-addedline').'>'.$lang['created'].'</span>';
$r .= "</td></tr>\n";
return $r;
}
@@ -1134,19 +1167,19 @@ function _lines($lines, $prefix=' ', $color="white") {
function _added($lines) {
foreach ($lines as $line) {
- print('<tr><td colspan="'.$this->colspan.'" class="diff-addedline">'. $line . "</td></tr>\n");
+ print('<tr><td colspan="'.$this->colspan.'" '.HTMLDiff::css('diff-addedline').'>'. $line . "</td></tr>\n");
}
}
function _deleted($lines) {
foreach ($lines as $line) {
- print('<tr><td colspan="'.$this->colspan.'" class="diff-deletedline"><del>' . $line . "</del></td></tr>\n");
+ print('<tr><td colspan="'.$this->colspan.'" '.HTMLDiff::css('diff-deletedline').'><del>' . $line . "</del></td></tr>\n");
}
}
function _context($lines) {
foreach ($lines as $line) {
- print('<tr><td colspan="'.$this->colspan.'" class="diff-context">'.$line."</td></tr>\n");
+ print('<tr><td colspan="'.$this->colspan.'" '.HTMLDiff::css('diff-context').'>'.$line."</td></tr>\n");
}
}
View
652 inc/Mailer.class.php
@@ -0,0 +1,652 @@
+<?php
+/**
+ * A class to build and send multi part mails (with HTML content and embedded
+ * attachments). All mails are assumed to be in UTF-8 encoding.
+ *
+ * Attachments are handled in memory so this shouldn't be used to send huge
+ * files, but then again mail shouldn't be used to send huge files either.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+
+// end of line for mail lines - RFC822 says CRLF but postfix (and other MTAs?)
+// think different
+if(!defined('MAILHEADER_EOL')) define('MAILHEADER_EOL',"\n");
+#define('MAILHEADER_ASCIIONLY',1);
+
+class Mailer {
+
+ protected $headers = array();
+ protected $attach = array();
+ protected $html = '';
+ protected $text = '';
+
+ protected $boundary = '';
+ protected $partid = '';
+ protected $sendparam= null;
+
+ protected $validator = null;
+ protected $allowhtml = true;
+
+ /**
+ * Constructor
+ *
+ * Initializes the boundary strings and part counters
+ */
+ public function __construct(){
+ global $conf;
+
+ $server = parse_url(DOKU_URL,PHP_URL_HOST);
+
+ $this->partid = md5(uniqid(rand(),true)).'@'.$server;
+ $this->boundary = '----------'.md5(uniqid(rand(),true));
+
+ $listid = join('.',array_reverse(explode('/',DOKU_BASE))).$server;
+ $listid = strtolower(trim($listid,'.'));
+
+ $this->allowhtml = (bool) $conf['htmlmail'];
+
+ // add some default headers for mailfiltering FS#2247
+ $this->setHeader('X-Mailer','DokuWiki '.getVersion());
+ $this->setHeader('X-DokuWiki-User', $_SERVER['REMOTE_USER']);
+ $this->setHeader('X-DokuWiki-Title', $conf['title']);
+ $this->setHeader('X-DokuWiki-Server', $server);
+ $this->setHeader('X-Auto-Response-Suppress', 'OOF');
+ $this->setHeader('List-Id',$conf['title'].' <'.$listid.'>');
+ }
+
+ /**
+ * Attach a file
+ *
+ * @param $path Path to the file to attach
+ * @param $mime Mimetype of the attached file
+ * @param $name The filename to use
+ * @param $embed Unique key to reference this file from the HTML part
+ */
+ public function attachFile($path,$mime,$name='',$embed=''){
+ if(!$name){
+ $name = basename($path);
+ }
+
+ $this->attach[] = array(
+ 'data' => file_get_contents($path),
+ 'mime' => $mime,
+ 'name' => $name,
+ 'embed' => $embed
+ );
+ }
+
+ /**
+ * Attach a file
+ *
+ * @param $path The file contents to attach
+ * @param $mime Mimetype of the attached file
+ * @param $name The filename to use
+ * @param $embed Unique key to reference this file from the HTML part
+ */
+ public function attachContent($data,$mime,$name='',$embed=''){
+ if(!$name){
+ list($junk,$ext) = split('/',$mime);
+ $name = count($this->attach).".$ext";
+ }
+
+ $this->attach[] = array(
+ 'data' => $data,
+ 'mime' => $mime,
+ 'name' => $name,
+ 'embed' => $embed
+ );
+ }
+
+ /**
+ * Callback function to automatically embed images referenced in HTML templates
+ */
+ protected function autoembed_cb($matches){
+ static $embeds = 0;
+ $embeds++;
+
+ // get file and mime type
+ $media = cleanID($matches[1]);
+ list($ext, $mime) = mimetype($media);
+ $file = mediaFN($media);
+ if(!file_exists($file)) return $matches[0]; //bad reference, keep as is
+
+ // attach it and set placeholder
+ $this->attachFile($file,$mime,'','autoembed'.$embeds);
+ return '%%autoembed'.$embeds.'%%';
+ }
+
+ /**
+ * Add an arbitrary header to the mail
+ *
+ * If an empy value is passed, the header is removed
+ *
+ * @param string $header the header name (no trailing colon!)
+ * @param string $value the value of the header
+ * @param bool $clean remove all non-ASCII chars and line feeds?
+ */
+ public function setHeader($header,$value,$clean=true){
+ $header = str_replace(' ','-',ucwords(strtolower(str_replace('-',' ',$header)))); // streamline casing
+ if($clean){
+ $header = preg_replace('/[^\w \-\.\+\@]+/','',$header);
+ $value = preg_replace('/[^\w \-\.\+\@<>]+/','',$value);
+ }
+
+ // empty value deletes
+ $value = trim($value);
+ if($value === ''){
+ if(isset($this->headers[$header])) unset($this->headers[$header]);
+ }else{
+ $this->headers[$header] = $value;
+ }
+ }
+
+ /**
+ * Set additional parameters to be passed to sendmail
+ *
+ * Whatever is set here is directly passed to PHP's mail() command as last
+ * parameter. Depending on the PHP setup this might break mailing alltogether
+ */
+ public function setParameters($param){
+ $this->sendparam = $param;
+ }
+
+ /**
+ * Set the text and HTML body and apply replacements
+ *
+ * This function applies a whole bunch of default replacements in addition
+ * to the ones specidifed as parameters
+ *
+ * If you pass the HTML part or HTML replacements yourself you have to make
+ * sure you encode all HTML special chars correctly
+ *
+ * @param string $text plain text body
+ * @param array $textrep replacements to apply on the text part
+ * @param array $htmlrep replacements to apply on the HTML part, leave null to use $textrep
+ * @param array $html the HTML body, leave null to create it from $text
+ * @param bool $wrap wrap the HTML in the default header/Footer
+ */
+ public function setBody($text, $textrep=null, $htmlrep=null, $html=null, $wrap=true){
+ global $INFO;
+ global $conf;
+ $htmlrep = (array) $htmlrep;
+ $textrep = (array) $textrep;
+
+ // create HTML from text if not given
+ if(is_null($html)){
+ $html = $text;
+ $html = hsc($html);
+ $html = preg_replace('/^-----*$/m','<hr >',$html);
+ $html = nl2br($html);
+ }
+ if($wrap){
+ $wrap = rawLocale('mailwrap','html');
+ $html = preg_replace('/\n-- <br \/>.*$/s','',$html); //strip signature
+ $html = str_replace('@HTMLBODY@',$html,$wrap);
+ }
+
+ // copy over all replacements missing for HTML (autolink URLs)
+ foreach($textrep as $key => $value){
+ if(isset($htmlrep[$key])) continue;
+ if(preg_match('/^https?:\/\//i',$value)){
+ $htmlrep[$key] = '<a href="'.hsc($value).'">'.hsc($value).'</a>';
+ }else{
+ $htmlrep[$key] = hsc($value);
+ }
+ }
+
+ // embed media from templates
+ $html = preg_replace_callback('/@MEDIA\(([^\)]+)\)@/',
+ array($this,'autoembed_cb'),$html);
+
+ // prepare default replacements
+ $ip = clientIP();
+ $cip = gethostsbyaddrs($ip);
+ $trep = array(
+ 'DATE' => dformat(),
+ 'BROWSER' => $_SERVER['HTTP_USER_AGENT'],
+ 'IPADDRESS' => $ip,
+ 'HOSTNAME' => $cip,
+ 'TITLE' => $conf['title'],
+ 'DOKUWIKIURL' => DOKU_URL,
+ 'USER' => $_SERVER['REMOTE_USER'],
+ 'NAME' => $INFO['userinfo']['name'],
+ 'MAIL' => $INFO['userinfo']['mail'],
+ );
+ $trep = array_merge($trep,(array) $textrep);
+ $hrep = array(
+ 'DATE' => '<i>'.hsc(dformat()).'</i>',
+ 'BROWSER' => hsc($_SERVER['HTTP_USER_AGENT']),
+ 'IPADDRESS' => '<code>'.hsc($ip).'</code>',
+ 'HOSTNAME' => '<code>'.hsc($cip).'</code>',
+ 'TITLE' => hsc($conf['title']),
+ 'DOKUWIKIURL' => '<a href="'.DOKU_URL.'">'.DOKU_URL.'</a>',
+ 'USER' => hsc($_SERVER['REMOTE_USER']),
+ 'NAME' => hsc($INFO['userinfo']['name']),
+ 'MAIL' => '<a href="mailto:"'.hsc($INFO['userinfo']['mail']).'">'.
+ hsc($INFO['userinfo']['mail']).'</a>',
+ );
+ $hrep = array_merge($hrep,(array) $htmlrep);
+
+ // Apply replacements
+ foreach ($trep as $key => $substitution) {
+ $text = str_replace('@'.strtoupper($key).'@',$substitution, $text);
+ }
+ foreach ($hrep as $key => $substitution) {
+ $html = str_replace('@'.strtoupper($key).'@',$substitution, $html);
+ }
+
+ $this->setHTML($html);
+ $this->setText($text);
+ }
+
+ /**
+ * Set the HTML part of the mail
+ *
+ * Placeholders can be used to reference embedded attachments
+ *
+ * You probably want to use setBody() instead
+ */
+ public function setHTML($html){
+ $this->html = $html;
+ }
+
+ /**
+ * Set the plain text part of the mail
+ *
+ * You probably want to use setBody() instead
+ */
+ public function setText($text){
+ $this->text = $text;
+ }
+
+ /**
+ * Add the To: recipients
+ *
+ * @see setAddress
+ * @param string $address Multiple adresses separated by commas
+ */
+ public function to($address){
+ $this->setHeader('To', $address, false);
+ }
+
+ /**
+ * Add the Cc: recipients
+ *
+ * @see setAddress
+ * @param string $address Multiple adresses separated by commas
+ */
+ public function cc($address){
+ $this->setHeader('Cc', $address, false);
+ }
+
+ /**
+ * Add the Bcc: recipients
+ *
+ * @see setAddress
+ * @param string $address Multiple adresses separated by commas
+ */
+ public function bcc($address){
+ $this->setHeader('Bcc', $address, false);
+ }
+
+ /**
+ * Add the From: address
+ *
+ * This is set to $conf['mailfrom'] when not specified so you shouldn't need
+ * to call this function
+ *
+ * @see setAddress
+ * @param string $address from address
+ */
+ public function from($address){
+ $this->setHeader('From', $address, false);
+ }
+
+ /**
+ * Add the mail's Subject: header
+ *
+ * @param string $subject the mail subject
+ */
+ public function subject($subject){
+ $this->headers['Subject'] = $subject;
+ }
+
+ /**
+ * Sets an email address header with correct encoding
+ *
+ * Unicode characters will be deaccented and encoded base64
+ * for headers. Addresses may not contain Non-ASCII data!
+ *
+ * Example:
+ * setAddress("föö <foo@bar.com>, me@somewhere.com","TBcc");
+ *
+ * @param string $address Multiple adresses separated by commas
+ * @param string returns the prepared header (can contain multiple lines)
+ */
+ public function cleanAddress($address){
+ // No named recipients for To: in Windows (see FS#652)
+ $names = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? false : true;
+
+ $address = preg_replace('/[\r\n\0]+/',' ',$address); // remove attack vectors
+
+ $headers = '';
+ $parts = explode(',',$address);
+ foreach ($parts as $part){
+ $part = trim($part);
+
+ // parse address
+ if(preg_match('#(.*?)<(.*?)>#',$part,$matches)){
+ $text = trim($matches[1]);
+ $addr = $matches[2];
+ }else{
+ $addr = $part;
+ }
+ // skip empty ones
+ if(empty($addr)){
+ continue;
+ }
+
+ // FIXME: is there a way to encode the localpart of a emailaddress?
+ if(!utf8_isASCII($addr)){
+ msg(htmlspecialchars("E-Mail address <$addr> is not ASCII"),-1);
+ continue;
+ }
+
+ if(is_null($this->validator)){
+ $this->validator = new EmailAddressValidator();
+ $this->validator->allowLocalAddresses = true;
+ }
+ if(!$this->validator->check_email_address($addr)){
+ msg(htmlspecialchars("E-Mail address <$addr> is not valid"),-1);
+ continue;
+ }
+
+ // text was given
+ if(!empty($text) && $names){
+ // add address quotes
+ $addr = "<$addr>";
+
+ if(defined('MAILHEADER_ASCIIONLY')){
+ $text = utf8_deaccent($text);
+ $text = utf8_strip($text);
+ }
+
+ if(!utf8_isASCII($text)){
+ $text = '=?UTF-8?B?'.base64_encode($text).'?=';
+ }
+ }else{
+ $text = '';
+ }
+
+ // add to header comma seperated
+ if($headers != ''){
+ $headers .= ', ';
+ }
+ $headers .= $text.' '.$addr;
+ }
+
+ if(empty($headers)) return false;
+
+ return $headers;
+ }
+
+
+ /**
+ * Prepare the mime multiparts for all attachments
+ *
+ * Replaces placeholders in the HTML with the correct CIDs
+ */
+ protected function prepareAttachments(){
+ $mime = '';
+ $part = 1;
+ // embedded attachments
+ foreach($this->attach as $media){
+ // create content id
+ $cid = 'part'.$part.'.'.$this->partid;
+
+ // replace wildcards
+ if($media['embed']){
+ $this->html = str_replace('%%'.$media['embed'].'%%','cid:'.$cid,$this->html);
+ }
+
+ $mime .= '--'.$this->boundary.MAILHEADER_EOL;
+ $mime .= 'Content-Type: '.$media['mime'].';'.MAILHEADER_EOL;
+ $mime .= 'Content-Transfer-Encoding: base64'.MAILHEADER_EOL;
+ $mime .= "Content-ID: <$cid>".MAILHEADER_EOL;
+ if($media['embed']){
+ $mime .= 'Content-Disposition: inline; filename="'.$media['name'].'"'.MAILHEADER_EOL;
+ }else{
+ $mime .= 'Content-Disposition: attachment; filename="'.$media['name'].'"'.MAILHEADER_EOL;
+ }
+ $mime .= MAILHEADER_EOL; //end of headers
+ $mime .= chunk_split(base64_encode($media['data']),74,MAILHEADER_EOL);
+
+ $part++;
+ }
+ return $mime;
+ }
+
+ /**
+ * Build the body and handles multi part mails
+ *
+ * Needs to be called before prepareHeaders!
+ *
+ * @return string the prepared mail body, false on errors
+ */
+ protected function prepareBody(){
+ global $conf;
+
+ // no HTML mails allowed? remove HTML body
+ if(!$this->allowhtml){
+ $this->html = '';
+ }
+
+ // check for body
+ if(!$this->text && !$this->html){
+ return false;
+ }
+
+ // add general headers
+ $this->headers['MIME-Version'] = '1.0';
+
+ $body = '';
+
+ if(!$this->html && !count($this->attach)){ // we can send a simple single part message
+ $this->headers['Content-Type'] = 'text/plain; charset=UTF-8';
+ $this->headers['Content-Transfer-Encoding'] = 'base64';
+ $body .= chunk_split(base64_encode($this->text),74,MAILHEADER_EOL);
+ }else{ // multi part it is
+ $body .= "This is a multi-part message in MIME format.".MAILHEADER_EOL;
+
+ // prepare the attachments
+ $attachments = $this->prepareAttachments();
+
+ // do we have alternative text content?
+ if($this->text && $this->html){
+ $this->headers['Content-Type'] = 'multipart/alternative;'.MAILHEADER_EOL.
+ ' boundary="'.$this->boundary.'XX"';
+ $body .= '--'.$this->boundary.'XX'.MAILHEADER_EOL;
+ $body .= 'Content-Type: text/plain; charset=UTF-8'.MAILHEADER_EOL;
+ $body .= 'Content-Transfer-Encoding: base64'.MAILHEADER_EOL;
+ $body .= MAILHEADER_EOL;
+ $body .= chunk_split(base64_encode($this->text),74,MAILHEADER_EOL);
+ $body .= '--'.$this->boundary.'XX'.MAILHEADER_EOL;
+ $body .= 'Content-Type: multipart/related;'.MAILHEADER_EOL.
+ ' boundary="'.$this->boundary.'"'.MAILHEADER_EOL;
+ $body .= MAILHEADER_EOL;
+ }
+
+ $body .= '--'.$this->boundary.MAILHEADER_EOL;
+ $body .= 'Content-Type: text/html; charset=UTF-8'.MAILHEADER_EOL;
+ $body .= 'Content-Transfer-Encoding: base64'.MAILHEADER_EOL;
+ $body .= MAILHEADER_EOL;
+ $body .= chunk_split(base64_encode($this->html),74,MAILHEADER_EOL);
+ $body .= MAILHEADER_EOL;
+ $body .= $attachments;
+ $body .= '--'.$this->boundary.'--'.MAILHEADER_EOL;
+
+ // close open multipart/alternative boundary
+ if($this->text && $this->html){
+ $body .= '--'.$this->boundary.'XX--'.MAILHEADER_EOL;
+ }
+ }
+
+ return $body;
+ }
+
+ /**
+ * Cleanup and encode the headers array
+ */
+ protected function cleanHeaders(){
+ global $conf;
+
+ // clean up addresses
+ if(empty($this->headers['From'])) $this->from($conf['mailfrom']);
+ $addrs = array('To','From','Cc','Bcc');
+ foreach($addrs as $addr){
+ if(isset($this->headers[$addr])){
+ $this->headers[$addr] = $this->cleanAddress($this->headers[$addr]);
+ }
+ }
+
+ if(isset($this->headers['Subject'])){
+ // add prefix to subject
+ if(empty($conf['mailprefix'])){
+ if(utf8_strlen($conf['title']) < 20) {
+ $prefix = '['.$conf['title'].']';
+ }else{
+ $prefix = '['.utf8_substr($conf['title'], 0, 20).'...]';
+ }
+ }else{
+ $prefix = '['.$conf['mailprefix'].']';
+ }
+ $len = strlen($prefix);
+ if(substr($this->headers['Subject'],0,$len) != $prefix){
+ $this->headers['Subject'] = $prefix.' '.$this->headers['Subject'];
+ }
+
+ // encode subject
+ if(defined('MAILHEADER_ASCIIONLY')){
+ $this->headers['Subject'] = utf8_deaccent($this->headers['Subject']);
+ $this->headers['Subject'] = utf8_strip($this->headers['Subject']);
+ }
+ if(!utf8_isASCII($this->headers['Subject'])){
+ $this->headers['Subject'] = '=?UTF-8?B?'.base64_encode($this->headers['Subject']).'?=';
+ }
+ }
+
+ // wrap headers
+ foreach($this->headers as $key => $val){
+ $this->headers[$key] = wordwrap($val,78,MAILHEADER_EOL.' ');
+ }
+ }
+
+ /**
+ * Create a string from the headers array
+ *
+ * @returns string the headers
+ */
+ protected function prepareHeaders(){
+ $headers = '';
+ foreach($this->headers as $key => $val){
+ $headers .= "$key: $val".MAILHEADER_EOL;
+ }
+ return $headers;
+ }
+
+ /**
+ * return a full email with all headers
+ *
+ * This is mainly intended for debugging and testing but could also be
+ * used for MHT exports
+ *
+ * @return string the mail, false on errors
+ */
+ public function dump(){
+ $this->cleanHeaders();
+ $body = $this->prepareBody();
+ if($body === 'false') return false;
+ $headers = $this->prepareHeaders();
+
+ return $headers.MAILHEADER_EOL.$body;
+ }
+
+ /**
+ * Send the mail
+ *
+ * Call this after all data was set
+ *
+ * @triggers MAIL_MESSAGE_SEND
+ * @return bool true if the mail was successfully passed to the MTA
+ */
+ public function send(){
+ $success = false;
+
+ // prepare hook data
+ $data = array(
+ // pass the whole mail class to plugin
+ 'mail' => $this,
+ // pass references for backward compatibility
+ 'to' => &$this->headers['To'],
+ 'cc' => &$this->headers['Cc'],
+ 'bcc' => &$this->headers['Bcc'],
+ 'from' => &$this->headers['From'],
+ 'subject' => &$this->headers['Subject'],
+ 'body' => &$this->text,
+ 'params' => &$this->sendparams,
+ 'headers' => '', // plugins shouldn't use this
+ // signal if we mailed successfully to AFTER event
+ 'success' => &$success,
+ );
+
+ // do our thing if BEFORE hook approves
+ $evt = new Doku_Event('MAIL_MESSAGE_SEND', $data);
+ if ($evt->advise_before(true)) {
+ // clean up before using the headers
+ $this->cleanHeaders();
+
+ // any recipients?
+ if(trim($this->headers['To']) === '' &&
+ trim($this->headers['Cc']) === '' &&
+ trim($this->headers['Bcc']) === '') return false;
+
+ // The To: header is special
+ if(isset($this->headers['To'])){
+ $to = $this->headers['To'];
+ unset($this->headers['To']);
+ }else{
+ $to = '';
+ }
+
+ // so is the subject
+ if(isset($this->headers['Subject'])){
+ $subject = $this->headers['Subject'];
+ unset($this->headers['Subject']);
+ }else{
+ $subject = '';
+ }
+
+ // make the body
+ $body = $this->prepareBody();
+ if($body === 'false') return false;
+
+ // cook the headers
+ $headers = $this->prepareHeaders();
+ // add any headers set by legacy plugins
+ if(trim($data['headers'])){
+ $headers .= MAILHEADER_EOL.trim($data['headers']);
+ }
+
+ // send the thing
+ if(is_null($this->sendparam)){
+ $success = @mail($to,$subject,$body,$headers);
+ }else{
+ $success = @mail($to,$subject,$body,$headers,$this->sendparam);
+ }
+ }
+ // any AFTER actions?
+ $evt->advise_after();
+ return $success;
+ }
+}
View
28 inc/RemoteAPICore.php
@@ -169,7 +169,7 @@ function getTime() {
* @return page text.
*/
function rawPage($id,$rev=''){
- $id = cleanID($id);
+ $id = $this->resolvePageId($id);
if(auth_quickaclcheck($id) < AUTH_READ){
throw new RemoteAccessDeniedException('You are not allowed to read this file', 111);
}
@@ -228,7 +228,7 @@ function getAttachmentInfo($id){
* Return a wiki page rendered to html
*/
function htmlPage($id,$rev=''){
- $id = cleanID($id);
+ $id = $this->resolvePageId($id);
if(auth_quickaclcheck($id) < AUTH_READ){
throw new RemoteAccessDeniedException('You are not allowed to read this page', 111);
}
@@ -356,14 +356,14 @@ function listAttachments($ns, $options = array()) {
* Return a list of backlinks
*/
function listBackLinks($id){
- return ft_backlinks(cleanID($id));
+ return ft_backlinks($this->resolvePageId($id));
}
/**
* Return some basic data about a page
*/
function pageInfo($id,$rev=''){
- $id = cleanID($id);
+ $id = $this->resolvePageId($id);
if(auth_quickaclcheck($id) < AUTH_READ){
throw new RemoteAccessDeniedException('You are not allowed to read this page', 111);
}
@@ -394,7 +394,7 @@ function putPage($id, $text, $params) {
global $TEXT;
global $lang;
- $id = cleanID($id);
+ $id = $this->resolvePageId($id);
$TEXT = cleanText($text);
$sum = $params['sum'];
$minor = $params['minor'];
@@ -507,7 +507,7 @@ function deleteAttachment($id){
* Returns the permissions of a given wiki page
*/
function aclCheck($id) {
- $id = cleanID($id);
+ $id = $this->resolvePageId($id);
return auth_quickaclcheck($id);
}
@@ -517,7 +517,7 @@ function aclCheck($id) {
* @author Michael Klier <chi@chimeric.de>
*/
function listLinks($id) {
- $id = cleanID($id);
+ $id = $this->resolvePageId($id);
if(auth_quickaclcheck($id) < AUTH_READ){
throw new RemoteAccessDeniedException('You are not allowed to read this page', 111);
}
@@ -633,7 +633,7 @@ function getRecentMediaChanges($timestamp) {
* @author Michael Klier <chi@chimeric.de>
*/
function pageVersions($id, $first) {
- $id = cleanID($id);
+ $id = $this->resolvePageId($id);
if(auth_quickaclcheck($id) < AUTH_READ) {
throw new RemoteAccessDeniedException('You are not allowed to read this page', 111);
}
@@ -711,7 +711,7 @@ function setLocks($set){
$unlockfail = array();
foreach((array) $set['lock'] as $id){
- $id = cleanID($id);
+ $id = $this->resolvePageId($id);
if(auth_quickaclcheck($id) < AUTH_EDIT || checklock($id)){
$lockfail[] = $id;
}else{
@@ -721,7 +721,7 @@ function setLocks($set){
}
foreach((array) $set['unlock'] as $id){
- $id = cleanID($id);
+ $id = $this->resolvePageId($id);
if(auth_quickaclcheck($id) < AUTH_EDIT || !unlock($id)){
$unlockfail[] = $id;
}else{
@@ -764,6 +764,14 @@ function login($user,$pass){
return $ok;
}
+ private function resolvePageId($id) {
+ $id = cleanID($id);
+ if(empty($id)) {
+ global $conf;
+ $id = cleanID($conf['start']);
+ }
+ return $id;
+ }
}
View
50 inc/auth.php
@@ -669,22 +669,17 @@ function auth_sendPassword($user,$password){
if(!$userinfo['mail']) return false;
$text = rawLocale('password');
- $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text);
- $text = str_replace('@FULLNAME@',$userinfo['name'],$text);
- $text = str_replace('@LOGIN@',$user,$text);
- $text = str_replace('@PASSWORD@',$password,$text);
- $text = str_replace('@TITLE@',$conf['title'],$text);
-
- if(empty($conf['mailprefix'])) {
- $subject = $lang['regpwmail'];
- } else {
- $subject = '['.$conf['mailprefix'].'] '.$lang['regpwmail'];
- }
+ $trep = array(
+ 'FULLNAME' => $userinfo['name'],
+ 'LOGIN' => $user,
+ 'PASSWORD' => $password
+ );
- return mail_send($userinfo['name'].' <'.$userinfo['mail'].'>',
- $subject,
- $text,
- $conf['mailfrom']);
+ $mail = new Mailer();
+ $mail->to($userinfo['name'].' <'.$userinfo['mail'].'>');
+ $mail->subject($lang['regpwmail']);
+ $mail->setBody($text,$trep);
+ return $mail->send();
}
/**
@@ -941,22 +936,17 @@ function act_resendpwd(){
io_saveFile($tfile,$user);
$text = rawLocale('pwconfirm');
- $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text);
- $text = str_replace('@FULLNAME@',$userinfo['name'],$text);
- $text = str_replace('@LOGIN@',$user,$text);
- $text = str_replace('@TITLE@',$conf['title'],$text);
- $text = str_replace('@CONFIRM@',$url,$text);
-
- if(empty($conf['mailprefix'])) {
- $subject = $lang['regpwmail'];
- } else {
- $subject = '['.$conf['mailprefix'].'] '.$lang['regpwmail'];
- }
+ $trep = array(
+ 'FULLNAME' => $userinfo['name'],
+ 'LOGIN' => $user,
+ 'CONFIRM' => $url
+ );
- if(mail_send($userinfo['name'].' <'.$userinfo['mail'].'>',
- $subject,
- $text,
- $conf['mailfrom'])){
+ $mail = new Mailer();
+ $mail->to($userinfo['name'].' <'.$userinfo['mail'].'>');
+ $mail->subject($lang['regpwmail']);
+ $mail->setBody($text,$trep);
+ if($mail->send()){
msg($lang['resendpwdconfirm'],1);
}else{
msg($lang['regmailfail'],-1);
View
90 inc/common.php
@@ -789,8 +789,8 @@ function formText($text){
*
* @author Andreas Gohr <andi@splitbrain.org>
*/
-function rawLocale($id){
- return io_readFile(localeFN($id));
+function rawLocale($id,$ext='txt'){
+ return io_readFile(localeFN($id,$ext));
}
/**
@@ -1086,8 +1086,9 @@ function notify($id,$who,$rev='',$summary='',$minor=false,$replace=array()){
global $lang;
global $conf;
global $INFO;
+ global $DIFF_INLINESTYLES;
- // decide if there is something to do
+ // decide if there is something to do, eg. whom to mail
if($who == 'admin'){
if(empty($conf['notify'])) return; //notify enabled?
$text = rawLocale('mailtext');
@@ -1112,49 +1113,54 @@ function notify($id,$who,$rev='',$summary='',$minor=false,$replace=array()){
return; //just to be safe
}
- $ip = clientIP();
- $text = str_replace('@DATE@',dformat(),$text);
- $text = str_replace('@BROWSER@',$_SERVER['HTTP_USER_AGENT'],$text);
- $text = str_replace('@IPADDRESS@',$ip,$text);
- $text = str_replace('@HOSTNAME@',gethostsbyaddrs($ip),$text);
- $text = str_replace('@NEWPAGE@',wl($id,'',true,'&'),$text);
- $text = str_replace('@PAGE@',$id,$text);
- $text = str_replace('@TITLE@',$conf['title'],$text);
- $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text);
- $text = str_replace('@SUMMARY@',$summary,$text);
- $text = str_replace('@USER@',$_SERVER['REMOTE_USER'],$text);
- $text = str_replace('@NAME@',$INFO['userinfo']['name'],$text);
- $text = str_replace('@MAIL@',$INFO['userinfo']['mail'],$text);
-
- foreach ($replace as $key => $substitution) {
- $text = str_replace('@'.strtoupper($key).'@',$substitution, $text);
- }
+ // prepare replacements (keys not set in hrep will be taken from trep)
+ $trep = array(
+ 'NEWPAGE' => wl($id,'',true,'&'),
+ 'PAGE' => $id,
+ 'SUMMARY' => $summary
+ );
+ $trep = array_merge($trep,$replace);
+ $hrep = array();
+ // prepare content
if($who == 'register'){
- $subject = $lang['mail_new_user'].' '.$summary;
+ $subject = $lang['mail_new_user'].' '.$summary;
}elseif($rev){
- $subject = $lang['mail_changed'].' '.$id;
- $text = str_replace('@OLDPAGE@',wl($id,"rev=$rev",true,'&'),$text);
- $df = new Diff(explode("\n",rawWiki($id,$rev)),
- explode("\n",rawWiki($id)));
- $dformat = new UnifiedDiffFormatter();
- $diff = $dformat->format($df);
+ $subject = $lang['mail_changed'].' '.$id;
+ $trep['OLDPAGE'] = wl($id,"rev=$rev",true,'&');
+ $df = new Diff(explode("\n",rawWiki($id,$rev)),
+ explode("\n",rawWiki($id)));
+ $dformat = new UnifiedDiffFormatter();
+ $tdiff = $dformat->format($df);
+
+ $DIFF_INLINESTYLES = true;
+ $dformat = new InlineDiffFormatter();
+ $hdiff = $dformat->format($df);
+ $hdiff = '<table>'.$hdiff.'</table>';
+ $DIFF_INLINESTYLES = false;
}else{
- $subject=$lang['mail_newpage'].' '.$id;
- $text = str_replace('@OLDPAGE@','none',$text);
- $diff = rawWiki($id);
- }
- $text = str_replace('@DIFF@',$diff,$text);
- if(empty($conf['mailprefix'])) {
- if(utf8_strlen($conf['title']) < 20) {
- $subject = '['.$conf['title'].'] '.$subject;
- }else{
- $subject = '['.utf8_substr($conf['title'], 0, 20).'...] '.$subject;
- }
- }else{
- $subject = '['.$conf['mailprefix'].'] '.$subject;
- }
- mail_send($to,$subject,$text,$conf['mailfrom'],'',$bcc);
+ $subject = $lang['mail_newpage'].' '.$id;
+ $trep['OLDPAGE'] = '---';
+ $tdiff = rawWiki($id);
+ $hdiff = nl2br(hsc($tdiff));
+ }
+ $trep['DIFF'] = $tdiff;
+ $hrep['DIFF'] = $hdiff;
+
+ // send mail
+ $mail = new Mailer();
+ $mail->to($to);
+ $mail->bcc($bcc);
+ $mail->subject($subject);
+ $mail->setBody($text,$trep,$hrep);
+ if($who == 'subscribers'){
+ $mail->setHeader(
+ 'List-Unsubscribe',
+ '<'.wl($id,array('do'=>'subscribe'),true,'&').'>',
+ false
+ );
+ }
+ return $mail->send();
}
/**
View
2  inc/fulltext.php
@@ -405,6 +405,8 @@ function ft_snippet_re_preprocess($term) {
}else{
$term = $term.'\b';
}
+
+ if($term == '\b' || $term == '\b\b') $term = '';
return $term;
}
View
7 inc/html.php
@@ -280,8 +280,11 @@ function html_draft(){
* @author Harry Fuecks <hfuecks@gmail.com>
*/
function html_hilight($html,$phrases){
- $phrases = array_filter((array) $phrases);
- $regex = join('|',array_map('ft_snippet_re_preprocess', array_map('preg_quote_cb',$phrases)));
+ $phrases = (array) $phrases;
+ $phrases = array_map('preg_quote_cb', $phrases);
+ $phrases = array_map('ft_snippet_re_preprocess', $phrases);
+ $phrases = array_filter($phrases);
+ $regex = join('|',$phrases);
if ($regex === '') return $html;
if (!utf8_check($regex)) return $html;
View
13 inc/lang/en/mailwrap.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+ <title>@TITLE@</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+</head>
+<body>
+
+@HTMLBODY@
+
+<br /><hr />
+<small>This mail was generated by DokuWiki at @DOKUWIKIURL@.</small>
+</body>
+</html>
View
2  inc/lang/en/subscr_digest.txt
@@ -15,6 +15,6 @@ To cancel the page notifications, log into the wiki at
@SUBSCRIBE@
and unsubscribe page and/or namespace changes.
---
+--
This mail was generated by DokuWiki at
@DOKUWIKIURL@
View
2  inc/lang/en/subscr_list.txt
@@ -12,6 +12,6 @@ To cancel the page notifications, log into the wiki at
@SUBSCRIBE@
and unsubscribe page and/or namespace changes.
---
+--
This mail was generated by DokuWiki at
@DOKUWIKIURL@
View
2  inc/lang/en/subscr_single.txt
@@ -18,6 +18,6 @@ To cancel the page notifications, log into the wiki at
@NEWPAGE@
and unsubscribe page and/or namespace changes.
---
+--
This mail was generated by DokuWiki at
@DOKUWIKIURL@
View
3  inc/load.php
@@ -76,8 +76,9 @@ function load_autoload($name){
'SafeFN' => DOKU_INC.'inc/SafeFN.class.php',
'Sitemapper' => DOKU_INC.'inc/Sitemapper.php',
'PassHash' => DOKU_INC.'inc/PassHash.class.php',
+ 'Mailer' => DOKU_INC.'inc/Mailer.class.php',
'RemoteAPI' => DOKU_INC.'inc/remote.php',
- 'RemoteAPICore' => DOKU_INC.'inc/RemoteAPICore.php',
+ 'RemoteAPICore' => DOKU_INC.'inc/RemoteAPICore.php',
'DokuWiki_Action_Plugin' => DOKU_PLUGIN.'action.php',
'DokuWiki_Admin_Plugin' => DOKU_PLUGIN.'admin.php',
View
34 inc/media.php
@@ -516,6 +516,7 @@ function media_contentcheck($file,$mime){
* Send a notify mail on uploads
*
* @author Andreas Gohr <andi@splitbrain.org>
+ * @fixme this should embed thumbnails of images in HTML version
*/
function media_notify($id,$file,$mime,$old_rev=false){
global $lang;
@@ -523,31 +524,24 @@ function media_notify($id,$file,$mime,$old_rev=false){
global $INFO;
if(empty($conf['notify'])) return; //notify enabled?
- $ip = clientIP();
-
$text = rawLocale('uploadmail');
- $text = str_replace('@DATE@',dformat(),$text);
- $text = str_replace('@BROWSER@',$_SERVER['HTTP_USER_AGENT'],$text);
- $text = str_replace('@IPADDRESS@',$ip,$text);
- $text = str_replace('@HOSTNAME@',gethostsbyaddrs($ip),$text);
- $text = str_replace('@DOKUWIKIURL@',DOKU_URL,$text);
- $text = str_replace('@USER@',$_SERVER['REMOTE_USER'],$text);
- $text = str_replace('@MIME@',$mime,$text);
- $text = str_replace('@MEDIA@',ml($id,'',true,'&',true),$text);
- $text = str_replace('@SIZE@',filesize_h(filesize($file)),$text);
- if ($old_rev && $conf['mediarevisions']) {
- $text = str_replace('@OLD@', ml($id, "rev=$old_rev", true, '&', true), $text);
- } else {
- $text = str_replace('@OLD@', '', $text);
- }
+ $trep = array(
+ 'MIME' => $mime,
+ 'MEDIA' => ml($id,'',true,'&',true),
+ 'SIZE' => filesize_h(filesize($file)),
+ );
- if(empty($conf['mailprefix'])) {
- $subject = '['.$conf['title'].'] '.$lang['mail_upload'].' '.$id;
+ if ($old_rev && $conf['mediarevisions']) {
+ $trep['OLD'] = ml($id, "rev=$old_rev", true, '&', true);
} else {
- $subject = '['.$conf['mailprefix'].'] '.$lang['mail_upload'].' '.$id;
+ $trep['OLD'] = '---';
}
- mail_send($conf['notify'],$subject,$text,$conf['mailfrom']);
+ $mail = new Mailer();
+ $mail->to($conf['notify']);
+ $mail->subject($lang['mail_upload'].' '.$id);
+ $mail->setBody($text,$trep);
+ return $mail->send();
}
/**
View
12 inc/pageutils.php
@@ -355,19 +355,21 @@ function mediaFN($id, $rev=''){
}
/**
- * Returns the full filepath to a localized textfile if local
+ * Returns the full filepath to a localized file if local
* version isn't found the english one is returned
*
+ * @param string $id The id of the local file
+ * @param string $ext The file extension (usually txt)
* @author Andreas Gohr <andi@splitbrain.org>
*/
-function localeFN($id){
+function localeFN($id,$ext='txt'){
global $conf;
- $file = DOKU_CONF.'/lang/'.$conf['lang'].'/'.$id.'.txt';
+ $file = DOKU_CONF.'/lang/'.$conf['lang'].'/'.$id.'.'.$ext;
if(!@file_exists($file)){
- $file = DOKU_INC.'inc/lang/'.$conf['lang'].'/'.$id.'.txt';
+ $file = DOKU_INC.'inc/lang/'.$conf['lang'].'/'.$id.'.'.$ext;
if(!@file_exists($file)){
//fall back to english
- $file = DOKU_INC.'inc/lang/en/'.$id.'.txt';
+ $file = DOKU_INC.'inc/lang/en/'.$id.'.'.$ext;
}
}
return $file;
View
22 inc/subscription.php
@@ -377,18 +377,20 @@ function subscription_send_list($subscriber_mail, $ids, $ns_id) {
*/
function subscription_send($subscriber_mail, $replaces, $subject, $id, $template) {
global $conf;
+ global $lang;
$text = rawLocale($template);
- $replaces = array_merge($replaces, array('TITLE' => $conf['title'],
- 'DOKUWIKIURL' => DOKU_URL,
- 'PAGE' => $id));
-
- foreach ($replaces as $key => $substitution) {
- $text = str_replace('@'.strtoupper($key).'@', $substitution, $text);
- }
+ $trep = array_merge($replaces, array('PAGE' => $id));
- global $lang;
$subject = $lang['mail_' . $subject] . ' ' . $id;
- mail_send('', '['.$conf['title'].'] '. $subject, $text,
- $conf['mailfrom'], '', $subscriber_mail);
+ $mail = new Mailer();
+ $mail->bcc($subscriber_mail);
+ $mail->subject($subject);
+ $mail->setBody($text,$trep);
+ $mail->setHeader(
+ 'List-Unsubscribe',
+ '<'.wl($id,array('do'=>'subscribe'),true,'&').'>',
+ false
+ );
+ return $mail->send();
}
View
3  lib/exe/xmlrpc.php
@@ -30,10 +30,11 @@ function call($methodname, $args){
} catch (RemoteAccessDeniedException $e) {
if (!isset($_SERVER['REMOTE_USER'])) {
header('HTTP/1.1 401 Unauthorized');
+ return new IXR_Error(-32603, "server error. not authorized to call method $methodname");
} else {
header('HTTP/1.1 403 Forbidden');
+ return new IXR_Error(-32604, "server error. forbidden to call the method $methodname");
}
- return new IXR_Error(-32603, "server error. not authorized to call method $methodname");
} catch (RemoteException $e) {
return new IXR_Error($e->getCode(), $e->getMessage());
}
View
1  lib/plugins/config/lang/en/lang.php
@@ -147,6 +147,7 @@
$lang['registernotify'] = 'Always send info on newly registered users to this email address';
$lang['mailfrom'] = 'Sender email address to use for automatic mails';
$lang['mailprefix'] = 'Email subject prefix to use for automatic mails. Leave blank to use the wiki title';
+$lang['htmlmail'] = 'Send better looking, but larger in size HTML multipart emails. Disable for plain text only mails.';
/* Syndication Settings */
$lang['sitemap'] = 'Generate Google sitemap this often (in days). 0 to disable';
View
1  lib/plugins/config/settings/config.metadata.php
@@ -177,6 +177,7 @@
$meta['registernotify'] = array('email');
$meta['mailfrom'] = array('richemail');
$meta['mailprefix'] = array('string');
+$meta['htmlmail'] = array('onoff');
$meta['_syndication'] = array('fieldset');
$meta['sitemap'] = array('numeric');
Please sign in to comment.
Something went wrong with that request. Please try again.