More security/sanitizing fixes
--- a/aws/awsStartup.sh
+++ b/aws/awsStartup.sh
@@ -5,6 +5,7 @@
#http://www.how2forge.org/installing-lighttpd-with-php5-and-mysql-support-on-fedora-12
cp -rfv /tmp/busui/* /var/www
+cp /root/aws.php /tmp/
chcon -h system_u:object_r:httpd_sys_content_t /var/www
chcon -R -h root:object_r:httpd_sys_content_t /var/www/*
chcon -R -t httpd_sys_content_rw_t /var/www/staticmaplite/cache
--- a/common.inc.php
+++ b/common.inc.php
@@ -4,7 +4,10 @@
$cloudmadeAPIkey="daa03470bb8740298d4b10e3f03d63e6";
$googleMapsAPIkey="ABQIAAAA95XYXN0cki3Yj_Sb71CFvBTPaLd08ONybQDjcH_VdYtHHLgZvRTw2INzI_m17_IoOUqH3RNNmlTk1Q";
$otpAPIurl = 'http://localhost:8080/opentripplanner-api-webapp/';
-if (isDebug()) error_reporting(E_ALL ^ E_NOTICE);
+$owaSiteID = 'fe5b819fa8c424a99ff0764d955d23f3';
+//$debugOkay = Array("session","json","phperror","other");
+$debugOkay = Array("session","json","phperror");
+if (isDebug("phperror")) error_reporting(E_ALL ^ E_NOTICE);
// SELECT array_to_string(array(SELECT REPLACE(name_2006, ',', '\,') as name FROM suburbs order by name), ',')
$suburbs = explode(",","Acton,Ainslie,Amaroo,Aranda,Banks,Barton,Belconnen,Bonner,Bonython,Braddon,Bruce,Calwell,Campbell,Chapman,Charnwood,Chifley,Chisholm,City,Conder,Cook,Curtin,Deakin,Dickson,Downer,Duffy,Dunlop,Evatt,Fadden,Farrer,Fisher,Florey,Flynn,Forrest,Franklin,Fraser,Fyshwick,Garran,Gilmore,Giralang,Gordon,Gowrie,Greenway,Griffith,Gungahlin,Hackett,Hall,Harrison,Hawker,Higgins,Holder,Holt,Hughes,Hume,Isaacs,Isabella Plains,Kaleen,Kambah,Kingston,Latham,Lawson,Lyneham,Lyons,Macarthur,Macgregor,Macquarie,Mawson,McKellar,Melba,Mitchell,Monash,Narrabundah,Ngunnawal,Nicholls,Oaks Estate,O'Connor,O'Malley,Oxley,Page,Palmerston,Parkes,Pearce,Phillip,Pialligo,Red Hill,Reid,Richardson,Rivett,Russell,Scullin,Spence,Stirling,Symonston,Tharwa,Theodore,Torrens,Turner,Wanniassa,Waramanga,Watson,Weetangera,Weston,Yarralumla");
@@ -18,37 +21,58 @@
$_SESSION['time'] = filter_var($_REQUEST['time'],FILTER_SANITIZE_STRING);
}
if (isset($_REQUEST['geolocate'])) {
+ $geocoded = false;
if (isset($_REQUEST['lat']) && isset($_REQUEST['lon'])) {
- $_SESSION['lat'] = $_REQUEST['lat'];
- $_SESSION['lon'] = $_REQUEST['lon'];
+ $_SESSION['lat'] = filter_var($_REQUEST['lat'],FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
+ $_SESSION['lon'] = filter_var($_REQUEST['lon'],FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
} else {
- $contents = geocode(var_filter($_REQUEST['geolocate'],FILTER_SANITIZE_URL),true);
+ $contents = geocode(filter_var($_REQUEST['geolocate'],FILTER_SANITIZE_URL),true);
if (isset($contents[0]->centroid)) {
+ $geocoded = true;
$_SESSION['lat'] = $contents[0]->centroid->coordinates[0];
$_SESSION['lon'] = $contents[0]->centroid->coordinates[1];
- }
- else {
+ }
+ else {
$_SESSION['lat'] = "";
$_SESSION['lon'] = "";
}
}
+ if ($_SESSION['lat'] != "" && isMetricsOn()) {
+// Create a new Instance of the tracker
+$owa = new owa_php($config);
+// Set the ID of the site being tracked
+$owa->setSiteId($owaSiteID);
+// Create a new event object
+$event = $owa->makeEvent();
+// Set the Event Type, in this case a "video_play"
+$event->setEventType('geolocate');
+// Set a property
+$event->set('lat',$_SESSION['lat']);
+$event->set('lon',$_SESSION['lon']);
+$event->set('geocoded',$geocoded);
+// Track the event
+$owa->trackEvent($event);
+ }
}
-//print_r ($_SESSION);
-function isDebug()
-{
- return $_SERVER['SERVER_NAME'] == "10.0.1.154" || $_SERVER['SERVER_NAME'] == "localhost" || $_SERVER['SERVER_NAME'] == "127.0.0.1" || !$_SERVER['SERVER_NAME'];
+debug(print_r($_SESSION,true));
+function isDebug($debugReason = "other")
+{
+ global $debugOkay;
+ return in_array($debugReason,$debugOkay,false) && $_SERVER['SERVER_NAME'] == "10.0.1.154" || $_SERVER['SERVER_NAME'] == "localhost" || $_SERVER['SERVER_NAME'] == "127.0.0.1" || !$_SERVER['SERVER_NAME'];
}
function isMetricsOn()
{
- return false;
-}
-
-function debug($msg) {
- if (isDebug()) echo "\n<!-- $msg -->\n";
+ return !isDebug();
+}
+
+function debug($msg, $debugReason = "other") {
+ if (isDebug($debugReason)) echo "\n<!-- ".date(DATE_RFC822)."\n $msg -->\n";
}
function isFastDevice() {
+ $ua = $_SERVER['HTTP_USER_AGENT'];
$fastDevices = Array("Mozilla/5.0 (X11;", "Mozilla/5.0 (Windows;", "Mozilla/5.0 (iP", "Mozilla/5.0 (Linux; U; Android", "Mozilla/4.0 (compatible; MSIE");
+
$slowDevices = Array("J2ME","MIDP","Opera/","Mozilla/2.0 (compatible;","Mozilla/3.0 (compatible;");
return true;
}
@@ -105,7 +129,7 @@
function success(position) {
$('#geolocate').val(position.coords.latitude+','+position.coords.longitude);
-// setCookie('geolocate',position.coords.latitude+','+position.coords.longitude,1);
+$.ajax({ url: \"common.inc.php?geolocate=yes&lat=\"+position.coords.latitude+\"&lon=\"+position.coords.longitude });
$('#here').click(function(event) { $('#geolocate').val(doAJAXrequestForGeolocSessionHere()); return false;});
$('#here').show();
}
@@ -126,7 +150,8 @@
require_once('owa/owa_env.php');
require_once(OWA_DIR.'owa_php.php');
$owa = new owa_php();
- $owa->setSiteId('bus.lambdacomplex.org');
+ global $owaSiteID;
+ $owa->setSiteId($owaSiteID);
$owa->setPageTitle($pageTitle);
$owa->setPageType($pageType);
$owa->trackPageView();
@@ -221,7 +246,15 @@
return $f;
}
-function staticmap($mapPoints, $zoom = 0, $markerImage = "iconb")
+function curPageURL() {
+$isHTTPS = (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on");
+$port = (isset($_SERVER["SERVER_PORT"]) && ((!$isHTTPS && $_SERVER["SERVER_PORT"] != "80") || ($isHTTPS && $_SERVER["SERVER_PORT"] != "443")));
+$port = ($port) ? ':'.$_SERVER["SERVER_PORT"] : '';
+$url = ($isHTTPS ? 'https://' : 'http://').$_SERVER["SERVER_NAME"].$port.dirname($_SERVER['PHP_SELF'])."/";
+return $url;
+}
+
+function staticmap($mapPoints, $zoom = 0, $markerImage = "iconb", $collapsible = true)
{
$width = 300;
$height = 300;
@@ -245,7 +278,7 @@
if (sizeof($mapPoints) === 1) {
if ($zoom == 0) $zoom = 14;
$markers .= "{$mapPoints[0][0]},{$mapPoints[0][1]},$markerimage";
- $center = "{$mapPoints[0][0]},{$mapPoints[0][1]}";
+ $center = "{$mapPoints[0][0]},{$mapPoints[0][1]}";
} else {
foreach ($mapPoints as $index => $mapPoint) {
$markers .= $mapPoint[0].",".$mapPoint[1].",".$markerImage.($index+1);
@@ -267,9 +300,9 @@
$center = $totalLat/sizeof($mapPoints).",".$totalLon/sizeof($mapPoints);
}
$output = "";
- if(basename($_SERVER['PHP_SELF']) != "tripPlanner.php") $output .= '<div data-role="collapsible" data-collapsed="true"><h3>Open Map...</h3>';
- $output .= '<center><img src="staticmaplite/staticmap.php?center='.$center.'&zoom='.$zoom.'&size='.$width.'x'.$height.'&maptype=mapnik&markers='.$markers.'" width='.$width.' height='.$height.'></center>';
- if(basename($_SERVER['PHP_SELF']) != "tripPlanner.php") $output .= '</div>';
+ if ($collapsible) $output .= '<div data-role="collapsible" data-collapsed="true"><h3>Open Map...</h3>';
+ $output .= '<center><img src="'.curPageURL().'staticmaplite/staticmap.php?center='.$center.'&zoom='.$zoom.'&size='.$width.'x'.$height.'&maptype=mapnik&markers='.$markers.'" width='.$width.' height='.$height.'></center>';
+ if ($collapsible) $output .= '</div>';
return $output;
}
@@ -454,11 +487,11 @@
<div class="ui-body">
<div data-role="fieldcontain">
<label for="geolocate"> Current Location: </label>
- <input type="text" id="geolocate" name="geolocate" value="Enter co-ordinates or address here"/> <a href="#" style="display:none" name="here" id="here"/>Here?</a>
+ <input type="text" id="geolocate" name="geolocate" value="'. (isset($_SESSION['lat']) && isset($_SESSION['lon']) ? $_SESSION['lat'] .",". $_SESSION['lon'] :"Enter co-ordinates or address here"). '"/> <a href="#" style="display:none" name="here" id="here"/>Here?</a>
</div>
<div data-role="fieldcontain">
<label for="time"> Time: </label>
- <input type="time" name="time" id="time" value="'. ($_SESSION['time'] ? $_SESSION['time'] : date("H:m")).'"/> <a href="#" name="currentTime" id="currentTime"/>Current Time?</a>
+ <input type="time" name="time" id="time" value="'. (isset($_SESSION['time']) ? $_SESSION['time'] : date("H:i")).'"/> <a href="#" name="currentTime" id="currentTime"/>Current Time?</a>
</div>
<div data-role="fieldcontain">
<label for="service_period"> Service Period: </label>
@@ -475,5 +508,7 @@
</form>
</div></div>';
}
+
+
?>
--- a/feedback.php
+++ b/feedback.php
@@ -1,18 +1,54 @@
<?php
include('common.inc.php');
-include_header("Feedback","feedback")
+include_header("Feedback","feedback");
+function sendEmail($topic, $message) {
+ $address = "maxious@lambdacomplex.org";
+
+ if (file_exists("/tmp/aws.php") ) {
+ include_once('ses.php');
+ include_once("/tmp/aws.php");
+$con=new SimpleEmailService($accessKey,$secretKey);
+//$con->verifyEmailAddress($address);
+//$con->listVerifiedEmailAddresses();
+
+$m = new SimpleEmailServiceMessage();
+$m->addTo($address);
+$m->setFrom($address);
+$m->setSubject($topic);
+$m->setMessageFromString($message);
+$con->sendEmail($m);
+} else {
+// In case any of our lines are larger than 70 characters, we should use wordwrap()
+$message = wordwrap($message, 70);
+
+// Send
+mail($address, $topic, $message);
+}
+}
+
+
?>
<h3>Add/Move/Delete a Bus Stop Location</h3>
StopID:
or StopCode:
+<small> if you click on feedback from a stop page, these will get filled in automatically. else describe the location/street of the stop <input type="text" name="stoplocation" /> </small>
Suggested Stop Location (lat/long or words):
+<small> if your device supports javascript, you can pick a location from the map above</small>
+
+Submit!
<h3>Bug Report/Feedback</h3>
+Please leave feedback about bugs/errors or general suggestions about improvements that could be made to the way the data is presented!
+<textarea id="feedback">
+</textarea>
<textarea id="extrainfo">
Referrer URL
User Agent
User host/IP
+ Server host/IP
Current date/time
Dump of $_SESSION
</textarea>
+
+Submit!
--- a/layar_api.php
+++ b/layar_api.php
@@ -6,10 +6,14 @@
$max_page = 10;
$max_results = 50;
-$page_start = 0+$_REQUEST['pageKey'];
-$page_end = $max_page+$_REQUEST['pageKey'];
+$page_start = 0+filter_var($_REQUEST['pageKey'],FILTER_SANITIZE_NUMBER_INT);
+$page_end = $max_page+filter_var($_REQUEST['pageKey'],FILTER_SANITIZE_NUMBER_INT);
-$url = $APIurl."/json/neareststops?lat={$_REQUEST['lat']}&lon={$_REQUEST['lon']}&limit=50";
+$lat = filter_var($_REQUEST['lat'],FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
+$lon = filter_var($_REQUEST['lon'],FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
+if (isset($_REQUEST['radius'])) $radius = filter_var($_REQUEST['radius'],FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
+
+$url = $APIurl."/json/neareststops?lat=$lat&lon=$lon&limit=50";
$contents = json_decode(getPage($url));
debug(print_r($contents,true));
$stopNum = 0;
@@ -24,9 +28,9 @@
$hotspot['lat'] = floor($row[2]*1000000);
$hotspot['lon'] = floor($row[3]*1000000);
$hotspot['distance'] = distance($row[2], $row[3], $_REQUEST['lat'], $_REQUEST['lon']);
- if (!isset($_REQUEST['radius']) || $hotspot['distance'] < $_REQUEST['radius']) {
+ if (!isset($_REQUEST['radius']) || $hotspot['distance'] < $radius) {
$hotspot['actions'] = Array(Array("label" => 'View more trips/information', 'uri' => 'http://bus.lambdacomplex.org/'.'stop.php?stopid='.$row[0]));
- $url = $APIurl."/json/stoptrips?stop=".$row[0]."&time=".midnight_seconds()."&service_period=".service_period();
+ $url = $APIurl."/json/stoptrips?stop=".$row[0]."&time=".midnight_seconds()."&service_period=".service_period()."&limit=4&time_range=".str(90*60);
$trips = json_decode(getPage($url));
debug(print_r($trips,true));
foreach ($trips as $key => $row)
--- a/schedule_viewer.py
+++ b/schedule_viewer.py
@@ -421,22 +421,71 @@
if s.stop_id.lower() == query:
return StopToTuple(s)
return []
+ def handle_json_GET_stoproutes(self, params):
+ """Given a stop_id return all routes to visit the stop."""
+ schedule = self.server.schedule
+ stop = schedule.GetStop(params.get('stop', None))
+ service_period = params.get('service_period', None)
+ trips = stop.GetTrips(schedule)
+ result = {}
+ for trip in trips:
+ route = schedule.GetRoute(trip.route_id)
+ if not trip.route_id in result:
+ result[trip.route_id] = (route.route_id, route.route_short_name, route.route_long_name, trip.trip_id)
+ return result
+
+ def handle_json_GET_stopalltrips(self, params):
+ """Given a stop_id return all trips to visit the stop."""
+ schedule = self.server.schedule
+ stop = schedule.GetStop(params.get('stop', None))
+ service_period = params.get('service_period', None)
+ time_trips = stop.GetStopTimeTrips(schedule)
+ result = []
+ for time, (trip, index), tp in time_trips:
+ headsign = None
+ # Find the most recent headsign from the StopTime objects
+ for stoptime in trip.GetStopTimes()[index::-1]:
+ if stoptime.stop_headsign:
+ headsign = stoptime.stop_headsign
+ break
+ # If stop_headsign isn't found, look for a trip_headsign
+ if not headsign:
+ headsign = trip.trip_headsign
+ route = schedule.GetRoute(trip.route_id)
+ trip_name = ''
+ if route.route_short_name:
+ trip_name += route.route_short_name
+ if route.route_long_name:
+ if len(trip_name):
+ trip_name += " - "
+ trip_name += route.route_long_name
+ if service_period == None or trip.service_id == service_period:
+ result.append((time, (trip.trip_id, trip_name, trip.service_id), tp))
+ return result
def handle_json_GET_stoptrips(self, params):
"""Given a stop_id and time in seconds since midnight return the next
trips to visit the stop."""
schedule = self.server.schedule
stop = schedule.GetStop(params.get('stop', None))
- time = int(params.get('time', 0))
+ requested_time = int(params.get('time', 0))
+ limit = int(params.get('limit', 15))
service_period = params.get('service_period', None)
- time_trips = stop.GetStopTimeTrips(schedule)
- time_trips.sort() # OPT: use bisect.insort to make this O(N*ln(N)) -> O(N)
- # Keep the first 15 after param 'time'.
- # Need make a tuple to find correct bisect point
- time_trips = time_trips[bisect.bisect_left(time_trips, (time, 0)):]
- time_trips = time_trips[:15]
+ time_range = params.get('time_range', 24*60*60)
+
+
+ filtered_time_trips = []
+ for trip, index in stop._GetTripIndex(schedule):
+ tripstarttime = trip.GetStartTime()
+ if tripstarttime > requested_time and tripstarttime < (requested_time + time_range):
+ time, stoptime, tp = trip.GetTimeInterpolatedStops()[index]
+ if time > requested_time and time < (requested_time + time_range):
+ bisect.insort(filtered_time_trips, (time, (trip, index), tp))
+
result = []
- for time, (trip, index), tp in time_trips:
+ for time, (trip, index), tp in filtered_time_trips:
+ if len(result) > limit:
+ break
headsign = None
# Find the most recent headsign from the StopTime objects
for stoptime in trip.GetStopTimes()[index::-1]:
--- /dev/null
+++ b/ses.php
@@ -1,1 +1,704 @@
-
+<?php
+/**
+*
+* Copyright (c) 2011, Dan Myers.
+* Parts copyright (c) 2008, Donovan Schonknecht.
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are met:
+*
+* - Redistributions of source code must retain the above copyright notice,
+* this list of conditions and the following disclaimer.
+* - Redistributions in binary form must reproduce the above copyright
+* notice, this list of conditions and the following disclaimer in the
+* documentation and/or other materials provided with the distribution.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+* POSSIBILITY OF SUCH DAMAGE.
+*
+* This is a modified BSD license (the third clause has been removed).
+* The BSD license may be found here:
+* http://www.opensource.org/licenses/bsd-license.php
+*
+* Amazon Simple Email Service is a trademark of Amazon.com, Inc. or its affiliates.
+*
+* SimpleEmailService is based on Donovan Schonknecht's Amazon S3 PHP class, found here:
+* http://undesigned.org.za/2007/10/22/amazon-s3-php-class
+*
+*/
+
+/**
+* Amazon SimpleEmailService PHP class
+*
+* @link http://sourceforge.net/projects/php-aws-ses/
+* version 0.8.1
+*
+*/
+class SimpleEmailService
+{
+ protected $__accessKey; // AWS Access key
+ protected $__secretKey; // AWS Secret key
+ protected $__host;
+
+ public function getAccessKey() { return $this->__accessKey; }
+ public function getSecretKey() { return $this->__secretKey; }
+ public function getHost() { return $this->__host; }
+
+ protected $__verifyHost = 1;
+ protected $__verifyPeer = 1;
+
+ // verifyHost and verifyPeer determine whether curl verifies ssl certificates.
+ // It may be necessary to disable these checks on certain systems.
+ // These only have an effect if SSL is enabled.
+ public function verifyHost() { return $this->__verifyHost; }
+ public function enableVerifyHost($enable = true) { $this->__verifyHost = $enable; }
+
+ public function verifyPeer() { return $this->__verifyPeer; }
+ public function enableVerifyPeer($enable = true) { $this->__verifyPeer = $enable; }
+
+ /**
+ * Constructor
+ *
+ * @param string $accessKey Access key
+ * @param string $secretKey Secret key
+ * @return void
+ */
+ public function __construct($accessKey = null, $secretKey = null, $host = 'email.us-east-1.amazonaws.com') {
+ if ($accessKey !== null && $secretKey !== null) {
+ $this->setAuth($accessKey, $secretKey);
+ }
+ $this->__host = $host;
+ }
+
+ /**
+ * Set AWS access key and secret key
+ *
+ * @param string $accessKey Access key
+ * @param string $secretKey Secret key
+ * @return void
+ */
+ public function setAuth($accessKey, $secretKey) {
+ $this->__accessKey = $accessKey;
+ $this->__secretKey = $secretKey;
+ }
+
+ /**
+ * Lists the email addresses that have been verified and can be used as the 'From' address
+ *
+ * @return An array containing two items: a list of verified email addresses, and the request id.
+ */
+ public function listVerifiedEmailAddresses() {
+ $rest = new SimpleEmailServiceRequest($this, 'GET');
+ $rest->setParameter('Action', 'ListVerifiedEmailAddresses');
+
+ $rest = $rest->getResponse();
+ if($rest->error === false && $rest->code !== 200) {
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ }
+ if($rest->error !== false) {
+ $this->__triggerError('listVerifiedEmailAddresses', $rest->error);
+ return false;
+ }
+
+ $response = array();
+ if(!isset($rest->body)) {
+ return $response;
+ }
+
+ $addresses = array();
+ foreach($rest->body->ListVerifiedEmailAddressesResult->VerifiedEmailAddresses->member as $address) {
+ $addresses[] = (string)$address;
+ }
+
+ $response['Addresses'] = $addresses;
+ $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
+
+ return $response;
+ }
+
+ /**
+ * Requests verification of the provided email address, so it can be used
+ * as the 'From' address when sending emails through SimpleEmailService.
+ *
+ * After submitting this request, you should receive a verification email
+ * from Amazon at the specified address containing instructions to follow.
+ *
+ * @param string email The email address to get verified
+ * @return The request id for this request.
+ */
+ public function verifyEmailAddress($email) {
+ $rest = new SimpleEmailServiceRequest($this, 'POST');
+ $rest->setParameter('Action', 'VerifyEmailAddress');
+ $rest->setParameter('EmailAddress', $email);
+
+ $rest = $rest->getResponse();
+ if($rest->error === false && $rest->code !== 200) {
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ }
+ if($rest->error !== false) {
+ $this->__triggerError('verifyEmailAddress', $rest->error);
+ return false;
+ }
+
+ $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
+ return $response;
+ }
+
+ /**
+ * Removes the specified email address from the list of verified addresses.
+ *
+ * @param string email The email address to remove
+ * @return The request id for this request.
+ */
+ public function deleteVerifiedEmailAddress($email) {
+ $rest = new SimpleEmailServiceRequest($this, 'DELETE');
+ $rest->setParameter('Action', 'DeleteVerifiedEmailAddress');
+ $rest->setParameter('EmailAddress', $email);
+
+ $rest = $rest->getResponse();
+ if($rest->error === false && $rest->code !== 200) {
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ }
+ if($rest->error !== false) {
+ $this->__triggerError('deleteVerifiedEmailAddress', $rest->error);
+ return false;
+ }
+
+ $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
+ return $response;
+ }
+
+ /**
+ * Retrieves information on the current activity limits for this account.
+ * See http://docs.amazonwebservices.com/ses/latest/APIReference/API_GetSendQuota.html
+ *
+ * @return An array containing information on this account's activity limits.
+ */
+ public function getSendQuota() {
+ $rest = new SimpleEmailServiceRequest($this, 'GET');
+ $rest->setParameter('Action', 'GetSendQuota');
+
+ $rest = $rest->getResponse();
+ if($rest->error === false && $rest->code !== 200) {
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ }
+ if($rest->error !== false) {
+ $this->__triggerError('getSendQuota', $rest->error);
+ return false;
+ }
+
+ $response = array();
+ if(!isset($rest->body)) {
+ return $response;
+ }
+
+ $response['Max24HourSend'] = (string)$rest->body->GetSendQuotaResult->Max24HourSend;
+ $response['MaxSendRate'] = (string)$rest->body->GetSendQuotaResult->MaxSendRate;
+ $response['SentLast24Hours'] = (string)$rest->body->GetSendQuotaResult->SentLast24Hours;
+ $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
+
+ return $response;
+ }
+
+ /**
+ * Retrieves statistics for the last two weeks of activity on this account.
+ * See http://docs.amazonwebservices.com/ses/latest/APIReference/API_GetSendStatistics.html
+ *
+ * @return An array of activity statistics. Each array item covers a 15-minute period.
+ */
+ public function getSendStatistics() {
+ $rest = new SimpleEmailServiceRequest($this, 'GET');
+ $rest->setParameter('Action', 'GetSendStatistics');
+
+ $rest = $rest->getResponse();
+ if($rest->error === false && $rest->code !== 200) {
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ }
+ if($rest->error !== false) {
+ $this->__triggerError('getSendStatistics', $rest->error);
+ return false;
+ }
+
+ $response = array();
+ if(!isset($rest->body)) {
+ return $response;
+ }
+
+ $datapoints = array();
+ foreach($rest->body->GetSendStatisticsResult->SendDataPoints->member as $datapoint) {
+ $p = array();
+ $p['Bounces'] = (string)$datapoint->Bounces;
+ $p['Complaints'] = (string)$datapoint->Complaints;
+ $p['DeliveryAttempts'] = (string)$datapoint->DeliveryAttempts;
+ $p['Rejects'] = (string)$datapoint->Rejects;
+ $p['Timestamp'] = (string)$datapoint->Timestamp;
+
+ $datapoints[] = $p;
+ }
+
+ $response['SendDataPoints'] = $datapoints;
+ $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
+
+ return $response;
+ }
+
+
+ /**
+ * Given a SimpleEmailServiceMessage object, submits the message to the service for sending.
+ *
+ * @return An array containing the unique identifier for this message and a separate request id.
+ * Returns false if the provided message is missing any required fields.
+ */
+ public function sendEmail($sesMessage) {
+ if(!$sesMessage->validate()) {
+ return false;
+ }
+
+ $rest = new SimpleEmailServiceRequest($this, 'POST');
+ $rest->setParameter('Action', 'SendEmail');
+
+ $i = 1;
+ foreach($sesMessage->to as $to) {
+ $rest->setParameter('Destination.ToAddresses.member.'.$i, $to);
+ $i++;
+ }
+
+ if(is_array($sesMessage->cc)) {
+ $i = 1;
+ foreach($sesMessage->cc as $cc) {
+ $rest->setParameter('Destination.CcAddresses.member.'.$i, $cc);
+ $i++;
+ }
+ }
+
+ if(is_array($sesMessage->bcc)) {
+ $i = 1;
+ foreach($sesMessage->bcc as $bcc) {
+ $rest->setParameter('Destination.BccAddresses.member.'.$i, $bcc);
+ $i++;
+ }
+ }
+
+ if(is_array($sesMessage->replyto)) {
+ $i = 1;
+ foreach($sesMessage->replyto as $replyto) {
+ $rest->setParameter('ReplyToAddresses.member.'.$i, $replyto);
+ $i++;
+ }
+ }
+
+ $rest->setParameter('Source', $sesMessage->from);
+
+ if($sesMessage->returnpath != null) {
+ $rest->setParameter('ReturnPath', $sesMessage->returnpath);
+ }
+
+ if($sesMessage->subject != null && strlen($sesMessage->subject) > 0) {
+ $rest->setParameter('Message.Subject.Data', $sesMessage->subject);
+ if($sesMessage->subjectCharset != null && strlen($sesMessage->subjectCharset) > 0) {
+ $rest->setParameter('Message.Subject.Charset', $sesMessage->subjectCharset);
+ }
+ }
+
+
+ if($sesMessage->messagetext != null && strlen($sesMessage->messagetext) > 0) {
+ $rest->setParameter('Message.Body.Text.Data', $sesMessage->messagetext);
+ if($sesMessage->messageTextCharset != null && strlen($sesMessage->messageTextCharset) > 0) {
+ $rest->setParameter('Message.Body.Text.Charset', $sesMessage->messageTextCharset);
+ }
+ }
+
+ if($sesMessage->messagehtml != null && strlen($sesMessage->messagehtml) > 0) {
+ $rest->setParameter('Message.Body.Html.Data', $sesMessage->messagehtml);
+ if($sesMessage->messageHtmlCharset != null && strlen($sesMessage->messageHtmlCharset) > 0) {
+ $rest->setParameter('Message.Body.Html.Charset', $sesMessage->messageHtmlCharset);
+ }
+ }
+
+ $rest = $rest->getResponse();
+ if($rest->error === false && $rest->code !== 200) {
+ $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
+ }
+ if($rest->error !== false) {
+ $this->__triggerError('sendEmail', $rest->error);
+ return false;
+ }
+
+ $response['MessageId'] = (string)$rest->body->SendEmailResult->MessageId;
+ $response['RequestId'] = (string)$rest->body->ResponseMetadata->RequestId;
+ return $response;
+ }
+
+ /**
+ * Trigger an error message
+ *
+ * @internal Used by member functions to output errors
+ * @param array $error Array containing error information
+ * @return string
+ */
+ public function __triggerError($functionname, $error)
+ {
+ if($error == false) {
+ trigger_error(sprintf("SimpleEmailService::%s(): Encountered an error, but no description given", $functionname), E_USER_WARNING);
+ }
+ else if(isset($error['curl']) && $error['curl'])
+ {
+ trigger_error(sprintf("SimpleEmailService::%s(): %s %s", $functionname, $error['code'], $error['message']), E_USER_WARNING);
+ }
+ else if(isset($error['Error']))
+ {
+ $e = $error['Error'];
+ $message = sprintf("SimpleEmailService::%s(): %s - %s: %s\nRequest Id: %s\n", $functionname, $e['Type'], $e['Code'], $e['Message'], $error['RequestId']);
+ trigger_error($message, E_USER_WARNING);
+ }
+ }
+
+ /**
+ * Callback handler for 503 retries.
+ *
+ * @internal Used by SimpleDBRequest to call the user-specified callback, if set
+ * @param $attempt The number of failed attempts so far
+ * @return The retry delay in microseconds, or 0 to stop retrying.
+ */
+ public function __executeServiceTemporarilyUnavailableRetryDelay($attempt)
+ {
+ if(is_callable($this->__serviceUnavailableRetryDelayCallback)) {
+ $callback = $this->__serviceUnavailableRetryDelayCallback;
+ return $callback($attempt);
+ }
+ return 0;
+ }
+}
+
+final class SimpleEmailServiceRequest
+{
+ private $ses, $verb, $parameters = array();
+ public $response;
+
+ /**
+ * Constructor
+ *
+ * @param string $ses The SimpleEmailService object making this request
+ * @param string $action action
+ * @param string $verb HTTP verb
+ * @return mixed
+ */
+ function __construct($ses, $verb) {
+ $this->ses = $ses;
+ $this->verb = $verb;
+ $this->response = new STDClass;
+ $this->response->error = false;
+ }
+
+ /**
+ * Set request parameter
+ *
+ * @param string $key Key
+ * @param string $value Value
+ * @param boolean $replace Whether to replace the key if it already exists (default true)
+ * @return void
+ */
+ public function setParameter($key, $value, $replace = true) {
+ if(!$replace && isset($this->parameters[$key]))
+ {
+ $temp = (array)($this->parameters[$key]);
+ $temp[] = $value;
+ $this->parameters[$key] = $temp;
+ }
+ else
+ {
+ $this->parameters[$key] = $value;
+ }
+ }
+
+ /**
+ * Get the response
+ *
+ * @return object | false
+ */
+ public function getResponse() {
+