File Coverage

blib/lib/Email/Folder/Exchange/EWS.pm
Criterion Covered Total %
statement 31 33 93.9
branch n/a
condition n/a
subroutine 11 11 100.0
pod n/a
total 42 44 95.4


line stmt bran cond sub pod time code
1             package Email::Folder::Exchange::EWS;
2 2     2   22 use base qw(Email::Folder);
  2         5  
  2         190  
3              
4 2     2   10 use strict;
  2         4  
  2         69  
5 2     2   11 use warnings;
  2         2  
  2         61  
6              
7 2     2   9 use Email::Folder;
  2         3  
  2         54  
8 2     2   10 use URI::Escape;
  2         5  
  2         147  
9 2     2   13 use LWP::UserAgent;
  2         2  
  2         47  
10 2     2   9 use Carp qw(croak cluck);
  2         3  
  2         119  
11 2     2   1637 use LWP::Debug;
  2         1121  
  2         11  
12              
13 2     2   194 use constant TYPES_NS => 'http://schemas.microsoft.com/exchange/services/2006/types';
  2         2  
  2         123  
14 2     2   9 use constant MESSAGES_NS => 'http://schemas.microsoft.com/exchange/services/2006/messages';
  2         3  
  2         73  
15              
16 2     2   1960 use SOAP::Lite;
  0            
  0            
