Commit 00c89d91 authored by Kyle M Hall's avatar Kyle M Hall

Bug 9021 - Add SMS via email as an alternative to SMS services via SMS::Send drivers

Nearly all cellular providers allow a person to send an text message to a cellular
phone by sending an email to phonenumber@provider. We can leverage this capability
to add the ability for Koha to send sms messages to patrons without the need to
subscribe to an sms gateway server.

Basic plan:
1. Add a table sms_providers to the db to tell Koha what service providers are available, and what domain emails should be sent to.
2. Add borrowers.sms_provider_id to tell Koha which mobile service the patron subscribes to for the number given in smsalertnumber
3. Modify Koha to send an email rather than using SMS::Send if the driver is set to 'Email'

Test plan:
0) Get a mobile phone
1) Apply the patch
2) Run updatedatabase.pl
3) Set the value of SMSSendDriver to 'Email'
4) Go to the admin page, the "Additional parameters" area should now have the link "SMS cellular providers"
5) On this page, add some providers. Make sure to add the provider for your own cellular phone service.

Here are some examples:
Sprint   phonenumber@messaging.sprintpcs.com
Verizon  phonenumber@vtext.com
T-Mobile phonenumber@tmomail.net
AT&T     phonenumber@txt.att.net

Only add the domain part in the 'domain' field. So for Verizon, that would be 'vtext.com'

6) Create an account for yourself, add your SMS number, and select your provider from the dropdown box directly below it.

