1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Contains ExchangeWebServices. |
4
|
|
|
*/ |
5
|
|
|
|
6
|
|
|
|
7
|
|
|
namespace garethp\ews\API; |
8
|
|
|
|
9
|
|
|
use garethp\ews\API\Exception\ExchangeException; |
10
|
|
|
use garethp\ews\API\Exception\NoResponseReturnedException; |
11
|
|
|
use garethp\ews\API\Exception\ServiceUnavailableException; |
12
|
|
|
use garethp\ews\API\Exception\UnauthorizedException; |
13
|
|
|
use garethp\ews\API\ExchangeWebServices\MiddlewareFactory; |
14
|
|
|
use garethp\ews\API\Message; |
15
|
|
|
use garethp\ews\API\Type\EmailAddressType; |
16
|
|
|
use \Closure; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Base class of the Exchange Web Services application. |
20
|
|
|
* |
21
|
|
|
* |
22
|
|
|
* |
23
|
|
|
* @package php-ews\Client |
24
|
|
|
* |
25
|
|
|
* @method Type AddDelegate($request) |
26
|
|
|
* @method Type ApplyConversationAction($request) |
27
|
|
|
* @method Type ConvertId($request) |
28
|
|
|
* @method Type CopyFolder($request) |
29
|
|
|
* @method Type CopyItem($request) |
30
|
|
|
* @method Type CreateAttachment($request) |
31
|
|
|
* @method Type CreateFolder($request) |
32
|
|
|
* @method Type CreateItem($request) |
33
|
|
|
* @method Type CreateManagedFolder($request) |
34
|
|
|
* @method Type CreateUserConfiguration($request) |
35
|
|
|
* @method Type DeleteAttachment($request) |
36
|
|
|
* @method Type DeleteFolder($request) |
37
|
|
|
* @method Type DeleteItem($request) |
38
|
|
|
* @method Type DeleteUserConfiguration($request) |
39
|
|
|
* @method Type DisconnectPhoneCall($request) |
40
|
|
|
* @method Type EmptyFolder($request) |
41
|
|
|
* @method Type ExpandDL($request) |
42
|
|
|
* @method Type ExportItems($request) |
43
|
|
|
* @method Type FindConversation($request) |
44
|
|
|
* @method Type FindFolder($request) |
45
|
|
|
* @method Type FindItem($request) |
46
|
|
|
* @method Type FindMessageTrackingReport($request) |
47
|
|
|
* @method Type GetAttachment($request) |
48
|
|
|
* @method Type GetDelegate($request) |
49
|
|
|
* @method Type GetEvents($request) |
50
|
|
|
* @method Type GetFolder($request) |
51
|
|
|
* @method Type GetInboxRules($request) |
52
|
|
|
* @method Type GetItem($request) |
53
|
|
|
* @method Type GetMailTips($request) |
54
|
|
|
* @method Type GetMessageTrackingReport($request) |
55
|
|
|
* @method Type GetPasswordExpirationDate($request) |
56
|
|
|
* @method Type GetPhoneCallInformation($request) |
57
|
|
|
* @method Type GetRoomLists($request) |
58
|
|
|
* @method Type GetRooms($request) |
59
|
|
|
* @method Type GetServerTimeZones($request) |
60
|
|
|
* @method Type GetServiceConfiguration($request) |
61
|
|
|
* @method Type GetSharingFolder($request) |
62
|
|
|
* @method Type GetSharingMetadata($request) |
63
|
|
|
* @method Type GetStreamingEvents($request) |
64
|
|
|
* @method Type GetUserAvailability($request) |
65
|
|
|
* @method Type GetUserConfiguration($request) |
66
|
|
|
* @method Type GetUserOofSettings($request) |
67
|
|
|
* @method Type MoveFolder($request) |
68
|
|
|
* @method Type MoveItem($request) |
69
|
|
|
* @method Type PlayOnPhone($request) |
70
|
|
|
* @method Type RefreshSharingFolder($request) |
71
|
|
|
* @method Type RemoveDelegate($request) |
72
|
|
|
* @method Type ResolveNames($request) |
73
|
|
|
* @method Type SendItem($request) |
74
|
|
|
* @method Type SetUserOofSettings($request) |
75
|
|
|
* @method Type Subscribe($request) |
76
|
|
|
* @method Type SyncFolderHierarchy($request) |
77
|
|
|
* @method Type SyncFolderItems($request) |
78
|
|
|
* @method Type Unsubscribe($request) |
79
|
|
|
* @method Type UpdateDelegate($request) |
80
|
|
|
* @method Type UpdateFolder($request) |
81
|
|
|
* @method Type UpdateInboxRules($request) |
82
|
|
|
* @method Type UpdateItem($request) |
83
|
|
|
* @method Type UpdateUserConfiguration($request) |
84
|
|
|
* @method Type UploadItems($request) |
85
|
|
|
*/ |
86
|
|
|
class ExchangeWebServices |
87
|
|
|
{ |
88
|
|
|
const VERSION_2007 = 'Exchange2007'; |
89
|
|
|
|
90
|
|
|
const VERSION_2007_SP1 = 'Exchange2007_SP1'; |
91
|
|
|
|
92
|
|
|
const VERSION_2010 = 'Exchange2010'; |
93
|
|
|
|
94
|
|
|
const VERSION_2010_SP1 = 'Exchange2010_SP1'; |
95
|
|
|
|
96
|
|
|
const VERSION_2010_SP2 = 'Exchange2010_SP2'; |
97
|
|
|
|
98
|
|
|
const VERSION_2013 = 'Exchange2013'; |
99
|
|
|
|
100
|
|
|
const VERSION_2013_SP1 = 'Exchange2013_SP1'; |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Password to use when connecting to the Exchange server. |
104
|
|
|
* |
105
|
|
|
* @var string |
106
|
|
|
*/ |
107
|
|
|
protected $password = null; |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* Location of the Exchange server. |
111
|
|
|
* |
112
|
|
|
* @var string |
113
|
|
|
*/ |
114
|
|
|
protected $server = null; |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* SOAP client used to make the request |
118
|
|
|
* |
119
|
|
|
* @var NTLMSoapClient |
120
|
|
|
*/ |
121
|
|
|
protected $soap = null; |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* Username to use when connecting to the Exchange server. |
125
|
|
|
* |
126
|
|
|
* @var string |
127
|
|
|
*/ |
128
|
|
|
protected $username = null; |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* @var EmailAddressType |
132
|
|
|
*/ |
133
|
|
|
protected $primarySmtpMailbox = null; |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* @var Callable[] |
137
|
|
|
*/ |
138
|
|
|
protected static $middlewareStack = false; |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* A setting to check whether or not responses should be drilled down before being |
142
|
|
|
* returned. Setting this to false |
143
|
|
|
* will return the raw responses without any filtering |
144
|
|
|
* |
145
|
|
|
* @var bool |
146
|
|
|
*/ |
147
|
|
|
protected $drillDownResponses = true; |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* Miscrosoft Exchange version that we are going to connect to |
151
|
|
|
* |
152
|
|
|
* @var string |
153
|
|
|
*/ |
154
|
|
|
protected $version = null; |
155
|
|
|
|
156
|
|
|
protected $options = null; |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* The timezone for the client |
160
|
|
|
* |
161
|
|
|
* @var bool |
162
|
|
|
*/ |
163
|
|
|
protected $timezone = false; |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* @return EmailAddressType |
167
|
|
|
*/ |
168
|
27 |
|
public function getPrimarySmtpMailbox() |
169
|
|
|
{ |
170
|
27 |
|
return $this->primarySmtpMailbox; |
171
|
|
|
} |
172
|
|
|
|
173
|
1 |
|
public function getPrimarySmtpEmailAddress() |
174
|
|
|
{ |
175
|
1 |
|
if ($this->primarySmtpMailbox == null) { |
176
|
1 |
|
return null; |
177
|
|
|
} |
178
|
|
|
|
179
|
1 |
|
return $this->primarySmtpMailbox->getEmailAddress(); |
180
|
|
|
} |
181
|
|
|
|
182
|
2 |
|
public function setPrimarySmtpEmailAddress($emailAddress) |
183
|
|
|
{ |
184
|
2 |
|
$mailbox = new EmailAddressType(); |
185
|
2 |
|
$mailbox->setEmailAddress($emailAddress); |
186
|
2 |
|
$this->primarySmtpMailbox = $mailbox; |
187
|
|
|
|
188
|
2 |
|
return $this; |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* @param boolean $timezone |
193
|
|
|
*/ |
194
|
|
|
public function setTimezone($timezone) |
195
|
|
|
{ |
196
|
|
|
$this->timezone = $timezone; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* @return string |
201
|
|
|
*/ |
202
|
|
|
public function getVersion() |
203
|
|
|
{ |
204
|
|
|
return $this->version; |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* @return string |
209
|
|
|
*/ |
210
|
|
|
public function getServer() |
211
|
|
|
{ |
212
|
|
|
return $this->server; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* Constructor for the ExchangeWebServices class |
217
|
|
|
* |
218
|
|
|
* @param string $server |
219
|
|
|
* @param string $username |
220
|
|
|
* @param string $password |
221
|
|
|
* @param array $options |
222
|
|
|
*/ |
223
|
35 |
|
protected function __construct($server = null, $username = null, $password = null, $options = array()) |
224
|
|
|
{ |
225
|
35 |
|
if ($server !== null) { |
226
|
|
|
$this->createClient( |
227
|
|
|
$server, |
228
|
|
|
ExchangeWebServicesAuth::fromUsernameAndPassword($username, $password), |
229
|
|
|
$options |
230
|
|
|
); |
231
|
|
|
} |
232
|
|
|
|
233
|
35 |
|
$this->buildMiddlewareStack(); |
234
|
35 |
|
} |
235
|
|
|
|
236
|
34 |
|
public static function fromUsernameAndPassword($server, $username, $password, $options) |
237
|
|
|
{ |
238
|
34 |
|
$self = new self(); |
239
|
34 |
|
$self->createClient($server, ExchangeWebServicesAuth::fromUsernameAndPassword($username, $password), $options); |
240
|
34 |
|
$self->options = $options; |
241
|
|
|
|
242
|
34 |
|
return $self; |
243
|
|
|
} |
244
|
|
|
|
245
|
1 |
|
public static function fromCallbackToken($server, $token, $options) |
246
|
|
|
{ |
247
|
1 |
|
$self = new self(); |
248
|
1 |
|
$self->createClient($server, ExchangeWebServicesAuth::fromCallbackToken($token), $options); |
249
|
1 |
|
$self->options = $options; |
250
|
|
|
|
251
|
1 |
|
return $self; |
252
|
|
|
} |
253
|
|
|
|
254
|
35 |
|
protected function createClient($server, $auth, $options) |
255
|
|
|
{ |
256
|
35 |
|
$location = 'https://' . $this->cleanServerUrl($server) . '/EWS/Exchange.asmx'; |
257
|
|
|
|
258
|
35 |
|
$options = array_replace_recursive([ |
259
|
35 |
|
'version' => self::VERSION_2007, |
260
|
35 |
|
'trace' => 1, |
261
|
35 |
|
'exceptions' => true, |
262
|
35 |
|
'classmap' => ClassMap::getClassMap(), |
263
|
|
|
'drillDownResponses' => true |
264
|
35 |
|
], $options); |
265
|
|
|
|
266
|
35 |
|
$this->server = $server; |
267
|
35 |
|
$this->version = $options['version']; |
268
|
|
|
|
269
|
35 |
|
$backup = libxml_disable_entity_loader(false); |
270
|
35 |
|
$this->soap = new NTLMSoapClient( |
271
|
35 |
|
$location, |
272
|
35 |
|
$auth, |
273
|
35 |
|
dirname(__FILE__) . '/../../Resources/wsdl/services.wsdl', |
274
|
|
|
$options |
275
|
35 |
|
); |
276
|
35 |
|
libxml_disable_entity_loader($backup); |
277
|
|
|
|
278
|
35 |
|
if (isset($options['primarySmtpEmailAddress'])) { |
279
|
1 |
|
$this->setPrimarySmtpEmailAddress($options['primarySmtpEmailAddress']); |
280
|
1 |
|
} |
281
|
|
|
|
282
|
35 |
|
if (isset($options['impersonation'])) { |
283
|
1 |
|
$this->setPrimarySmtpEmailAddress($options['impersonation']); |
284
|
1 |
|
} |
285
|
|
|
|
286
|
35 |
|
$this->drillDownResponses = $options['drillDownResponses']; |
287
|
35 |
|
} |
288
|
|
|
|
289
|
|
|
/** |
290
|
|
|
* @codeCoverageIgnore |
291
|
|
|
* |
292
|
|
|
* @param $name |
293
|
|
|
* @param $arguments |
294
|
|
|
* @return Type |
295
|
|
|
* @throws \garethp\ews\API\Exception |
296
|
|
|
*/ |
297
|
|
|
public function __call($name, $arguments) |
298
|
|
|
{ |
299
|
|
|
$request = MiddlewareRequest::newRequest($name, $arguments, $this->options); |
300
|
|
|
$response = $this->executeMiddlewareStack(self::$middlewareStack, $request); |
301
|
|
|
$response = $response->getResponse(); |
302
|
|
|
return $response; |
303
|
|
|
return $this->processResponse($response); |
|
|
|
|
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
/** |
307
|
|
|
* Returns the SOAP Client that may be used to make calls against the server |
308
|
|
|
* |
309
|
|
|
* @return NTLMSoapClient |
310
|
|
|
*/ |
311
|
31 |
|
public function getClient() |
312
|
|
|
{ |
313
|
31 |
|
return $this->soap; |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* Cleans the server URL for usage |
318
|
|
|
* |
319
|
|
|
* @param $server |
320
|
|
|
* @return string |
321
|
|
|
*/ |
322
|
42 |
|
public function cleanServerUrl($server) |
323
|
|
|
{ |
324
|
42 |
|
$url = parse_url($server); |
325
|
42 |
|
if (!isset($url['host']) && isset($url['path'])) { |
326
|
37 |
|
$url['host'] = $url['path']; |
327
|
37 |
|
unset($url['path']); |
328
|
37 |
|
} |
329
|
|
|
|
330
|
42 |
|
$server = $url['host']; |
331
|
42 |
|
if (isset($url['port'])) { |
332
|
2 |
|
$server .= ':' . $url['port']; |
333
|
2 |
|
} |
334
|
|
|
|
335
|
42 |
|
if (isset($url['path'])) { |
336
|
4 |
|
$server .= $url['path']; |
337
|
4 |
|
} |
338
|
|
|
|
339
|
42 |
|
$server = rtrim($server, "/"); |
340
|
|
|
|
341
|
42 |
|
return $server; |
342
|
|
|
} |
343
|
|
|
|
344
|
|
|
/** |
345
|
|
|
* Process a response to verify that it succeeded and take the appropriate |
346
|
|
|
* action |
347
|
|
|
* |
348
|
|
|
* @param \garethp\ews\API\Message\BaseResponseMessageType $response |
349
|
|
|
* @return Type[] |
350
|
|
|
* @throws \garethp\ews\API\Exception |
351
|
|
|
*/ |
352
|
29 |
|
protected function processResponse($response) |
353
|
|
|
{ |
354
|
|
|
// If the soap call failed then we need to thow an exception. |
355
|
29 |
|
$code = $this->getClient()->getResponseCode(); |
356
|
29 |
|
$this->handleNonSuccessfulResponses($response, $code); |
357
|
|
|
|
358
|
29 |
|
if (!$this->drillDownResponses) { |
359
|
|
|
return $response; |
360
|
|
|
} |
361
|
|
|
|
362
|
29 |
|
if (!$response->exists('responseMessages')) { |
363
|
|
|
return $response; |
364
|
|
|
} |
365
|
|
|
|
366
|
29 |
|
$response = $response->getResponseMessages(); |
367
|
29 |
|
$response = $this->drillDownResponseLevels($response); |
368
|
|
|
|
369
|
29 |
|
return $response; |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
/** |
373
|
|
|
* @param $response |
374
|
|
|
* @return array |
375
|
|
|
* @throws \garethp\ews\API\Exception |
376
|
|
|
*/ |
377
|
29 |
|
public function drillDownResponseLevels($response) |
378
|
|
|
{ |
379
|
29 |
|
$items = $this->getItemsFromResponse($response); |
380
|
|
|
|
381
|
29 |
|
if (count($items) == 1) { |
382
|
29 |
|
reset($items); |
383
|
29 |
|
$key = key($items); |
384
|
29 |
|
$methodName = "get$key"; |
385
|
29 |
|
$response = $response->$methodName(); |
386
|
|
|
|
387
|
29 |
|
return $this->drillDownResponseLevels($response); |
388
|
|
|
} |
389
|
|
|
|
390
|
29 |
|
if (is_array($items) && isset($items[1]) && $items[1] instanceof Message\ResponseMessageType) { |
|
|
|
|
391
|
3 |
|
$response = array(); |
392
|
3 |
|
foreach ($items as $responseItem) { |
393
|
3 |
|
$response[] = $this->drillDownResponseLevels($responseItem); |
394
|
3 |
|
} |
395
|
|
|
|
396
|
3 |
|
return $response; |
397
|
|
|
} |
398
|
|
|
|
399
|
29 |
|
return $response; |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
/** |
403
|
|
|
* @param $response |
404
|
|
|
* @return array |
405
|
|
|
* @throws ExchangeException |
406
|
|
|
*/ |
407
|
29 |
|
protected function getItemsFromResponse($response) |
408
|
|
|
{ |
409
|
29 |
|
$items = array(); |
410
|
29 |
|
if ($response instanceof Type) { |
411
|
29 |
|
$items = $response->getNonNullItems(); |
412
|
29 |
|
} |
413
|
|
|
|
414
|
29 |
|
if (is_array($response)) { |
415
|
3 |
|
$items = $response; |
416
|
3 |
|
} |
417
|
|
|
|
418
|
29 |
|
if ($response instanceof Message\ResponseMessageType) { |
|
|
|
|
419
|
29 |
|
if ($response->getResponseClass() !== "Success") { |
420
|
1 |
|
throw new ExchangeException($response->getMessageText()); |
421
|
|
|
} |
422
|
|
|
|
423
|
29 |
|
unset($items['responseClass']); |
424
|
29 |
|
unset($items['responseCode']); |
425
|
29 |
|
} |
426
|
|
|
|
427
|
29 |
|
return $items; |
428
|
|
|
} |
429
|
|
|
|
430
|
|
|
/** |
431
|
|
|
* @param Message\BaseResponseMessageType $response |
432
|
|
|
* @param $code |
433
|
|
|
* @throws ExchangeException |
434
|
|
|
* @throws NoResponseReturnedException |
435
|
|
|
* @throws ServiceUnavailableException |
436
|
|
|
* @throws UnauthorizedException |
437
|
|
|
*/ |
438
|
29 |
|
protected function handleNonSuccessfulResponses($response, $code) |
439
|
|
|
{ |
440
|
29 |
|
if ($code == 401) { |
441
|
|
|
throw new UnauthorizedException(); |
442
|
|
|
} |
443
|
|
|
|
444
|
29 |
|
if ($code == 503) { |
445
|
|
|
throw new ServiceUnavailableException(); |
446
|
|
|
} |
447
|
|
|
|
448
|
29 |
|
if ($code >= 300) { |
449
|
|
|
throw new ExchangeException('SOAP client returned status of ' . $code, $code); |
450
|
|
|
} |
451
|
|
|
|
452
|
29 |
|
if (empty($response) || empty($response->getNonNullResponseMessages())) { |
453
|
|
|
throw new NoResponseReturnedException(); |
454
|
|
|
} |
455
|
29 |
|
} |
456
|
|
|
|
457
|
35 |
|
protected function buildMiddlewareStack() |
458
|
|
|
{ |
459
|
35 |
|
if (self::$middlewareStack === false) { |
460
|
1 |
|
$factory = new MiddlewareFactory(); |
461
|
|
|
|
462
|
1 |
|
self::$middlewareStack = [ |
463
|
|
|
//Make the actual SOAP call |
464
|
1 |
|
$factory->getSoapCall(), |
465
|
|
|
|
466
|
|
|
//Transform an object of type Type to an XML Object |
467
|
1 |
|
$factory->getTypeToXMLObject(), |
468
|
|
|
|
469
|
|
|
//The SyncScope option isn't available for Exchange 2007 SP1 and below |
470
|
1 |
|
$factory->getStripSyncScopeForExchange2007(), |
471
|
|
|
|
472
|
|
|
//Add response processing |
473
|
1 |
|
$factory->getProcessResponse(), |
474
|
|
|
|
475
|
|
|
//Adds last request to FindFolder and FindItem responses |
476
|
1 |
|
$factory->getAddLastRequestToPagedResults() |
477
|
1 |
|
]; |
478
|
1 |
|
} |
479
|
35 |
|
} |
480
|
|
|
|
481
|
|
|
/** |
482
|
|
|
* @param array $middlewareStack |
483
|
|
|
* @param MiddlewareRequest $request |
484
|
|
|
* @return MiddlewareResponse |
485
|
|
|
*/ |
486
|
29 |
|
protected function executeMiddlewareStack(array $middlewareStack, MiddlewareRequest $request) |
487
|
|
|
{ |
488
|
29 |
|
$newStack = []; |
489
|
29 |
|
foreach ($middlewareStack as $key => $current) { |
490
|
|
|
/** @var $current callable */ |
491
|
|
|
$last = function () { |
492
|
29 |
|
}; |
493
|
|
|
|
494
|
29 |
|
if ($key != 0) { |
495
|
29 |
|
$last = $newStack[$key - 1]; |
496
|
29 |
|
} |
497
|
|
|
|
498
|
29 |
|
$current = Closure::bind($current, $this, $this); |
499
|
29 |
|
$newStack[] = function (MiddlewareRequest $request) use ($current, $last) { |
500
|
29 |
|
return $current($request, $last); |
501
|
|
|
}; |
502
|
29 |
|
} |
503
|
|
|
|
504
|
|
|
/** @var $newStack callable[] */ |
505
|
29 |
|
$newStack = array_reverse($newStack); |
506
|
|
|
|
507
|
29 |
|
$top = $newStack[0]; |
508
|
|
|
|
509
|
|
|
/** @var $top callable */ |
510
|
29 |
|
return $top($request); |
511
|
|
|
} |
512
|
|
|
} |
513
|
|
|
|
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.