File Coverage

blib/lib/SMS/Send.pm
Criterion Covered Total %
statement 86 93 92.4
branch 33 42 78.5
condition 18 21 85.7
subroutine 15 15 100.0
pod 3 3 100.0
total 155 174 89.0


line stmt bran cond sub pod time code
1             package SMS::Send;
2              
3             =pod
4              
5             =head1 NAME
6              
7             SMS::Send - Driver-based API for sending SMS messages
8              
9             =head1 SYNOPSIS
10              
11             # Create a sender
12             my $sender = SMS::Send->new('SomeDriver',
13             _login => 'myname',
14             _password => 'mypassword',
15             );
16            
17             # Send a message
18             my $sent = $sender->send_sms(
19             text => 'This is a test message',
20             to => '+61 (4) 1234 5678',
21             );
22            
23             # Did the send succeed.
24             if ( $sent ) {
25             print "Message sent ok\n";
26             } else {
27             print "Failed to send message\n";
28             }
29              
30             =head1 DESCRIPTION
31              
32             C is intended to provide a driver-based single API for sending SMS
33             and MMS messages. The intent is to provide a single API against which to
34             write the code to send an SMS message.
35              
36             At the same time, the intent is to remove the limits of some of the previous
37             attempts at this sort of API, like "must be free internet-based SMS services".
38              
39             C drivers are installed seperately, and might use the web, email or
40             physical SMS hardware. It could be a free or paid. The details shouldn't
41             matter.
42              
43             You should not have to care how it is actually sent, only that it has been
44             sent (although some drivers may not be able to provide certainty).
45              
46             =head1 METHODS
47              
48             =cut
49              
50 4     4   136668 use 5.006;
  4         97  
  4         225  
51 4     4   29 use strict;
  4         8  
  4         194  
52 4     4   34 use Carp ();
  4         8  
  4         68  
53 4     4   5270 use SMS::Send::Driver ();
  4         10  
  4         104  
54 4     4   4751 use Params::Util 0.14 ();
  4         37704  
  4         297  
55              
56             # We are a type of Adapter
57             use Class::Adapter::Builder 1.05
58 4     4   17795 AUTOLOAD => 'PUBLIC';
  4         18269  
  4         34  
59              
60             # We need plugin support to find the drivers
61             use Module::Pluggable 3.7
62 4         48 require => 0,
63             inner => 0,
64             search_path => [ 'SMS::Send' ],
65             except => [ 'SMS::Send::Driver' ],
66 4     4   22210 sub_name => '_installed_drivers';
  4         80241  
67              
68 4     4   398 use vars qw{$VERSION};
  4         7  
  4         573  