7) Enable SMS messaging for Item check-in and Item checkout
8) Check out an item to yourself
9) Run process_message_queue.pl
10) Wait! You should receive a text message shortly, when I tested it, I received my sms message within the minute.
Signed-off-by: Mark Tompsett's avatarMark Tompsett <mtompset@hotmail.com>
Signed-off-by: Mark Tompsett's avatarMark Tompsett <mtompset@hotmail.com>
Signed-off-by: joubu's avatarJonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: default avatarKyle M Hall <kyle@bywatersolutions.com>
parent f3633586
......@@ -31,6 +31,8 @@ use C4::Log;
use C4::SMS;
use C4::Debug;
use Koha::DateUtils;
use Koha::SMS::Provider;
use Date::Calc qw( Add_Delta_Days );
use Encode;
use Carp;
......@@ -983,7 +985,14 @@ sub SendQueuedMessages {
_send_message_by_email( $message, $params->{'username'}, $params->{'password'}, $params->{'method'} );
}
elsif ( lc( $message->{'message_transport_type'} ) eq 'sms' ) {
_send_message_by_sms( $message );
if ( C4::Context->preference('SMSSendDriver') eq 'Email' ) {
my $member = C4::Members::GetMember( 'borrowernumber' => $message->{'borrowernumber'} );
my $sms_provider = Koha::SMS::Provider->find( $member->{'sms_provider_id'} );
$message->{to_address} .= '@' . $sms_provider->domain();
_send_message_by_email( $message, $params->{'username'}, $params->{'password'}, $params->{'method'} );
} else {
_send_message_by_sms( $message );
}
}
}
return scalar( @$unsent_messages );
......@@ -1222,6 +1231,7 @@ sub _send_message_by_email {
}
_update_message_to_address($message->{'message_id'},$to_address) unless $message->{to_address}; #if initial message address was empty, coming here means that a to address was found and queue should be updated
if ( sendmail( %sendmail_params ) ) {
_set_message_status( { message_id => $message->{'message_id'},
status => 'sent' } );
......
......@@ -645,6 +645,7 @@ sub ModMember {
$data{password} = hash_password($data{password});
}
}
my $old_categorycode = GetBorrowerCategorycode( $data{borrowernumber} );
# get only the columns of a borrower
......@@ -653,15 +654,19 @@ sub ModMember {
my $new_borrower = { map { join(' ', @columns) =~ /$_/ ? ( $_ => $data{$_} ) : () } keys(%data) };
delete $new_borrower->{flags};
$new_borrower->{dateofbirth} ||= undef if exists $new_borrower->{dateofbirth};
$new_borrower->{dateenrolled} ||= undef if exists $new_borrower->{dateenrolled};
$new_borrower->{dateexpiry} ||= undef if exists $new_borrower->{dateexpiry};
$new_borrower->{debarred} ||= undef if exists $new_borrower->{debarred};
$new_borrower->{dateofbirth} ||= undef if exists $new_borrower->{dateofbirth};
$new_borrower->{dateenrolled} ||= undef if exists $new_borrower->{dateenrolled};
$new_borrower->{dateexpiry} ||= undef if exists $new_borrower->{dateexpiry};
$new_borrower->{debarred} ||= undef if exists $new_borrower->{debarred};
$new_borrower->{sms_provider_id} ||= undef if exists $new_borrower->{sms_provider_id};
my $rs = $schema->resultset('Borrower')->search({
borrowernumber => $new_borrower->{borrowernumber},
});
my $execute_success = $rs->update($new_borrower);
if ($execute_success ne '0E0') { # only proceed if the update was a success
# ok if its an adult (type) it may have borrowers that depend on it as a guarantor
# so when we update information for an adult we should check for guarantees and update the relevant part
# of their records, ie addresses and phone numbers
......
package Koha::SMS::Provider;
# Copyright 2012 ByWater Solutions
#
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with Koha; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
=head1 NAME
Koha::SMS::Provider - class to manage sms providers
=head1 SYNOPSIS
Object-oriented class that encapsulates sms providers in Koha.
=head1 DESCRIPTION
SMS::Provider data.
=cut
use Modern::Perl;
use C4::Context;
use base qw(Class::Accessor);
__PACKAGE__->mk_accessors(qw( id name domain ));
=head2 new
my $provider = Koha::SMS::Provider->new($data);
Create a new Koha::SMS::Provider object based on the provided record.
=cut
sub new {
my $class = shift;
my $data = shift;
my $self = $class->SUPER::new($data);
bless $self, $class;
return $self;
}
=head2 store
Creates or updates the object in the database
=cut
sub store {
my $self = shift;
if ( $self->id ) {
return C4::Context->dbh->do( "UPDATE sms_providers SET name = ?, domain = ? WHERE id = ?", undef, ( $self->name, $self->domain, $self->id ) );
} else {
return C4::Context->dbh->do( "INSERT INTO sms_providers ( name, domain ) VALUES ( ?, ? )", undef, ( $self->name, $self->domain ) );
}
}
=head2 delete
=cut
sub delete {
my $self = shift;
return C4::Context->dbh->do( "DELETE FROM sms_providers WHERE id = ?", undef, ( $self->id ) );
}
=head2 all
my $providers = Koha::SMS::Provider->all();
=cut
sub all {
my $class = shift;
my $query = "SELECT * FROM sms_providers ORDER BY name";
my $sth = C4::Context->dbh->prepare($query);
$sth->execute();
my @providers;
while ( my $row = $sth->fetchrow_hashref() ) {
my $p = Koha::SMS::Provider->new($row);
push( @providers, $p );
}
return @providers;
}
=head2 find
my $provider = Koha::SMS::Provider->find( $id );
=cut
sub find {
my $class = shift;
my $id = shift;
my $query = "SELECT * FROM sms_providers WHERE ID = ?";
my $sth = C4::Context->dbh->prepare($query);
$sth->execute($id);
my $row = $sth->fetchrow_hashref();
my $p = Koha::SMS::Provider->new($row);
return $p;
}
=head2 search
my @providers = Koha::SMS::Provider->search({ [name => $name], [domain => $domain] });
=cut
sub search {
my $class = shift;
my $params = shift;
my $query = "SELECT * FROM sms_providers WHERE ";
my @params = map( $params->{$_}, keys %$params );
$query .= join( " AND ", map( "$_ = ?", keys %$params ) );
$query .= " ORDER BY name";
my $sth = C4::Context->dbh->prepare($query);
$sth->execute(@params);
my @providers;
while ( my $row = $sth->fetchrow_hashref() ) {
my $p = Koha::SMS::Provider->new($row);
push( @providers, $p );
}
return @providers;
}
1;
......@@ -34,4 +34,8 @@ my ($template, $loggedinuser, $cookie)
debug => 1,
});
$template->param(
SMSSendDriver => C4::Context->preference('SMSSendDriver'),
);
output_html_with_http_headers $query, $cookie, $template->output;
#!/usr/bin/perl
# Copyright 2012 ByWater Solutions
#
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with Koha; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
use strict;
use warnings;
use CGI;
use C4::Context;
use C4::Auth;
use C4::Output;
use Koha::SMS::Provider;
my $cgi = new CGI;
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
{ template_name => "admin/sms_providers.tt",
query => $cgi,
type => "intranet",
authnotrequired => 0,
flagsrequired => { parameters => 'parameters_remaining_permissions' },
debug => 1,
}
);
my $op = $cgi->param('op');
my $id = $cgi->param('id');
my $name = $cgi->param('name');
my $domain = $cgi->param('domain');
if ( $op eq 'add_update' ) {
if ( $name && $domain ) {
Koha::SMS::Provider->new( { id => $id, name => $name, domain => $domain } )->store();
}
} elsif ( $op eq 'delete' ) {
Koha::SMS::Provider->find($id)->delete();
}
my @providers = Koha::SMS::Provider->all();
$template->param( providers => \@providers );
output_html_with_http_headers $cgi, $cookie, $template->output;
CREATE TABLE sms_providers (
id INT( 11 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
name VARCHAR( 255 ) NOT NULL ,
domain VARCHAR( 255 ) NOT NULL ,
UNIQUE (
name
)
) ENGINE = INNODB CHARACTER SET utf8;
ALTER TABLE borrowers ADD sms_provider_id INT( 11 ) NULL DEFAULT NULL AFTER smsalertnumber, ADD INDEX ( sms_provider_id );
ALTER TABLE borrowers ADD FOREIGN KEY ( sms_provider_id ) REFERENCES sms_providers ( id );
......@@ -263,6 +263,7 @@ CREATE TABLE `borrowers` ( -- this table includes information about your patrons
`altcontactcountry` text default NULL, -- the country for the alternate contact for the patron/borrower
`altcontactphone` varchar(50) default NULL, -- the phone number for the alternate contact for the patron/borrower
`smsalertnumber` varchar(50) default NULL, -- the mobile phone number where the patron/borrower would like to receive notices (if SNS turned on)
`sms_provider_id` int(11) DEFAULT NULL, -- the provider of the mobile phone number defined in smsalertnumber
`privacy` integer(11) DEFAULT '1' NOT NULL, -- patron/borrower's privacy settings related to their reading history
`privacy_guarantor_checkouts` tinyint(1) NOT NULL DEFAULT '0', -- controls if relatives can see this patron's checkouts
UNIQUE KEY `cardnumber` (`cardnumber`),
......@@ -274,8 +275,10 @@ CREATE TABLE `borrowers` ( -- this table includes information about your patrons
KEY `surname_idx` (`surname`(255)),
KEY `firstname_idx` (`firstname`(255)),
KEY `othernames_idx` (`othernames`(255)),
KEY `sms_provider_id` (`sms_provider_id`),
CONSTRAINT `borrowers_ibfk_1` FOREIGN KEY (`categorycode`) REFERENCES `categories` (`categorycode`),
CONSTRAINT `borrowers_ibfk_2` FOREIGN KEY (`branchcode`) REFERENCES `branches` (`branchcode`)
CONSTRAINT `borrowers_ibfk_3` FOREIGN KEY (`sms_provider_id`) REFERENCES `sms_providers` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
--
......@@ -1996,6 +1999,19 @@ CREATE TABLE sessions (
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
--
-- Table structure for table `sms_providers`
--
DROP TABLE IF EXISTS sms_providers;
CREATE TABLE `sms_providers` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`domain` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
--
-- Table structure for table `special_holidays`
--
......
......@@ -110,6 +110,10 @@
<dd>Hide or show columns for tables.</dd>
<dt><a href="/cgi-bin/koha/admin/audio_alerts.pl">Audio alerts</a></dt>
<dd>Define which events trigger which sounds</dd>
[% IF SMSSendDriver == 'Email' %]
<dt><a href="/cgi-bin/koha/admin/sms_providers.pl">SMS cellular providers</a></dt>
<dd>Define a list of cellular providers for sending SMS messages via email.</dd>
[% END %]
</dl>
</div>
</div>
......
[% INCLUDE 'doc-head-open.inc' %]
<title>Koha &rsaquo; Administration &rsaquo; SMS cellular providers</title>
[% INCLUDE 'doc-head-close.inc' %]
</head>
<script type="text/javascript">
$(document).ready(function() {
$('#submit_update').hide();
$("#name").focus();
});
function edit_provider( id ) {
cancel_edit();
$("#id").val( id );
$("#name").val( $("#name_" + id).text() );
$("#domain").val( $("#domain_" + id).text() );
$("#name_" + id).parent().addClass("warn");
$("#submit_save").hide();
$("#submit_update").show();
$("#name").focus();
}
function cancel_edit() {
$("#id").val("");
$("#name").val("");
$("#domain").val("");
$("tr").removeClass("warn");
$("#submit_update").hide();
$("#submit_save").show();
}
function delete_provider( id ) {
if ( confirm( _("Are you sure you want to delete ") + $("#name_" + id).html() + _("?") ) ) {
$("#op").val('delete');
$("#id").val( id );
$("#sms_form").submit();
}
}
</script>
<body id="admin_sms_providers" class="admin">
[% INCLUDE 'header.inc' %]
<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/admin/admin-home.pl">Administration</a> &rsaquo; SMS cellular providers</div>
<div id="doc3" class="yui-t2">
<div id="bd">
<div id="yui-main">
<div class="yui-b">
<h2>SMS cellular providers</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Domain</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
[% FOREACH p IN providers %]
<tr>
<td id="name_[% p.id %]">[% p.name %]</td>
<td id="domain_[% p.id %]">[% p.domain %]</td>
<td><a href="#" id="edit_[% p.id %]" class="edit" onclick="edit_provider( [% p.id %] );">Edit</td>
<td><a href="#" id="delete_[% p.id %]" class="delete" onclick="delete_provider( [% p.id %] );">Delete</td>
</tr>
[% END %]
</tbody>
<tfoot>
<form id="sms_form" action="sms_providers.pl" method="post">
<input type="hidden" id="id" name="id" value="" />
<input type="hidden" id="op" name="op" value="add_update" />
<tr>
<td><input type="text" id="name" name="name" /></td>
<td><input type="text" id="domain" name="domain" size="40"/></td>
<td>
<input id="submit_save" type="submit" value="Add new">
<input id="submit_update" type="submit" value="Update">
</td>
<td><a id="cancel" href="#" onclick="cancel_edit()">Cancel</a></td>
</tr>
</form>
</tfoot>
</table>
</div>
</div>
<div class="yui-b">
[% INCLUDE 'admin-menu.inc' %]
</div>
</div>
</div>
[% INCLUDE 'intranet-bottom.inc' %]
......@@ -1115,6 +1115,19 @@
<p><label for="SMSnumber">SMS number:</label>
<input type="text" id="SMSnumber" name="SMSnumber" value="[% SMSnumber %]" />
</p>
<p>
<label for="sms_provider_id">SMS provider:</label>
<select id="sms_provider_id" name="sms_provider_id"/>
<option value="">Unknown</option>
[% FOREACH s IN sms_providers %]
[% IF s.id == sms_provider_id %]
<option value="[% s.id %]" selected="selected">[% s.name %]</option>
[% ELSE %]
<option value="[% s.id %]">[% s.name %]</option>
[% END %]
[% END %]
</select>
</p>
[% END %]
</fieldset>
[% END %] [% END %]
......
......@@ -119,9 +119,30 @@
</table>
<fieldset class="rows">
[% IF ( SMSSendDriver ) %]
<ol><li><label for="SMSnumber">SMS number:</label> <input type="text" id="SMSnumber" name="SMSnumber" value="[% SMSnumber %]" /></li></ol>
[% END %]
[% IF ( SMSSendDriver ) %]
<ol><li><label>Notice:</label>Some charges for text messages may be incurred when using this service. Please check with your mobile service provider if you have questions.</li></ol>
<ol><li>
<label for="SMSnumber">SMS number:</label> <input type="text" id="SMSnumber" name="SMSnumber" value="[% SMSnumber %]" />
<i>Please enter numbers only. <b>(123) 456-7890</b> would be entered as <b>1234567890</b>.</i>
</li></ol>
[% END %]
[% IF ( SMSSendDriver == 'Email' ) %]
<ol><li>
<label for="sms_provider_id">SMS provider:</label>
<select id="sms_provider_id" name="sms_provider_id"/>
<option value="">Unknown</option>
[% FOREACH s IN sms_providers %]
[% IF s.id == sms_provider_id %]
<option value="[% s.id %]" selected="selected">[% s.name %]</option>
[% ELSE %]
<option value="[% s.id %]">[% s.name %]</option>
[% END %]
[% END %]
</select>
<i>Please contact a library staff member if you are unsure of your mobile service provider, or you do not see your provider in this list.</i>
</li></ol>
[% END %]
</fieldset>
<fieldset class="action">
......
......@@ -46,6 +46,7 @@ use Module::Load;
if ( C4::Context->preference('NorwegianPatronDBEnable') && C4::Context->preference('NorwegianPatronDBEnable') == 1 ) {
load Koha::NorwegianPatronDB, qw( NLGetSyncDataFromBorrowernumber );
}
use Koha::SMS::Provider;
use vars qw($debug);
......@@ -68,6 +69,11 @@ my ($template, $loggedinuser, $cookie)
debug => ($debug) ? 1 : 0,
});
if ( C4::Context->preference('SMSSendDriver') eq 'Email' ) {
my @providers = Koha::SMS::Provider->all();
$template->param( sms_providers => \@providers );
}
my $guarantorid = $input->param('guarantorid');
my $borrowernumber = $input->param('borrowernumber');
my $actionType = $input->param('actionType') || '';
......
......@@ -50,8 +50,11 @@ my $messaging_options = C4::Members::Messaging::GetMessagingOptions();
if ( defined $query->param('modify') && $query->param('modify') eq 'yes' ) {
my $sms = $query->param('SMSnumber');
if ( defined $sms && ( $borrower->{'smsalertnumber'} // '' ) ne $sms ) {
ModMember( borrowernumber => $borrowernumber,
smsalertnumber => $sms );
ModMember(
borrowernumber => $borrowernumber,
smsalertnumber => $sms,
sms_provider_id => $query->param('sms_provider_id'),
);
$borrower = C4::Members::GetMember( borrowernumber => $borrowernumber );
}
......@@ -66,4 +69,9 @@ $template->param( BORROWER_INFO => $borrower,
SMSSendDriver => C4::Context->preference("SMSSendDriver"),
TalkingTechItivaPhone => C4::Context->preference("TalkingTechItivaPhoneNotification") );
if ( C4::Context->preference("SMSSendDriver") eq 'Email' ) {
my @providers = Koha::SMS::Provider->all();
$template->param( sms_providers => \@providers, sms_provider_id => $borrower->{'sms_provider_id'} );
}
output_html_with_http_headers $query, $cookie, $template->output, undef, { force_no_caching => 1 };
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment