Browse Source

really basic interface for broadcasting SMS messages

master
Aaron Parecki 7 years ago
parent
commit
e4c36c6db4
No known key found for this signature in database GPG Key ID: 276C2817346D6056
10 changed files with 309 additions and 3 deletions
  1. +114
    -0
      app/Http/Controllers/SmsController.php
  2. +12
    -0
      app/PhoneNumber.php
  3. +16
    -0
      app/SentMessage.php
  4. +2
    -1
      composer.json
  5. +47
    -1
      composer.lock
  6. +33
    -0
      database/migrations/2017_07_08_032850_BulkSMS.php
  7. +33
    -0
      database/migrations/2017_07_08_033123_sms_log.php
  8. +2
    -1
      resources/views/layouts/app.blade.php
  9. +46
    -0
      resources/views/sms.blade.php
  10. +4
    -0
      routes/web.php

+ 114
- 0
app/Http/Controllers/SmsController.php View File

@ -0,0 +1,114 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use DB, Twitter;
use \App\PhoneNumber;
class SMSController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
}
private function max_message_length($numbers) {
$max_name_length = 0;
foreach($numbers as $n) {
if(strlen($n->name) > $max_name_length) {
$max_name_length = strlen($n->name);
}
}
return 160 - $max_name_length - 1;
}
public function index() {
$this->authorize('admin');
$numbers = PhoneNumber::orderBy('name')->get();
$maxlen = $this->max_message_length($numbers);
return view('sms', [
'numbers' => $numbers,
'maxlen' => $maxlen
]);
}
public function save(Request $request) {
$this->authorize('admin');
if(preg_match_all('/(.+)\s+([0-9\-]+)/', $request->input('input'), $matches)) {
$contacts = [];
$errors = [];
foreach($matches[1] as $i=>$name) {
if(preg_match('/^1?(\d{3})-?(\d{3})-?(\d{4})$/', $matches[2][$i], $pm)) {
$contacts[] = [trim($name), $pm[1].'-'.$pm[2].'-'.$pm[3]];
} else {
$errors[] = $matches[0][$i];
}
}
if(count($errors)) {
$request->session()->flash('status', 'danger');
$request->session()->flash('status-message', 'There was a problem with some of your entries. No numbers were changed. The lines below had errors:<br>'.implode('<br>', array_map('htmlspecialchars', $errors)));
} else {
DB::table('phone_numbers')->delete();
foreach($contacts as $c) {
$p = new PhoneNumber;
$p->name = $c[0];
$p->phone = $c[1];
$p->save();
}
$request->session()->flash('status', 'success');
$request->session()->flash('status-message', 'Phone numbers saved!');
}
} else {
$request->session()->flash('status', 'danger');
$request->session()->flash('status-message', 'Invalid input, phone numbers were not modified');
}
return redirect('sms');
}
public function send(Request $request) {
$this->authorize('admin');
$numbers = PhoneNumber::orderBy('name')->get();
$maxlen = $this->max_message_length($numbers);
if(strlen($request->input('text')) <= $maxlen) {
$client = new \Twilio\Rest\Client(env('TWILIO_SID'), env('TWILIO_TOKEN'));
foreach($numbers as $number) {
$text = $request->input('text');
$text = str_replace('{name}', $number->name, $text);
$message = $client->messages->create(
'+1'.str_replace('-','',$number->phone),
array(
'from' => env('TWILIO_NUMBER'),
'body' => $text
)
);
}
$request->session()->flash('status', 'success');
$request->session()->flash('status-message', 'Message was sent! It will take about 1 second per number to deliver all the messages!');
} else {
$request->session()->flash('status', 'danger');
$request->session()->flash('status-message', 'Your message was too long!');
}
return redirect('sms');
}
}

+ 12
- 0
app/PhoneNumber.php View File

@ -0,0 +1,12 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class PhoneNumber extends Model
{
protected $fillable = [
'name', 'phone'
];
}

+ 16
- 0
app/SentMessage.php View File

@ -0,0 +1,16 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class SentMessage extends Model
{
protected $fillable = [
'text', 'phone'
];
public function user() {
return $this->belongsTo('\App\User');
}
}

+ 2
- 1
composer.json View File

