SpamAssassin doesn't like HTML email without a) a Mime-Version header and b) an <html> tag in the body. Although not everyone uses SA, there are probably other spam systems that implement similar rules. And it's always nice to play nice.
		
			
				
	
	
		
			246 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			246 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * StatusNet - the distributed open-source microblogging tool
 | |
|  *
 | |
|  * Handler for queue items of type 'usersum', sends an email summaries
 | |
|  * to a particular user.
 | |
|  *
 | |
|  * This program is free software: you can redistribute it and/or modify
 | |
|  * it under the terms of the GNU Affero General Public License as published by
 | |
|  * the Free Software Foundation, either version 3 of the License, or
 | |
|  * (at your option) any later version.
 | |
|  *
 | |
|  * This program is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|  * GNU Affero General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU Affero General Public License
 | |
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | |
|  *
 | |
|  * @category  Sample
 | |
|  * @package   StatusNet
 | |
|  * @author    Evan Prodromou <evan@status.net>
 | |
|  * @copyright 2010 StatusNet, Inc.
 | |
|  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 | |
|  * @link      http://status.net/
 | |
|  */
 | |
| 
 | |
| if (!defined('STATUSNET')) {
 | |
|     exit(1);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Handler for queue items of type 'usersum', sends an email summaries
 | |
|  * to a particular user.
 | |
|  *
 | |
|  * @category  Email
 | |
|  * @package   StatusNet
 | |
|  * @author    Evan Prodromou <evan@status.net>
 | |
|  * @copyright 2010 StatusNet, Inc.
 | |
|  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 | |
|  * @link      http://status.net/
 | |
|  */
 | |
| class UserEmailSummaryHandler extends QueueHandler
 | |
| {
 | |
|     // Maximum number of notices to include by default. This is probably too much.
 | |
|     const MAX_NOTICES = 200;
 | |
| 
 | |
|     /**
 | |
|      * Return transport keyword which identifies items this queue handler
 | |
|      * services; must be defined for all subclasses.
 | |
|      *
 | |
|      * Must be 8 characters or less to fit in the queue_item database.
 | |
|      * ex "email", "jabber", "sms", "irc", ...
 | |
|      *
 | |
|      * @return string
 | |
|      */
 | |
|     function transport()
 | |
|     {
 | |
|         return 'usersum';
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Send a summary email to the user
 | |
|      *
 | |
|      * @param mixed $object
 | |
|      * @return boolean true on success, false on failure
 | |
|      */
 | |
|     function handle($user_id)
 | |
|     {
 | |
|         // Skip if they've asked not to get summaries
 | |
| 
 | |
|         $ess = Email_summary_status::staticGet('user_id', $user_id);
 | |
| 
 | |
|         if (!empty($ess) && !$ess->send_summary) {
 | |
|             common_log(LOG_INFO, sprintf('Not sending email summary for user %s by request.', $user_id));
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         $since_id = null;
 | |
| 
 | |
|         if (!empty($ess)) {
 | |
|             $since_id = $ess->last_summary_id;
 | |
|         }
 | |
| 
 | |
|         $user = User::staticGet('id', $user_id);
 | |
| 
 | |
|         if (empty($user)) {
 | |
|             common_log(LOG_INFO, sprintf('Not sending email summary for user %s; no such user.', $user_id));
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         if (empty($user->email)) {
 | |
|             common_log(LOG_INFO, sprintf('Not sending email summary for user %s; no email address.', $user_id));
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         $profile = $user->getProfile();
 | |
| 
 | |
|         if (empty($profile)) {
 | |
|             common_log(LOG_WARNING, sprintf('Not sending email summary for user %s; no profile.', $user_id));
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         $stream = new InboxNoticeStream($user, $user->getProfile());
 | |
| 
 | |
|         $notice = $stream->getNotices(0, self::MAX_NOTICES, $since_id);
 | |
| 
 | |
|         if (empty($notice) || $notice->N == 0) {
 | |
|             common_log(LOG_WARNING, sprintf('Not sending email summary for user %s; no notices.', $user_id));
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         // XXX: This is risky fingerpoken in der objektvars, but I didn't feel like
 | |
|         // figuring out a better way. -ESP
 | |
| 
 | |
|         $new_top = null;
 | |
| 
 | |
|         if ($notice instanceof ArrayWrapper) {
 | |
|             $new_top = $notice->_items[0]->id;
 | |
|         }
 | |
| 
 | |
|         // TRANS: Subject for e-mail.
 | |
|         $subject = sprintf(_m('Your latest updates from %s'), common_config('site', 'name'));
 | |
| 
 | |
|         $out = new XMLStringer(true);
 | |
| 
 | |
|         $out->elementStart('html');
 | |
|         $out->elementStart('head');
 | |
|         $out->element('title', null, $subject);
 | |
|         $out->elementEnd('head');
 | |
|         $out->elementStart('body');
 | |
|         $out->elementStart('div', array('width' => '100%',
 | |
|                                         'style' => 'background-color: #ffffff; border: 4px solid #4c609a; padding: 10px;'));
 | |
| 
 | |
|         $out->elementStart('div', array('style' => 'color: #ffffff; background-color: #4c609a; font-weight: bold; margin-bottom: 10px; padding: 4px;'));
 | |
|         // TRANS: Text in e-mail summary.
 | |
|         // TRANS: %1$s is the StatusNet sitename, %2$s is the recipient's profile name.
 | |
|         $out->raw(sprintf(_m('Recent updates from %1$s for %2$s:'),
 | |
|                           common_config('site', 'name'),
 | |
|                           $profile->getBestName()));
 | |
|         $out->elementEnd('div');
 | |
| 
 | |
|         $out->elementStart('table', array('width' => '550px',
 | |
|                                           'style' => 'border: none; border-collapse: collapse;', 'cellpadding' => '6'));
 | |
| 
 | |
|         while ($notice->fetch()) {
 | |
|             $profile = Profile::staticGet('id', $notice->profile_id);
 | |
| 
 | |
|             if (empty($profile)) {
 | |
|                 continue;
 | |
|             }
 | |
| 
 | |
|             $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
 | |
| 
 | |
|             $out->elementStart('tr');
 | |
|             $out->elementStart('td', array('width' => AVATAR_STREAM_SIZE,
 | |
|                                            'height' => AVATAR_STREAM_SIZE,
 | |
|                                            'align' => 'left',
 | |
|                                            'valign' => 'top',
 | |
|                                            'style' => 'border-bottom: 1px dotted #C5CEE3; padding: 10px 6px 10px 6px;'));
 | |
|             $out->element('img', array('src' => ($avatar) ?
 | |
|                                        $avatar->displayUrl() :
 | |
|                                        Avatar::defaultImage(AVATAR_STREAM_SIZE),
 | |
|                                        'width' => AVATAR_STREAM_SIZE,
 | |
|                                        'height' => AVATAR_STREAM_SIZE,
 | |
|                                        'alt' => $profile->getBestName()));
 | |
|             $out->elementEnd('td');
 | |
|             $out->elementStart('td', array('align' => 'left',
 | |
|                                            'valign' => 'top',
 | |
|                                            'style' => 'border-bottom: 1px dotted #C5CEE3; padding: 10px 6px 10px 6px;'));
 | |
|             $out->element('a', array('href' => $profile->profileurl),
 | |
|                           $profile->nickname);
 | |
|             $out->text(' ');
 | |
|             $out->raw($notice->rendered);
 | |
|             $out->elementStart('div', array('style' => 'font-size: 0.8em; padding-top: 4px;'));
 | |
|             $noticeurl = $notice->bestUrl();
 | |
|             // above should always return an URL
 | |
|             assert(!empty($noticeurl));
 | |
|             $out->elementStart('a', array('rel' => 'bookmark',
 | |
|                                           'href' => $noticeurl));
 | |
|             $dt = common_date_iso8601($notice->created);
 | |
|             $out->element('abbr', array('style' => 'border-bottom: none;',
 | |
|                                         'title' => $dt),
 | |
|                           common_date_string($notice->created));
 | |
|             $out->elementEnd('a');
 | |
|             if ($notice->hasConversation()) {
 | |
|                 $conv = Conversation::staticGet('id', $notice->conversation);
 | |
|                 $convurl = $conv->uri;
 | |
|                 if (!empty($convurl)) {
 | |
|                     $out->text(' ');
 | |
|                     $out->element('a',
 | |
|                                   array('href' => $convurl.'#notice-'.$notice->id),
 | |
|                                   // TRANS: Link text for link to conversation view.
 | |
|                                   _m('in context'));
 | |
|                 }
 | |
|             }
 | |
|             $out->elementEnd('div');
 | |
|             $out->elementEnd('td');
 | |
|             $out->elementEnd('tr');
 | |
|         }
 | |
| 
 | |
|         $out->elementEnd('table');
 | |
| 
 | |
|         // TRANS: Link text for link to e-mail settings.
 | |
|         // TRANS: %1$s is a link to the e-mail settings, %2$s is the StatusNet sitename.
 | |
|         $out->raw("<p>" . sprintf(_m('<a href="%1$s">change your email settings for %2$s</a>'),
 | |
|                           common_local_url('emailsettings'),
 | |
|                           common_config('site', 'name'))."</p>");
 | |
| 
 | |
|         $out->elementEnd('div');
 | |
|         $out->elementEnd('body');
 | |
|         $out->elementEnd('html');
 | |
| 
 | |
|         $body = $out->getString();
 | |
| 
 | |
|         // FIXME: do something for people who don't like HTML email
 | |
| 
 | |
|         mail_to_user($user,
 | |
|                      $subject,
 | |
|                      $body,
 | |
|                      array('Content-Type' => 'text/html; charset=utf-8',
 | |
|                            'Mime-Version' => '1.0'));
 | |
| 
 | |
|         if (empty($ess)) {
 | |
|             $ess = new Email_summary_status();
 | |
| 
 | |
|             $ess->user_id         = $user_id;
 | |
|             $ess->created         = common_sql_now();
 | |
|             $ess->last_summary_id = $new_top;
 | |
|             $ess->modified        = common_sql_now();
 | |
| 
 | |
|             $ess->insert();
 | |
|         } else {
 | |
|             $orig = clone($ess);
 | |
| 
 | |
|             $ess->last_summary_id = $new_top;
 | |
|             $ess->modified        = common_sql_now();
 | |
| 
 | |
|             $ess->update($orig);
 | |
|         }
 | |
| 
 | |
|         return true;
 | |
|     }
 | |
| }
 |