3 Mailblogger plugin for Elgg
4 Initial author: Sven Edge <sven@elgg.net> September 2006
6 Plugin to read messages from an IMAP mailbox and parse and post them to users' blogs.
10 $CFG->mailblogger_imapusername - required
11 $CFG->mailblogger_imappassword - required
12 $CFG->mailblogger_toaddress - required, defines the address messages are sent to
14 $CFG->mailblogger_hostname - optional, defaults to "localhost"
15 $CFG->mailblogger_imapport - optional, defaults to 143 (SSL is 993)
16 $CFG->mailblogger_imapmailbox - optional, defaults to "INBOX"
18 $CFG->mailblogger_imapflags - optional, see http://php.net/manual/en/function.imap-open.php
19 e.g. "/ssl" or "/ssl/novalidate-cert"
21 NB: SSL unlikely to work on Windows
23 $CFG->mailblogger_disablecron - optional, defaults to false;
24 $CFG->mailblogger_imaprejectedmailbox - optional, if not set, rejected messages are deleted. can not be the same as $CFG->mailblogger_imapmailbox
25 $CFG->mailblogger_imapposteddmailbox - optional, if not set, posted messages are deleted. can not be the same as $CFG->mailblogger_imapmailbox
26 $CFG->mailblogger_maildomain - Used in To address detection and in user blurb - NOT CURRENTLY USED
29 /*========================================================================*/
32 function mailblogger_pagesetup() {
34 } // end function mailblogger_pagesetup
37 /*========================================================================*/
39 function mailblogger_init() {
44 if (!empty($CFG->mailblogger_maildomain
)) {
45 $CFG->mailblogger_maildomain
= trim($CFG->mailblogger_maildomain
);
47 $CFG->mailblogger_maildomain
= '';
50 if (!empty($CFG->mailblogger_toaddress
)) {
51 $CFG->mailblogger_toaddress
= trim($CFG->mailblogger_toaddress
);
53 $CFG->mailblogger_toaddress
= '';
57 $function['userdetails:init'][] = path
. "mod/mailblogger/mailblogger_userdetails_actions.php";
58 // User details editable options
59 $function['userdetails:edit:details'][] = path
. "mod/mailblogger/mailblogger_userdetails_edit_details.php";
61 } // end function mailblogger_init
64 /*========================================================================*/
67 function mailblogger_cron() {
71 if (empty($CFG->mailblogger_disablecron
)) {
72 if (!empty($CFG->mailblogger_lastcron
) && (time() - 300) < $CFG->mailblogger_lastcron
) {
75 mailblogger_processnewmessages();
78 set_config('mailblogger_lastcron',time());
81 } // end function mailblogger_cron
83 /*========================================================================*/
86 function mailblogger_openmailbox() {
91 if (!empty($CFG->mailblogger_imapusername
) && !empty($CFG->mailblogger_imappassword
)) {
93 if (empty($CFG->mailblogger_hostname
)) {
94 $CFG->mailblogger_hostname
= 'localhost';
96 $CFG->mailblogger_hostname
= trim($CFG->mailblogger_hostname
);
99 if (!isset($CFG->mailblogger_imapport
)) {
100 $CFG->mailblogger_imapport
= 143;
102 $CFG->mailblogger_imapport
= (int) $CFG->mailblogger_imapport
;
103 if (!$CFG->mailblogger_imapport
) {
104 $CFG->mailblogger_imapport
= 143;
108 if (empty($CFG->mailblogger_imapmailbox
)) {
109 $CFG->mailblogger_imapmailbox
= 'INBOX';
111 $CFG->mailblogger_imapmailbox
= trim($CFG->mailblogger_imapmailbox
);
114 if (empty($CFG->mailblogger_imapflags
)) {
115 $CFG->mailblogger_imapflags
= '';
117 $CFG->mailblogger_imapflags
= trim($CFG->mailblogger_imapflags
);
120 $mailbox = '{' . $CFG->mailblogger_hostname
. ':' . $CFG->mailblogger_imapport
. $CFG->mailblogger_imapflags
. '}' . $CFG->mailblogger_imapmailbox
;
122 if ($resource = imap_open($mailbox, $CFG->mailblogger_imapusername
, $CFG->mailblogger_imappassword
)) {
123 $returnvalue = $resource;
125 mtrace("mailblogger error: could not open IMAP mailbox.");
126 $errs = imap_errors();
127 if (is_array($errs)) {
128 mtrace(implode("\n", $errs));
133 mtrace("mailblogger error: IMAP username or password not specified.");
139 /*========================================================================*/
141 function mailblogger_closemailbox($imapresource) {
142 if (is_resource($imapresource)) {
143 imap_expunge($imapresource);
144 imap_close($imapresource);
148 /*========================================================================*/
152 // search imap mailbox
153 // returns array of message UIDs on success, false on failure
154 function mailblogger_findmessages($imapresource, $searchfield, $searchvalue) {
156 $searchfield = trim($searchfield);
157 $returnvalue = array();
159 if (is_resource($imapresource) && $searchfield) {
160 switch ($searchfield) {
162 // searchvalue is username/email alias/etc
163 $searchvalue = trim($searchvalue);
165 $searchresult = imap_search ($imapresource, 'UNDELETED TO "' . $searchvalue . '"', SE_UID
);
169 //searchvalue is unix timestamp
170 $searchvalue = (int) $searchvalue;
172 $imapdate = gmdate("D, d M Y H:i:s +0000", $searchvalue);
173 //var_dump($imapdate);
174 $searchresult = imap_search ($imapresource, 'UNDELETED SINCE "' . $imapdate . '"', SE_UID
);
178 $searchresult = imap_search ($imapresource, 'UNDELETED', SE_UID
);
181 //var_dump($searchresult);
182 if (is_array($searchresult)) {
183 $returnvalue = $searchresult;
188 } // end function mailblogger_findmessages
191 /*========================================================================*/
193 function mailblogger_processnewmessages() {
197 if ($imap = mailblogger_openmailbox()) {
198 $msgs = mailblogger_findmessages($imap, "ALL", '');
199 //$msgs = mailblogger_findmessages($imap, "SINCE", gmmktime(12, 00, 00, 9, 11, 2006));
202 $messages = count($msgs);
204 foreach ($msgs as $auid) {
205 @set_time_limit
(120);
206 $messaged +
= mailblogger_processmessage($imap, $auid);
208 mtrace('mailblogger: posted ' . $messaged . ' of ' . $messages . ' messages.');
210 mailblogger_closemailbox($imap);
215 /*========================================================================*/
218 function mailblogger_processmessage($imapresource, $uid) {
223 if (is_resource($imapresource) && $uid) {
224 if ($structure = imap_fetchstructure($imapresource, $uid, FT_UID
)) {
225 $overview = array_shift(imap_fetch_overview($imapresource, $uid, FT_UID
));
228 // TODO - this regexp might want tuning if impure addresses are expected
229 if (preg_match('/([0-9a-zA-Z]+)([0-9]{4})$/', $overview->subject
, $matches)) {
230 $username = $matches[1];
231 $userpin = $matches[2];
232 // $domain = $matches[3];
234 if ($CFG->mailblogger_maildomain && $CFG->mailblogger_maildomain != $domain) {
235 mtrace('mailblogger: ignored a message due to bad To domain');
238 if ($userid = user_info_username('ident', $username)) {
240 //in the absence of a general "can this username log in" function, cheat...
241 $userpass = user_info_username('password', $username);
242 // if ($loginsuccess = authenticate_account($username,$userpass)) {
244 $userpindb = user_flag_get('mailblogger_pin', $userid);
245 if ($userpin && $userpindb && $userpin == $userpindb) {
246 //authenticated, continue posting
250 mtrace('mailblogger: ignored a message due to bad PIN');
254 // mtrace('mailblogger: ignored a message due to disabled user account');
257 mtrace('mailblogger: ignored a message due to bad username');
260 mtrace('mailblogger: ignored a message due to missing username/PIN');
265 if (!isset($structure->parts
)) {
266 //put top node into a tree if there are no children
267 $structure2 = new StdClass
;
268 $structure2->parts
[] = $structure;
269 $structure = $structure2;
272 $billofparts = mailblogger_msgpartwalker($structure->parts
, '', 0);
273 //var_dump($billofparts);
275 //var_dump($structure);
276 //var_dump($overview);
280 $uploadedfiles = false;
282 foreach ($billofparts as $apartid => $apart) {
284 @set_time_limit
(120);
286 if ($guff = imap_fetchbody($imapresource, $uid, $apartid, FT_UID
)) {
287 $guff = mailblogger_transfer_decode($apart->encoding
, $guff);
289 if ($apart->type
== 0 && strtolower($apart->subtype
) == 'plain') {
296 //check file for goodness.
297 //TODO - could do with using at least some of uploadlib, but that assumes POSTed files
299 $total_quota = get_field_sql('SELECT sum(size) FROM '.$CFG->prefix
.'files WHERE owner = ?',array($userid));
300 $max_quota = get_field('users','file_quota','ident',$userid);
301 $maxbytes = $max_quota - $total_quota;
303 if (strlen($guff) <= $maxbytes) {
305 $reldir = "files/" . substr($username, 0, 1) . "/" . $username . "/";
306 make_upload_directory($reldir, false);
307 $dir = $CFG->dataroot
.$reldir;
309 //$newfileloc = tempnam($dir, "mailblog_");
313 if ($apart->ifdparameters
) {
314 foreach ($apart->dparameters
as $adparam) {
315 if (strtolower($adparam->attribute
) == 'filename') {
316 $f->originalname
= trim($adparam->value
);
321 //elgg mimetype handling relies on the file extension, so have to generate our own filename rather than using tempnam.
323 if (!empty($f->originalname
)) {
324 $filename = time() . '_' . md5(serialize($apart)) . '_' . basename($f->originalname
);
326 $filename = time() . '_' . md5(serialize($apart));
327 require_once($CFG->libdir
.'/filelib.php');
328 if (function_exists("mimetype_to_extension")) { // filelib.php svn rev 674+
329 if ($ext = mimetype_to_extension(strtolower(mailblogger_mimetype_number_to_name($apart->type
) . '/' . $apart->subtype
) )) {
330 $filename .= '.' . $ext;
333 $f->originalname
= $filename;
336 $newfileloc = $dir . $filename;
338 if ($fhandle = @fopen
($newfileloc, "wb")) {
339 fwrite($fhandle, $guff);
343 $f->files_owner
= $userid;
346 if ($CFG->default_access
== 'PRIVATE') {
347 $f->access
= 'user' . $userid;
349 $f->access
= $CFG->default_access
;
352 $f->title
= 'Untitled';
353 $f->description
= 'Uploaded from a mail attachment';
354 $f->location
= $reldir . $filename;
355 $f->size
= filesize($newfileloc);
356 $f->time_uploaded
= time();
357 if ($file_id = insert_record('files',$f)) {
358 $uploadedfiles = true;
359 $postbody .= "\n{{file:" . $file_id . "}}\n";
362 } // end if opened file
365 // file is over quota
368 } // end text vs attachment
369 } // end if got part from imap
372 if ($uploadedfiles) {
373 $rssresult = run("files:rss:publish", array($userid, false));
377 $post = new StdClass
;
378 // $post->title = $overview->subject;
379 $post->title
= __gettext("External message");
380 $post->body
= $postbody;
381 if ($CFG->default_access
== 'PRIVATE') {
382 $post->access
= 'user' . $userid;
384 $post->access
= $CFG->default_access
;
386 $post->posted
= time();
387 $post->owner
= $userid;
388 $post->weblog
= $userid;
390 $insert_id = insert_record('weblog_posts',$post);
392 $rssresult = run("profile:rss:publish", array($userid, false));
393 $rssresult = run("weblogs:rss:publish", array($userid, false));
398 if (!empty($CFG->mailblogger_imapposteddmailbox
) && $CFG->mailblogger_imapposteddmailbox
!= $CFG->mailblogger_imapmailbox
) {
399 imap_mail_move($imapresource, $uid, $CFG->mailblogger_imapposteddmailbox
, CP_UID
);
401 imap_delete($imapresource, $uid, FT_UID
);
406 if (!empty($CFG->mailblogger_imaprejectedmailbox
) && $CFG->mailblogger_imaprejectedmailbox
!= $CFG->mailblogger_imapmailbox
) {
407 imap_mail_move($imapresource, $uid, $CFG->mailblogger_imaprejectedmailbox
, CP_UID
);
409 imap_delete($imapresource, $uid, FT_UID
);
414 } // end if imap_fetchstructure
416 } // end if valid params
421 /*========================================================================*/
423 //recursive function to flatten mime part tree into a simple array of leaf nodes
424 function mailblogger_msgpartwalker($partsarray, $mypartid, $depth) {
426 $depth = (int) $depth;
427 $mypartid = trim($mypartid);
431 if (is_array($partsarray)) {
433 foreach ($partsarray as $apartid => $apart) {
435 // array is 0-based, imap message parts are 1-based
437 $childpartid = $mypartid . '.' . ($apartid +
1);
439 $childpartid = ($apartid +
1);
442 if (isset($apart->parts
) && is_array($apart->parts
) && count($apart->parts
)) {
443 //part is a group of subparts
445 if ($depth < 10){ // prevent excessive recursion
446 $apartresult = mailblogger_msgpartwalker($apart->parts
, $childpartid, ++
$depth);
448 $flatdata = array_merge($flatdata, $apartresult);
453 //part is leaf node, add contents
454 $flatdata[$childpartid] = $apart;
463 /*========================================================================*/
465 // examples in php manual seem to try to do to much
466 // then again, this plugin ignores character sets
467 function mailblogger_transfer_decode($encoding, $data) {
471 // return imap_utf7_decode($data);
474 // return imap_utf8($data);
480 return base64_decode($data);
483 return imap_qprint($data);
492 /*========================================================================*/
494 // imap_fetchstructure represents the major mimetype part as a number
495 function mailblogger_mimetype_number_to_name ($typenum) {
508 if (isset($types[$typenum])) {
509 return $types[$typenum];
515 /*========================================================================*/
517 // user account settings page form
518 function mailblogger_userdetails_edit_details($userid) {
522 $title = __gettext("Mobile/Mail blogging:");
523 $blurb = __gettext("Choose a 4-digit PIN to identify yourself to the server in a phone/mail post.");
524 $userpin = user_flag_get('mailblogger_pin', $userid);
525 $username = user_info('username', $userid);
526 if ($username && $userpin && $CFG->mailblogger_toaddress
) {
527 $blurb .= '<br />' . sprintf(__gettext("To blog by phone/mail, send your post to %s with %s as your subject line."), $CFG->mailblogger_toaddress
, $username . $userpin);
535 // TODO - password or normal text box? second box for confirmation?
537 $body .= templates_draw( array(
538 'context' => 'databox',
539 'name' => __gettext("PIN: "),
540 'column1' => '<input type="text" maxlength="4" size="5" name="mailblogger_pin" value="' . $userpin . '"/>'
546 } // end function mailblogger_userdetails_edit_details
548 /*========================================================================*/
550 // user account settings page actions
551 function mailblogger_userdetails_actions() {
555 // Save the user's PIN
556 $action = optional_param('action');
557 $id = optional_param('id',0,PARAM_INT
);
558 $form_pin = optional_param('mailblogger_pin',0,PARAM_INT
);
559 $db_pin = user_flag_get('mailblogger_pin', $id);
561 if (logged_on
&& !empty($action) && run("permissions:check", array("userdetails:change",$id)) && $action == "userdetails:update") {
562 if (!empty($form_pin)) {
563 if (strlen($form_pin) == 4) {
564 if ($form_pin == $db_pin) {
565 //$messages[] .= __gettext("Your PIN has been saved.");
567 if (user_flag_set('mailblogger_pin', $form_pin, $id)) {
568 $messages[] .= __gettext("Your PIN has been saved.");
570 $messages[] .= __gettext("Your PIN could not be saved.");
574 $messages[] .= __gettext("Your PIN could not be saved. It was not 4 digits long.");
577 user_flag_unset('mailblogger_pin', $id);
578 $messages[] .= __gettext("Your PIN has been deleted. Mobile/Mail posting is disabled until you set a new PIN.");
582 } // end function mailblogger_userdetails_actions
584 /*========================================================================*/