@ -12,7 +12,8 @@
"mnabialek/laravel-sql-logger": "^1.1", "mnabialek/laravel-sql-logger": "^1.1",
"predis/predis": "^1.1", "predis/predis": "^1.1",
"pusher/pusher-php-server": "^2.6", "pusher/pusher-php-server": "^2.6",
"thujohn/twitter": "^2.2"
"thujohn/twitter": "^2.2",
"twilio/sdk": "^5.11"
}, },
"require-dev": { "require-dev": {
"fzaninotto/faker": "~1.4", "fzaninotto/faker": "~1.4",

+ 47
- 1
composer.lock View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "5681db78f7fa8c08618795173764986e",
"content-hash": "41238565556ee0b0f7094b70d9cae03e",
"packages": [ "packages": [
{ {
"name": "dnoegel/php-xdg-base-dir", "name": "dnoegel/php-xdg-base-dir",
@ -2461,6 +2461,52 @@
"homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles",
"time": "2016-09-20T12:50:39+00:00" "time": "2016-09-20T12:50:39+00:00"
}, },
{
"name": "twilio/sdk",
"version": "5.11.0",
"source": {
"type": "git",
"url": "https://github.com/twilio/twilio-php.git",
"reference": "6887cc761df5b011bce5460ad643e846ad8a5548"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twilio/twilio-php/zipball/6887cc761df5b011bce5460ad643e846ad8a5548",
"reference": "6887cc761df5b011bce5460ad643e846ad8a5548",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"apigen/apigen": "^4.1",
"phpunit/phpunit": "4.5.*"
},
"type": "library",
"autoload": {
"psr-4": {
"Twilio\\": "Twilio/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Twilio API Team",
"email": "api@twilio.com"
}
],
"description": "A PHP wrapper for Twilio's API",
"homepage": "http://github.com/twilio/twilio-php",
"keywords": [
"api",
"sms",
"twilio"
],
"time": "2017-06-16T20:19:46+00:00"
},
{ {
"name": "vlucas/phpdotenv", "name": "vlucas/phpdotenv",
"version": "v2.4.0", "version": "v2.4.0",

+ 33
- 0
database/migrations/2017_07_08_032850_BulkSMS.php View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class BulkSMS extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('phone_numbers', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
$table->string('name', 255);
$table->string('phone', 255);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('phone_numbers');
}
}

+ 33
- 0
database/migrations/2017_07_08_033123_sms_log.php View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class SmsLog extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('sent_messages', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
$table->text('text');
$table->integer('user_id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('sent_messages');
}
}

+ 2
- 1
resources/views/layouts/app.blade.php View File

@ -39,8 +39,9 @@
@if(!Auth::guest()) @if(!Auth::guest())
<li><a href="{{ route('dashboard') }}">Dashboard</a></li> <li><a href="{{ route('dashboard') }}">Dashboard</a></li>
@endif @endif
<li><a href="{{ route('teams') }}">Teams</a></li>
@can('admin') @can('admin')
<li><a href="{{ route('teams') }}">Teams</a></li>
<li><a href="{{ route('sms') }}">SMS</a></li>
@endcan @endcan
<li><a href="{{ route('slideshow') }}">Slideshow</a></li> <li><a href="{{ route('slideshow') }}">Slideshow</a></li>
<li><a href="{{ route('scoreboard') }}">Scoreboard</a></li> <li><a href="{{ route('scoreboard') }}">Scoreboard</a></li>

+ 46
- 0
resources/views/sms.blade.php View File

@ -0,0 +1,46 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
@if(session('status'))
<div class="alert alert-{{ session('status') }}" role="alert">{!! session('status-message') !!}</div>
@endif
<form action="{{ route('sms-send') }}" method="post">
<h3>Broadcast a Message</h3>
<p>Enter a message to send. The message length is limited based on the longest person's name. To include the person's name, enter the placeholder <code>{name}</code>.</p>
<div class="form-group">
<textarea name="text" class="form-control" rows="2" maxlength="{{ $maxlen }}">Hi {name}!</textarea>
</div>
<button type="submit" class="btn btn-primary">Broadcast</button>
{{ csrf_field() }}
</form>
<form action="{{ route('sms-save') }}" method="post">
<h3>Phone Numbers</h3>
<p>Enter your phone number database, one person per line. First name followed by their phone number. Numbers will be normalized to US format when saved.</p>
<div class="form-group">
<textarea name="input" class="form-control" rows="8">
@foreach($numbers as $number)
{{ $number->name }} {{ $number->phone }}
@endforeach</textarea>
</div>
<button type="submit" class="btn btn-primary">Save</button>
{{ csrf_field() }}
</form>
</div>
</div>
</div>
@endsection

+ 4
- 0
routes/web.php View File

@ -43,3 +43,7 @@ Route::post('/twitter/stream', 'TwitterController@input');
Route::get('/mission/{mission}', 'ShowTweetsController@mission')->name('mission'); Route::get('/mission/{mission}', 'ShowTweetsController@mission')->name('mission');
Route::get('/team/{team}', 'ShowTweetsController@team')->name('team'); Route::get('/team/{team}', 'ShowTweetsController@team')->name('team');
Route::get('/sms', 'SMSController@index')->name('sms');
Route::post('/sms/save-numbers', 'SMSController@save')->name('sms-save');
Route::post('/sms/send', 'SMSController@send')->name('sms-send');

Loading…
Cancel
Save