Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Support for scheduled calls and messages.

  • Loading branch information...
commit 90df7c4d5494b0d77d6ed0753fb0bd9337a1ef73 1 parent adddd7f
Chad Smith authored
36 README.md
View
@@ -1,6 +1,6 @@
# Outbound Flows for OpenVBX
-This plugin allows you to call out using flows or trigger outgoing calls or texts within another flow.
+This plugin allows you to call out using flows, schedule calls and messages, or trigger outgoing calls or texts within another flow.
## Installation
@@ -19,6 +19,24 @@ Once installed, OUTBOUND will appear in the OpenVBX sidebar
3. Select the Flow to call with
4. Select the caller ID (OpenVBX number) to call with
+### Schedule an outgoing call
+
+1. Click Schedule Flow in the OpenVBX sidebar
+2. Click Add Call
+3. Enter the number to call
+4. Enter the date and time to call
+5. Select the Flow to call with
+6. Select the caller ID (OpenVBX number) to call with
+
+### Schedule a text message
+
+1. Click Schedule Flow in the OpenVBX sidebar
+2. Click Add SMS
+3. Enter the number to call
+4. Enter the date and time to call
+5. Select the caller ID (OpenVBX number) to send with
+6. Enter the message to text
+
### Trigger a call to another number from a Flow
1. Add the New Call applet to a Call or SMS flow
@@ -34,3 +52,19 @@ Once installed, OUTBOUND will appear in the OpenVBX sidebar
3. Enter the message to text*
`* Use %caller% or %sender% to substitute the caller's number, %number% for the number called or %body% for the message body`
+
+## OpenVBX requirements ##
+
+This plugin requires a modified version of OpenVBX to allow for plugin hooks, subpages and cron jobs. Download the modified version from my fork [here][2].
+
+[2]: https://github.com/chadsmith/OpenVBX
+
+## Set Cron Job ##
+
+A cron job must be set to send scheduled calls and messages every 5 minutes. If you have access to crontab, enter:
+
+`*/5 * * * * /usr/bin/php5 /PATH_TO_OPENVBX/plugins/outbound/cron.php`
+
+If using cron source or a poorman's cron use:
+
+`http://YOUR_DOMAIN/hook/outbound/queue`
4 cron.php
View
@@ -0,0 +1,4 @@
+<?php
+$_SERVER['PATH_INFO'] = '/hook/outbound/queue';
+chdir(dirname(dirname(dirname(__FILE__))));
+require('index.php');
11 db.sql
View
@@ -0,0 +1,11 @@
+CREATE TABLE IF NOT EXISTS `outbound_queue` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `tenant` bigint(20) NOT NULL,
+ `number` varchar(15) NOT NULL,
+ `type` varchar(4) NOT NULL,
+ `time` int(11) NOT NULL,
+ `callerId` varchar(15) NOT NULL,
+ `data` text,
+ PRIMARY KEY (`id`),
+ KEY `tenant` (`tenant`,`type`,`time`)
+) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
12 outbound.php
View
@@ -2,24 +2,24 @@
$user = OpenVBX::getCurrentUser();
$tenant_id = $user->values['tenant_id'];
$ci =& get_instance();
- if(($recipient = $_POST['recipient'])&&($number = $_POST['number'])&&($id = intval($_POST['flow']))){
+ if(($recipient = $_POST['recipient']) && ($number = $_POST['number']) && ($id = intval($_POST['flow']))) {
require_once(APPPATH . 'libraries/twilio.php');
$ci->twilio = new TwilioRestClient($ci->twilio_sid, $ci->twilio_token, $ci->twilio_endpoint);
- if($id&&($flow = OpenVBX::getFlows(array('id' => $id, 'tenant_id' => $tenant_id)))&&$flow[0]->values['data'])
- $ci->twilio->request("Accounts/{$this->twilio_sid}/Calls", 'POST', array('From' => $number, 'To' => normalize_phone_to_E164($_POST['recipient']), 'Url' => site_url('twiml/start/voice/'.$id)));
+ if($id && ($flow = OpenVBX::getFlows(array('id' => $id, 'tenant_id' => $tenant_id))) && $flow[0]->values['data'])
+ $ci->twilio->request("Accounts/{$this->twilio_sid}/Calls", 'POST', array('From' => $number, 'To' => normalize_phone_to_E164($_POST['recipient']), 'Url' => site_url('twiml/start/voice/' . $id)));
}
$flows = OpenVBX::getFlows(array('tenant_id' => $tenant_id));
?>
<style>
- .vbx-subscriptions form {
+ .vbx-outbound form {
padding:20px 5%;
}
</style>
<div class="vbx-content-main">
<div class="vbx-content-menu vbx-content-menu-top">
<h2 class="vbx-content-heading">Start Flow</h2>
- </div><!-- .vbx-content-menu -->
- <div class="vbx-table-section vbx-subscriptions">
+ </div>
+ <div class="vbx-table-section vbx-outbound">
<form method="post" action="">
<fieldset class="vbx-input-container">
<?php if(count($callerid_numbers)): ?>
17 plugin.json
View
@@ -1,12 +1,25 @@
{
"name" : "Outbound Flows",
"author" : "Chad Smith <chad@nospam.me>",
- "description" : "Lets you call out with a flow or start calls and messages (to someone else) within a flow.",
+ "description" : "Lets you call out with a flow now or later. Start or schedule calls and messages within a flow.",
"url" : "http://twitter.com/chadsmith",
"links" : [{
"menu" : "Outbound",
"url" : "outbound",
"script" : "outbound.php",
"label" : "Start Flow"
+ },
+ {
+ "menu" : "Outbound",
+ "url" : "outbound/schedule",
+ "script" : "schedule.php",
+ "label" : "Schedule Flow"
+ },
+ {
+ "menu" : "Outbound",
+ "url" : "outbound/queue",
+ "script" : "queue.php",
+ "label" : "Manage Queue",
+ "hook" : true
}]
-}
+}
18 queue.js
View
@@ -0,0 +1,18 @@
+$(function() {
+ $('.vbx-queue .event a.delete').click(function() {
+ var $event = $(this).parent().parent().parent(), id = $event.attr('id'), type = $event.children().children('span').eq(1).text();
+ if(confirm('You are about to delete a' + ('sms' == type ? 'n ' : ' ') + type + ' to ' + $event.children().children('span').eq(0).text()+'.'))
+ $.ajax({
+ type: 'POST',
+ url: window.location,
+ data: {
+ remove: id.match(/([\d]+)/)[1]
+ },
+ success: function() {
+ $event.hide(500);
+ },
+ dataType: 'text'
+ });
+ return false
+ });
+})
96 queue.php
View
@@ -0,0 +1,96 @@
+<?php
+ $ci =& get_instance();
+ if(defined("HOOK") AND HOOK) {
+ set_time_limit(0);
+ $events = $ci->db->query(sprintf('SELECT id, tenant, number, type, time, callerId, data FROM outbound_queue WHERE time < %d ORDER BY time ASC', time()))->result();
+ if(count($events)) {
+ require_once(APPPATH . 'libraries/twilio.php');
+ foreach($events as $event) {
+ $event->data = json_decode($event->data);
+ $tenant = $ci->settings->get_tenant_by_id($event->tenant);
+ $twilio_sid = $ci->settings->get('twilio_sid', $event->tenant);
+ $twilio_token = $ci->settings->get('twilio_token', $event->tenant);
+ $twilio = new TwilioRestClient($twilio_sid, $twilio_token, $ci->twilio_endpoint);
+ if('sms' == $event->type)
+ $twilio->request("Accounts/{$twilio_sid}/SMS/Messages", 'POST', array('From' => $event->callerId, 'To' => $event->number, 'Body' => $event->data->message));
+ else
+ $twilio->request("Accounts/{$twilio_sid}/Calls", 'POST', array('From' => $event->callerId, 'To' => $event->number, 'Url' => site_url(($tenant->url_prefix ? $tenant->url_prefix . '/' : '') . 'twiml/start/voice/' . $event->data->id)));
+ $ci->db->delete('queue', array('id' => $event->id));
+ }
+ }
+ die;
+ }
+ $user = OpenVBX::getCurrentUser();
+ $tenant_id = $user->values['tenant_id'];
+ $queries = explode(';', file_get_contents(dirname(__FILE__) . '/db.sql'));
+ foreach($queries as $query)
+ if(trim($query))
+ $ci->db->query($query);
+ if($remove = intval($_POST['remove'])) {
+ $ci->db->delete('outbound_queue', array('id' => $remove, 'tenant' => $tenant_id));
+ die;
+ }
+ $events = $ci->db->query(sprintf('SELECT id, number, type, time, callerId, data FROM outbound_queue WHERE tenant=%d ORDER BY time ASC', $tenant_id))->result();
+ OpenVBX::addJS('queue.js');
+?>
+<style>
+ .vbx-queue h3 {
+ font-size: 16px;
+ font-weight: bold;
+ margin-top: 0;
+ }
+ .vbx-queue .event {
+ clear: both;
+ width: 95%;
+ overflow: hidden;
+ margin: 0 auto;
+ padding: 5px 0;
+ border-bottom: 1px solid #eee;
+ }
+ .vbx-queue .event span {
+ display: inline-block;
+ width: 20%;
+ text-align: center;
+ float: left;
+ vertical-align: middle;
+ line-height: 24px;
+ }
+ .vbx-queue a.delete {
+ display: inline-block;
+ height: 24px;
+ width: 24px;
+ text-indent: -999em;
+ background: transparent url(/assets/i/action-icons-sprite.png) no-repeat -68px 0;
+ }
+</style>
+<div class="vbx-content-main">
+ <div class="vbx-content-menu vbx-content-menu-top">
+ <h2 class="vbx-content-heading">Scheduled Events</h2>
+ </div>
+ <div class="vbx-table-section vbx-queue">
+ <div class="event">
+ <h3>
+ <span>Number</span>
+ <span>Type</span>
+ <span>Time</span>
+ <span>Caller ID</span>
+ <span>Delete</span>
+ </h3>
+ </div>
+<?php foreach($events as $event): $event->data = json_decode($event->data); ?>
+ <div class="event" id="event_<?php echo $event->id; ?>">
+ <p>
+ <span><?php echo $event->number; ?></span>
+<?php if('sms' == $event->type): ?>
+ <span title="<?php echo htmlentities($event->data->message); ?>">SMS</span>
+<?php else: ?>
+ <span><?php echo htmlentities($event->data->name); ?></span>
+<?php endif; ?>
+ <span><?php echo date('j-M-Y g:i:sa', $event->time); ?></span>
+ <span><?php echo $event->callerId; ?></span>
+ <span><a href="" class="delete">X</a></span>
+ </p>
+ </div>
+<?php endforeach; ?>
+ </div>
+</div>
9 schedule.js
View
@@ -0,0 +1,9 @@
+$(function() {
+ $('.add-button').click(function() {
+ var $form = $('form.' + $(this).attr('id'));
+ $('.vbx-schedule form').not($form).slideUp();
+ $form.slideToggle();
+ return false;
+ });
+ $('.time').timePicker({ step: 05, show24Hours: false });
+})
160 schedule.php
View
@@ -0,0 +1,160 @@
+<?php
+ $user = OpenVBX::getCurrentUser();
+ $tenant_id = $user->values['tenant_id'];
+ $ci =& get_instance();
+ $queries = explode(';', file_get_contents(dirname(__FILE__) . '/db.sql'));
+ foreach($queries as $query)
+ if(trim($query))
+ $ci->db->query($query);
+ if($_POST['type']) {
+ $type = $_POST['type'];
+ $number = normalize_phone_to_E164($_POST['number']);
+ $callerId = normalize_phone_to_E164($_POST['callerId']);
+ $time = strtotime($_POST['date'] . ' ' . $_POST['time']);
+ if('sms' == $type && $_POST['message']) {
+ $ci->db->insert('outbound_queue', array(
+ 'tenant' => $tenant_id,
+ 'number' => $number,
+ 'type' => $type,
+ 'time' => $time,
+ 'callerId' => $callerId,
+ 'data' => json_encode(array(
+ 'message' => $_POST['message']
+ ))
+ ));
+ }
+ elseif('call' == $type) {
+ $flow = OpenVBX::getFlows(array('id' => $_POST['flow'], 'tenant_id' => $tenant_id));
+ if($flow && $flow[0]->values['data'])
+ $ci->db->insert('outbound_queue', array(
+ 'tenant' => $tenant_id,
+ 'number' => $number,
+ 'type' => $type,
+ 'time' => $time,
+ 'callerId' => $callerId,
+ 'data' => json_encode(array(
+ 'id' => $flow[0]->values['id'],
+ 'name' => $flow[0]->values['name']
+ ))
+ ));
+ }
+ }
+ $flows = OpenVBX::getFlows(array('tenant_id' => $tenant_id));
+ OpenVBX::addJS('schedule.js');
+?>
+<style>
+ .vbx-schedule form {
+ display: none;
+ padding: 20px 5%;
+ background: #eee;
+ border-bottom: 1px solid #ccc;
+ }
+ .vbx-schedule h3 {
+ font-size: 16px;
+ font-weight: bold;
+ margin-top: 0;
+ }
+</style>
+<div class="vbx-content-main">
+ <div class="vbx-content-menu vbx-content-menu-top">
+ <h2 class="vbx-content-heading">Schedule Flow</h2>
+ <ul class="vbx-menu-items-right">
+ <li class="menu-item">
+ <button id="schedule-call" class="inline-button add-button"><span>Add Call</span></button>
+ </li>
+ <li class="menu-item">
+ <button id="schedule-sms" class="inline-button add-button"><span>Add SMS</span></button>
+ </li>
+ </ul>
+ </div>
+ <div class="vbx-table-section vbx-schedule">
+ <form class="schedule-sms" method="post" action="">
+ <h3>Schedule SMS</h3>
+ <fieldset class="vbx-input-container">
+<?php if(count($callerid_numbers)): ?>
+ <p>
+ <label class="field-label">Number<br/>
+ <input type="text" name="number" class="medium" />
+ </label>
+ </p>
+ <p>
+ <label class="field-label">Date<br/>
+ <input type="text" name="date" class="medium" value="<?php echo date('j-M-Y'); ?>" />
+ </label>
+ </p>
+ <p>
+ <label class="field-label">Time<br/>
+ <input type="text" name="time" class="time medium" value="12:00 AM" />
+ </label>
+ </p>
+ <p>
+ <label class="field-label">Caller ID<br/>
+ <select name="callerId" class="medium">
+<?php foreach($callerid_numbers as $number): ?>
+ <option value="<?php echo $number->phone; ?>"><?php echo $number->name; ?></option>
+<?php endforeach; ?>
+ </select>
+ </label>
+ </p>
+ <p><input type="hidden" name="type" value="sms" /></p>
+ <p>
+ <label class="field-label">Message
+ <textarea rows="20" cols="100" name="message" class="medium"></textarea>
+ </label>
+ </p>
+ <p><button type="submit" class="submit-button"><span>Add</span></button></p>
+<?php else: ?>
+ <p>You do not have any phone numbers!</p>
+<?php endif; ?>
+ </fieldset>
+ </form>
+ <form class="schedule-call" method="post" action="">
+ <h3>Schedule Call</h3>
+ <fieldset class="vbx-input-container">
+<?php if(count($callerid_numbers)): ?>
+ <p>
+ <label class="field-label">Number<br/>
+ <input type="text" name="number" class="medium" />
+ </label>
+ </p>
+ <p>
+ <label class="field-label">Date<br/>
+ <input type="text" name="date" class="medium" value="<?php echo date('j-M-Y'); ?>" />
+ </label>
+ </p>
+ <p>
+ <label class="field-label">Time<br/>
+ <input type="text" name="time" class="time medium" value="12:00 AM" />
+ </label>
+ </p>
+<?php if(count($flows)): ?>
+ <p>
+ <label class="field-label">Flow<br/>
+ <select name="flow" class="medium">
+<?php foreach($flows as $flow): ?>
+ <option value="<?php echo $flow->values['id']; ?>"><?php echo $flow->values['name']; ?></option>
+<?php endforeach; ?>
+ </select>
+ </label>
+ </p>
+ <p>
+ <label class="field-label">Caller ID<br/>
+ <select name="callerId" class="medium">
+<?php foreach($callerid_numbers as $number): ?>
+ <option value="<?php echo $number->phone; ?>"><?php echo $number->name; ?></option>
+<?php endforeach; ?>
+ </select>
+ </label>
+ </p>
+ <p><input type="hidden" name="type" value="call" /></p>
+ <p><button type="submit" class="submit-button"><span>Add</span></button></p>
+<?php else: ?>
+ <p>You do not have any flows!</p>
+<?php endif; ?>
+<?php else: ?>
+ <p>You do not have any phone numbers!</p>
+<?php endif; ?>
+ </fieldset>
+ </form>
+ </div>
+</div>
Please sign in to comment.
Something went wrong with that request. Please try again.