Wikia code/includes/UserMailer.php

--- D:\Programming\SVN\mediawiki\branches\REL1_16\phase3\includes\UserMailer.php	2011-07-18 22:31:28.093750000 +0100
+++ D:\Programming\SVN\wikia\trunk\includes\UserMailer.php	2011-08-17 15:28:46.457031200 +0100
@@ -37,6 +37,13 @@
 			$this->address = $address->getEmail();
 			$this->name = $address->getName();
 			$this->realName = $address->getRealName();
+		/* Wikia change begin - @author: wladek */
+		/* #57817: Email "from" field description change */
+		} else if( is_object( $address ) && $address instanceof MailAddress ) {
+			$this->address = strval( $address->address );
+			$this->name = $name ? $name : strval( $address->name );
+			$this->realName = $realName ? $realName : strval( $address->realName );
+		/* Wikia change end */
 		} else {
 			$this->address = strval( $address );
 			$this->name = strval( $name );
@@ -103,11 +110,13 @@
 	 * @param $body String: email's text.
 	 * @param $replyto MailAddress: optional reply-to email (default: null).
 	 * @param $contentType String: optional custom Content-Type
+	 * @param $category String: optional category for statistic [added by Wikia]
+	 * @param $priority int: optional priority for email (added by wikia)
 	 * @return mixed True on success, a WikiError object on failure.
 	 */
-	static function send( $to, $from, $subject, $body, $replyto=null, $contentType=null ) {
+	static function send( $to, $from, $subject, $body, $replyto=null, $contentType=null, $category='unknown', $priority = 0 ) {
 		global $wgSMTP, $wgOutputEncoding, $wgErrorString, $wgEnotifImpersonal;
-		global $wgEnotifMaxRecips;
+		global $wgEnotifMaxRecips, $wgForceSendgridEmail, $wgForceSchwartzEmail;
 
 		if ( is_array( $to ) ) {
 			wfDebug( __METHOD__.': sending mail to ' . implode( ',', $to ) . "\n" );
@@ -115,7 +124,35 @@
 			wfDebug( __METHOD__.': sending mail to ' . implode( ',', array( $to->toString() ) ) . "\n" );
 		}
 
-		if (is_array( $wgSMTP )) {
+		/* Wikia change begin - @author: Marooned */
+		/* Send a message to the MQ */
+		global $wgReportMailViaStomp;
+		if(!empty($wgReportMailViaStomp)) {
+			wfProfileIn(__METHOD__ . '-stomp');
+			global $wgStompServer, $wgStompUser, $wgStompPassword, $wgCityId;
+			try {
+				$stomp = new Stomp($wgStompServer);
+				$stomp->connect($wgStompUser, $wgStompPassword);
+				$stomp->sync = false;
+				$key = 'wikia.email.' . $category;
+				$count = is_array($to) ? count($to) : count(explode("\n", $to));
+				$stomp->send($key,
+					Wikia::json_encode(array(
+						'category' => $category,
+						'wikiID' => $wgCityId,
+						'count' => $count
+					)),
+					array('exchange' => 'amq.topic', 'bytes_message' => 1)
+				);
+			}
+			catch( StompException $e ) {
+				Wikia::log( __METHOD__, 'stomp_exception', $e->getMessage() );
+			}
+			wfProfileOut(__METHOD__ . '-stomp');
+		}
+		/* Wikia change end */
+
+		if (is_array( $wgSMTP ) || $wgForceSendgridEmail || $wgForceSchwartzEmail) {
 			require_once( 'Mail.php' );
 
 			$msgid = str_replace(" ", "_", microtime());
@@ -138,7 +175,7 @@
 				$headers['To'] = implode( ", ", (array )$dest );
 			}
 
-			if ( $replyto ) {
+			if ( $replyto instanceof MailAddress /* Wikia change (BugId:6519) */ ) {
 				$headers['Reply-To'] = $replyto->toString();
 			}
 			$headers['Subject'] = wfQuotedPrintable( $subject );
@@ -150,14 +187,39 @@
 			$headers['Message-ID'] = "<$msgid@" . $wgSMTP['IDHost'] . '>'; // FIXME
 			$headers['X-Mailer'] = 'MediaWiki mailer';
 
+			/* Wikia change begin - @author: Marooned */
+			/* Add category to header to allow easier data gathering */
+			$headers['X-Msg-Category'] = $category;
+			// Priority for internal wikia mail queue
+			if ($priority) {
+				$headers['X-Priority'] = $priority;
+			}
+			// Add a header for the server-name (helps us route where SendGrid will send bounces).
+			if(!empty($_SERVER) && isset( $_SERVER['SERVER_NAME'] ) ) {
+				$headers["X-ServerName"] = $_SERVER['SERVER_NAME'];
+			}
+			/* Wikia change end */
+
+			/* Wikia change begin - @author: Sean Colombo */
 			// Create the mail object using the Mail::factory method
+			// Wikia: for now, if the email is to us, use the new system.
+
+			if ( $wgForceSendgridEmail ) {
+				$mail_object =& Mail::factory('wikiadb');
+			} else if ( $wgForceSchwartzEmail ) {
+				$mail_object =& Mail::factory('theschwartzhttp');
+			} else {
 			$mail_object =& Mail::factory('smtp', $wgSMTP);
+			}
+			/* Wikia change end */
 			if( PEAR::isError( $mail_object ) ) {
 				wfDebug( "PEAR::Mail factory failed: " . $mail_object->getMessage() . "\n" );
 				return new WikiError( $mail_object->getMessage() );
 			}
 
 			wfDebug( "Sending mail via PEAR::Mail to $dest\n" );
+			/* Wikia change here */
+			if ( $wgEnotifImpersonal ) {
 			$chunks = array_chunk( (array)$dest, $wgEnotifMaxRecips );
 			foreach ($chunks as $chunk) {
 				$e = self::sendWithPear($mail_object, $chunk, $headers, $body);
@@ -165,6 +227,18 @@
 					return $e;
 			}
 		} else	{
+				if ( !is_array( $to ) ) {
+					$to = array( $to );
+				}
+				# array of MailAddress objects
+				$chunks = array_chunk( (array)$to, $wgEnotifMaxRecips );
+				foreach ($chunks as $chunk) {
+					$e = self::sendMail( $mail_object, $chunk, $headers, $headers['Subject'], $body );
+					if( WikiError::isError( $e ) )
+						return $e;
+				}
+			}
+		} else	{
 			# In the following $headers = expression we removed "Reply-To: {$from}\r\n" , because it is treated differently
 			# (fifth parameter of the PHP mail function, see some lines below)
 
@@ -184,10 +258,15 @@
 				"Content-Transfer-Encoding: 8bit$endl" .
 				"X-Mailer: MediaWiki mailer$endl".
 				'From: ' . $from->toString();
-			if ($replyto) {
+			if ( $replyto instanceof MailAddress /* Wikia change (BugId:6519) */ ) {
 				$headers .= "{$endl}Reply-To: " . $replyto->toString();
 			}
 
+			/* Wikia change begin - @author: Marooned */
+			/* Add category to header to allow easier data gathering */
+			$headers .= "{$endl}X-Msg-Category: $category";
+			/* Wikia change end */
+
 			wfDebug( "Sending mail via internal mail() function\n" );
 			
 			$wgErrorString = '';
@@ -198,10 +277,10 @@
 			if (function_exists('mail')) {
 				if (is_array($to)) {
 					foreach ($to as $recip) {
-						$sent = mail( $recip->toString(), wfQuotedPrintable( $subject ), $body, $headers );
+						$sent = self::sendMail( 'mail', $recip, $headers, $subject, $body );
 					}
 				} else {
-					$sent = mail( $to->toString(), wfQuotedPrintable( $subject ), $body, $headers );
+					$sent = self::sendMail( 'mail', $to, $headers, $subject, $body );
 				}
 			} else {
 				$wgErrorString = 'PHP is not configured to send mail';
@@ -224,6 +303,127 @@
 	}
 
 	/**
+	 * This function will perform a direct (authenticated) login to
+	 * a SMTP Server to use for mail relaying if 'wgSMTP' specifies an
+	 * array of parameters. If wgSchwartzMailer is defined it will supersede wgSMTP.
+	 * It sends e-mails in rich format using HTML syntax.
+	 *
+	 * @param $to MailAddress: recipient's email
+	 * @param $from MailAddress: sender's email
+	 * @param $subject String: email's subject.
+	 * @param $body String: email's text (plain).
+	 * @param $bodyHTML String: email's text (rich).
+	 * @param $replyto MailAddress: optional reply-to email (default: null).
+	 * @param $category String: optional category for statistic [added by Wikia]
+	 * @return mixed True on success, a WikiError object on failure.
+	 */
+	static function sendHTML( $to, $from, $subject, $body, $bodyHTML, $replyto=null, $category='unknown', $priority = 0 ) {
+		global $wgSMTP, $wgForceSendgridEmail, $wgForceSchwartzEmail;
+
+		//unlike mail(), this function takes only one recipient at the time
+		if (is_array($to)) {
+			return new WikiError('Parameter $to can not be an array.');
+		}
+
+		if (!is_array($wgSMTP) && !$wgForceSendgridEmail && !$wgForceSchwartzEmail) {
+			return new WikiError('Sending HTML via PHP mail() is not supported.');
+		}
+
+		/* Send a message to the MQ */
+		global $wgReportMailViaStomp;
+		if(!empty($wgReportMailViaStomp)) {
+			wfProfileIn(__METHOD__ . '-stomp');
+			global $wgStompServer, $wgStompUser, $wgStompPassword, $wgCityId;
+			try {
+				$stomp = new Stomp($wgStompServer);
+				$stomp->connect($wgStompUser, $wgStompPassword);
+				$stomp->sync = false;
+				$key = 'wikia.email.' . $category;
+				$count = count(explode("\n", $to));
+				$stomp->send($key,
+					Wikia::json_encode(array(
+						'category' => $category,
+						'wikiID' => $wgCityId,
+						'count' => $count
+					)),
+					array('exchange' => 'amq.topic', 'bytes_message' => 1)
+				);
+			}
+			catch( StompException $e ) {
+				Wikia::log( __METHOD__, 'stomp_exception', $e->getMessage() );
+			}
+			wfProfileOut(__METHOD__ . '-stomp');
+		}
+
+		require_once( 'Mail.php' );
+		require_once( 'Mail/mime.php' );
+
+		$msgid = str_replace(" ", "_", microtime());
+		if (function_exists('posix_getpid'))
+			$msgid .= '.' . posix_getpid();
+
+		$headers = array(
+			'From' => $from->toString(),
+			'To' => $to->address,
+			'Subject' => wfQuotedPrintable( $subject ),
+			'Date' => date( 'r' ),
+			'Message-ID' => "<$msgid@" . $wgSMTP['IDHost'] . '>', // FIXME
+			'X-Mailer' => 'MediaWiki mailer'
+		);
+		if ( $replyto instanceof MailAddress /* Wikia change (BugId:6519) */ ) {
+			$headers['Reply-To'] = $replyto->toString();
+		}
+
+		/* Add category to header to allow easier data gathering */
+		$headers['X-Msg-Category'] = $category;
+		// Priority for internal wikia mail queue
+		if ($priority) {
+			$headers['X-Priority'] = $priority;
+		}
+
+		// Add a header for the server-name (helps us route where SendGrid will send bounces).
+ 		if(isset($_SERVER['SERVER_NAME'])) {
+			$headers["X-ServerName"] = $_SERVER['SERVER_NAME'];
+		}
+
+		$mime = new Mail_mime();
+
+		$mime->setTXTBody($body);
+		$mime->setHTMLBody($bodyHTML);
+		//$file = './image.png';
+		//$mime->addAttachment($file, 'image/png');
+
+		//do not ever try to call these lines in reverse order
+		$body = $mime->get(array('text_encoding' => '8bit', 'html_charset' => 'UTF-8', 'text_charset' => 'UTF-8'));
+		$headers = $mime->headers($headers);
+
+		/* Wikia change begin - @author: Sean Colombo */
+		// Create the mail object using the Mail::factory method
+		// Wikia: for now, if the email is to us, use the new system.
+
+		if ( $wgForceSendgridEmail ){
+			$mail_object =& Mail::factory('wikiadb');
+		} else if ( $wgForceSchwartzEmail ) {
+			$mail_object =& Mail::factory('theschwartzhttp');
+		} else {
+			$mail_object =& Mail::factory('smtp', $wgSMTP);
+		}
+
+		if( PEAR::isError( $mail_object ) ) {
+			wfDebug( "PEAR::Mail factory failed: " . $mail_object->getMessage() . "\n" );
+			return new WikiError( $mail_object->getMessage() );
+		}
+
+		wfDebug( "Sending mail via PEAR::Mail to {$to->address}\n" );
+		$e = self::sendMail( $mail_object, array($to), $headers, $subject, $body );
+		if( WikiError::isError( $e ) ) {
+			return $e;
+		}
+
+		return true;
+	}
+
+	/**
 	 * Get the mail error message in global $wgErrorString
 	 *
 	 * @param $code Integer: error number
@@ -241,6 +441,49 @@
 		$phrase = strtr( $phrase, array( "\r" => '', "\n" => '', '"' => '' ) );
 		return '"' . $phrase . '"';
 	}
+
+	/**
+	 * Wikia changes - @author: moli
+	 * Send email to user
+	 *
+	 * @param $mail String: mail, Mail ...
+	 * @param $emails Array of MailAddress objects
+	 * @param $headers Array: email's headers
+	 * @param $subject String
+	 * @param $body String
+	 */
+	 static private function sendMail( $mail, $emails, $headers, $subject, $body ) {
+		$res = true;
+
+		if ( empty($emails) ) {
+			return $res;
+		}
+
+		if ( !is_array( $emails ) ) {
+			$emails = array ( $emails );
+		}
+
+		foreach ( $emails as $to ) {
+			if ( $to instanceof MailAddress ) {
+
+				wfRunHooks('ComposeMail', array( $to, &$body, &$subject ));
+
+				if ( $mail == 'mail' ) {
+					$res = mail( $to->toString(), wfQuotedPrintable( $subject ), $body, $headers );
+				} elseif ( is_object( $mail ) ) {
+					$res = self::sendWithPear( $mail, array($to->address), $headers, $body );
+					if ( WikiError::isError( $res ) ) {
+						return $res;
+					}
+				} else {
+					wfDebug( "Invalid use of " . __METHOD__ . ": mail = $mail \n" );
+					$res = false;
+				}
+			}
+		}
+
+		return $res;
+	}
 }
 
 /**
@@ -264,7 +507,8 @@
  *
  */
 class EmailNotification {
-	protected $to, $subject, $body, $replyto, $from;
+	protected $action;
+	protected $to, $subject, $body, $replyto, $from, $bodyHTML;
 	protected $user, $title, $timestamp, $summary, $minorEdit, $oldid, $composed_common, $editor;
 	protected $mailTargets = array();
 
@@ -281,15 +525,33 @@
 	 * @param $minorEdit
 	 * @param $oldid (default: false)
 	 */
-	function notifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid = false) {
+	function notifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid = false, $action = '', $otherParam = array()) {
 		global $wgEnotifUseJobQ, $wgEnotifWatchlist, $wgShowUpdatedMarker;
 
 		if ($title->getNamespace() < 0)
 			return;
 
+		if( !wfRunHooks( 'AllowNotifyOnPageChange', array( $editor, $title ) ) ) {
+			return false;
+		}
+
 		// Build a list of users to notfiy
 		$watchers = array();
 		if ($wgEnotifWatchlist || $wgShowUpdatedMarker) {
+
+			global $wgEnableWatchlistNotificationTimeout, $wgWatchlistNotificationTimeout;
+			/* Wikia change begin - @author: wladek & tomek */
+			/* RT#55604: Add a timeout to the watchlist email block */
+			if ( !empty( $otherParam['notisnull'] ) ) {
+				$notificationTimeoutSql = "1";
+			} elseif ( !empty($wgEnableWatchlistNotificationTimeout) && isset($wgWatchlistNotificationTimeout) ) {
+				$blockTimeout = wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $timestamp ) - intval( $wgWatchlistNotificationTimeout ) );
+				$notificationTimeoutSql = "wl_notificationtimestamp IS NULL OR wl_notificationtimestamp < '$blockTimeout'";
+			} else {
+				$notificationTimeoutSql = 'wl_notificationtimestamp IS NULL';
+			}
+			/* Wikia change end */
+
 			$dbw = wfGetDB( DB_MASTER );
 			$res = $dbw->select( array( 'watchlist' ),
 				array( 'wl_user' ),
@@ -297,7 +559,7 @@
 					'wl_title' => $title->getDBkey(),
 					'wl_namespace' => $title->getNamespace(),
 					'wl_user != ' . intval( $editor->getID() ),
-					'wl_notificationtimestamp IS NULL',
+					$notificationTimeoutSql,
 				), __METHOD__
 			);
 			while ($row = $dbw->fetchObject( $res ) ) {
@@ -306,18 +568,14 @@
 			if ($watchers) {
 				// Update wl_notificationtimestamp for all watching users except
 				// the editor
-				$dbw->begin();
-				$dbw->update( 'watchlist',
-					array( /* SET */
-						'wl_notificationtimestamp' => $dbw->timestamp( $timestamp )
-					), array( /* WHERE */
-						'wl_title' => $title->getDBkey(),
-						'wl_namespace' => $title->getNamespace(),
-						'wl_user' => $watchers
-					), __METHOD__
-				);
-				$dbw->commit();
+				$wl = WatchedItem::fromUserTitle( $editor, $title );
+				$wl->updateWatch( $watchers, $timestamp );
+
 			}
+
+			/* Wikia change begin - @author: Jakub Kurcek */
+			wfRunHooks( 'NotifyOnSubPageChange', array ( $watchers, $title, $editor, $notificationTimeoutSql ) );
+			/* Wikia change end */
 		}
 
 		if ($wgEnotifUseJobQ) {
@@ -328,11 +586,14 @@
 				"summary" => $summary,
 				"minorEdit" => $minorEdit,
 				"oldid" => $oldid,
-				"watchers" => $watchers);
+				"watchers" => $watchers,
+				"action" => $action,
+				"othersParam" => $othersParam
+				);
 			$job = new EnotifNotifyJob( $title, $params );
 			$job->insert();
 		} else {
-			$this->actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid, $watchers );
+			$this->actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid, $watchers, $action, $otherParam);
 		}
 
 	}
