File Coverage

blib/lib/Email/ConstantContact.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package Email::ConstantContact;
2              
3 1     1   34954 use warnings;
  1         2  
  1         35  
4 1     1   7 use strict;
  1         2  
  1         117  
5 1     1   7 use Carp;
  1         5  
  1         92  
6 1     1   4229 use LWP::UserAgent;
  1         104995  
  1         44  
7 1     1   740 use Email::ConstantContact::Resource;
  1         3  
  1         50  
8 1     1   1153 use Email::ConstantContact::List;
  0            
  0            
9             use Email::ConstantContact::Contact;
10             use Email::ConstantContact::Activity;
11             use Email::ConstantContact::Campaign;
12             use HTTP::Request::Common qw(POST GET);
13             use URI::Escape;
14             use XML::Simple;
15              
16             =head1 NAME
17              
18             Email::ConstantContact - Perl interface to the ConstantContact API
19              
20             =head1 VERSION
21              
22             Version 0.05
23              
24             =cut
25              
26             use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
27              
28             require Exporter;
29              
30             @ISA = qw(Exporter);
31             @EXPORT = qw( );
32              
33             our $VERSION = '0.05';
34              
35             =head1 SYNOPSIS
36              
37             This module allows you to interact with the ConstantContact mass email
38             marketing service from perl, such as creating and mainting contacts and
39             contact lists.
40              
41             Before using this module, you must register your application with the
42             ConstantContact company, agree to their terms & conditions, and apply
43             for an API access key. You will use this key, in combination with a
44             ConstantContact username and password to interact with the service.
45              
46             use Email::ConstantContact;
47              
48             my $apikey = 'ABCDEFG1234567';
49             my $username = 'mycompany';
50             my $password = 'topsecret';
51              
52             my $cc = new Email::ConstantContact($apikey, $username, $password);
53              
54             # How to enumerate existing Contact Lists:
55             my @all_lists = $cc->lists();
56             foreach my $list (@all_lists) {
57             print "Found list: ", $list->{Name}, "\n";
58             }
59              
60             # How to create a new Contact List:
61             my $new_list = $cc->newList('JAPH Newsletter', {
62             SortOrder => '70',
63             DisplayOnSignup => 'false',
64             OptInDefault => 'false',
65             });
66              
67             # How to add a new contact:
68             my $new_contact = $cc->newContact('jdoe@example.com', {
69             FirstName => 'John',
70             LastName => 'Doe',
71             CompanyName => 'JD Industries',
72             ContactLists => [ $new_list ],
73             });
74              
75             # How to modify existing contact:
76             my $old_contact = $cc->getContact('yogi@example.com');
77             print "Yogi no longer works for ", $old_contact->{CompanyName}, "\n";
78             $old_contact->{CompanyName} = 'Acme Corp';
79              
80             # Enumerate List Membership
81             print "Member of Lists: \n";
82             foreach my $listid (@{ $old_contact->{ContactLists} }) {
83             my $listobj = $cc->getList($listid);
84             print $listobj->{Name}, "\n";
85             }
86              
87             # Manage List Membership
88             $old_contact->removeFromList($some_list_id);
89             $old_contact->clearAllLists();
90             $old_contact->addToList($new_list);
91             $old_contact->save();
92              
93             # Opt-Out of all future emails
94             $old_contact->optOut();
95             $old_contact->save();
96              
97             # Display recent activities
98             my @recent_activities = $cc->activities();
99              
100             foreach my $activity (@recent_activities) {
101             print "Found recent activity, Type= ", $activity->{Type},
102             "Status= ", $activity->{Status}, "\n";
103             }
104              
105             # Obtain bounced email addresses.
106             foreach my $camp ($cc->campaigns('SENT')) {
107             foreach my $event ($camp->events('bounces')) {
108             if ($event->{Code} eq 'B') {
109             print "Bounced: ", $event->{Contact}->{EmailAddress}, "\n";
110             }
111             }
112             }
113              
114              
115             =cut
116              
117             sub new {
118             my $class = shift;
119             my $self = {
120             apikey => shift,
121             username => shift,
122             password => shift,
123             };
124              
125             bless ($self, $class);
126              
127             $self->{cchome} = 'https://api.constantcontact.com';
128             $self->{rooturl} = $self->{cchome} . '/ws/customers/' . uri_escape($self->{username});
129              
130             return $self;
131             }
132              
133             sub getActivity {
134             my $self = shift;
135             my $activityname = shift;
136             my $url = '';
137              
138             if ($activityname =~ /^http/) {
139             #they passed in the actual REST link, so we can use it directly.
140             $url = lc($activityname);
141             $url =~ s/^http:/https:/;
142             }
143             else {
144             #they passed in the list's ID string, we must construct the url.
145             $url = lc($self->{rooturl} . '/activities/' . $activityname);
146             }
147              
148             my $req = GET($url);
149             $req->authorization_basic($self->{apikey} . '%' . $self->{username}, $self->{password});
150              
151             my $ua = new LWP::UserAgent;
152             my $res = $ua->request($req);
153              
154             if ($res->code == 200) {
155             my $xs = XML::Simple->new(KeyAttr => [], SuppressEmpty => 'undef',
156             GroupTags => { Errors => 'Error' }, ForceArray => ['link','entry','Error']);
157             my $xmlobj = $xs->XMLin($res->content);
158              
159             return new Email::ConstantContact::Activity($self, $xmlobj);
160             }
161             else {
162             carp "Activity individual request returned code " . $res->status_line;
163             return wantarray? (): undef;
164             }
165             }
166              
167             sub activities {
168             my $self = shift;
169              
170             my $url = lc($self->{rooturl} . '/activities');
171             my $req = GET($url);
172             $req->authorization_basic($self->{apikey} . '%' . $self->{username}, $self->{password});
173              
174             my $ua = new LWP::UserAgent;
175             my $res = $ua->request($req);
176             my @activities;
177              
178             if ($res->code == 200) {
179             my $xs = XML::Simple->new(KeyAttr => [], SuppressEmpty => 'undef',
180             GroupTags => { Errors => 'Error' }, ForceArray => ['link','entry','Error']);
181             my $xmlobj = $xs->XMLin($res->content);
182              
183             if (defined($xmlobj->{'entry'}) && ref($xmlobj->{'entry'})) {
184             foreach my $subobj (@{$xmlobj->{'entry'}}) {
185             push (@activities, new Email::ConstantContact::Activity($self, $subobj));
186             }
187             }
188             return @activities;
189             }
190             else {
191             carp "Activities request returned code " . $res->status_line;
192             return wantarray? (): undef;
193             }
194             }
195              
196             sub newList {
197             my $self = shift;
198             my $list_name = shift;
199             my $data = shift;
200              
201             my $new_list = new Email::ConstantContact::List($self);
202             $new_list->{Name} = $list_name;
203             $new_list->{SortOrder} = ($data && $data->{SortOrder}) ? $data->{SortOrder} : 1;
204             $new_list->{DisplayOnSignup} = ($data && $data->{DisplayOnSignup}) ? $data->{DisplayOnSignup} : 'false';
205             $new_list->{OptInDefault} = ($data && $data->{OptInDefault}) ? $data->{OptInDefault} : 'false';
206             my $updated = $new_list->create();
207              
208             if ($updated->{id}) {
209             return $updated;
210             }
211             }
212              
213             sub lists {
214             my $self = shift;
215              
216             my $ua = new LWP::UserAgent;
217             my @URLS = (lc($self->{rooturl} . '/lists'));
218             my @lists;
219              
220             while (my $url = shift(@URLS)) {
221             my $req = GET($url);
222             $req->authorization_basic($self->{apikey} . '%' . $self->{username}, $self->{password});
223             my $res = $ua->request($req);
224              
225             if ($res->code == 200) {
226             my $xs = XML::Simple->new(SuppressEmpty => 'undef', KeyAttr => [], ForceArray => ['link','entry']);
227             my $xmlobj = $xs->XMLin($res->content);
228              
229             if (defined($xmlobj->{'entry'}) && ref($xmlobj->{'entry'})) {
230             foreach my $subobj (@{$xmlobj->{'entry'}}) {
231             push (@lists, new Email::ConstantContact::List($self, $subobj));
232             }
233             if (defined($xmlobj->{'link'}) && ref($xmlobj->{'link'})) {
234             foreach my $subobj (@{$xmlobj->{'link'}}) {
235             if ($subobj->{'rel'} && ($subobj->{'rel'} eq "next")) {
236             push (@URLS, $self->{cchome} . $subobj->{'href'});
237             }
238             }
239             }
240             }
241             }
242             else {
243             carp "Contact Lists request returned code " . $res->status_line;
244             return wantarray? (): undef;
245             }
246             }
247             return @lists;
248             }
249              
250             sub newContact {
251             my $self = shift;
252             my $email = shift;
253             my $data = shift;
254              
255             my $new_contact = new Email::ConstantContact::Contact($self);
256             $new_contact->{EmailAddress} = $email;
257             $new_contact->{OptInSource} = ($data && $data->{OptInSource}) ? $data->{OptInSource} : 'ACTION_BY_CUSTOMER';
258              
259             if (exists($data->{'ContactLists'}) && ref($data->{'ContactLists'})) {
260             foreach my $cl (@{$data->{'ContactLists'}}) {
261             $new_contact->addToList($cl);
262             }
263             }
264             delete $data->{'ContactLists'};
265              
266             foreach my $key (keys %$data) {
267             $new_contact->{$key} = $data->{$key};
268             }
269              
270             my $updated = $new_contact->create();
271              
272             if ($updated && $updated->{id}) {
273             return $updated;
274             }
275             }
276              
277             sub contacts {
278             my $self = shift;
279              
280             my $url = lc($self->{rooturl} . '/contacts');
281             my $req = GET($url);
282             $req->authorization_basic($self->{apikey} . '%' . $self->{username}, $self->{password});
283              
284             my $ua = new LWP::UserAgent;
285             my $res = $ua->request($req);
286             my @contacts;
287              
288             if ($res->code == 200) {
289             my $xs = XML::Simple->new(KeyAttr => [], SuppressEmpty => 'undef',
290             GroupTags => { ContactLists => 'ContactList' }, ForceArray => ['link','entry','ContactList']);
291             my $xmlobj = $xs->XMLin($res->content);
292              
293             if (defined($xmlobj->{'entry'}) && ref($xmlobj->{'entry'})) {
294             foreach my $subobj (@{$xmlobj->{'entry'}}) {
295             push (@contacts, new Email::ConstantContact::Contact($self, $subobj));
296             }
297             }
298             return @contacts;
299             }
300             else {
301             carp "Contacts request returned code " . $res->status_line;
302             return wantarray? (): undef;
303             }
304             }
305              
306             sub getContact {
307             my $self = shift;
308             my $contactname = shift;
309             my $url = '';
310              
311             my $ua = new LWP::UserAgent;
312              
313             if ($contactname =~ /^http/) {
314             #they passed in the actual REST link, so we can use it directly.
315             $url = lc($contactname);
316             $url =~ s/^http:/https:/;
317             }
318             elsif ($contactname =~ /@/) {
319             #they passed in an email address, we must query for it.
320             my $url1 = lc($self->{rooturl} . '/contacts?email=' . uri_escape($contactname));
321             my $req1 = GET($url1);
322             $req1->authorization_basic($self->{apikey} . '%' . $self->{username}, $self->{password});
323             my $res1 = $ua->request($req1);
324              
325             unless ($res1->code == 200) {
326             return wantarray? (): undef;
327             }
328              
329             my $xs1 = XML::Simple->new(KeyAttr => [], SuppressEmpty => 'undef',
330             GroupTags => { ContactLists => 'ContactList' }, ForceArray => ['link','entry','ContactList']);
331             my $xmlobj1 = $xs1->XMLin($res1->content);
332              
333             unless (defined($xmlobj1->{'entry'}) && ref($xmlobj1->{'entry'})) {
334             return wantarray? (): undef;
335             }
336              
337             my $subobj1 = $xmlobj1->{'entry'}->[0];
338             my $contact1 = new Email::ConstantContact::Contact($self, $subobj1);
339              
340             unless ($contact1 && $contact1->{'id'}) {
341             return wantarray? (): undef;
342             }
343              
344             $url = lc($contact1->{'id'});
345             $url =~ s/^http:/https:/;
346             }
347             else {
348             #they passed in the contact's ID number, we must construct the url.
349             $url = lc($self->{rooturl} . '/contacts/' . $contactname);
350             }
351              
352             my $req = GET($url);
353             $req->authorization_basic($self->{apikey} . '%' . $self->{username}, $self->{password});
354              
355             my $res = $ua->request($req);
356              
357             if ($res->code == 200) {
358             my $xs = XML::Simple->new(KeyAttr => [], SuppressEmpty => 'undef',
359             GroupTags => { ContactLists => 'ContactList' }, ForceArray => ['link','entry','ContactList']);
360             my $xmlobj = $xs->XMLin($res->content);
361              
362             return new Email::ConstantContact::Contact($self, $xmlobj);
363             }
364             else {
365             carp "Contact individual request returned code " . $res->status_line;
366             return wantarray? (): undef;
367             }
368             }
369              
370             sub getList {
371             my $self = shift;
372             my $listname = shift;
373             my $url = '';
374              
375             if ($listname =~ /^http/) {
376             #they passed in the actual REST link, so we can use it directly.
377             $url = lc($listname);
378             $url =~ s/^http:/https:/;
379             }
380             else {
381             #they passed in the list's ID number, we must construct the url.
382             $url = lc($self->{rooturl} . '/lists/' . $listname);
383             }
384              
385             my $req = GET($url);
386             $req->authorization_basic($self->{apikey} . '%' . $self->{username}, $self->{password});
387              
388             my $ua = new LWP::UserAgent;
389             my $res = $ua->request($req);
390              
391             if ($res->code == 200) {
392             my $xs = XML::Simple->new(SuppressEmpty => 'undef', KeyAttr => [], ForceArray => ['link','entry']);
393             my $xmlobj = $xs->XMLin($res->content);
394              
395             return new Email::ConstantContact::List($self, $xmlobj);
396             }
397             else {
398             carp "Contact List individual request returned code " . $res->status_line;
399             return wantarray? (): undef;
400             }
401             }
402              
403             sub resources {
404             my $self = shift;
405              
406             my $url = lc($self->{rooturl} . "/");
407             my $req = GET($url);
408             $req->authorization_basic($self->{apikey} . '%' . $self->{username}, $self->{password});
409              
410             my $ua = new LWP::UserAgent;
411             my $res = $ua->request($req);
412             my @resources;
413              
414             if ($res->code == 200) {
415             my $xs = XML::Simple->new(SuppressEmpty => 'undef', KeyAttr => [], ForceArray => ['collection']);
416             my $xmlobj = $xs->XMLin($res->content);
417              
418             if (defined($xmlobj->{'workspace'}->{'collection'}) &&
419             ref($xmlobj->{'workspace'}->{'collection'})) {
420              
421             foreach my $subobj (@{$xmlobj->{'workspace'}->{'collection'}}) {
422             push (@resources, new Email::ConstantContact::Resource($self, $subobj));
423             }
424             }
425             return @resources;
426             }
427             else {
428             carp "Service Document request returned code " . $res->status_line;
429             return wantarray? (): undef;
430             }
431              
432             }
433              
434             sub campaigns {
435             my $self = shift;
436             my $status = shift;
437              
438             my $url = lc($self->{rooturl} . '/campaigns' . ($status ? ('?status=' . $status) : ''));
439             my $req = GET($url);
440             $req->authorization_basic($self->{apikey} . '%' . $self->{username}, $self->{password});
441              
442             my $ua = new LWP::UserAgent;
443             my $res = $ua->request($req);
444             my @lists;
445              
446             if ($res->code == 200) {
447             my $xs = XML::Simple->new(SuppressEmpty => 'undef', KeyAttr => [], ForceArray => ['link','entry']);
448             my $xmlobj = $xs->XMLin($res->content);
449              
450             if (defined($xmlobj->{'entry'}) && ref($xmlobj->{'entry'})) {
451             foreach my $subobj (@{$xmlobj->{'entry'}}) {
452             push (@lists, new Email::ConstantContact::Campaign($self, $subobj));
453             }
454             }
455             return @lists;
456             }
457             else {
458             carp "Campaigns request returned code " . $res->status_line;
459             return wantarray? (): undef;
460             }
461             }
462              
463             sub getCampaign {
464             my $self = shift;
465             my $campaignname = shift;
466             my $url = '';
467              
468             if ($campaignname =~ /^http/) {
469             #they passed in the actual REST link, so we can use it directly.
470             $url = lc($campaignname);
471             $url =~ s/^http:/https:/;
472             }
473             else {
474             #they passed in the list's ID string, we must construct the url.
475             $url = lc($self->{rooturl} . '/campaigns/' . $campaignname);
476             }
477              
478             my $req = GET($url);
479             $req->authorization_basic($self->{apikey} . '%' . $self->{username}, $self->{password});
480              
481             my $ua = new LWP::UserAgent;
482             my $res = $ua->request($req);
483              
484             if ($res->code == 200) {
485             my $xs = XML::Simple->new(KeyAttr => [], SuppressEmpty => 'undef',
486             GroupTags => { Errors => 'Error' }, ForceArray => ['link','entry','Error']);
487             my $xmlobj = $xs->XMLin($res->content);
488              
489             return new Email::ConstantContact::Campaign($self, $xmlobj);
490             }
491             else {
492             carp "Campaign individual request returned code " . $res->status_line;
493             return wantarray? (): undef;
494             }
495             }
496              
497             =head1 TODO
498              
499             =over 4
500              
501             =item * Implement method for enumerating members of a specified list.
502              
503             =item * Implement method for enumerating contacts
504              
505             =item * Implement method for enumerating campaign events per contact
506              
507             =item * Implement method for enumerating campaign contacts per event
508              
509             =item * Implement methods for bulk operations (import/export)
510              
511             =back
512              
513             =head1 AUTHOR
514              
515             Adam Rich, C<< >>
516              
517             =head1 BUGS
518              
519             Please report any bugs or feature requests to C, or through
520             the web interface at L. I will be notified, and then you'll
521             automatically be notified of progress on your bug as I make changes.
522              
523              
524              
525              
526             =head1 SUPPORT
527              
528             You can find documentation for this module with the perldoc command.
529              
530             perldoc Email::ConstantContact
531              
532              
533             You can also look for information at:
534              
535             =over 4
536              
537             =item * RT: CPAN's request tracker
538              
539             L
540              
541             =item * AnnoCPAN: Annotated CPAN documentation
542              
543             L
544              
545             =item * CPAN Ratings
546              
547             L
548              
549             =item * Search CPAN
550              
551             L
552              
553             =back
554              
555              
556             =head1 COPYRIGHT & LICENSE
557              
558             Copyright 2009-2011 Adam Rich, all rights reserved.
559              
560             This program is free software; you can redistribute it and/or modify it
561             under the same terms as Perl itself.
562              
563              
564             =cut
565              
566             1; # End of Email::ConstantContact