File Coverage

blib/lib/Email/ExactTarget/Subscriber.pm
Criterion Covered Total %
statement 21 130 16.1
branch 0 76 0.0
condition 0 19 0.0
subroutine 7 29 24.1
pod 18 18 100.0
total 46 272 16.9


line stmt bran cond sub pod time code
1             =encoding utf8
2              
3             =cut
4              
5             package Email::ExactTarget::Subscriber;
6              
7 2     2   5105 use warnings;
  2         4  
  2         79  
8 2     2   13 use strict;
  2         5  
  2         76  
9              
10 2     2   11 use Carp;
  2         3  
  2         114  
11 2     2   30 use Data::Dumper;
  2         5  
  2         89  
12 2     2   1974 use Data::Validate::Type;
  2         29411  
  2         140  
13 2     2   3197 use Try::Tiny;
  2         15472  
  2         167  
14 2     2   22 use URI::Escape;
  2         5  
  2         11857  
15              
16              
17             =head1 NAME
18              
19             Email::ExactTarget::Subscriber - Object representing ExactTarget subscribers.
20              
21              
22             =head1 VERSION
23              
24             Version 1.6.2
25              
26             =cut
27              
28             our $VERSION = '1.6.2';
29              
30              
31             =head1 SYNOPSIS
32              
33             # Create a new subscriber object.
34             my $subscriber = Email::ExactTarget::Subscriber->new();
35              
36             # Set attributes.
37             $subscriber->set_attributes(
38             {
39             'First Name' => 'John',
40             'Last Name' => 'Doe',
41             }
42             );
43              
44             # Get attributes.
45             my $first_name = $subscriber->get_attribute('First Name');
46              
47             # ExactTarget's subscriber ID, if applicable.
48             my $subscriber_id = $subscriber->id();
49              
50              
51             =head1 METHODS
52              
53             =head2 new()
54              
55             Creates a new Subscriber object.
56              
57             my $subscriber = Email::ExactTarget::Subscriber->new();
58              
59             =cut
60              
61             sub new
62             {
63 0     0 1   my ( $class, %args ) = @_;
64              
65             # Create the object.
66 0           my $self = bless(
67             {
68             'attributes' => {},
69             'staged_attributes' => {},
70             'lists' => {},
71             'staged_lists' => {},
72             'properties' => {},
73             'staged_properties' => {},
74             },
75             $class
76             );
77              
78 0           return $self;
79             }
80              
81              
82             =head2 id()
83              
84             Returns the Subscriber ID associated to the current Subscriber in Exact Target's
85             database.
86              
87             $subscriber->id( 123456789 );
88              
89             my $subscriber_id = $subscriber->id();
90              
91             This will return undef if the object hasn't loaded the subscriber information
92             from the database, or if a new subscriber hasn't been committed to the database.
93              
94             =cut
95              
96             sub id
97             {
98 0     0 1   my ( $self, $id ) = @_;
99              
100 0 0         if ( defined( $id ) )
101             {
102 0 0         confess 'Subscriber ID format is incorrect'
103             unless $id =~ m/^\d+$/;
104              
105 0 0         confess 'The subscriber ID is already set on this object'
106             if defined( $self->{'id'} );
107              
108 0 0         confess 'Cannot modify an object flagged as permanently deleted'
109             if $self->is_deleted_permanently();
110              
111 0           $self->{'id'} = $id;
112             }
113              
114 0           return $self->{'id'};
115             }
116              
117              
118             =head1 MANAGING ATTRIBUTES
119              
120             =head2 get_attributes()
121              
122             Retrieve a hashref containing all the attributes of the current object.
123              
124             By default, it retrieves the live data (i.e., attributes synchronized with
125             ExactTarget). If you want to retrieve the staged data, you can set
126             I to 0 in the parameters.
127              
128             # Retrieve staged attributes (i.e., not synchronized yet with ExactTarget).
129             my $attributes = $subscriber->get_attributes( 'is_live' => 0 );
130              
131             # Retrieve live attributes.
132             my $attributes = $subscriber->get_attributes( 'is_live' => 1 );
133             my $attributes = $subscriber->get_attributes();
134              
135             =cut
136              
137             sub get_attributes
138             {
139 0     0 1   my ( $self, %args ) = @_;
140 0           my $is_live = delete( $args{'is_live'} );
141 0 0         $is_live = 1 unless defined( $is_live );
142              
143 0 0         my $storage_key = $is_live
144             ? 'attributes'
145             : 'staged_attributes';
146              
147             # Make a copy of the attributes before returning them, in case the caller
148             # needs to modify the hash.
149 0 0         return { %{ $self->{ $storage_key } || {} } };
  0            
150             }
151              
152              
153             =head2 get_attribute()
154              
155             Retrieve the value corresponding to the attribute name passed as first
156             parameter.
157              
158             # Retrieve staged (non-synchronized with ExactTarget) attribute named
159             # 'Email Address'.
160             my $staged_email_address = $subscriber->get_attribute(
161             'Email Address',
162             is_live => 0,
163             );
164              
165             # If you've retrieved the subscriber object from ExactTarget, this
166             # retrieves the live attribute that was returned by the webservice.
167             my $live_email_address = $subscriber->get_attribute(
168             'Email Address',
169             is_live => 1,
170             );
171              
172             =cut
173              
174             sub get_attribute
175             {
176 0     0 1   my ( $self, $attribute, %args ) = @_;
177 0           my $is_live = delete( $args{'is_live'} );
178 0 0         $is_live = 1 unless defined( $is_live );
179              
180 0 0 0       confess 'An attribute name is required to retrieve the corresponding value'
181             if !defined( $attribute ) || ( $attribute eq '' );
182              
183 0 0         my $storage_key = $is_live
184             ? 'attributes'
185             : 'staged_attributes';
186              
187 0 0         carp "The attribute '$attribute' does not exist on the Subscriber object"
188             unless exists( $self->{ $storage_key }->{ $attribute } );
189              
190 0           return $self->{ $storage_key }->{ $attribute };
191             }
192              
193              
194             =head2 set_attributes()
195              
196             Sets the attributes and values for the current subscriber object.
197              
198             $subscriber->set_attributes(
199             {
200             'Email Address' => $email,
201             'First Name' => $first_name,
202             },
203             'is_live' => $boolean, #default 0
204             );
205              
206             The I parameter allows specifying whether the data in the hashref are
207             local only or if they are already synchronized with ExactTarget's database. By
208             default, changes are considered local only and you will explicitely have to
209             synchronize them using the functions of
210             Email::ExactTarget::SubscriberOperations.
211              
212             =cut
213              
214             sub set_attributes
215             {
216 0     0 1   my ( $self, $attributes, %args ) = @_;
217 0   0       my $is_live = delete( $args{'is_live'} ) || 0;
218              
219 0 0         confess 'Cannot modify an object flagged as permanently deleted'
220             if $self->is_deleted_permanently();
221              
222 0 0         my $storage_key = $is_live
223             ? 'attributes'
224             : 'staged_attributes';
225              
226 0           while ( my ( $name, $value ) = each( %$attributes ) )
227             {
228 0           $self->{ $storage_key }->{ $name } = $value;
229             }
230              
231 0           return 1;
232             }
233              
234              
235             =head2 apply_staged_attributes()
236              
237             Moves the staged attribute changes onto the current object, effectively
238             'applying' the changes.
239              
240             $subscriber->apply_staged_attributes(
241             [
242             'Email Address',
243             'First Name',
244             ]
245             ) || confess Dumper( $subscriber->errors() );
246              
247             =cut
248              
249             sub apply_staged_attributes
250             {
251 0     0 1   my ( $self, $fields ) = @_;
252              
253 0 0         confess 'The first parameter needs to be an arrayref of fields to apply'
254             if !Data::Validate::Type::is_arrayref( $fields );
255              
256 0 0         confess 'Cannot modify an object flagged as permanently deleted'
257             if $self->is_deleted_permanently();
258              
259 0           my $errors_count = 0;
260 0           foreach my $field ( @$fields )
261             {
262             try
263             {
264 0     0     $self->set_attributes(
265             {
266             $field => $self->{'staged_attributes'}->{ $field },
267             },
268             'is_live' => 1,
269             );
270              
271 0           delete( $self->{'staged_attributes'}->{ $field } );
272             }
273             catch
274             {
275 0     0     $errors_count++;
276 0           $self->add_error( "Failed to apply the staged values for the following attribute: $field." );
277 0           };
278             }
279              
280 0 0         return $errors_count > 0 ? 0 : 1;
281             }
282              
283              
284             =head1 MANAGING LIST SUBSCRIPTIONS
285              
286             =head2 get_lists_status()
287              
288             Returns the subscription status for the lists on the current object.
289              
290             By default, it retrieves the live data (i.e., list subscriptions synchronized
291             with ExactTarget). If you want to retrieve the staged data, you can set
292             I to 0 in the parameters.
293              
294             This function takes one mandatory parameter, which indicates whether you want
295             the staged list information (lists subscribed to locally but not yet
296             synchronized with ExactTarget) or the live list information (lists subscribed to
297             in ExactTarget's database). The respective options are I for the staged
298             information, and I for the live information.
299              
300             # Retrieve staged attributes (i.e., not synchronized yet with ExactTarget).
301             my $lists_status = $self->get_lists_status( 'is_live' => 0 );
302              
303             # Retrieve live attributes.
304             my $lists_status = $self->get_lists_status( 'is_live' => 1 );
305             my $lists_status = $self->get_lists_status();
306              
307             =cut
308              
309             sub get_lists_status
310             {
311 0     0 1   my ( $self, %args ) = @_;
312 0           my $is_live = delete( $args{'is_live'} );
313 0 0         $is_live = 1 unless defined( $is_live );
314              
315 0 0         my $storage_key = $is_live
316             ? 'lists'
317             : 'staged_lists';
318              
319 0 0         return { %{ $self->{ $storage_key } || {} } };
  0            
320             }
321              
322              
323             =head2 set_lists_status()
324              
325             Stores the list IDs and corresponding subscription status.
326              
327             $subscriber->set_lists_status(
328             {
329             '1234567' => 'Active',
330             '1234568' => 'Unsubscribed',
331             },
332             'is_live' => $boolean, #default 0
333             );
334              
335             The I parameter allows specifying whether the data in the hashref are
336             local only or if they are already synchronized with ExactTarget's database. By
337             default, changes are considered local only and you will explicitely have to
338             synchronize them using the functions of
339             Email::ExactTarget::SubscriberOperations.
340              
341             'Active' and 'Unsubscribed' are the two valid statuses for list subscriptions.
342              
343             =cut
344              
345             sub set_lists_status
346             {
347 0     0 1   my ( $self, $statuses, %args ) = @_;
348 0   0       my $is_live = delete( $args{'is_live'} ) || 0;
349              
350 0 0         confess 'Cannot modify an object flagged as permanently deleted'
351             if $self->is_deleted_permanently();
352              
353             # Verify the new status for each list.
354 0           while ( my ( $list_id, $status ) = each( %$statuses ) )
355             {
356 0 0         confess "The status for list ID >$list_id< must be defined"
357             unless defined( $status );
358              
359             # See the following page for an explanation of the valid statuses:
360             # http://wiki.memberlandingpages.com/System_Guides/Bounce_Mail_Management#Subscriber_Status
361 0 0         confess "The status >$status< for list ID >$list_id< is incorrect"
362             unless $status =~ m/^(?:Active|Unsubscribed|Held|Bounced|Deleted)$/x;
363             }
364              
365             # If all the status passed are valid, we can now proceed with updating the
366             # subscriber object (we want all updates or none).
367 0 0         my $storage_key = $is_live ? 'lists' : 'staged_lists';
368 0           while ( my ( $list_id, $status ) = each( %$statuses ) )
369             {
370 0           $self->{ $storage_key }->{ $list_id } = $status;
371             }
372              
373 0           return 1;
374             }
375              
376              
377             =head2 apply_staged_lists_status()
378              
379             Moves the staged list subscription changes onto the current object, effectively
380             'applying' the changes.
381              
382             $subscriber->apply_staged_lists_status(
383             [
384             '1234567'
385             '1234568',
386             ]
387             ) || confess Dumper( $subscriber->errors() );
388              
389             =cut
390              
391             sub apply_staged_lists_status
392             {
393 0     0 1   my ( $self, $lists_status ) = @_;
394              
395 0 0         confess 'The first parameter needs to be an hashref of list IDs and statuses to apply'
396             if !Data::Validate::Type::is_hashref( $lists_status );
397              
398 0 0         confess 'Cannot modify an object flagged as permanently deleted'
399             if $self->is_deleted_permanently();
400              
401 0           my $errors_count = 0;
402 0           while ( my ( $list_id, $status ) = each( %$lists_status ) )
403             {
404             try
405             {
406 0     0     $self->set_lists_status(
407             {
408             $list_id => $status,
409             },
410             'is_live' => 1,
411             );
412              
413 0           delete( $self->{'staged_lists'}->{ $list_id } );
414             }
415             catch
416             {
417 0     0     $errors_count++;
418 0           $self->add_error( "Failed to apply the staged list statuses for the following list ID: $list_id." );
419 0           };
420             }
421              
422 0 0         return $errors_count > 0 ? 0 : 1;
423             }
424              
425              
426             =head1 MANAGING PROPERTIES
427              
428             =head2 get_properties()
429              
430             Retrieve a hashref containing all the properties of the current object.
431              
432             By default, it retrieves the live data (i.e., properties synchronized with
433             ExactTarget). If you want to retrieve the staged data, you can set
434             I to 0 in the parameters.
435              
436             # Retrieve staged properties (i.e., not synchronized yet with ExactTarget).
437             my $properties = $subscriber->get_properties( 'is_live' => 0 );
438              
439             # Retrieve live properties.
440             my $properties = $subscriber->get_properties( 'is_live' => 1 );
441             my $properties = $subscriber->get_properties();
442              
443             =cut
444              
445             sub get_properties
446             {
447 0     0 1   my ( $self, %args ) = @_;
448 0           my $is_live = delete( $args{'is_live'} );
449 0 0         $is_live = 1 unless defined( $is_live );
450              
451 0 0         my $storage_key = $is_live
452             ? 'properties'
453             : 'staged_properties';
454              
455             # Make a copy of the attributes before returning them, in case the caller
456             # needs to modify the hash.
457 0 0         return { %{ $self->{ $storage_key } || {} } };
  0            
458             }
459              
460              
461             =head2 get_property()
462              
463             Retrieve the value corresponding to the property name passed as first
464             parameter.
465              
466             # Retrieve staged (non-synchronized with ExactTarget) property named
467             # EmailTypePreference.
468             my $staged_email_type_preference = $subscriber->get_property(
469             'EmailTypePreference',
470             is_live => 0,
471             );
472              
473             # If you've retrieved the subscriber object from ExactTarget, this
474             # retrieves the live property that was returned by the webservice.
475             my $live_email_type_preference = $subscriber->get_property(
476             'EmailTypePreference',
477             is_live => 1,
478             );
479              
480             =cut
481              
482             sub get_property
483             {
484 0     0 1   my ( $self, $property, %args ) = @_;
485 0           my $is_live = delete( $args{'is_live'} );
486 0 0         $is_live = 1 unless defined( $is_live );
487              
488 0 0 0       confess 'An property name is required to retrieve the corresponding value'
489             if !defined( $property ) || ( $property eq '' );
490              
491 0 0         my $storage_key = $is_live
492             ? 'properties'
493             : 'staged_properties';
494              
495 0 0         carp "The property '$property' does not exist on the Subscriber object"
496             unless exists( $self->{ $storage_key }->{ $property } );
497              
498 0           return $self->{ $storage_key }->{ $property };
499             }
500              
501              
502             =head2 set_properties()
503              
504             Sets the properties and corresponding values for the current subscriber object.
505              
506             $subscriber->set_properties(
507             {
508             EmailTypePreference => 'Text',
509             },
510             'is_live' => $boolean, #default 0
511             );
512              
513             The I parameter allows specifying whether the data in the hashref are
514             local only or if they are already synchronized with ExactTarget's database. By
515             default, changes are considered local only and you will explicitely have to
516             synchronize them using the functions of
517             L.
518              
519             =cut
520              
521             sub set_properties
522             {
523 0     0 1   my ( $self, $properties, %args ) = @_;
524 0   0       my $is_live = delete( $args{'is_live'} ) || 0;
525              
526 0 0         confess 'Cannot modify an object flagged as permanently deleted'
527             if $self->is_deleted_permanently();
528              
529 0 0         my $storage_key = $is_live
530             ? 'properties'
531             : 'staged_properties';
532              
533 0           while ( my ( $name, $value ) = each( %$properties ) )
534             {
535 0           $self->{ $storage_key }->{ $name } = $value;
536             }
537              
538 0           return 1;
539             }
540              
541              
542             =head1 MANAGING ERRORS
543              
544             =head2 add_error()
545              
546             Adds a new error message to the current object.
547              
548             $subscriber->add_error( 'Cannot update object.' ) || confess 'Failed to add error';
549              
550             =cut
551              
552             sub add_error
553             {
554 0     0 1   my ( $self, $error ) = @_;
555              
556 0 0 0       if ( !defined( $error ) || ( $error eq '' ) )
557             {
558 0           carp 'No error text specified';
559 0           return 0;
560             }
561              
562 0   0       $self->{'errors'} ||= [];
563 0           push( @{ $self->{'errors'} }, $error );
  0            
564 0           return 1;
565             }
566              
567              
568             =head2 errors()
569              
570             Returns the errors stored on the current object as an arrayref if there is any,
571             otherwise returns undef.
572              
573             # Retrieve the errors.
574             my $errors = $subscriber->errors();
575             if ( defined( $errors ) )
576             {
577             print Dumper( $errors );
578             }
579              
580             # Retrieve and remove the errors.
581             my $errors = $subscriber->errors( reset => 1 );
582             if ( defined( $errors ) )
583             {
584             print Dumper( $errors );
585             }
586              
587             =cut
588              
589             sub errors
590             {
591 0     0 1   my ( $self, %args ) = @_;
592 0   0       my $reset = delete( $args{'reset'} ) || 0;
593              
594 0           my $errors = $self->{'errors'};
595              
596             # If the options require it, removes the errors on the current object.
597 0 0         $self->{'errors'} = []
598             if $reset;
599              
600 0           return $errors;
601             }
602              
603              
604             =head1 MANAGING DELETED SUBSCRIBERS
605              
606             =head2 flag_as_deleted_permanently()
607              
608             Flags the subscriber as having been deleted in ExactTarget's database. Any
609             subsequent operation on this object will be denied.
610              
611             $subscriber->flag_as_deleted_permanently();
612              
613             =cut
614              
615             sub flag_as_deleted_permanently
616             {
617 0     0 1   my ( $self ) = @_;
618              
619 0           delete( $self->{'id'} );
620 0           $self->{'deleted_permanently'} = 1;
621              
622 0           return 1;
623             }
624              
625              
626             =head2 is_deleted_permanently()
627              
628             Returns a boolean indicating if the current object has been removed from
629             ExactTarget's database.
630              
631             my $is_removed = $subscriber->is_deleted_permanently();
632              
633             =cut
634              
635             sub is_deleted_permanently
636             {
637 0     0 1   my ( $self ) = @_;
638              
639 0 0         return $self->{'deleted_permanently'} ? 1 : 0;
640             }
641              
642              
643             =head1 DEPRECATED METHODS
644              
645             =head2 get()
646              
647             Please use C instead.
648              
649             =cut
650              
651             sub get
652             {
653 0     0 1   croak 'get() has been deprecated, please use get_attribute() instead.';
654             }
655              
656              
657             =head2 set()
658              
659             Please use C instead.
660              
661             =cut
662              
663             sub set ## no critic (NamingConventions::ProhibitAmbiguousNames)
664             {
665 0     0 1   croak 'set() has been deprecated, please use set_attribute() instead.';
666             }
667              
668              
669             =head1 BUGS
670              
671             Please report any bugs or feature requests through the web interface at
672             L.
673             I will be notified, and then you'll automatically be notified of progress on
674             your bug as I make changes.
675              
676              
677             =head1 SUPPORT
678              
679             You can find documentation for this module with the perldoc command.
680              
681             perldoc Email::ExactTarget::Subscriber
682              
683              
684             You can also look for information at:
685              
686             =over 4
687              
688             =item * GitHub's request tracker
689              
690             L
691              
692             =item * AnnoCPAN: Annotated CPAN documentation
693              
694             L
695              
696             =item * CPAN Ratings
697              
698             L
699              
700             =item * MetaCPAN
701              
702             L
703              
704             =back
705              
706              
707             =head1 AUTHOR
708              
709             L,
710             C<< >>.
711              
712              
713             =head1 COPYRIGHT & LICENSE
714              
715             Copyright 2009-2014 Guillaume Aubert.
716              
717             This program is free software: you can redistribute it and/or modify it under
718             the terms of the GNU General Public License version 3 as published by the Free
719             Software Foundation.
720              
721             This program is distributed in the hope that it will be useful, but WITHOUT ANY
722             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
723             PARTICULAR PURPOSE. See the GNU General Public License for more details.
724              
725             You should have received a copy of the GNU General Public License along with
726             this program. If not, see http://www.gnu.org/licenses/
727              
728             =cut
729              
730             1;