From a41b769c6bedf4b8bdea3a4fb5bf4d07f2d7e1fa Mon Sep 17 00:00:00 2001 From: David Kobia Date: Tue, 8 Oct 2013 16:35:25 -0400 Subject: [PATCH 1/3] Added SwiftMailer --- .gitmodules | 3 +++ modules/email | 1 + 2 files changed, 4 insertions(+) create mode 160000 modules/email diff --git a/.gitmodules b/.gitmodules index be32b5f..ae52430 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,3 +28,6 @@ [submodule "KO3-Event"] path = modules/KO3-Event url = git://github.com/thinkclay/KO3-Event.git +[submodule "modules/email"] + path = modules/email + url = git://github.com/shadowhand/email.git diff --git a/modules/email b/modules/email new file mode 160000 index 0000000..6005b39 --- /dev/null +++ b/modules/email @@ -0,0 +1 @@ +Subproject commit 6005b3965b395b7fae5ac50e9fa58452bfcf6453 From 5325b4b4f7c30687fb8732d69192633ebb374e37 Mon Sep 17 00:00:00 2001 From: David Kobia Date: Tue, 8 Oct 2013 20:42:49 -0400 Subject: [PATCH 2/3] Added Email Pings * Settings for incoming/outgoing email * Send sms, email or both * Add tagline to SMS's * Customize email body for email pings * refs #38 --- README.md | 2 +- application/classes/Controller/Messages.php | 93 +++++++++---- application/classes/Controller/Settings.php | 10 ++ application/classes/Model/Message.php | 41 +++++- application/classes/PingApp/Ping.php | 80 ++++++++++- application/config/modules.template | 1 + application/messages/models/message.php | 15 +++ application/migrations/1/20131008180256.php | 25 ++++ .../tests/classes/models/MessageModelTest.php | 125 ++++++++++++++++++ .../{js/send.php => messages/js/new.php} | 2 +- .../pages/{send.php => messages/new.php} | 26 +++- .../views/pages/settings/customize.php | 52 ++++++++ application/views/pages/settings/email.php | 89 +++++++++++-- application/views/pages/settings/index.php | 1 + 14 files changed, 513 insertions(+), 49 deletions(-) create mode 100644 application/messages/models/message.php create mode 100644 application/migrations/1/20131008180256.php create mode 100644 application/tests/classes/models/MessageModelTest.php rename application/views/pages/{js/send.php => messages/js/new.php} (92%) rename application/views/pages/{send.php => messages/new.php} (53%) create mode 100644 application/views/pages/settings/customize.php diff --git a/README.md b/README.md index 1b5168b..a6e3b8c 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ To install the platform on your computer/server, the target system must meet the 1. Open a terminal 2. Clone the project ```git clone https://github.com/ushahidi/pingapp.git``` -3. Update the submodules ```git submodule update --init``` +3. Update the submodules ```git submodule update --init --recursive``` 4. Install the Twilio packages using [Composer](http://getcomposer.org) by running ```composer install```. diff --git a/application/classes/Controller/Messages.php b/application/classes/Controller/Messages.php index b925a5f..022e908 100644 --- a/application/classes/Controller/Messages.php +++ b/application/classes/Controller/Messages.php @@ -9,6 +9,8 @@ class Controller_Messages extends Controller_PingApp { * @var array */ private $_errors = array(); + + private $_post = NULL; /** * Creates a new message @@ -16,13 +18,13 @@ class Controller_Messages extends Controller_PingApp { */ public function action_new() { - $this->template->content = View::factory('pages/send') + $this->template->content = View::factory('pages/messages/new') ->bind('user', $this->user) - ->bind('post', $post) + ->bind('post', $this->_post) ->bind('errors', $this->_errors) ->bind('done', $done); - $this->template->footer->js = View::factory('pages/js/send'); + $this->template->footer->js = View::factory('pages/messages/js/new'); if ($this->request->method() === 'POST') { @@ -45,6 +47,8 @@ public function action_new() */ private function _broadcast_message() { + $this->_post = $this->request->post(); + // Get the SMS provider to use try { @@ -64,6 +68,20 @@ private function _broadcast_message() $this->_errors[] = 'No recipients selected'; return FALSE; } + + // Send Type Included? + if ( ! $this->request->post('type') OR ! is_array($this->request->post('type')) ) + { + $this->_errors[] = 'No message type selected'; + return FALSE; + } + + // Type is Email or SMS? + if ( ! in_array('email', $this->request->post('type')) AND ! in_array('sms', $this->request->post('type'))) + { + $this->_errors[] = 'Message type must be SMS or Email'; + return FALSE; + } // If EVERYONE is selected, ignore the others $operator = 'IN'; @@ -73,7 +91,6 @@ private function _broadcast_message() $recipients = 0; } $contacts = ORM::factory('Contact') - ->where('type', '=', 'phone') ->join('contacts_people')->on('contact.id', '=', 'contacts_people.contact_id') ->join('people')->on('people.id', '=', 'contacts_people.person_id') ->where('people.user_id', '=', $this->user->id) @@ -82,37 +99,57 @@ private function _broadcast_message() if ( ! $contacts->count() ) { - $this->_errors[] = 'None of your recipients have phone numbers to send to'; + $this->_errors[] = 'No recipients to send to'; return FALSE; } - // Create the message - $message = ORM::factory('Message'); try { - // Set values and save - $message->values(array( - 'message' => $this->request->post('message'), - 'user_id' => $this->user->id, - 'type' => 'sms' - )); - $message->save(); - - // Save Ping - foreach ($contacts as $contact) + foreach ($this->request->post('type') as $type) { - $ping = ORM::factory('Ping'); - $ping->values(array( - 'message_id' => $message->id, - 'tracking_id' => '0', - 'type' => 'sms', - 'contact_id' => $contact->id, - 'provider' => strtolower(PingApp_SMS_Provider::$sms_provider), - 'status' => 'pending', - 'sent' => 0 + // Create the message + $$type = ORM::factory('Message'); + + // Set values and save + ${$type}->values(array( + 'type' => $type, + 'message' => $this->request->post('message'), + 'title' => $this->request->post('title'), + 'user_id' => $this->user->id )); - $ping->save(); - } + ${$type}->check(); + } + + foreach ($this->request->post('type') as $type) + { + ${$type}->save(); + + // Save Ping + foreach ($contacts as $contact) + { + $ping = ORM::factory('Ping'); + $ping->values(array( + 'message_id' => ${$type}->id, + 'tracking_id' => '0', + 'contact_id' => $contact->id, + 'provider' => 0, + 'status' => 'pending', + 'sent' => 0 + )); + + if ($type == 'sms' AND $contact->type == 'phone') + { + $ping->type = $type; + $ping->save(); + } + + if ($type == 'email' AND $contact->type == 'email') + { + $ping->type = $type; + $ping->save(); + } + } + } } catch (ORM_Validation_Exception $e) { diff --git a/application/classes/Controller/Settings.php b/application/classes/Controller/Settings.php index e228e6f..f7317ef 100644 --- a/application/classes/Controller/Settings.php +++ b/application/classes/Controller/Settings.php @@ -39,6 +39,16 @@ public function action_email() $this->_action('email'); } + /** + * Customize Messages + * + * @return void + */ + public function action_customize() + { + $this->_action('customize'); + } + /** * Edit TOS Settings * diff --git a/application/classes/Model/Message.php b/application/classes/Model/Message.php index de31029..bda1151 100644 --- a/application/classes/Model/Message.php +++ b/application/classes/Model/Message.php @@ -17,6 +17,13 @@ class Model_Message extends ORM { 'pings' => array(), ); + /** + * A message belongs to a user + */ + protected $_belongs_to = array( + 'user' => array() + ); + // Insert/Update Timestamps protected $_created_column = array('column' => 'created', 'format' => 'Y-m-d H:i:s'); protected $_updated_column = array('column' => 'updated', 'format' => 'Y-m-d H:i:s'); @@ -24,11 +31,20 @@ class Model_Message extends ORM { public function rules() { return array( + 'title' => array( + array('min_length', array(':value', 2)), + array('max_length', array(':value', 120)), + array(array($this, 'valid_title'), array(':validation', ':field')), + ), 'message' => array( array('not_empty'), array('min_length', array(':value', 2)), - array('max_length', array(':value', 140)), - ) + array('max_length', array(':value', 120)), + ), + 'type' => array( + array('not_empty'), + array('in_array', array(':value', array('sms', 'email', 'twitter')) ), + ), ); } @@ -40,9 +56,30 @@ public function rules() public function filters() { return array( + 'title' => array( + array('trim'), + ), 'message' => array( array('trim'), ), ); } + + /** + * Validate Message Against Message Type + * + * @param array $validation + * @param string $field field name + * @param [type] [varname] [description] + * @return void + */ + public function valid_title($validation, $field) + { + // Valid Email? + if ( isset($validation['type']) AND + $validation['type'] == 'email' AND ! $validation[$field] ) + { + $validation->error($field, 'invalid_title'); + } + } } \ No newline at end of file diff --git a/application/classes/PingApp/Ping.php b/application/classes/PingApp/Ping.php index 15b32be..98dd9b8 100644 --- a/application/classes/PingApp/Ping.php +++ b/application/classes/PingApp/Ping.php @@ -153,7 +153,9 @@ private static function _sms($ping, $contact) Kohana::$log->add(Log::ERROR, $e->getMessage()); } - if (($tracking_id = $provider->send($contact->contact, $ping->message->message)) !== FALSE) + $tagline = PingApp_Settings::get('message_sms'); + + if (($tracking_id = $provider->send($contact->contact, $ping->message->message.' '.$tagline)) !== FALSE) { $ping->tracking_id = $tracking_id; $ping->provider = $provider::$sms_provider; // Update the provider in case its changed @@ -170,6 +172,80 @@ private static function _sms($ping, $contact) private static function _email($ping, $contact) { - + $driver = PingApp_Settings::get('email_outgoing_type'); + $options = array( + 'hostname' => PingApp_Settings::get('email_outgoing_host'), + 'port' => PingApp_Settings::get('email_outgoing_port'), + 'encryption' => (PingApp_Settings::get('email_outgoing_security') != 'none') + ? PingApp_Settings::get('email_outgoing_security') : '', + 'username' => PingApp_Settings::get('email_outgoing_username'), + 'password' => PingApp_Settings::get('email_outgoing_password') + ); + + $tracking_id = self::_tracking_id(); + + $config = Kohana::$config->load('email'); + $config->set('driver', $driver); + $config->set('options', $options); + + $title = $ping->message->title; + + $person = $contact->people->order_by('created', 'ASC')->find(); + + $sender_name = $ping->message->user->first_name.' '.$ping->message->user->last_name; + $sender_email = $ping->message->user->email; + $sender = ($sender_name) ? $sender_name : $sender_email; + + $prepend = 'Ping requested by: '.$sender."\n\n"; + + $body = PingApp_Settings::get('message_email'); + $body = str_replace('{{name}}', $person->name, $body); + $body = str_replace('{{message}}', $ping->message->message, $body); + $body = $prepend.$body."\n\n\n".' [{'.$tracking_id.'}]';$body."\n\n\n".' [{'.$tracking_id.'}]'; + + $from = PingApp_Settings::get('email_from'); + $from_name = PingApp_Settings::get('email_from_name'); + + try + { + $result = Email::factory($title, $body) + ->to($contact->contact) + ->from($from, $sender) + ->send(); + + $ping->provider = (PingApp_Settings::get('email_outgoing_host')) ? PingApp_Settings::get('email_outgoing_host') : 'email'; + $ping->tracking_id = $tracking_id; + $ping->sent = 1; + $ping->save(); + } + catch (Exception $e) + { + // Failed + $ping->status = 'failed'; + $ping->save(); + + Kohana::$log->add(Log::ERROR, $e->getMessage()); + } + } + + private static function _tracking_id() + { + $unique = FALSE; + $code = NULL; + while ( ! $unique) + { + $code = Text::random('alnum', 32); + $ping = ORM::factory('Ping') + ->where('type', '=', 'email') + ->where('tracking_id', '=', $code) + ->find(); + + if ( ! $ping->loaded() ) + { + $unique = TRUE; + } + } + + return $code; } } \ No newline at end of file diff --git a/application/config/modules.template b/application/config/modules.template index 30066e2..44890f7 100644 --- a/application/config/modules.template +++ b/application/config/modules.template @@ -11,5 +11,6 @@ return array( 'minion' => MODPATH.'minion', // CLI Tasks 'migrations' => MODPATH.'migrations', // Minion Migrations 'KO3-Event' => MODPATH.'KO3-Event', // Event Module + 'email' => MODPATH.'email', // Email Module 'crowdmapid' => MODPATH.'crowdmapid', // CrowdmapID Authentication ); diff --git a/application/messages/models/message.php b/application/messages/models/message.php new file mode 100644 index 0000000..73e0943 --- /dev/null +++ b/application/messages/models/message.php @@ -0,0 +1,15 @@ + + * @package Ushahidi\Application\Messages + * @copyright Ushahidi - http://www.ushahidi.com + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License Version 3 (GPLv3) + */ +return array +( + 'title' => array( + 'invalid_title' => 'a title is required for email pings', + ), +); \ No newline at end of file diff --git a/application/migrations/1/20131008180256.php b/application/migrations/1/20131008180256.php new file mode 100644 index 0000000..92376a3 --- /dev/null +++ b/application/migrations/1/20131008180256.php @@ -0,0 +1,25 @@ +query(NULL, "ALTER TABLE `messages` ADD `title` VARCHAR(255) NULL DEFAULT NULL AFTER `user_id`;"); + } + + /** + * Run queries needed to remove this migration + * + * @param Kohana_Database $db Database connection + */ + public function down(Kohana_Database $db) + { + $db->query(NULL, "ALTER TABLE `messages` DROP `title`;"); + } + +} diff --git a/application/tests/classes/models/MessageModelTest.php b/application/tests/classes/models/MessageModelTest.php new file mode 100644 index 0000000..37efc68 --- /dev/null +++ b/application/tests/classes/models/MessageModelTest.php @@ -0,0 +1,125 @@ + + * @package Ushahidi\Application\Tests + * @copyright Ushahidi - http://www.ushahidi.com + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License Version 3 (GPLv3) + */ + +class MessageModelTest extends Unittest_TestCase { + /** + * Provider for test_validate_valid + * + * @access public + * @return array + */ + public function provider_validate_valid() + { + return array( + array( + // Valid message data + array( + 'content' => 'I am okay', + 'type' => 'sms', + ) + ), + array( + // Valid message data + array( + 'content' => ' I am not okay', + 'type' => 'email', + ) + ), + array( + // Valid message data + array( + 'content' => 'okay', + 'type' => 'sms', + ) + ) + ); + } + + /** + * Provider for test_validate_invalid + * + * @access public + * @return array + */ + public function provider_validate_invalid() + { + return array( + array( + // Invalid message data set 1 - No Data + array() + ), + array( + // Invalid message data set 2 - Invalid Type + array( + 'content' => 'I am okay', + 'type' => 'facebook', + ) + ), + array( + // Invalid message data set 2 - Missing Content + array( + 'content' => ' ', + 'type' => 'email', + ) + ), + array( + // Invalid message data set 4 - Missing Type + array( + 'content' => 'I am okay', + ) + ) + ); + } + + /** + * Test Validate Valid Entries + * + * @dataProvider provider_validate_valid + * @return void + */ + public function test_validate_valid($set) + { + $message = ORM::factory('Message'); + $message->values($set); + + try + { + $message->check(); + } + catch (ORM_Validation_Exception $e) + { + $this->fail('This entry qualifies as invalid when it should be valid: '. json_encode($e->errors('models'))); + } + } + + /** + * Test Validate Invalid Entries + * + * @dataProvider provider_validate_invalid + * @return void + */ + public function test_validate_invalid($set) + { + $message = ORM::factory('Message'); + $message->values($set); + + try + { + $message->check(); + } + catch (ORM_Validation_Exception $e) + { + return; + } + + $this->fail('This entry qualifies as valid when it should be invalid'); + } +} \ No newline at end of file diff --git a/application/views/pages/js/send.php b/application/views/pages/messages/js/new.php similarity index 92% rename from application/views/pages/js/send.php rename to application/views/pages/messages/js/new.php index 484e8bb..d558531 100644 --- a/application/views/pages/js/send.php +++ b/application/views/pages/messages/js/new.php @@ -19,5 +19,5 @@ function setCount(src, elem) { $(document).ready(function() { var elem = $("#chars"); - $("#message").limiter(140, elem); + $("#message").limiter(120, elem); }); \ No newline at end of file diff --git a/application/views/pages/send.php b/application/views/pages/messages/new.php similarity index 53% rename from application/views/pages/send.php rename to application/views/pages/messages/new.php index a41d989..164e6a0 100644 --- a/application/views/pages/send.php +++ b/application/views/pages/messages/new.php @@ -20,16 +20,36 @@ New Message
- + "recipients[]", "minlength" => "3", "class" => "medium recipients-dropdown", "multiple" => "multiple")); ?>
+
+
+

