File Coverage

blib/lib/SMS/Send.pm
Criterion Covered Total %
statement 81 88 92.0
branch 33 42 78.5
condition 18 21 85.7
subroutine 13 13 100.0
pod 3 3 100.0
total 148 167 88.6


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