5 * Copyright 1999-2000, Tim Perdue/Sourceforge
6 * Copyright 2002, Tim Perdue/GForge, LLC
7 * Copyright 2009, Roland Mas
9 * This file is part of FusionForge. FusionForge is free software;
10 * you can redistribute it and/or modify it under the terms of the
11 * GNU General Public License as published by the Free Software
12 * Foundation; either version 2 of the Licence, or (at your option)
15 * FusionForge is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License along
21 * with FusionForge; if not, write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 require_once $gfcommon.'include/Error.class.php';
26 include_once $gfcommon.'include/TextSanitizer.class.php'; // for parsing the mail body
27 include_once $gfcommon.'include/User.class.php';
29 class ForumMessage extends Error {
31 var $awaits_moderation;//boolean -> true if the message was inserted for approval (pending), false if not
33 * Associative array of data from db.
35 * @var array $data_array.
49 * @param object The Forum object to which this ForumMessage is associated.
50 * @param int The message_id.
51 * @param array The associative array of data.
52 * @param boolean Whether the message is a pending one.
53 * @return boolean success.
55 function ForumMessage(&$Forum, $msg_id=false, $arr=false, $pending=false) {
57 if (!$Forum || !is_object($Forum)) {
58 $this->setError(_('ForumMessage:: No Valid Forum Object'));
61 if ($Forum->isError()) {
62 $this->setError('ForumMessage:: '.$Forum->getErrorMessage());
65 $this->Forum =& $Forum;
69 //we are going to create the pending message to show it to the admin for moderation
70 if (!$this->fetchModeratedData($msg_id)) {
73 $this->awaits_moderation = true;
75 $this->awaits_moderation = false;
76 if (!$arr || !is_array($arr)) {
77 if (!$this->fetchData($msg_id)) {
81 $this->data_array =& $arr;
83 // Verify this message truly belongs to this Forum
85 if ($this->data_array['group_forum_id'] != $this->Forum->getID()) {
86 $this->setError(_('Group_forum_id in db result does not match Forum Object'));
87 $this->data_array=null;
97 * insertmoderated - inserts the message into the table for moderation (forum_pending_messages)
98 * @param string The subject of the message.
99 * @param string The body of the message.
100 * @param int The thread_id of the message, if known.
101 * @param int The message_id of the parent message, if any.
102 * @param int The id of the user that is posting the message
103 * @return boolean success.
106 function insertmoderated($subject, $body, $thread_id='', $is_followup_to='',$user_id) {
108 $thread_id=$this->Forum->getNextThreadID();
111 $this->setError('ForumMessage::create() '._('Getting next thread_id failed'));
117 $result = db_query_params ('INSERT INTO forum_pending_messages (group_forum_id,posted_by,subject,
118 body,post_date,is_followup_to,thread_id,most_recent_date) VALUES ($1,$2,$3,$4,$5,$6,$7,$8)',
119 array ($this->Forum->getID(),
121 htmlspecialchars($subject),
127 if (!$result || db_affected_rows($result) < 1) {
128 $this->setError(_('ForumMessage::create() Posting Failed').' '.db_error());
132 $msg_id=db_insertid($result,'forum_pending_messages','msg_id');
133 if (!$this->fetchModeratedData($msg_id)) {
139 $this->setError(_('ForumMessage::create() Unable to get new message id'));
142 if (!$this->sendNewModeratedMsgNotice()) {
147 $this->awaits_moderation = true;
155 * insertreleasedmsg - inserts the released message into the main table (forum)
156 * @param string The Forum id
157 * @param string The subject of the message.
158 * @param string The body of the message.
159 * @param string The post date
160 * @param int The thread_id of the message
161 * @param int The message_id of the parent message, if any.
162 * @param int The id of the user that is posting the message
163 * @param int has followups?
164 * @param int most recent date
165 * @return boolean success.
168 function insertreleasedmsg($group_forum_id,$subject, $body,$post_date, $thread_id, $is_followup_to,$posted_by,$has_followups,$most_recent_date) {
169 if ($is_followup_to != 0) {
171 //was posted in reply to another thread
172 //we must check whether that thread still exists. if it does, post the message. else, set the error
174 // increment the parent's followup count if necessary
177 $res4 = db_query_params ('UPDATE forum SET most_recent_date=$1 WHERE thread_id=$2 AND is_followup_to=0',
180 if (!$res4 || db_affected_rows($res4) < 1) {
181 $this->setError(_('Couldn\'t Update Master Thread parent with current time'));
186 // mark the parent with followups as an optimization later
188 $res3 = db_query_params ('UPDATE forum SET has_followups=1,most_recent_date=$1 WHERE msg_id=$2',
192 $this->setError(_('Could Not Update Parent'));
202 $result = db_query_params ('INSERT INTO forum (group_forum_id,posted_by,subject,body,post_date,is_followup_to,thread_id,most_recent_date) VALUES ($1,$2,$3,$4,$5,$6,$7,$8)',
203 array ($group_forum_id,
205 htmlspecialchars($subject),
210 $most_recent_date)) ;
212 if (!$result || db_affected_rows($result) < 1) {
213 $this->setError(_('ForumMessage::create() Posting Failed').' '.db_error());
217 $msg_id=db_insertid($result,'forum','msg_id');
218 if (!$this->fetchData($msg_id)) {
219 $this->setError(_('ForumMessage::create() Posting Failed').' '.db_error());
224 $this->setError(_('ForumMessage::create() Unable to get new message id'));
228 if (!$this->sendNotice()) {
233 $this->awaits_moderation = false;
242 * insertmsg - inserts the message into the main table (forum)
243 * @param string The subject of the message.
244 * @param string The body of the message.
245 * @param int The thread_id of the message, if known.
246 * @param int The message_id of the parent message, if any.
247 * @param int The id of the user that is posting the message
248 * @param boolean Whether the message has an attach associated. Defaults to false
249 * @return boolean success.
252 function insertmsg($subject, $body, $thread_id='', $is_followup_to='',$user_id,$has_attach=false) {
254 $thread_id=$this->Forum->getNextThreadID();
257 $this->setError('ForumMessage::create() '._('Getting next thread_id failed'));
263 // increment the parent's followup count if necessary
265 $res4 = db_query_params ('UPDATE forum SET most_recent_date=$1 WHERE thread_id=$2 AND is_followup_to=0',
268 if (!$res4 || db_affected_rows($res4) < 1) {
269 $this->setError(_('Couldn\'t Update Master Thread parent with current time'));
274 // mark the parent with followups as an optimization later
276 $res3 = db_query_params ('UPDATE forum SET has_followups=1,most_recent_date=$1 WHERE msg_id=$2',
280 $this->setError(_('Could Not Update Parent'));
287 $result = db_query_params ('INSERT INTO forum (group_forum_id,posted_by,subject,body,post_date,is_followup_to,thread_id,most_recent_date) VALUES ($1,$2,$3,$4,$5,$6,$7,$8)',
288 array ($this->Forum->getID(),
290 htmlspecialchars($subject),
296 if (!$result || db_affected_rows($result) < 1) {
297 $this->setError(_('ForumMessage::create() Posting Failed').' '.db_error());
302 $msg_id=db_insertid($result,'forum','msg_id');
303 if (!$this->fetchData($msg_id)) {
309 $this->setError(_('ForumMessage::create() Unable to get new message id'));
314 if (!$this->sendNotice($has_attach)) {
320 //echo "db_error()".db_error();
321 $this->awaits_moderation = false;
327 * create - use this function to create a new message in the database.
329 * @param string The subject of the message.
330 * @param string The body of the message.
331 * @param int The thread_id of the message, if known.
332 * @param int The message_id of the parent message, if any.
333 * @param boolean Whether the message has an attach associated. Defaults to false
334 * @return boolean success.
336 function create($subject, $body, $thread_id='', $is_followup_to='',$has_attach=false) {
337 if (!$body || !$subject) {
338 $this->setError(_('Must Include A Message Body And Subject'));
341 if (!forge_check_perm ('forum', $this->Forum->getID(), 'post')) {
342 $this->setPermissionDeniedError();
345 if (!session_loggedin()) {
348 $user_id=user_getid();
351 if ($is_followup_to) {
352 $ParentMessage=new ForumMessage($this->Forum,$is_followup_to);
353 if (!$ParentMessage || !is_object($ParentMessage)) {
354 $this->setError("ForumMessage::create()"._('ForumMessage::create() No Valid ParentMessage Object'));
357 if ($ParentMessage->isError()) {
358 $this->setError('ForumMessage::create() '.$ParentMessage->getErrorMessage());
362 if (!$is_followup_to) {
368 //now we check the moderation status of the forum and act accordingly
369 if (forge_check_perm ('forum', $this->Forum->getID(), 'unmoderated_post')) {
371 return $this->insertmsg($subject, $body, $thread_id, $is_followup_to,$user_id,$has_attach);
373 return $this->insertmoderated($subject, $body, $thread_id, $is_followup_to,$user_id);
378 * fetchData - re-fetch the data for this forum_message from the database.
380 * @param int The message ID.
381 * @return boolean success.
383 function fetchData($msg_id) {
384 $res = db_query_params ('SELECT * FROM forum_user_vw WHERE msg_id=$1 AND group_forum_id=$2',
386 $this->Forum->getID())) ;
387 if (!$res || db_numrows($res) < 1) {
388 $this->setError(_('ForumMessage::fetchData() Invalid MessageID').db_error());
391 $this->data_array = db_fetch_array($res);
392 db_free_result($res);
397 * fetchModeratedData - re-fetch the data for this forum_message from the database, for pending messages
399 * @param int The message ID.
400 * @return boolean success.
402 function fetchModeratedData($msg_id) {
403 $res = db_query_params ('SELECT * FROM forum_pending_user_vw WHERE msg_id=$1 AND group_forum_id=$2',
405 $this->Forum->getID())) ;
406 if (!$res || db_numrows($res) < 1) {
407 $this->setError(_('ForumMessage::fetchData() Invalid MessageID').db_error());
410 $this->data_array = db_fetch_array($res);
411 db_free_result($res);
416 * getForum - get the Forum object this ForumMessage is associated with.
418 * @return object The Forum object.
420 function &getForum() {
425 * getID - get this message_id.
427 * @return int The message_id.
430 return $this->data_array['msg_id'];
434 * getPosterName - get the unix user_name of this message's poster.
436 * @return string The poster's unix name.
438 function getPosterName() {
439 return $this->data_array['user_name'];
443 * getPosterID - get this user_id of this message's poster.
445 * @return int The user_id.
447 function getPosterID() {
448 return $this->data_array['posted_by'];
452 * getPosterRealName - get the real name of this message's poster.
454 * @return string The real name.
456 function getPosterRealName() {
457 return $this->data_array['realname'];
461 * getSubject - get the subject of this message.
463 * @return string The subject.
465 function getSubject() {
466 return $this->data_array['subject'];
470 * getBody - get the body of this message.
472 * @return String The body.
475 return $this->data_array['body'];
479 * getPostDate - get the post date of this message.
481 * @return int The post date.
483 function getPostDate() {
484 return $this->data_array['post_date'];
488 * getParentID - get the id of the parent message, if this is a followup.
490 * @return int The parent id.
492 function getParentID() {
493 return $this->data_array['is_followup_to'];
497 * isPending - is the message pending, awaiting moderation?
499 * @return int awaits_moderation
501 function isPending() {
502 return $this->awaits_moderation;
506 * getThreadID - get the thread_id of the message.
508 * @return int The thread_id.
510 function getThreadID() {
511 return $this->data_array['thread_id'];
515 * getMostRecentDate - get the date of the most recent followup.
517 * @return int The date of the most recent followup.
519 function getMostRecentDate() {
520 return $this->data_array['most_recent_date'];
524 * hasFollowups - whether this message has any followups.
526 * @return boolean has_followups.
528 function hasFollowups() {
529 return $this->data_array['has_followups'];
533 * hasAttach - whether this message has an attachment.
535 * @return boolean has_attach.
538 function hasAttach() {
539 if ($this->isPending()) {
540 $res = db_query_params ('SELECT attachmentid FROM forum_pending_attachment WHERE msg_id=$1',
541 array ($this->getID())) ;
543 $res = db_query_params ('SELECT attachmentid FROM forum_attachment WHERE msg_id=$1',
544 array ($this->getID())) ;
546 if (db_numrows($res) > 0) {
553 * delete - Delete this message and its followups.
555 * @return int The count of deleted messages.
558 $msg_id=$this->getID();
560 $this->setError(_('Invalid Message ID'));
564 if (!forge_check_perm ('forum_admin', $this->Forum->Group->getID())) {
565 $this->setPermissionDeniedError();
569 $result = db_query_params ('SELECT msg_id FROM forum
570 WHERE is_followup_to=$1
571 AND group_forum_id=$2',
573 $this->Forum->getID())) ;
574 $rows=db_numrows($result);
577 for ($i=0;$i<$rows;$i++) {
578 $msg = new ForumMessage($this->Forum,db_result($result,$i,'msg_id'));
579 $count += $msg->delete();
581 $toss = db_query_params ('DELETE FROM forum
583 AND group_forum_id=$2',
585 $this->Forum->getID())) ;
587 $res = db_query_params ('DELETE FROM forum_attachment where msg_id=$1',
594 * removebbcode - workaround to remove bbcode tags.
596 * @return string converted text
598 function removebbcode($text) {
599 //$replaced = preg_replace("/\[[_a-zA-Z]:.+\](.+)\[\/[_a-zA-Z]:.*\]/","$1",$text);
600 $replaced = preg_replace("/\[.+\](.+)\[\/.+\]/","$1",$text);
605 * sendNotice - contains the logic to send out email followups when a message is posted.
607 * @param boolean Whether the message has an attach associated. Defaults to false
609 * @return boolean success.
611 function sendNotice($has_attach=false) {
612 $ids = $this->Forum->getMonitoringIDs();
614 $recipients = array ();
615 foreach ($ids as $id) {
616 $recipient = user_get_object ($id) ;
617 if ($recipient->isActive()) {
618 $recipients[] = $recipient ;
621 if ($this->Forum->getSendAllPostsTo()) {
622 $sapt = explode (',', $this->Forum->getSendAllPostsTo()) ;
623 foreach ($sapt as $r) {
628 if (count ($recipients) == 0) {
632 foreach ($recipients as $recipient) {
633 if (is_a ($recipient, 'GFUser')) {
634 setup_gettext_for_user ($recipient) ;
635 $dest_email = $recipient->getEmail ();
637 setup_gettext_from_sys_lang ();
638 $dest_email = $recipient ;
641 $body = sprintf(_("\nRead and respond to this message at: \n%s"), util_make_url ('/forum/message.php?msg_id='.$this->getID()));
642 if (forge_get_config('use_mail')) {
643 $body .= stripcslashes(sprintf(_('
644 Or reply to this e-mail entering your response between the following markers:
646 (enter your response here)
647 %1$s'), FORUM_MAIL_MARKER));
649 $body .= sprintf(_("\n\n\nBy: %s\n"), $this->getPosterRealName());
652 //if there's an attachment for the message, make it note.
653 //Note: We can't give a link for the attachment here because it hasn't been created yet (first the message needs to be created
654 $body .= _("A file has been uploaded with this message.")."\n\n";
658 $sanitizer = new TextSanitizer();
659 $text = $this->getBody();
660 $text = $sanitizer->convertNeededTagsForEmail($text);
661 $text= strip_tags($this->removebbcode(util_line_wrap($text)));
662 $text = $sanitizer->convertExtendedCharsForEmail($text);
664 "%s\n\n______________________________________________________________________\n".
665 _("You are receiving this email because you elected to monitor this forum.".
666 "\nTo stop monitoring this forum, login to %s and visit: \n%s\n"),
668 forge_get_config ('forge_name'),
669 util_make_url('/forum/monitor.php?forum_id='.$this->Forum->getID().
670 '&group_id='.$this->Forum->Group->getID().'&stop=1')
673 $extra_headers = "Return-Path: <noreply@".forge_get_config('web_host').">\n";
674 $extra_headers .= "Errors-To: <noreply@".forge_get_config('web_host').">\n";
675 $extra_headers .= "Sender: <noreply@".forge_get_config('web_host').">\n";
676 $extra_headers .= "Reply-To: ".$this->Forum->getReturnEmailAddress()."\n";
677 $extra_headers .= "Precedence: Bulk\n"
678 ."List-Id: ".$this->Forum->getName()." <forum".$this->Forum->getId()."@".forge_get_config('web_host').">\n"
679 ."List-Help: ".util_make_url ('/forum/forum.php?id='.$this->Forum->getId())."\n"
680 ."Message-Id: <forumpost".$this->getId()."@".forge_get_config('web_host').">";
681 $parentid = $this->getParentId();
682 if (!empty($parentid)) {
683 $extra_headers .= "\nIn-Reply-To: ".$this->Forum->getReturnEmailAddress()."\n"
684 ."References: <forumpost".$this->getParentId()."@".forge_get_config('web_host').">";
687 $subject="[" . $this->Forum->getUnixName() ."][".$this->getID()."] ".util_unconvert_htmlspecialchars($this->getSubject());
689 util_send_message($dest_email,$subject,$body,"noreply@".forge_get_config('web_host'),'','Forum',$extra_headers);
692 // Switch back to the user language settings
693 setup_gettext_from_context();
698 * sendNewModeratedMsgNotice - contains the logic to send out email notifications to the forum admins when a new moderated message is posted
700 * @return boolean success.
702 function sendNewModeratedMsgNotice() {
704 $engine = RBACEngine::getInstance () ;
705 $moderators = $engine->getUsersByAllowedAction ('forum', $this->Forum->getID(), 'moderate') ;
707 foreach ($moderators as $m) {
708 $ids[] = $m->getID () ;
712 // See if there is anyone to send messages to
714 if (!count($ids) > 0 && !$this->Forum->getSendAllPostsTo()) {
718 $f =& $this->getForum();
719 $g =& $f->getGroup();
721 $body = "\nRead to this message and approve/reject it at: ".
722 "\n".util_make_url('/forum/admin/pending.php?action=view_pending&group_id='. $g->getID() . "&forum_id=" . $f->getID()) .
723 "\nBy: " . $this->getPosterRealName() . "\n\n";
725 $text = $this->getBody();
726 $sanitizer = new TextSanitizer();
727 $text = $sanitizer->convertNeededTagsForEmail($text);
728 $text= strip_tags($this->removebbcode(util_line_wrap($text)));
729 $text = $sanitizer->convertExtendedCharsForEmail($text);
731 "\n\n______________________________________________________________________".
732 "\nYou are receiving this email because the forum you administrate has a new moderated message awaiting your approval.";
734 //$extra_headers = 'Reply-to: '.$this->Forum->getUnixName().'@'.forge_get_config('web_host');
735 $extra_headers = "Return-Path: <noreply@".forge_get_config('web_host').">\n";
736 $extra_headers .= "Errors-To: <noreply@".forge_get_config('web_host').">\n";
737 $extra_headers .= "Sender: <noreply@".forge_get_config('web_host').">\n";
738 $extra_headers .= "Reply-To: ".$this->Forum->getReturnEmailAddress()."\n";
739 $extra_headers .= "Precedence: Bulk\n"
740 ."List-Id: ".$this->Forum->getName()." <forum".$this->Forum->getId()."@".forge_get_config('web_host').">\n"
741 ."List-Help: ".util_make_url('/forum/forum.php?id='.$this->Forum->getId())."\n"
742 ."Message-Id: <forumpost".$this->getId()."@".forge_get_config('web_host').">";
743 $parentid = $this->getParentId();
744 if (!empty($parentid)) {
745 $extra_headers .= "\nIn-Reply-To: ".$this->Forum->getReturnEmailAddress()."\n"
746 ."References: <forumpost".$this->getParentId()."@".forge_get_config('web_host').">";
749 $subject="[" . $this->Forum->getUnixName() ."][".$this->getID()."] ".util_unconvert_htmlspecialchars($this->getSubject());
750 if (count($ids) != 0) {
751 $bccres = db_query_params ('SELECT email FROM users WHERE status=$1 AND user_id = ANY ($2)',
753 db_int_array_to_any_clause ($ids))) ;
756 $BCC = implode(util_result_column_to_array($bccres),',').','.$this->Forum->getSendAllPostsTo();
757 $User = user_get_object($this->getPosterID());
758 //util_send_message('',$subject,$body,$User->getEmail(),$BCC,$this->getPosterRealName(),$extra_headers);
759 util_send_message('',$subject,$body,"noreply@".forge_get_config('web_host'),$BCC,'Forum',$extra_headers);
760 // util_handle_message(array_unique($ids),$subject,$body,$this->Forum->getSendAllPostsTo(),'','forumgateway@'.forge_get_config('web_host'));
765 * updatemsg - impacts in the DB the new content of the message
767 * @param string The forum ID
768 * @param int The id of the user that is posting the message
769 * @param string The subject of the message.
770 * @param string The body of the message.
771 * @param string The post date
772 * @param int The message_id of the parent message, if any.
773 * @param int The thread_id of the message, if known.
774 * @param int has followups?
775 * @param string The most recent date.
777 * @return boolean success.
779 function updatemsg($group_forum_id,$posted_by,$subject,$body,$post_date,$is_followup_to,$thread_id,$has_followups,$most_recent_date) {
780 $subject = htmlspecialchars($subject);
782 $msg_id = $this->getID();
783 $res = db_query_params ('UPDATE forum
784 SET group_forum_id=$1, posted_by=$2, subject=$3,
785 body=$4, post_date=$5, is_followup_to=$6,
786 thread_id=$7, most_recent_date=$8
788 array ($group_forum_id,
798 $this->setError(db_error());
801 if (db_affected_rows($res)<1) {
802 $this->setError("Message not found");
810 * sendAttachNotice - contains the logic to send out email attachement followups when a message is posted.
812 * @param int attach_id - The id of the file that has been attached
814 * @return boolean success.
816 function sendAttachNotice($attach_id) {
818 $ids = $this->Forum->getMonitoringIDs();
821 // See if there is anyone to send messages to
823 if (!count($ids) > 0 && !$this->Forum->getSendAllPostsTo()) {
827 $body = "\nRead and respond to this message at: ".
828 "\n".util_make_url('/forum/message.php?msg_id='.$this->getID()).
829 "\nBy: " . $this->getPosterRealName() . "\n\n";
831 $body .= "A file has been uploaded to this message, you can download it at: ".
832 "\n".util_make_url('/forum/attachment.php?attachid='. $attach_id . "&group_id=" . $this->Forum->Group->getID() . "&forum_id=" . $this->Forum->getID()) . "\n\n";
835 "\n\n______________________________________________________________________".
836 "\nYou are receiving this email because you elected to monitor this forum.".
837 "\nTo stop monitoring this forum, login to ".forge_get_config ('forge_name')." and visit: ".
838 "\n".util_make_url ('/forum/monitor.php?forum_id='.$this->Forum->getID() .'&group_id='.$this->Forum->Group->getID().'&stop=1');
840 $extra_headers = "Return-Path: <noreply@".forge_get_config('web_host').">\n";
841 $extra_headers .= "Errors-To: <noreply@".forge_get_config('web_host').">\n";
842 $extra_headers .= "Sender: <noreply@".forge_get_config('web_host').">\n";
843 $extra_headers .= "Reply-To: ".$this->Forum->getReturnEmailAddress()."\n";
844 $extra_headers .= "Precedence: Bulk\n"
845 ."List-Id: ".$this->Forum->getName()." <forum".$this->Forum->getId()."@".forge_get_config('web_host').">\n"
846 ."List-Help: ".util_make_url('/forum/forum.php?id='.$this->Forum->getId())."\n"
847 ."Message-Id: <forumpost".$this->getId()."@".forge_get_config('web_host').">";
848 $parentid = $this->getParentId();
849 if (!empty($parentid)) {
850 $extra_headers .= "\nIn-Reply-To: ".$this->Forum->getReturnEmailAddress()."\n"
851 ."References: <forumpost".$this->getParentId()."@".forge_get_config('web_host').">";
854 $subject="[" . $this->Forum->getUnixName() ."][".$this->getID()."] ".util_unconvert_htmlspecialchars($this->getSubject());
855 if (count($ids) != 0) {
856 $bccres = db_query_params ('SELECT email FROM users WHERE status=$1 AND user_id = ANY ($2)',
858 db_int_array_to_any_clause ($ids))) ;
860 $BCC = implode(util_result_column_to_array($bccres),',').','.$this->Forum->getSendAllPostsTo();
861 $User = user_get_object($this->getPosterID());
862 util_send_message('',$subject,$body,"noreply@".forge_get_config('web_host'),$BCC,'Forum',$extra_headers);
872 // c-file-style: "bsd"