17             use SOAP::Lite::Utils qw(__mk_accessors);
18              
19             use HTTP::Request;
20             use HTTP::Headers;
21             use MIME::Base64;
22              
23             #SOAP::Lite->import( +trace => 'all' );
24              
25             BEGIN {
26             __PACKAGE__->__mk_accessors(qw(soap folder_id unread_count display_name child_folder_count total_count _folder_ids _message_ids));
27             };
28              
29             sub new {
30             my ($self, $class, $url, $username, $password) = ({}, @_);
31             bless $self, $class;
32              
33             croak "URL required" unless $url;
34              
35             my $uri = URI->new($url);
36              
37             # guess the path to the exchange web service
38             if(! $uri->path) {
39             $uri->path('/EWS/Exchange.asmx');
40             }
41              
42             # build soap accessor
43             my $soap = SOAP::Lite->proxy(
44             $uri->as_string,
45             keep_alive => 1,
46             credentials => [
47             $uri->host . ':' . ( $uri->scheme eq 'https' ? '443' : '80' ),
48             # $uri->host,
49             '',
50             $username,
51             $password
52             ],
53             requests_redirectable => [ 'GET', 'POST', 'HEAD' ],
54             );
55             $self->soap($soap);
56             # EWS requires the path and method to be separated by slash, not pound
57             $soap->on_action( sub { MESSAGES_NS . "/$_[1]" });
58             # setup the schemas
59             $soap->ns(TYPES_NS, 't');
60             $soap->default_ns(MESSAGES_NS);
61             $soap->uri(MESSAGES_NS);
62             # EWS does not like the encodingStyle attribute
63             $soap->encodingStyle("");
64              
65             $self->folder_id('inbox');
66             $self->refresh;
67              
68             return $self;
69             }
70              
71             sub new_from_id {
72             my ($self, $class, $soap, $folder_id) = ({}, @_);
73             bless $self, $class;
74              
75             $self->soap($soap);
76             $self->folder_id($folder_id);
77             $self->refresh;
78              
79             return $self;
80             }
81              
82             sub refresh {
83             my ($self) = @_;
84              
85             my $soap = $self->soap;
86              
87             my $som = do {
88             local $^W; # disable warnings from SOAP::Transport::HTTP
89              
90             $soap->GetFolder(
91             SOAP::Data
92             ->name('FolderShape')
93             ->value(
94             \SOAP::Data
95             ->name('BaseShape')
96             ->value('Default')
97             ->prefix('t')
98             ->type('')
99             ),
100             SOAP::Data
101             ->name('FolderIds')
102             ->value(
103             \SOAP::Data
104             # CAUTION: cheap hack!
105             # if the folder id is longer than 64 characters then treat it as a folder id. otherwise, treat it as a named folder like 'inbox'
106             ->name( ( length($self->folder_id) > 64 ? 'FolderId' : 'DistinguishedFolderId' ) )
107             ->prefix('t')
108             ->attr({ Id => $self->folder_id })
109             )
110             );
111             };
112              
113             # handle SOAP-level fault
114             if($som->fault) {
115             die $som->faultstring;
116             }
117              
118             # handle method-level fault [why!?!!]
119             my $response_message = $som->valueof('//MessageText');
120             die $response_message if $response_message;
121            
122             # map the return data into myself
123             $self->folder_id( $som->dataof('//FolderId')->attr->{Id} );
124             $self->unread_count( $som->valueof('//Folder/UnreadCount') );
125             $self->display_name( $som->valueof('//Folder/DisplayName') );
126             $self->child_folder_count( $som->valueof('//Folder/ChildFolderCount') );
127             $self->total_count( $som->valueof('//Folder/TotalCount') );
128             }
129              
130             sub refresh_folders {
131             my ($self) = @_;
132              
133             my $soap = $self->soap;
134              
135             # example of using FindFolder to get subfolders
136             my $method = SOAP::Data
137             ->name('FindFolder')
138             ->attr({ Traversal => 'Shallow', xmlns => MESSAGES_NS });
139              
140             my $som = do {
141             local $^W; # disable warnings from SOAP::Transport::HTTP
142              
143             $soap->call( $method,
144             SOAP::Data
145             ->name('FolderShape')
146             ->value(
147             \SOAP::Data
148             ->name('BaseShape')
149             ->value('IdOnly')
150             ->prefix('t')
151             ->type('')
152             ),
153             SOAP::Data
154             ->name('ParentFolderIds')
155             ->value(
156             \SOAP::Data
157             # CAUTION: cheap hack!
158             # if the folder id is longer than 64 characters then treat it as a folder id. otherwise, treat it as a named folder like 'inbox'
159             ->name( ( length($self->folder_id) > 64 ? 'FolderId' : 'DistinguishedFolderId' ) )
160             ->prefix('t')
161             ->attr({ Id => $self->folder_id })
162             )
163             );
164             };
165              
166             # handle SOAP-level fault
167             if($som->fault) {
168             die $som->faultstring;
169             }
170              
171             # handle method-level fault [why!?!!]
172             my $response_message = $som->valueof('//MessageText');
173             die $response_message if $response_message;
174              
175             my @folder_ids;
176             for my $folderid_som ( $som->dataof('//FolderId') ) {
177             push @folder_ids, $folderid_som->attr->{Id};
178             }
179             $self->_folder_ids(\@folder_ids);
180             return @folder_ids;
181             }
182              
183             sub folders {
184             my ($self) = @_;
185              
186             # lazy-refresh of subfolders
187             if(! defined $self->_folder_ids) {
188             $self->refresh_folders;
189             }
190              
191             # fetch folder details
192             return map {
193             __PACKAGE__->new_from_id($self->soap, $_)
194             } @{ $self->_folder_ids };
195             }
196              
197             sub next_folder {
198             my ($self) = @_;
199              
200             # lazy-refresh of subfolders
201             if(! defined $self->_folder_ids) {
202             $self->refresh_folders;
203             }
204              
205             # fetch folder details
206             my $folder_id = shift @{ $self->_folder_ids };
207             return unless $folder_id;
208              
209             return __PACKAGE__->new_from_id($self->soap, $folder_id);
210             }
211              
212             sub refresh_messages {
213             my ($self) = @_;
214              
215             my $soap = $self->soap;
216              
217             my $method = SOAP::Data
218             ->name('FindItem')
219             ->attr({ Traversal => 'Shallow', xmlns => MESSAGES_NS });
220              
221             my $som = do {
222             local $^W; # disable warnings from SOAP::Transport::HTTP
223              
224             $soap->call( $method,
225             SOAP::Data
226             ->name('ItemShape' =>
227             \SOAP::Data->value(
228             SOAP::Data
229             ->name('BaseShape')
230             ->value('IdOnly')
231             ->prefix('t')
232             ->type(''),
233             )),
234             SOAP::Data
235             ->name('ParentFolderIds')
236             ->value(
237             \SOAP::Data
238             # CAUTION: cheap hack!
239             # if the folder id is longer than 64 characters then treat it as a folder id. otherwise, treat it as a named folder like 'inbox'
240             ->name( ( length($self->folder_id) > 64 ? 'FolderId' : 'DistinguishedFolderId' ) )
241             ->prefix('t')
242             ->attr({ Id => $self->folder_id })
243             )
244             );
245             };
246            
247             # handle soap-level fault
248             if($som->fault) {
249             die $som->faultstring;
250             }
251              
252             # handle method-level fault [why!?!!]
253             my $response_message = $som->valueof('//MessageText');
254             die $response_message if $response_message;
255            
256             my @message_ids;
257             for my $itemid_som ( $som->dataof('//ItemId') ) {
258             push @message_ids, $itemid_som->attr->{'Id'};
259             }
260             $self->_message_ids(\@message_ids);
261              
262             return @message_ids;
263             }
264              
265             sub _get_message {
266             my ($self, $message_id) = @_;
267              
268              
269             my $soap = $self->soap;
270              
271             my $method = SOAP::Data
272             ->name('GetItem')
273             ->attr({ xmlns => MESSAGES_NS });
274              
275             my $som = do {
276             local $^W; # disable warnings from SOAP::Transport::HTTP
277              
278             $soap->call( $method,
279             SOAP::Data
280             ->name('ItemShape' =>
281             \SOAP::Data->value(
282             SOAP::Data
283             ->name('BaseShape')
284             ->value('IdOnly')
285             ->prefix('t')
286             ->type(''),
287             SOAP::Data
288             ->name('IncludeMimeContent')
289             ->value('true')
290             ->prefix('t')
291             ->type('')
292             )),
293             SOAP::Data
294             ->name('ItemIds')
295             ->value(
296             \SOAP::Data
297             ->name('ItemId')
298             ->prefix('t')
299             ->attr({ Id => $message_id })
300             )
301             );
302             };
303              
304             # handle SOAP-level fault
305             if($som->fault) {
306             die $som->faultstring;
307             }
308              
309             # handle method-level fault [why!?!!]
310             my $response_message = $som->valueof('//MessageText');
311             die $response_message if $response_message;
312              
313             # find the MIME content
314             my $content = $som->valueof('//MimeContent');
315             my $msg = $self->bless_message(decode_base64($content));
316             return $self->bless_message(decode_base64($content));
317             }
318              
319             sub messages {
320             my ($self) = @_;
321              
322             # lazy-refresh of messages
323             if(! defined $self->_message_ids) {
324             $self->refresh_messages;
325             }
326              
327             # fetch folder details
328             return map {
329             $self->_get_message($_)
330             } @{ $self->_message_ids };
331             }
332              
333             sub next_message {
334             my ($self) = @_;
335              
336             # lazy-refresh of messages
337             if(! defined $self->_message_ids) {
338             $self->refresh_messages;
339             }
340              
341             # fetch message details
342             my $message_id = shift @{ $self->_message_ids };
343             return unless $message_id;
344              
345             return $self->_get_message($message_id);
346             }
347              
348             1;
349              
350             __END__