Simple status box for the sidebar.
[elgg_plugins.git] / mailblogger / lib.php
blob2f9ffd97b34e4a6e4293e4af78f854a226d536f6
1 <?php
2 /*
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.
8 Config options:
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 /*========================================================================*/
31 //required function
32 function mailblogger_pagesetup() {
34 } // end function mailblogger_pagesetup
37 /*========================================================================*/
39 function mailblogger_init() {
41 global $function;
42 global $CFG;
44 if (!empty($CFG->mailblogger_maildomain)) {
45 $CFG->mailblogger_maildomain = trim($CFG->mailblogger_maildomain);
46 } else {
47 $CFG->mailblogger_maildomain = '';
50 if (!empty($CFG->mailblogger_toaddress)) {
51 $CFG->mailblogger_toaddress = trim($CFG->mailblogger_toaddress);
52 } else {
53 $CFG->mailblogger_toaddress = '';
56 // Action handling
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 /*========================================================================*/
66 // called by cron.php
67 function mailblogger_cron() {
69 global $CFG;
71 if (empty($CFG->mailblogger_disablecron)) {
72 if (!empty($CFG->mailblogger_lastcron) && (time() - 300) < $CFG->mailblogger_lastcron) {
73 return true;
74 } else {
75 mailblogger_processnewmessages();
78 set_config('mailblogger_lastcron',time());
81 } // end function mailblogger_cron
83 /*========================================================================*/
85 // open connection
86 function mailblogger_openmailbox() {
88 global $CFG;
89 $returnvalue = false;
91 if (!empty($CFG->mailblogger_imapusername) && !empty($CFG->mailblogger_imappassword)) {
93 if (empty($CFG->mailblogger_hostname)) {
94 $CFG->mailblogger_hostname = 'localhost';
95 } else {
96 $CFG->mailblogger_hostname = trim($CFG->mailblogger_hostname);
99 if (!isset($CFG->mailblogger_imapport)) {
100 $CFG->mailblogger_imapport = 143;
101 } else {
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';
110 } else {
111 $CFG->mailblogger_imapmailbox = trim($CFG->mailblogger_imapmailbox);
114 if (empty($CFG->mailblogger_imapflags)) {
115 $CFG->mailblogger_imapflags = '';
116 } else {
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;
124 } else {
125 mtrace("mailblogger error: could not open IMAP mailbox.");
126 $errs = imap_errors();
127 if (is_array($errs)) {
128 mtrace(implode("\n", $errs));
132 } else {
133 mtrace("mailblogger error: IMAP username or password not specified.");
136 return $returnvalue;
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) {
161 case "TO":
162 // searchvalue is username/email alias/etc
163 $searchvalue = trim($searchvalue);
164 if ($searchvalue) {
165 $searchresult = imap_search ($imapresource, 'UNDELETED TO "' . $searchvalue . '"', SE_UID);
167 break;
168 case "SINCE":
169 //searchvalue is unix timestamp
170 $searchvalue = (int) $searchvalue;
171 if ($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);
176 break;
177 case "ALL":
178 $searchresult = imap_search ($imapresource, 'UNDELETED', SE_UID);
179 break;
181 //var_dump($searchresult);
182 if (is_array($searchresult)) {
183 $returnvalue = $searchresult;
187 return $returnvalue;
188 } // end function mailblogger_findmessages
191 /*========================================================================*/
193 function mailblogger_processnewmessages() {
195 global $CFG;
197 if ($imap = mailblogger_openmailbox()) {
198 $msgs = mailblogger_findmessages($imap, "ALL", '');
199 //$msgs = mailblogger_findmessages($imap, "SINCE", gmmktime(12, 00, 00, 9, 11, 2006));
201 $messaged = 0;
202 $messages = count($msgs);
203 if ($messages) {
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) {
220 global $CFG;
221 $posted = 0;
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));
227 $continue = false;
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');
236 } else
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
247 $continue = true;
248 } else {
249 //not authenticated
250 mtrace('mailblogger: ignored a message due to bad PIN');
253 // } else {
254 // mtrace('mailblogger: ignored a message due to disabled user account');
255 // }
256 } else {
257 mtrace('mailblogger: ignored a message due to bad username');
259 } else {
260 mtrace('mailblogger: ignored a message due to missing username/PIN');
263 if ($continue) {
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);
274 //echo '<hr />';
275 //var_dump($structure);
276 //var_dump($overview);
277 //return;
279 $postbody = '';
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') {
290 //text
291 $postbody .= $guff;
293 } else {
294 //attachment
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_");
311 $f = new StdClass;
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);
325 } else {
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);
340 fclose($fhandle);
342 $f->owner = $userid;
343 $f->files_owner = $userid;
344 $f->folder = '-1';
346 if ($CFG->default_access == 'PRIVATE') {
347 $f->access = 'user' . $userid;
348 } else {
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
364 } else {
365 // file is over quota
368 } // end text vs attachment
369 } // end if got part from imap
370 } // end foreach
372 if ($uploadedfiles) {
373 $rssresult = run("files:rss:publish", array($userid, false));
376 if ($postbody) {
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;
383 } else {
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));
395 $posted = 1;
398 if (!empty($CFG->mailblogger_imapposteddmailbox) && $CFG->mailblogger_imapposteddmailbox != $CFG->mailblogger_imapmailbox) {
399 imap_mail_move($imapresource, $uid, $CFG->mailblogger_imapposteddmailbox, CP_UID);
400 } else {
401 imap_delete($imapresource, $uid, FT_UID);
404 } else {
406 if (!empty($CFG->mailblogger_imaprejectedmailbox) && $CFG->mailblogger_imaprejectedmailbox != $CFG->mailblogger_imapmailbox) {
407 imap_mail_move($imapresource, $uid, $CFG->mailblogger_imaprejectedmailbox, CP_UID);
408 } else {
409 imap_delete($imapresource, $uid, FT_UID);
412 } // end if continue
414 } // end if imap_fetchstructure
416 } // end if valid params
418 return $posted;
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);
428 $childpartid = '';
429 $flatdata = array();
431 if (is_array($partsarray)) {
433 foreach ($partsarray as $apartid => $apart) {
435 // array is 0-based, imap message parts are 1-based
436 if ($mypartid) {
437 $childpartid = $mypartid . '.' . ($apartid + 1);
438 } else {
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);
447 if ($apartresult) {
448 $flatdata = array_merge($flatdata, $apartresult);
452 } else {
453 //part is leaf node, add contents
454 $flatdata[$childpartid] = $apart;
460 return $flatdata;
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) {
469 switch ($encoding) {
470 case 0:
471 // return imap_utf7_decode($data);
472 // break;
473 case 1:
474 // return imap_utf8($data);
475 // break;
476 case 2:
477 return $data;
478 break;
479 case 3:
480 return base64_decode($data);
481 break;
482 case 4:
483 return imap_qprint($data);
484 break;
485 case 5:
486 default:
487 return $data;
488 break;
492 /*========================================================================*/
494 // imap_fetchstructure represents the major mimetype part as a number
495 function mailblogger_mimetype_number_to_name ($typenum) {
497 $types = array(
498 0 => 'text',
499 1 => 'multipart',
500 2 => 'message',
501 3 => 'application',
502 4 => 'audio',
503 5 => 'image',
504 6 => 'video',
505 7 => 'other'
508 if (isset($types[$typenum])) {
509 return $types[$typenum];
510 } else {
511 return $types[7];
515 /*========================================================================*/
517 // user account settings page form
518 function mailblogger_userdetails_edit_details($userid) {
520 global $CFG;
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);
530 $body = <<< END
531 <h2>$title</h2>
532 <p>$blurb</p>
533 END;
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 . '"/>'
544 return $body;
546 } // end function mailblogger_userdetails_edit_details
548 /*========================================================================*/
550 // user account settings page actions
551 function mailblogger_userdetails_actions() {
553 global $messages;
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.");
566 } else {
567 if (user_flag_set('mailblogger_pin', $form_pin, $id)) {
568 $messages[] .= __gettext("Your PIN has been saved.");
569 } else {
570 $messages[] .= __gettext("Your PIN could not be saved.");
573 } else {
574 $messages[] .= __gettext("Your PIN could not be saved. It was not 4 digits long.");
576 } else {
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 /*========================================================================*/