+
+
+

+
+
+ +
+
+ + "120")); ?> +
+
+
- + "message")); ?> -
140
+
120
diff --git a/application/views/pages/settings/customize.php b/application/views/pages/settings/customize.php new file mode 100644 index 0000000..b4e70dd --- /dev/null +++ b/application/views/pages/settings/customize.php @@ -0,0 +1,52 @@ +

Customize Messages

+ + +
+ +
+ + × +
+ + + +
+ + × +
+ + + 'custom')); ?> + +
+ SMS Tagline +
+
+ "30")); ?> +
+
+
+ +
+ Email Template +
+
+ {{name}} - Recipient Name
+ {{message}} - Message Content

+
+
+
+
+ +
+
+
+ +
+ +
+ diff --git a/application/views/pages/settings/email.php b/application/views/pages/settings/email.php index 9817dc1..f3bb772 100644 --- a/application/views/pages/settings/email.php +++ b/application/views/pages/settings/email.php @@ -23,44 +23,109 @@ 'custom')); ?>
- Email + Email Provider
+
- > + > - > + >
+ +
+ +
+ > + + + > + + +
+
+
+ +
+
+ + +
- 'Sendmail', 'smtp' => 'SMTP'), (isset($post['settings']['email_type'])) ? $post['settings']['email_type'] : '', array("class" => "medium")) ;?> + +
+
+ +
+ Outgoing Email Settings
- - + + 'Sendmail', 'smtp' => 'SMTP'), (isset($post['settings']['email_outgoing_type'])) ? $post['settings']['email_outgoing_type'] : '', array("class" => "medium")) ;?>
- - + +
+ + +
+
+
+
'NO', 'yes' => 'YES'), (isset($post['settings']['email_smtp_auth'])) ? $post['settings']['email_smtp_auth'] : '', array("class" => "medium")) ;?>
+
+ + 'None', 'tls' => 'TLS', 'ssl' => 'SSL'), (isset($post['settings']['email_outgoing_security'])) ? $post['settings']['email_outgoing_security'] : '', array("class" => "medium")) ;?> +
+
+ + +
+
+ + +
+
+ +
+ Incoming Email Settings
-
+
+ + 'IMAP', 'pop3'=>'POP3'), (isset($post['settings']['email_incoming_type'])) ? $post['settings']['email_incoming_type'] : '', array("class" => "medium")) ;?> +
+
+ + +
+
+ + +
+
+
+
+ + 'None', 'tls' => 'TLS', 'ssl' => 'SSL'), (isset($post['settings']['email_incoming_security'])) ? $post['settings']['email_incoming_security'] : '', array("class" => "medium")) ;?> +
+
- +
-
+
- +
diff --git a/application/views/pages/settings/index.php b/application/views/pages/settings/index.php index 686e2aa..fd84581 100644 --- a/application/views/pages/settings/index.php +++ b/application/views/pages/settings/index.php @@ -6,5 +6,6 @@
SMS
Email
+
Customize Messages
Terms of Service
\ No newline at end of file From 3d6f6a92bf624083b406b076a63de7050892d373 Mon Sep 17 00:00:00 2001 From: David Kobia Date: Tue, 8 Oct 2013 20:48:26 -0400 Subject: [PATCH 3/3] Added Message Model Unit Tests --- .../tests/classes/models/MessageModelTest.php | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/application/tests/classes/models/MessageModelTest.php b/application/tests/classes/models/MessageModelTest.php index 37efc68..1eecc43 100644 --- a/application/tests/classes/models/MessageModelTest.php +++ b/application/tests/classes/models/MessageModelTest.php @@ -22,21 +22,23 @@ public function provider_validate_valid() array( // Valid message data array( - 'content' => 'I am okay', - 'type' => 'sms', + 'title' => 'Are you okay?', + 'message' => 'this is a test message', + 'type' => 'email', ) ), array( // Valid message data array( - 'content' => ' I am not okay', - 'type' => 'email', + 'message' => 'Are you okay?', + 'type' => 'sms', ) ), array( // Valid message data array( - 'content' => 'okay', + 'title' => 'Title not needed', + 'message' => 'this is an sms message', 'type' => 'sms', ) ) @@ -59,21 +61,23 @@ public function provider_validate_invalid() array( // Invalid message data set 2 - Invalid Type array( - 'content' => 'I am okay', + 'title' => 'Title not needed', + 'message' => 'this is an sms message', 'type' => 'facebook', ) ), array( - // Invalid message data set 2 - Missing Content + // Invalid message data set 2 - Missing Title in Email array( - 'content' => ' ', + 'message' => 'this is an email message', 'type' => 'email', ) ), array( // Invalid message data set 4 - Missing Type array( - 'content' => 'I am okay', + 'title' => 'Are you okay?', + 'message' => 'this is a test message', ) ) );