@@ -351,7 +612,7 @@
 	 * @param $oldid int Revision ID
 	 * @param $watchers array of user IDs
 	 */
-	function actuallyNotifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid, $watchers) {
+	function actuallyNotifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid, $watchers, $action='', $otherParam = array()) {
 		# we use $wgPasswordSender as sender's address
 		global $wgEnotifWatchlist;
 		global $wgEnotifMinorEdits, $wgEnotifUserTalk;
@@ -372,8 +633,10 @@
 		$this->summary = $summary;
 		$this->minorEdit = $minorEdit;
 		$this->oldid = $oldid;
+		$this->action = $action;
 		$this->editor = $editor;
 		$this->composed_common = false;
+		$this->other_param = $otherParam;
 
 		$userTalkId = false;
 
@@ -395,6 +658,16 @@
 				} else {
 					wfDebug( __METHOD__.": talk page owner doesn't want notifications\n" );
 				}
+
+				/* Wikia change begin - @author: Marooned */
+				/* Send mail to user when comment on his user talk has been added - see RT#44830 */
+				$fakeUser = null;
+				wfRunHooks('UserMailer::NotifyUser', array( $title, &$fakeUser ));
+				if ( $fakeUser instanceof User && $fakeUser->getOption( 'enotifusertalkpages' ) && $fakeUser->isEmailConfirmed() ) {
+					wfDebug( __METHOD__.": sending talk page update notification\n" );
+					$this->compose( $fakeUser );
+				}
+				/* Wikia change end */
 			}
 
 			if ( $wgEnotifWatchlist ) {
@@ -432,26 +705,45 @@
 
 		$this->composed_common = true;
 
-		$summary = ($this->summary == '') ? ' - ' : $this->summary;
-		$medit   = ($this->minorEdit) ? wfMsg( 'minoredit' ) : '';
+		// RT #1294 Bartek 07.05.2009, use the language of the wiki
+		$summary = ($this->summary == '') ? wfMsgForContent( 'enotif_no_summary' ) : wfMsgForContent( 'enotif_summary'  ) . '"' . $this->summary . '"';
+		$medit   = ($this->minorEdit) ? wfMsgForContent( 'minoredit' ) : '';
 
 		# You as the WikiAdmin and Sysops can make use of plenty of
 		# named variables when composing your notification emails while
 		# simply editing the Meta pages
 
+		$action = strtolower($this->action);
+		$subject = wfMsgForContent( 'enotif_subject_' . $action );
+		if(wfEmptyMsg( 'enotif_subject_' . $action, $subject )) {
 		$subject = wfMsgForContent( 'enotif_subject' );
-		$body    = wfMsgForContent( 'enotif_body' );
+		}
+
+		$msgKey = 'enotif_body' . ($action == '' ? '' : ('_' . $action));
+
+		global $wgLanguageCode;
+		list($body, $bodyHTML) = wfMsgHTMLwithLanguageAndAlternative($msgKey, 'enotif_body', $wgLanguageCode);
+
 		$from    = ''; /* fail safe */
 		$replyto = ''; /* fail safe */
 		$keys    = array();
 
 		if( $this->oldid ) {
-			$difflink = $this->title->getFullUrl( 'diff=0&oldid=' . $this->oldid );
-			$keys['$NEWPAGE'] = wfMsgForContent( 'enotif_lastvisited', $difflink );
+			/* WIKIA change, watchlist link tracking, rt#33913 */
+			$difflink = $this->title->getFullUrl( 's=wldiff&diff=0&oldid=' . $this->oldid );
+			list( $keys['$NEWPAGE'], $keys['$NEWPAGEHTML'] ) = wfMsgHTMLwithLanguageAndAlternative( 'enotif_lastvisited', 'enotif_lastvisited', $wgLanguageCode, array(), $difflink );
 			$keys['$OLDID']   = $this->oldid;
 			$keys['$CHANGEDORCREATED'] = wfMsgForContent( 'changed' );
 		} else {
+			if( $action == '') {
+				//no oldid + empty action = create edit, ok to use newpagetext
 			$keys['$NEWPAGE'] = wfMsgForContent( 'enotif_newpagetext' );
+				$keys['$NEWPAGEHTML'] = $keys['$NEWPAGE']; //cheat and dupe this for now
+			} else {
+				//no oldid + action = event, dont show anything, confuses users
+				$keys['$NEWPAGE'] = '';
+				$keys['$NEWPAGEHTML'] = '';
+			}
 			# clear $OLDID placeholder in the message template
 			$keys['$OLDID']   = '';
 			$keys['$CHANGEDORCREATED'] = wfMsgForContent( 'created' );
@@ -466,14 +758,22 @@
 					$this->title->getFullURL("oldid={$this->oldid}&diff=prev"));
 
 		$body = strtr( $body, $keys );
+		if ($bodyHTML) {
+			$bodyHTML = strtr( $bodyHTML, $keys );
+		}
 		$pagetitle = $this->title->getPrefixedText();
 		$keys['$PAGETITLE']          = $pagetitle;
-		$keys['$PAGETITLE_URL']      = $this->title->getFullUrl();
+		$keys['$PAGETITLE_URL']      = $this->title->getFullUrl('s=wl'); // watchlist tracking, rt#33913
 
 		$keys['$PAGEMINOREDIT']      = $medit;
 		$keys['$PAGESUMMARY']        = $summary;
 		$keys['$UNWATCHURL']         = $this->title->getFullUrl( 'action=unwatch' );
 
+		$keys['$ACTION']             = $this->action;
+
+		wfRunHooks('MailNotifyBuildKeys', array( &$keys, $this->action, $this->other_param ));
+
+		wfRunHooks('ComposeCommonSubjectMail', array( $this->title, &$keys, &$subject, $this->editor ));
 		$subject = strtr( $subject, $keys );
 
 		# Reveal the page editor's address as REPLY-TO address only if
@@ -481,7 +781,7 @@
 		# global configuration level.
 		$editor = $this->editor;
 		$name    = $wgEnotifUseRealName ? $editor->getRealName() : $editor->getName();
-		$adminAddress = new MailAddress( $wgPasswordSender, 'WikiAdmin' );
+		$adminAddress = new MailAddress( $wgPasswordSender, 'Wikia' );
 		$editorAddress = new MailAddress( $editor );
 		if( $wgEnotifRevealEditorAddress
 		    && ( $editor->getEmail() != '' )
@@ -507,11 +807,16 @@
 			$subject = str_replace('$PAGEEDITOR', $name, $subject);
 			$keys['$PAGEEDITOR']          = $name;
 			$emailPage = SpecialPage::getSafeTitleFor( 'Emailuser', $name );
-			$keys['$PAGEEDITOR_EMAIL'] = $emailPage->getFullUrl();
+			$keys['$PAGEEDITOR_EMAIL'] = $emailPage->escapeFullURL();
 		}
 		$userPage = $editor->getUserPage();
-		$keys['$PAGEEDITOR_WIKI'] = $userPage->getFullUrl();
+		$keys['$PAGEEDITOR_WIKI'] = $userPage->escapeFullURL();
+		wfRunHooks('ComposeCommonBodyMail', array( $this->title, &$keys, &$body, $editor ));
 		$body = strtr( $body, $keys );
+
+		if ($bodyHTML) {
+			$bodyHTML = strtr( $bodyHTML, $keys );
+		}
 		$body = wordwrap( $body, 72 );
 
 		# now save this as the constant user-independent part of the message
@@ -519,6 +824,7 @@
 		$this->replyto = $replyto;
 		$this->subject = $subject;
 		$this->body    = $body;
+		$this->bodyHTML= $bodyHTML;
 	}
 
 	/**
@@ -538,6 +844,7 @@
 		} else {
 			$this->sendPersonalised( $user );
 		}
+		wfRunHooks('NotifyOnPageChangeComplete', array( $this->title, $this->timestamp, &$user ));
 	}
 
 	/**
@@ -565,6 +872,8 @@
 		// From the PHP manual:
 		//     Note:  The to parameter cannot be an address in the form of "Something <someone@example.com>".
 		//     The mail command will not parse this properly while talking with the MTA.
+
+		$richMail = $watchingUser->getOption('htmlemails') && !empty($this->bodyHTML);
 		$to = new MailAddress( $watchingUser );
 		$name = $wgEnotifUseRealName ? $watchingUser->getRealName() : $watchingUser->getName();
 		$body = str_replace( '$WATCHINGUSERNAME', $name , $this->body );
@@ -583,7 +892,17 @@
 				$wgContLang->time( $this->timestamp, true, false, $timecorrection ) ),
 			$body);
 
-		return UserMailer::send($to, $this->from, $this->subject, $body, $this->replyto);
+		// RT #1294 Bartek 07.05.2009, use the language object with the wiki code
+		$timedate = $wgContLang->timeanddate( $this->timestamp, true, false, $timecorrection );
+		$body = str_replace('$PAGEEDITDATE', $timedate, $body);
+
+		if ($richMail) {
+			$bodyHTML = str_replace('$WATCHINGUSERNAME', $name , $this->bodyHTML);
+			$bodyHTML = str_replace('$PAGEEDITDATE', $timedate, $bodyHTML);
+			return UserMailer::sendHTML($to, $this->from, $this->subject, $body, $bodyHTML, $this->replyto, 'UserMailer');
+		} else {
+			return UserMailer::send($to, $this->from, $this->subject, $body, $this->replyto, null, 'UserMailer');
+		}
 	}
 
 	/**
@@ -603,7 +922,7 @@
 					$wgContLang->timeanddate($this->timestamp, true, false, false)),
 				$this->body);
 
-		return UserMailer::send($addresses, $this->from, $this->subject, $body, $this->replyto);
+		return UserMailer::send($addresses, $this->from, $this->subject, $body, $this->replyto, null, 'UserMailer');
 	}
 
 } # end of class EmailNotification
@@ -615,6 +934,6 @@
 	return UserMailer::rfc822Phrase( $s );
 }
 
-function userMailer( $to, $from, $subject, $body, $replyto=null ) {
-	return UserMailer::send( $to, $from, $subject, $body, $replyto );
+function userMailer( $to, $from, $subject, $body, $replyto=null, $category='unknown' ) {
+	return UserMailer::send( $to, $from, $subject, $body, $replyto, null, $category );
 }