Problem: Blank email from PHP web application.
Confirmed: App works in Linux, has various problems in Windows server environment. Blank emails are the last remaining problem.
PHP Version 5.2.6 on the server
I'm a librarian implementing a PHP based web application to help students complete their assignments.I have installed this application before on a Linux based free web host and had no problems.
Email is controlled by two files, email_functions.php and email.php. While email can be sent, all that is sent is a blank email.
My IT department is an ASP only shop, so I can get little to no help there. I also cannot install additional libraries like PHPmail or Swiftmailer.
You can see a functional copy at http://rpc.elm4you.org/ You can also download a copy from Sourceforge from the link there.
Thanks in advance for any insight into this!
email_functions.php
<?php
/**********************************************************
Function: build_multipart_headers
Author: Michael Berkowski
Last Modified: September 2007
***********************************************************
Purpose:
Creates email headers for a message of type multipart/mime
This will include a plain text part and HTML.
**********************************************************/
function build_multipart_headers($boundary_rand)
{
global $EMAIL_FROM_DISPLAY_NAME, $EMAIL_FROM_ADDRESS, $CALC_PATH, $CALC_TITLE, $SERVER_NAME;
// Using \n instead of \r\n because qmail doubles up the \r and screws everything up!
$crlf = "\n";
$message_date = date("r");
// Construct headers for multipart/mixed MIME email. It will have a plain text and HTML part
$headers = "X-Calc-Name: $CALC_TITLE" . $crlf;
$headers .= "X-Calc-Url: http://{$SERVER_NAME}/{$CALC_PATH}" . $crlf;
$headers .= "MIME-Version: 1.0" . $crlf;
$headers .= "Content-type: multipart/alternative;" . $crlf;
$headers .= " boundary=__$boundary_rand" . $crlf;
$headers .= "From: $EMAIL_FROM_DISPLAY_NAME <$EMAIL_FROM_ADDRESS>" . $crlf;
$headers .= "Sender: $EMAIL_FROM_DISPLAY_NAME <$EMAIL_FROM_ADDRESS>" . $crlf;
$headers .= "Reply-to: $EMAIL_FROM_DISPLAY_NAME <$EMAIL_FROM_ADDRESS>" . $crlf;
$headers .= "Return-Path: $EMAIL_FROM_DISPLAY_NAME <$EMAIL_FROM_ADDRESS>" . $crlf;
$headers .= "Date: $message_date" . $crlf;
$headers .= "Message-Id: $boundary_rand@$SERVER_NAME" . $crlf;
return $headers;
}
/**********************************************************
Function: build_multipart_body
Author: Michael Berkowski
Last Modified: September 2007
***********************************************************
Purpose:
Builds the email body content to go with the headers from
build_multipart_headers()
**********************************************************/
function build_multipart_body($plain_text_message, $html_message, $boundary_rand)
{
//$crlf = "\r\n";
$crlf = "\n";
$boundary = "__" . $boundary_rand;
// Begin constructing the MIME multipart message
$multipart_message = "This is a multipart message in MIME format." . $crlf . $crlf;
$multipart_message .= "--{$boundary}{$crlf}Content-type: text/plain; charset=\"us-ascii\"{$crlf}Content-Transfer-Encoding: 7bit{$crlf}{$crlf}";
$multipart_message .= $plain_text_message . $crlf . $crlf;
$multipart_message .= "--{$boundary}{$crlf}Content-type: text/html; charset=\"iso-8859-1\"{$crlf}Content-Transfer-Encoding: 7bit{$crlf}{$crlf}";
$multipart_message .= $html_message . $crlf . $crlf;
$multipart_message .= "--{$boundary}--$crlf$crlf";
return $multipart_message;
}
/**********************************************************
Function: build_step_email_body_text
Author: Michael Berkowski
Last Modified: September 2007
***********************************************************
Purpose:
Returns a plain text version of the email body to be used
for individually sent step reminders
**********************************************************/
function build_step_email_body_text($stepnum, $arr_instructions, $dates, $query_string, $teacher_info ,$name, $class, $project_id)
{
global $CALC_PATH, $CALC_TITLE, $SERVER_NAME;
$step_email_body =<<<BODY
$CALC_TITLE
Step $stepnum: {$arr_instructions["step$stepnum"]["title"]}
Name: $name
Class: $class
BODY;
$step_email_body .= build_text_single_step($stepnum, $arr_instructions, $dates, $query_string, $teacher_info);
$step_email_body .= "\n\n";
$step_email_body .=<<<FOOTER
The $CALC_TITLE offers suggestions, but be sure to check with your teacher to find out the best working schedule for your assignment!
If you would like to stop receiving further reminders for this project, click the link below:
http://$SERVER_NAME/$CALC_PATH/deleteproject.php?proj=$project_id
FOOTER;
// Wrap text to 78 chars per line
// Convert any remaining HTML <br /> to \r\n
// Strip out any remaining HTML tags.
$step_email_body = strip_tags(linebreaks_html2text(wordwrap($step_email_body, 78, "\n")));
return $step_email_body;
}
/**********************************************************
Function: build_step_email_body_html
Author: Michael Berkowski
Last Modified: September 2007
***********************************************************
Purpose:
Same as above, but with HTML
**********************************************************/
function build_step_email_body_html($stepnum, $arr_instructions, $dates, $query_string, $teacher_info, $name, $class, $project_id)
{
global $CALC_PATH, $CALC_TITLE, $SERVER_NAME;
$styles = build_html_styles();
$step_email_body =<<<BODY
<html>
<head>
<title> $CALC_TITLE </title>
$styles
</head>
<body>
<h1> $CALC_TITLE Schedule </h1>
<strong>Name:</strong> $name <br />
<strong>Class:</strong> $class <br />
BODY;
$step_email_body .= build_html_single_step($stepnum, $arr_instructions, $dates, $query_string, $teacher_info);
$step_email_body .=<<<FOOTER
<p>
The $CALC_TITLE offers suggestions, but be sure to check with your teacher to find out the best working schedule for your assignment!
</p>
<p>
If you would like to stop receiving further reminders for this project,
<a href="http://{$SERVER_NAME}/$CALC_PATH/deleteproject.php?proj=$project_id">click this link.</a>
</p>
</body>
</html>
FOOTER;
return $step_email_body;
}
/**********************************************************
Function: build_html_styles
Author: Michael Berkowski
Last Modified: September 2007
***********************************************************
Purpose:
Just returns a string of <style /> for the HTML message body
**********************************************************/
function build_html_styles()
{
$styles =<<<STYLES
<style type="text/css">
body { font-family: Arial, sans-serif; font-size: 85%; }
h1 { font-size: 120%; }
table { border: none; }
tr { vertical-align: top; }
img { display: none; }
hr { border: 0; }
</style>
STYLES;
return $styles;
}
/**********************************************************
Function: linebreaks_html2text
Author: Michael Berkowski
Last Modified: October 2007
***********************************************************
Purpose:
Convert <br /> html tags to \n line breaks
**********************************************************/
function linebreaks_html2text($in_string)
{
$out_string = "";
$arr_br = array("<br>", "<br />", "<br/>");
$out_string = str_replace($arr_br, "\n", $in_string);
return $out_string;
}
?>
email.php
<?php
require_once("include/config.php");
require_once("include/instructions.php");
require_once("dbase/dbfunctions.php");
require_once("include/email_functions.php");
ini_set("sendmail_from", "
[email protected].
qa");
ini_set("SMTP", "mail.qatar.net.
qa");
// Verify that the email has not already been sent by checking for a cookie
// whose value is generated each time the form is loaded freshly.
if (!(isset($_COOKIE['rpc_transid']) && $_COOKIE['rpc_transid'] == $_POST['transid']))
{
// Setup some preliminary variables for email.
// The scanning of $_POST['email']already took place when this file was included...
$to = $_POST['email'];
$subject = $EMAIL_SUBJECT;
$boundary_rand = md5(rand());
$mail_type = "";
switch ($_POST['reminder-type'])
{
case "progressive":
$arr_dbase_dates = array();
$conn = rpc_connect();
if (!$conn)
{
$mail_success = FALSE;
$mail_status_message = "Could not register address!";
break;
}
// Sanitize all the data that will be inserted into table...
// We need to remove "CONTENT-TYPE:" from name/class to defang them.
// Additionall, we can't allow any line-breaks in those fields to avoid
// hacks to email headers.
$ins_name = mysql_real_escape_string($name);
$ins_name = eregi_replace("CONTENT-TYPE", "...Content_Type...", $ins_name);
$ins_name = str_replace("\n", "", $ins_name);
$ins_class = mysql_real_escape_string($class);
$ins_class = eregi_replace("CONTENT-TYPE", "...Content_Type...", $ins_class);
$ins_class = str_replace("\n", "", $ins_class);
$ins_email = mysql_real_escape_string($email);
$ins_teacher_info = $teacher_info ? "YES" : "NO";
switch ($format)
{
case "Slides": $ins_format = "SLIDES"; break;
case "Video": $ins_format = "VIDEO"; break;
case "Essay":
default: $ins_format = "ESSAY"; break;
}
// The transid from the previous form will be used as a project identifier
// Steps will be grouped by project identifier.
$ins_project_id = mysql_real_escape_string($_POST['transid'] . md5(rand()));
$arr_dbase_dates = dbase_dates($dates);
$arr_past_dates = array();
// Iterate over the dates array and build a SQL statement for each one.
$insert_success = TRUE;
//
$min_reminder_date = date("Ymd", mktime(0,0,0,date("m"),date("d")+$EMAIL_REMINDER_DAYS_AHEAD,date("Y")));
for ($date_index = 0; $date_index < sizeof($arr_dbase_dates); $date_index++)
{
// Make sure we're using the right keys...
$ins_date_index = $date_index + 1;
// The insert will only happen if the date of the event is in the future.
// For dates today and earlier, no insert.
// For dates today or after the reminder deadline, we'll send the email immediately after the inserts.
if ($arr_dbase_dates[$date_index] > (int)$min_reminder_date)
{
$qry =<<<QRY
INSERT INTO email_queue
(
NOTIFICATION_ID,
PROJECT_ID,
EMAIL,
NAME,
CLASS,
FORMAT,
TEACHER_INFO,
STEP,
MESSAGE_DATE
)
VALUES (
NULL,
'$ins_project_id',
'$ins_email',
'$ins_name',
'$ins_class',
'$ins_format',
'$ins_teacher_info',
$ins_date_index, /*step number*/
{$arr_dbase_dates[$date_index]} /* Date in the integer format yyyymmdd */
)
QRY;
// Attempt to do the insert...
$result = mysql_query($qry);
// If even one insert fails, bail out.
if (!$result)
{
$mail_success = FALSE;
$mail_status_message = "Could not register address!";
break;
}
}
// For dates today or earlier, store the steps=>dates in an array so the mails can
// be sent immediately.
else
{
$arr_past_dates[$ins_date_index] = $arr_dbase_dates[$date_index];
}
}
// Close the connection resources.
mysql_close($conn);
// SEND OUT THE EMAILS THAT HAVE TO GO IMMEDIATELY...
// This should only be step 1, but who knows...
//var_dump($arr_past_dates);
for ($stepnum=1; $stepnum<=sizeof($arr_past_dates); $stepnum++)
{
$email_teacher_info = ($teacher_info && $EMAIL_TEACHER_REMINDERS) ? TRUE : FALSE;
$boundary = md5(rand());
$plain_text_body = build_step_email_body_text($stepnum, $arr_instructions, $dates, $query_string, $email_teacher_info ,$name, $class, $ins_project_id);
$html_body = build_step_email_body_html($stepnum, $arr_instructions, $dates, $query_string, $email_teacher_info ,$name, $class, $ins_project_id);
$multipart_headers = build_multipart_headers($boundary);
$multipart_body = build_multipart_body($plain_text_body, $html_body, $boundary);
mail($to, $subject . ": Step " . $stepnum, $multipart_body, $multipart_headers, "
[email protected]");
}
// Set appropriate flags and messages
$mail_success = TRUE;
$mail_status_message = "Email address registered!";
$mail_type = "progressive";
set_mail_success_cookie();
break;
// Default to a single email message.
case "single":
default:
// We don't want to send images in the message, so strip them out of the existing structure.
// This big ugly regex strips the whole table cell containing the image out of the table.
// Must find a better solution...
//$email_table_html = eregi_replace("<td class=\"stepImageContainer\" width=\"161px\">[\s\r\n\t]*<img class=\"stepImage\" src=\"images/[_a-zA-Z0-9]*\.gif\" alt=\"Step [1-9]{1} logo\" />[\s\r\n\t]*</td>", "\n", $table_html);
// Show more descriptive text based on the value of $format
switch ($format)
{
case "Video": $format_display = "Video"; break;
case "Slides": $format_display = "Presentation with electronic slides"; break;
case "Essay":
default:
$format_display = "Essay"; break;
}
$days = (int)$days;
$html_message = "";
$styles = build_html_styles();
$html_message =<<<HTMLMESSAGE
<html>
<head>
<title> $CALC_TITLE </title>
$styles
</head>
<body>
<h1> $CALC_TITLE Schedule </h1>
<strong>Name:</strong> $name <br />
<strong>Class:</strong> $class <br />
<strong>Email:</strong> $email <br />
<strong>Assignment type:</strong> $format_display <br /><br />
<strong>Starting on:</strong> $date1 <br />
<strong>Assignment due:</strong> $date2 <br />
<strong>You have $days days to finish.</strong><br />
<hr />
$email_table_html
</body>
</html>
HTMLMESSAGE;
// Create the plain text version of the message...
$plain_text_message = strip_tags(linebreaks_html2text(build_text_all_steps($arr_instructions, $dates, $query_string, $teacher_info)));
// Add the title, since it doesn't get built in by build_text_all_steps...
$plain_text_message = $CALC_TITLE . " Schedule\n\n" . $plain_text_message;
$plain_text_message = wordwrap($plain_text_message, 78, "\n");
$multipart_headers = build_multipart_headers($boundary_rand);
$multipart_message = build_multipart_body($plain_text_message, $html_message, $boundary_rand);
$mail_success = FALSE;
if (mail($to, $subject, $multipart_message, $multipart_headers, "
[email protected].
qa"))
{
$mail_success = TRUE;
$mail_status_message = "Email sent!";
$mail_type = "single";
set_mail_success_cookie();
}
else
{
$mail_success = FALSE;
$mail_status_message = "Could not send email!";
}
break;
}
}
function set_mail_success_cookie()
{
// Prevent the mail from being resent on page reload. Set a timestamp cookie.
// Expires in 24 hours.
setcookie("rpc_transid", $_POST['transid'], time() + 86400);
}
?>