File Coverage

blib/lib/Email/MIME/Header/AddressList.pm
Criterion Covered Total %
statement 103 121 85.1
branch 19 32 59.3
condition 11 18 61.1
subroutine 16 18 88.8
pod 13 13 100.0
total 162 202 80.2


line stmt bran cond sub pod time code
1             # Copyright (c) 2016-2017 by Pali
2              
3             package Email::MIME::Header::AddressList 1.953;
4              
5             # ABSTRACT: MIME support for list of Email::Address::XS objects
6              
7 3     3   2746 use v5.12.0;
  3         11  
8 3     3   19 use warnings;
  3         5  
  3         74  
9              
10 3     3   14 use Carp ();
  3         7  
  3         55  
11 3     3   16 use Email::Address::XS;
  3         6  
  3         118  
12 3     3   16 use Email::MIME::Encode;
  3         6  
  3         4575  
13              
14             #pod =encoding utf8
15             #pod
16             #pod =head1 SYNOPSIS
17             #pod
18             #pod my $address1 = Email::Address::XS->new('Name1' => 'address1@host.com');
19             #pod my $address2 = Email::Address::XS->new("Name2 \N{U+263A}" => 'address2@host.com');
20             #pod my $mime_address = Email::Address::XS->new('=?UTF-8?B?TmFtZTIg4pi6?=' => 'address2@host.com');
21             #pod
22             #pod my $list1 = Email::MIME::Header::AddressList->new($address1, $address2);
23             #pod
24             #pod $list1->append_groups('undisclosed-recipients' => []);
25             #pod
26             #pod $list1->first_address();
27             #pod # returns $address1
28             #pod
29             #pod $list1->groups();
30             #pod # returns (undef, [ $address1, $address2 ], 'undisclosed-recipients', [])
31             #pod
32             #pod $list1->as_string();
33             #pod # returns 'Name1 , "Name2 ☺" , undisclosed-recipients:;'
34             #pod
35             #pod $list1->as_mime_string();
36             #pod # returns 'Name1 , =?UTF-8?B?TmFtZTIg4pi6?= , undisclosed-recipients:;'
37             #pod
38             #pod my $list2 = Email::MIME::Header::AddressList->new_groups(Group => [ $address1, $address2 ]);
39             #pod
40             #pod $list2->append_addresses($address2);
41             #pod
42             #pod $list2->addresses();
43             #pod # returns ($address2, $address1, $address2)
44             #pod
45             #pod $list2->groups();
46             #pod # returns (undef, [ $address2 ], 'Group', [ $address1, $address2 ])
47             #pod
48             #pod my $list3 = Email::MIME::Header::AddressList->new_mime_groups('=?UTF-8?B?4pi6?=' => [ $mime_address ]);
49             #pod $list3->as_string();
50             #pod # returns '☺: "Name2 ☺" ;'
51             #pod
52             #pod my $list4 = Email::MIME::Header::AddressList->from_string('Name1 , "Name2 ☺" , undisclosed-recipients:;');
53             #pod my $list5 = Email::MIME::Header::AddressList->from_string('Name1 ', '"Name2 ☺" ', 'undisclosed-recipients:;');
54             #pod
55             #pod my $list6 = Email::MIME::Header::AddressList->from_mime_string('Name1 , =?UTF-8?B?TmFtZTIg4pi6?= , undisclosed-recipients:;');
56             #pod my $list7 = Email::MIME::Header::AddressList->from_mime_string('Name1 ', '=?UTF-8?B?TmFtZTIg4pi6?= ', 'undisclosed-recipients:;');
57             #pod
58             #pod =head1 DESCRIPTION
59             #pod
60             #pod This module implements object representation for the list of the
61             #pod L objects. It provides methods for
62             #pod L MIME encoding and decoding
63             #pod suitable for L address-list
64             #pod structure.
65             #pod
66             #pod =head2 EXPORT
67             #pod
68             #pod None
69             #pod
70             #pod =head2 Class Methods
71             #pod
72             #pod =over 4
73             #pod
74             #pod =item new_empty
75             #pod
76             #pod Construct new empty C object.
77             #pod
78             #pod =cut
79              
80             sub new_empty {
81 59     59 1 109 my ($class) = @_;
82 59         201 return bless { addresses => [], groups => [] }, $class;
83             }
84              
85             #pod =item new
86             #pod
87             #pod Construct new C object from list of
88             #pod L objects.
89             #pod
90             #pod =cut
91              
92             sub new {
93 0     0 1 0 my ($class, @addresses) = @_;
94 0         0 my $self = $class->new_empty();
95 0         0 $self->append_addresses(@addresses);
96 0         0 return $self;
97             }
98              
99             #pod =item new_groups
100             #pod
101             #pod Construct new C object from named groups of
102             #pod L objects.
103             #pod
104             #pod =cut
105              
106             sub new_groups {
107 59     59 1 11784 my ($class, @groups) = @_;
108 59         123 my $self = $class->new_empty();
109 59         184 $self->append_groups(@groups);
110 59         264 return $self;
111             }
112              
113             #pod =item new_mime_groups
114             #pod
115             #pod Like L|/new_groups> but in this method group names and objects properties are
116             #pod expected to be already MIME encoded, thus ASCII strings.
117             #pod
118             #pod =cut
119              
120             sub new_mime_groups {
121 35     35 1 98 my ($class, @groups) = @_;
122 35 50       92 if (scalar @groups % 2) {
123 0         0 Carp::carp 'Odd number of elements in argument list';
124 0         0 return;
125             }
126 35         114 foreach (0 .. scalar @groups / 2 - 1) {
127 35 100 100     144 $groups[2 * $_] = Email::MIME::Encode::mime_decode($groups[2 * $_])
128             if defined $groups[2 * $_] and $groups[2 * $_] =~ /=\?/;
129 35         55 $groups[2 * $_ + 1] = [ @{$groups[2 * $_ + 1]} ];
  35         130  
130 35         54 foreach (@{$groups[2 * $_ + 1]}) {
  35         114  
131 44 50       270 next unless Email::Address::XS->is_obj($_);
132 44   66     169 my $decode_phrase = (defined $_->phrase and $_->phrase =~ /=\?/);
133 44   33     673 my $decode_comment = (defined $_->comment and $_->comment =~ /=\?/);
134 44 100 66     303 next unless $decode_phrase or $decode_comment;
135 43         151 $_ = ref($_)->new(copy => $_);
136 43 50       3963 $_->phrase(Email::MIME::Encode::mime_decode($_->phrase))
137             if $decode_phrase;
138 43 50       442 $_->comment(Email::MIME::Encode::mime_decode($_->comment))
139             if $decode_comment;
140             }
141             }
142 35         104 return $class->new_groups(@groups);
143             }
144              
145             #pod =item from_string
146             #pod
147             #pod Construct new C object from input string arguments.
148             #pod Calls L.
149             #pod
150             #pod =cut
151              
152             sub from_string {
153 22     22 1 69 my ($class, @strings) = @_;
154 22         47 return $class->new_groups(map { Email::Address::XS::parse_email_groups($_) } @strings);
  24         307  
155             }
156              
157             #pod =item from_mime_string
158             #pod
159             #pod Like L|/from_string> but input string arguments are expected to
160             #pod be already MIME encoded.
161             #pod
162             #pod =cut
163              
164             sub from_mime_string {
165 35     35 1 81 my ($class, @strings) = @_;
166 35         66 return $class->new_mime_groups(map { Email::Address::XS::parse_email_groups($_) } @strings);
  35         397  
167             }
168              
169             #pod =back
170             #pod
171             #pod =head2 Object Methods
172             #pod
173             #pod =over 4
174             #pod
175             #pod =item as_string
176             #pod
177             #pod Returns string representation of C object.
178             #pod Calls L.
179             #pod
180             #pod =cut
181              
182             sub as_string {
183 21     21 1 49 my ($self) = @_;
184 21         45 return Email::Address::XS::format_email_groups($self->groups());
185             }
186              
187             #pod =item as_mime_string
188             #pod
189             #pod Like L|/as_string> but output string will be properly and
190             #pod unambiguously MIME encoded. MIME encoding is done before calling
191             #pod L.
192             #pod
193             #pod =cut
194              
195             sub as_mime_string {
196 24     24 1 51 my ($self, $arg) = @_;
197 24         48 my $charset = $arg->{charset};
198 24         34 my $header_name_length = $arg->{header_name_length};
199              
200 24         58 my @groups = $self->groups();
201 24         60 foreach (0 .. scalar @groups / 2 - 1) {
202 26 100       73 $groups[2 * $_] = Email::MIME::Encode::mime_encode($groups[2 * $_], $charset)
203             if Email::MIME::Encode::_needs_mime_encode_addr($groups[2 * $_]);
204 26         50 $groups[2 * $_ + 1] = [ @{$groups[2 * $_ + 1]} ];
  26         62  
205 26         45 foreach (@{$groups[2 * $_ + 1]}) {
  26         73  
206 27         83 my $encode_phrase = Email::MIME::Encode::_needs_mime_encode_addr($_->phrase);
207 27         89 my $encode_comment = Email::MIME::Encode::_needs_mime_encode_addr($_->comment);
208 27 100 66     77 next unless $encode_phrase or $encode_comment;
209 23         87 $_ = ref($_)->new(copy => $_);
210 23 50       2300 $_->phrase(Email::MIME::Encode::mime_encode($_->phrase, $charset))
211             if $encode_phrase;
212 23 50       233 $_->comment(Email::MIME::Encode::mime_encode($_->comment, $charset))
213             if $encode_comment;
214             }
215             }
216 24         452 return Email::Address::XS::format_email_groups(@groups);
217             }
218              
219             #pod =item first_address
220             #pod
221             #pod Returns first L object.
222             #pod
223             #pod =cut
224              
225             sub first_address {
226 7     7 1 15 my ($self) = @_;
227 7 50       11 return $self->{addresses}->[0] if @{$self->{addresses}};
  7         18  
228 7         14 my $groups = $self->{groups};
229 7         12 foreach (0 .. @{$groups} / 2 - 1) {
  7         19  
230 7 50       11 next unless @{$groups->[2 * $_ + 1]};
  7         19  
231 7         62 return $groups->[2 * $_ + 1]->[0];
232             }
233 0         0 return undef;
234             }
235              
236             #pod =item addresses
237             #pod
238             #pod Returns list of all L objects.
239             #pod
240             #pod =cut
241              
242             sub addresses {
243 2     2 1 10 my ($self) = @_;
244 2         6 my $t = 1;
245 2         3 my @addresses = @{$self->{addresses}};
  2         6  
246 2         3 push @addresses, map { @{$_} } grep { $t ^= 1 } @{$self->{groups}};
  2         3  
  2         6  
  4         9  
  2         5  
247 2         8 return @addresses;
248             }
249              
250             #pod =item groups
251             #pod
252             #pod Like L|/addresses> but returns objects with named groups.
253             #pod
254             #pod =cut
255              
256             sub groups {
257 51     51 1 98 my ($self) = @_;
258 51         71 my @groups = @{$self->{groups}};
  51         120  
259 53         160 $groups[2 * $_ + 1] = [ @{$groups[2 * $_ + 1]} ]
260 51         154 foreach 0 .. scalar @groups / 2 - 1;
261 0         0 unshift @groups, undef, [ @{$self->{addresses}} ]
262 51 50       79 if @{$self->{addresses}};
  51         108  
263 51         464 return @groups;
264             }
265              
266             #pod =item append_addresses
267             #pod
268             #pod Append L objects.
269             #pod
270             #pod =cut
271              
272             sub append_addresses {
273 0     0 1 0 my ($self, @addresses) = @_;
274 0         0 my @valid_addresses = grep { Email::Address::XS->is_obj($_) } @addresses;
  0         0  
275 0 0       0 Carp::carp 'Argument is not an Email::Address::XS object' if scalar @valid_addresses != scalar @addresses;
276 0         0 push @{$self->{addresses}}, @valid_addresses;
  0         0  
277             }
278              
279             #pod =item append_groups
280             #pod
281             #pod Like L|/append_addresses> but arguments are pairs of name of
282             #pod group and array reference of L objects.
283             #pod
284             #pod =cut
285              
286             sub append_groups {
287 59     59 1 112 my ($self, @groups) = @_;
288 59 50       154 if (scalar @groups % 2) {
289 0         0 Carp::carp 'Odd number of elements in argument list';
290 0         0 return;
291             }
292 59         94 my $carp_invalid = 1;
293 59         85 my @valid_groups;
294 59         192 foreach (0 .. scalar @groups / 2 - 1) {
295 61         129 push @valid_groups, $groups[2 * $_];
296 61         110 my $addresses = $groups[2 * $_ + 1];
297 61         86 my @valid_addresses = grep { Email::Address::XS->is_obj($_) } @{$addresses};
  71         440  
  61         119  
298 61 50 33     177 if ($carp_invalid and scalar @valid_addresses != scalar @{$addresses}) {
  61         210  
299 0         0 Carp::carp 'Array element is not an Email::Address::XS object';
300 0         0 $carp_invalid = 0;
301             }
302 61         152 push @valid_groups, \@valid_addresses;
303             }
304 59         98 push @{$self->{groups}}, @valid_groups;
  59         155  
305             }
306              
307             #pod =back
308             #pod
309             #pod =head1 SEE ALSO
310             #pod
311             #pod L,
312             #pod L,
313             #pod L,
314             #pod L
315             #pod
316             #pod =head1 AUTHOR
317             #pod
318             #pod Pali Epali@cpan.orgE
319             #pod
320             #pod =cut
321              
322             1;
323              
324             __END__