69             BEGIN {
70 4     4   5070 $VERSION = '1.06';
71             }
72              
73             # Private driver cache
74             my @DRIVERS = ();
75              
76             =pod
77              
78             =head2 installed_drivers
79              
80             The C the list of SMS::Send drivers that are installed
81             on the current system.
82              
83             =cut
84              
85             sub installed_drivers {
86 1     1 1 12 my $class = shift;
87              
88 1 50       6 unless ( @DRIVERS ) {
89 1         6 my @rawlist = $class->_installed_drivers;
90 1         2818 foreach my $d ( @rawlist ) {
91 2         9 $d =~ s/^SMS::Send:://;
92             }
93 1         4 @DRIVERS = @rawlist;
94             }
95              
96 1         6 return @DRIVERS;
97             }
98              
99              
100              
101              
102              
103             #####################################################################
104             # Constructor and Accessors
105              
106             =pod
107              
108             =head2 new
109              
110             # The most basic sender
111             $sender = SMS::Send->new('Test');
112            
113             # Indicate regional driver with ::
114             $sender = SMS::Send->new('AU::Test');
115            
116             # Pass arbitrary params to the driver
117             $sender = SMS::Send->new('MyDriver',
118             _login => 'adam',
119             _password => 'adam',
120             );
121              
122             The C constructor creates a new SMS sender.
123              
124             It takes as its first parameter a driver name. These names map the class
125             names. For example driver "Test" matches the testing driver
126             L.
127              
128             Any additional params should be key/value pairs, split into two types.
129              
130             Params without a leading underscore are "public" options and relate to
131             standardised features within the L API itself. At this
132             time, there are no usable public options.
133              
134             Params B a leading underscore are "private" driver-specific options
135             and will be passed through to the driver unchanged.
136              
137             Returns a new L object, or dies on error.
138              
139             =cut
140              
141             sub new {
142 25     25 1 12595 my $class = shift;
143 25         75 my $driver = $class->_DRIVER(shift);
144 9 50       58 my @params = Params::Util::_HASH0($_[0]) ? %{$_[0]} : @_;
  0         0  
145              
146             # Create the driver and verify
147 9         40 my $object = $driver->new( $class->_PRIVATE(@params) );
148 6 100       73 unless ( Params::Util::_INSTANCE($object, 'SMS::Send::Driver') ) {
149 2         329 Carp::croak("Driver Error: $driver->new did not return a driver object");
150             }
151              
152             # Hand off to create our object
153 4         37 my $self = $class->SUPER::new( $object );
154 4 50       200 unless ( Params::Util::_INSTANCE($self, $class) ) {
155 0         0 die "Internal Error: Failed to create a $class object";
156             }
157              
158 4         67 return $self;
159             }
160              
161             =pod
162              
163             =head2 send_sms
164              
165             # Send a message to a particular address
166             my $result = $sender->send_sms(
167             text => 'This is a test message',
168             to => '+61 4 1234 5678',
169             );
170              
171             The C method sends a standard text SMS message to a destination
172             phone number.
173              
174             It takes a set of named parameters to describe the message and its
175             destination, again split into two types.
176              
177             Params without a leading underscore are "public" options and relate to
178             standardised features within the L API itself.
179              
180             =over
181              
182             =item text
183              
184             The C param is compulsory and should be a plain text string of
185             non-zero length. The maximum length is currently determined by the
186             driver, and exceeding this length will result in an exception being
187             thrown if you breach it.
188              
189             Better functionality for determining the maximum-supported length is
190             expected in the future. You input would be welcome.
191              
192             =item to
193              
194             The C param is compulsory, and should be an international phone
195             number as indicated by a leading plus "+" character. Punctuation in
196             any form is allowed, and will be stripped out before it is provided
197             to the driver.
198              
199             If and only if your driver is a regional driver (as indicated by a
200             ::-seperated name such as AU::Test) the C number can also be in
201             a regional-specific dialing format, C a leading plus "+"
202             character.
203              
204             Providing a regional number to a non-regional driver will throw an
205             exception.
206              
207             =back
208              
209             Any parameters B a leading underscore are considered private
210             driver-specific options and will be passed through without alteration.
211              
212             Any other parameters B a leading underscore will be silently
213             stripped out and not passed through to the driver.
214              
215             After calling C the driver will do whatever is required to
216             send the message, including (potentially, but not always) waiting for
217             a confirmation from the network that the SMS has been sent.
218              
219             Given that drivers may do the actual mechanics of sending a message by
220             quite a large variety of different methods the C method may
221             potentially block for some time. Timeout functionality is expected to
222             be added later.
223              
224             The C returns true if the message was sent, or the driver
225             is fire-and-forget and unable to determine success, or false if the
226             message was not sent.
227              
228             =cut
229              
230             sub send_sms {
231 12     12 1 18 my $self = shift;
232 12         36 my %params = @_;
233              
234             # Get the text content
235 12         20 my $text = delete $params{text};
236 12 100       24 unless ( _STRING($text) ) {
237 6         920 Carp::croak("Did not provide a 'text' string param");
238             }
239              
240             # Get the destination number
241 6         14 my $to = delete $params{to};
242 6 100       12 unless ( _STRING($to) ) {
243 3         398 Carp::croak("Did not provide a 'to' message destination");
244             }
245              
246             # Clean up the number
247 3         19 $to =~ s/[\s\(\)\[\]\{\}\.-]//g;
248 3 100       8 unless ( _STRING($to) ) {
249 2         247 Carp::croak("Did not provide a 'to' message destination");
250             }
251 1 50       7 unless ( $to =~ /^\+?\d+$/ ) {
252 0         0 Carp::croak("Invalid phone number format '$params{to}'");
253             }
254              
255             # Extra validations of international or non-international issues
256 1 50       8 if ( $to =~ /^\+0/ ) {
257 0         0 Carp::croak("International phone numbers cannot have leading zeros");
258             }
259 1 50 33     12 if ( $to =~ /^\+/ and length($to) <= 7 ) {
260 0         0 Carp::croak("International phone numbers must be at least 6 digits");
261             }
262 1 50       6 unless ( ref($self->_OBJECT_) =~ /^SMS::Send::\w+::/ ) {
263             # International-only driver
264 1 50       14 unless ( $to =~ /^\+/ ) {
265 0         0 Carp::croak("Cannot use regional phone numbers with an international driver");
266             }
267             }
268              
269             # Merge params and hand off
270 1         4 my $rv = $self->_OBJECT_->send_sms(
271             text => $text,
272             to => $to,
273             $self->_PRIVATE(@_),
274             );
275              
276             # Verify we get some sort of result
277 1 50       4 unless ( defined $rv ) {
278 0         0 Carp::croak("Driver did not return a result");
279             }
280              
281 1         4 return $rv;
282             }
283              
284              
285              
286              
287              
288             #####################################################################
289             # Support Methods
290              
291             sub _STRING {
292 25   100 25   208 !! (defined $_[0] and ! ref $_[0] and length $_[0]);
293             }
294              
295             sub _DRIVER {
296 25     25   30 my $class = shift;
297              
298             # The driver should be a string (other than 'Driver')
299 25         36 my $name = $_[0];
300 25 100 100     178 unless ( defined $name and ! ref $name and length $name ) {
      100        
301 6         961 Carp::croak("Did not provide a SMS::Send driver name");
302             }
303 19 100       70 if ( $name =~ /^\d+$/ ) {
304             # Although pure-digit Foo::123 class names are technically
305             # allowed, we don't allow them as drivers, to reduce insanity.
306 1         107 Carp::croak("Not a valid SMS::Send driver name");
307             }
308              
309             # Clean up the driver name
310 18         37 my $driver = "SMS::Send::$name";
311 18 100       573 unless ( Params::Util::_CLASS($driver) ) {
312 4         631 Carp::croak("Not a valid SMS::Send driver name");
313             }
314              
315             # Load the driver
316 14         919 eval "require $driver;";
317 14 100 100     921 if ( $@ and $@ =~ /^Can't locate / ) {
    100          
318             # Driver does not exist
319 2         356 Carp::croak("SMS::Send driver $_[0] does not exist, or is not installed");
320             } elsif ( $@ ) {
321             # Fatal error within the driver itself
322             # Pass on without change
323 1         207 Carp::croak( $@ );
324             }
325              
326             # Verify that the class is actually a driver
327 11 100 100     207 unless ( $driver->isa('SMS::Send::Driver') and $driver ne 'SMS::Send::Driver' ) {
328 2         231 Carp::croak("$driver is not a subclass of SMS::Send::Driver");
329             }
330              
331 9         24 return $driver;
332             }
333              
334             # Filter params for only the private params
335             sub _PRIVATE {
336 10 100   10   30 my $class = ref $_[0] ? ref shift : shift;
337 10         18 my @input = @_;
338 10         20 my @output = ();
339 10         31 while ( @input ) {
340 4         6 my $key = shift @input;
341 4         5 my $value = shift @input;
342 4 100 66     7 if ( _STRING($key) and $key =~ /^_/ ) {
343 1         7 push @output, $key, $value;
344             }
345             }
346 10         46 return @output;
347             }
348              
349             1;
350              
351             =pod
352              
353             =head1 SUPPORT
354              
355             Bugs should be reported via the CPAN bug tracker at
356              
357             L
358              
359             For other issues, contact the author.
360              
361             =head1 AUTHOR
362              
363             Adam Kennedy Eadamk@cpan.orgE
364              
365             =head1 COPYRIGHT
366              
367             Copyright 2005 - 2011 Adam Kennedy.
368              
369             This program is free software; you can redistribute
370             it and/or modify it under the same terms as Perl itself.
371              
372             The full text of the license can be found in the
373             LICENSE file included with this module.
374              
375             =cut