1
|
|
|
<?php |
2
|
|
|
namespace garethp\ews\API; |
3
|
|
|
|
4
|
|
|
use garethp\ews\API; |
5
|
|
|
use garethp\ews\API\Exception\AutoDiscoverFailed; |
6
|
|
|
use garethp\ews\HttpPlayback\HttpPlayback; |
7
|
|
|
use XMLWriter; |
8
|
|
|
|
9
|
|
|
/** |
10
|
|
|
* Contains EWSAutodiscover. |
11
|
|
|
*/ |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* Exchange Web Services Autodiscover implementation |
15
|
|
|
* |
16
|
|
|
* This class supports POX (Plain Old XML), which is deprecated but functional |
17
|
|
|
* in Exchange 2010. It may make sense for you to combine your Autodiscovery |
18
|
|
|
* efforts with a SOAP Autodiscover request as well. |
19
|
|
|
* |
20
|
|
|
* USAGE: |
21
|
|
|
* |
22
|
|
|
* (after any auto-loading class incantation) |
23
|
|
|
* |
24
|
|
|
* $ews = EWSAutodiscover::getEWS($email, $password); |
25
|
|
|
* |
26
|
|
|
* -- OR -- |
27
|
|
|
* |
28
|
|
|
* If there are issues with your cURL installation that require you to specify |
29
|
|
|
* a path to a valid Certificate Authority, you can configure that manually. |
30
|
|
|
* |
31
|
|
|
* $auto = new EWSAutodiscover($email, $password); |
32
|
|
|
* $auto->setCAInfo('/path/to/your/cacert.pem'); |
33
|
|
|
* $ews = $auto->newEWS(); |
34
|
|
|
* |
35
|
|
|
* @link http://technet.microsoft.com/en-us/library/bb332063(EXCHG.80).aspx |
36
|
|
|
* @link https://www.testexchangeconnectivity.com/ |
37
|
|
|
* |
38
|
|
|
* @package php-ews\AutoDiscovery |
39
|
|
|
*/ |
40
|
|
|
class EWSAutodiscover |
41
|
|
|
{ |
42
|
|
|
/** |
43
|
|
|
* The path appended to the various schemes and hostnames used during |
44
|
|
|
* autodiscovery. |
45
|
|
|
* |
46
|
|
|
* @var string |
47
|
|
|
*/ |
48
|
|
|
const AUTODISCOVER_PATH = '/autodiscover/autodiscover.xml'; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* The Autodiscover XML request. Since it's used repeatedly, it's cached |
52
|
|
|
* in this property to avoid redundant re-generation. |
53
|
|
|
* |
54
|
|
|
* @var string |
55
|
|
|
*/ |
56
|
|
|
protected $requestXML; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* The Certificate Authority path. Should point to a directory containing |
60
|
|
|
* one or more certificates to use in SSL verification. |
61
|
|
|
* |
62
|
|
|
* @var string |
63
|
|
|
*/ |
64
|
|
|
protected $certificateAuthorityPath; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* The path to a specific Certificate Authority file. Get one and use it |
68
|
|
|
* for full Autodiscovery compliance. |
69
|
|
|
* |
70
|
|
|
* @var string |
71
|
|
|
* |
72
|
|
|
* @link http://curl.haxx.se/ca/cacert.pem |
73
|
|
|
* @link http://curl.haxx.se/ca/ |
74
|
|
|
*/ |
75
|
|
|
protected $certificateAuthorityInfo; |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* @var HttpPlayback |
79
|
|
|
*/ |
80
|
|
|
protected $httpPlayback; |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* An associative array of response headers that resulted from the |
84
|
|
|
* last request. Keys are lowercased for easy checking. |
85
|
|
|
* |
86
|
|
|
* @var array |
87
|
|
|
*/ |
88
|
|
|
public $last_response_headers; |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* The result of the most recent curl_exec. |
92
|
|
|
* |
93
|
|
|
* @var mixed |
94
|
|
|
*/ |
95
|
|
|
public $last_response; |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* The output of curl_info() relating to the most recent cURL request. |
99
|
|
|
* |
100
|
|
|
* @var mixed |
101
|
|
|
*/ |
102
|
|
|
public $last_info; |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* The cURL error code associated with the most recent cURL request. |
106
|
|
|
* |
107
|
|
|
* @var integer |
108
|
|
|
*/ |
109
|
|
|
public $last_curl_errno; |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* Human-readable description of the most recent cURL error. |
113
|
|
|
* |
114
|
|
|
* @var string |
115
|
|
|
*/ |
116
|
|
|
public $last_curl_error; |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Information about an Autodiscover Response containing an error will |
120
|
|
|
* be stored here. |
121
|
|
|
* |
122
|
|
|
* @var mixed |
123
|
|
|
*/ |
124
|
|
|
public $error = false; |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* Information about an Autodiscover Response with a redirect will be |
128
|
|
|
* retained here. |
129
|
|
|
* |
130
|
|
|
* @var mixed |
131
|
|
|
*/ |
132
|
|
|
public $redirect = false; |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* A successful, non-error and non-redirect parsed Autodiscover response |
136
|
|
|
* will be stored here. |
137
|
|
|
* |
138
|
|
|
* @var mixed |
139
|
|
|
*/ |
140
|
|
|
public $discovered = null; |
141
|
|
|
|
142
|
|
|
protected function __construct() |
143
|
|
|
{ |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* Parse the hex ServerVersion value and return a valid |
148
|
|
|
* ExchangeWebServices::VERSION_* constant. |
149
|
|
|
* |
150
|
|
|
* @param $version_hex |
151
|
|
|
* @return string|boolean A known version constant, or FALSE if it could not |
152
|
|
|
* be determined. |
153
|
|
|
* |
154
|
|
|
* @link http://msdn.microsoft.com/en-us/library/bb204122(v=exchg.140).aspx |
155
|
|
|
* @link http://blogs.msdn.com/b/pcreehan/archive/2009/09/21/parsing-serverversion-when-an-int-is-really-5-ints.aspx |
156
|
|
|
*/ |
157
|
|
|
protected function parseServerVersion($version_hex) |
158
|
|
|
{ |
159
|
|
|
$svbinary = base_convert($version_hex, 16, 2); |
160
|
|
|
if (strlen($svbinary) == 31) { |
161
|
|
|
$svbinary = '0' . $svbinary; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
$majorversion = base_convert(substr($svbinary, 4, 6), 2, 10); |
165
|
|
|
$minorversion = base_convert(substr($svbinary, 10, 6), 2, 10); |
166
|
|
|
|
167
|
|
|
if ($majorversion == 8) { |
168
|
|
View Code Duplication |
switch ($minorversion) { |
169
|
|
|
case 0: |
170
|
|
|
return ExchangeWebServices::VERSION_2007; |
171
|
|
|
case 1: |
172
|
|
|
return ExchangeWebServices::VERSION_2007_SP1; |
173
|
|
|
case 2: |
174
|
|
|
return ExchangeWebServices::VERSION_2007_SP2; |
175
|
|
|
case 3: |
176
|
|
|
return ExchangeWebServices::VERSION_2007_SP3; |
177
|
|
|
default: |
178
|
|
|
return ExchangeWebServices::VERSION_2007; |
179
|
|
|
} |
180
|
|
|
} elseif ($majorversion == 14) { |
181
|
|
View Code Duplication |
switch ($minorversion) { |
182
|
|
|
case 0: |
183
|
|
|
return ExchangeWebServices::VERSION_2010; |
184
|
|
|
case 1: |
185
|
|
|
return ExchangeWebServices::VERSION_2010_SP1; |
186
|
|
|
case 2: |
187
|
|
|
return ExchangeWebServices::VERSION_2010_SP2; |
188
|
|
|
default: |
189
|
|
|
return ExchangeWebServices::VERSION_2010; |
190
|
|
|
} |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
// Guess we didn't find a known version. |
194
|
|
|
return false; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
protected function newAPI($email, $password, $username = null, $options = []) |
198
|
|
|
{ |
199
|
|
|
$options = array_replace_recursive([ |
200
|
|
|
'httpPlayback' => [ |
201
|
|
|
'mode' => null |
202
|
|
|
] |
203
|
|
|
], $options); |
204
|
|
|
|
205
|
|
|
$this->httpPlayback = HttpPlayback::getInstance($options['httpPlayback']); |
206
|
|
|
|
207
|
|
|
if (!$username) { |
208
|
|
|
$username = $email; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
$settings = $this->discover($email, $password, $username); |
212
|
|
|
if ($settings === false) { |
213
|
|
|
throw new AutoDiscoverFailed(); |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
$server = false; |
217
|
|
|
$version = null; |
218
|
|
|
|
219
|
|
|
// Pick out the host from the EXPR (Exchange RPC over HTTP). |
220
|
|
|
foreach ($settings['Account']['Protocol'] as $protocol) { |
221
|
|
|
if (($protocol['Type'] == 'EXCH' || $protocol['Type'] == 'EXPR') |
222
|
|
|
&& isset($protocol['ServerVersion']) |
223
|
|
|
) { |
224
|
|
|
if ($version == null) { |
225
|
|
|
$sv = $this->parseServerVersion($protocol['ServerVersion']); |
226
|
|
|
if ($sv !== false) { |
227
|
|
|
$version = $sv; |
228
|
|
|
} |
229
|
|
|
} |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
if ($protocol['Type'] == 'EXPR' && isset($protocol['Server'])) { |
233
|
|
|
$server = $protocol['Server']; |
234
|
|
|
} |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
if ($server) { |
238
|
|
|
$options = []; |
239
|
|
|
if ($version !== null) { |
240
|
|
|
$options['version'] = $version; |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
return API::withUsernameAndPassword($server, $email, $password, $options); |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
return false; |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* Static method may fail if there are issues surrounding SSL certificates. |
251
|
|
|
* In such cases, set up the object as needed, and then call newEWS(). |
252
|
|
|
* |
253
|
|
|
* @param string $email |
254
|
|
|
* @param string $password |
255
|
|
|
* @param string $username If left blank, the email provided will be used. |
256
|
|
|
* @return mixed |
257
|
|
|
*/ |
258
|
|
|
public static function getAPI($email, $password, $username = null, $options = []) |
259
|
|
|
{ |
260
|
|
|
$auto = new static(); |
261
|
|
|
|
262
|
|
|
return $auto->newAPI($email, $password, $username, $options); |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* Execute the full discovery chain of events in the correct sequence |
267
|
|
|
* until a valid response is received, or all methods have failed. |
268
|
|
|
* |
269
|
|
|
* @param string $email |
270
|
|
|
* @param string $password |
271
|
|
|
* @param string $username |
272
|
|
|
* |
273
|
|
|
* @return string The discovered settings |
274
|
|
|
*/ |
275
|
|
|
protected function discover($email, $password, $username) |
276
|
|
|
{ |
277
|
|
|
$result = $this->tryTopLevelDomain($email, $password, $username); |
278
|
|
|
|
279
|
|
|
if ($result === false) { |
280
|
|
|
$result = $this->tryAutoDiscoverSubDomain($email, $password, $username); |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
if ($result === false) { |
284
|
|
|
$result = $this->trySubdomainUnauthenticatedGet($email, $password, $username); |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
if ($result === false) { |
288
|
|
|
$result = $this->trySRVRecord($email, $password, $username); |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
return $result; |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
/** |
295
|
|
|
* Perform an NTLM authenticated HTTPS POST to the top-level |
296
|
|
|
* domain of the email address. |
297
|
|
|
* |
298
|
|
|
* @param string $email |
299
|
|
|
* @param string $password |
300
|
|
|
* @param string $username |
301
|
|
|
* |
302
|
|
|
* @return string The discovered settings |
303
|
|
|
*/ |
304
|
|
View Code Duplication |
protected function tryTopLevelDomain($email, $password, $username) |
|
|
|
|
305
|
|
|
{ |
306
|
|
|
$topLevelDomain = $this->getTopLevelDomainFromEmail($email); |
307
|
|
|
$url = 'https://www.' . $topLevelDomain . self::AUTODISCOVER_PATH; |
308
|
|
|
|
309
|
|
|
return $this->doNTLMPost($url, $email, $password, $username); |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
/** |
313
|
|
|
* Perform an NTLM authenticated HTTPS POST to the 'autodiscover' |
314
|
|
|
* subdomain of the email address' TLD. |
315
|
|
|
* |
316
|
|
|
* @param string $email |
317
|
|
|
* @param string $password |
318
|
|
|
* @param string $username |
319
|
|
|
* |
320
|
|
|
* @return string The discovered settings |
321
|
|
|
*/ |
322
|
|
View Code Duplication |
protected function tryAutoDiscoverSubDomain($email, $password, $username) |
|
|
|
|
323
|
|
|
{ |
324
|
|
|
$topLevelDomain = $this->getTopLevelDomainFromEmail($email); |
325
|
|
|
$url = 'https://autodiscover.' . $topLevelDomain . self::AUTODISCOVER_PATH; |
326
|
|
|
|
327
|
|
|
return $this->doNTLMPost($url, $email, $password, $username); |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
/** |
331
|
|
|
* Perform an unauthenticated HTTP GET in an attempt to get redirected |
332
|
|
|
* via 302 to the correct location to perform the HTTPS POST. |
333
|
|
|
* |
334
|
|
|
* @param string $email |
335
|
|
|
* @param string $password |
336
|
|
|
* @param string $username |
337
|
|
|
* |
338
|
|
|
* @return string The discovered settings |
339
|
|
|
*/ |
340
|
|
|
protected function trySubdomainUnauthenticatedGet($email, $password, $username) |
341
|
|
|
{ |
342
|
|
|
$topLevelDomain = $this->getTopLevelDomainFromEmail($email); |
343
|
|
|
|
344
|
|
|
$url = 'http://autodiscover.' . $topLevelDomain . self::AUTODISCOVER_PATH; |
345
|
|
|
|
346
|
|
|
$client = $this->httpPlayback->getHttpClient(); |
347
|
|
|
$postOptions = [ |
348
|
|
|
'timeout' => 2, |
349
|
|
|
'allow_redirects' => false, |
350
|
|
|
'headers' => [ |
351
|
|
|
'Content-Type' => 'text/xml; charset=utf-8' |
352
|
|
|
], |
353
|
|
|
'curl' => [] |
354
|
|
|
]; |
355
|
|
|
|
356
|
|
|
try { |
357
|
|
|
$response = $client->get($url, $postOptions); |
358
|
|
|
|
359
|
|
|
if ($response->getStatusCode() == 301 || $response->getStatusCode() == 302) { |
360
|
|
|
return $this->doNTLMPost($response->getHeaderLine('Location'), $email, $password, $username); |
361
|
|
|
} |
362
|
|
|
} catch (\Exception $e) { |
363
|
|
|
return false; |
364
|
|
|
} |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
/** |
368
|
|
|
* Attempt to retrieve the autodiscover host from an SRV DNS record. |
369
|
|
|
* |
370
|
|
|
* @link http://support.microsoft.com/kb/940881 |
371
|
|
|
* |
372
|
|
|
* @param string $email |
373
|
|
|
* @param string $password |
374
|
|
|
* @param string $username |
375
|
|
|
* |
376
|
|
|
* @return string The discovered settings |
377
|
|
|
*/ |
378
|
|
|
protected function trySRVRecord($email, $password, $username) |
379
|
|
|
{ |
380
|
|
|
$topLevelDomain = $this->getTopLevelDomainFromEmail($email); |
381
|
|
|
$srvHost = '_autodiscover._tcp.' . $topLevelDomain; |
382
|
|
|
$lookup = dns_get_record($srvHost, DNS_SRV); |
383
|
|
|
if (sizeof($lookup) > 0) { |
384
|
|
|
$host = $lookup[0]['target']; |
385
|
|
|
$url = 'https://' . $host . self::AUTODISCOVER_PATH; |
386
|
|
|
|
387
|
|
|
return $this->doNTLMPost($url, $email, $password, $username); |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
return false; |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
/** |
394
|
|
|
* Set the path to the file to be used by CURLOPT_CAINFO. |
395
|
|
|
* |
396
|
|
|
* @param string $path Path to a certificate file such as cacert.pem |
397
|
|
|
* @return self |
398
|
|
|
*/ |
399
|
|
|
public function setCAInfo($path) |
400
|
|
|
{ |
401
|
|
|
if (file_exists($path) && is_file($path)) { |
402
|
|
|
$this->certificateAuthorityInfo = $path; |
403
|
|
|
} |
404
|
|
|
|
405
|
|
|
return $this; |
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
/** |
409
|
|
|
* Set the path to the file to be used by CURLOPT_CAPATH. |
410
|
|
|
* |
411
|
|
|
* @param string $path Path to a directory containing one or more CA |
412
|
|
|
* certificates. |
413
|
|
|
* @return self |
414
|
|
|
*/ |
415
|
|
|
public function setCertificateAuthorityPath($path) |
416
|
|
|
{ |
417
|
|
|
if (is_dir($path)) { |
418
|
|
|
$this->certificateAuthorityPath = $path; |
419
|
|
|
} |
420
|
|
|
|
421
|
|
|
return $this; |
422
|
|
|
} |
423
|
|
|
|
424
|
|
|
/** |
425
|
|
|
* Perform the NTLM authenticated post against one of the chosen |
426
|
|
|
* endpoints. |
427
|
|
|
* |
428
|
|
|
* @param string $url URL to try posting to |
429
|
|
|
* @param string $email |
430
|
|
|
* @param string $password |
431
|
|
|
* @param string $username |
432
|
|
|
* |
433
|
|
|
* @return string The discovered settings |
434
|
|
|
*/ |
435
|
|
|
protected function doNTLMPost($url, $email, $password, $username) |
436
|
|
|
{ |
437
|
|
|
$client = $this->httpPlayback->getHttpClient(); |
438
|
|
|
$postOptions = [ |
439
|
|
|
'body' => $this->getAutoDiscoverXML($email), |
440
|
|
|
'timeout' => 2, |
441
|
|
|
'allow_redirects' => true, |
442
|
|
|
'headers' => [ |
443
|
|
|
'Content-Type' => 'text/xml; charset=utf-8' |
444
|
|
|
], |
445
|
|
|
'curl' => [] |
446
|
|
|
]; |
447
|
|
|
$auth = ExchangeWebServicesAuth::fromUsernameAndPassword($username, $password); |
448
|
|
|
$postOptions = array_replace_recursive($postOptions, $auth); |
449
|
|
|
|
450
|
|
|
if (!empty($this->certificateAuthorityInfo)) { |
451
|
|
|
$postOptions['cur'][CURLOPT_CAINFO] = $this->certificateAuthorityInfo; |
452
|
|
|
} |
453
|
|
|
|
454
|
|
|
if (!empty($this->certificateAuthorityPath)) { |
455
|
|
|
$postOptions['cur'][CURLOPT_CAPATH] = $this->certificateAuthorityPath; |
456
|
|
|
} |
457
|
|
|
|
458
|
|
|
try { |
459
|
|
|
$response = $client->post($url, $postOptions); |
460
|
|
|
} catch (\Exception $e) { |
461
|
|
|
return false; |
462
|
|
|
} |
463
|
|
|
|
464
|
|
|
return $this->parseAutodiscoverResponse($response->getBody()->__toString()); |
465
|
|
|
} |
466
|
|
|
|
467
|
|
|
/** |
468
|
|
|
* Parse the Autoresponse Payload, particularly to determine if an |
469
|
|
|
* additional request is necessary. |
470
|
|
|
* |
471
|
|
|
* @param $response |
472
|
|
|
* @return array|bool |
473
|
|
|
* @throws AutoDiscoverFailed |
474
|
|
|
*/ |
475
|
|
|
protected function parseAutodiscoverResponse($response) |
476
|
|
|
{ |
477
|
|
|
// Content-type isn't trustworthy, unfortunately. Shame on Microsoft. |
478
|
|
|
if (substr($response, 0, 5) !== '<?xml') { |
479
|
|
|
throw new AutoDiscoverFailed(); |
480
|
|
|
} |
481
|
|
|
|
482
|
|
|
$response = $this->responseToArray($response); |
483
|
|
|
|
484
|
|
|
if (isset($response['Error'])) { |
485
|
|
|
$this->error = $response['Error']; |
486
|
|
|
|
487
|
|
|
return false; |
488
|
|
|
} |
489
|
|
|
|
490
|
|
|
// Check the account action for redirect. |
491
|
|
|
switch ($response['Account']['Action']) { |
492
|
|
|
case 'redirectUrl': |
493
|
|
|
$this->redirect = array( |
494
|
|
|
'redirectUrl' => $response['Account']['redirectUrl'] |
495
|
|
|
); |
496
|
|
|
|
497
|
|
|
return false; |
498
|
|
|
case 'redirectAddr': |
499
|
|
|
$this->redirect = array( |
500
|
|
|
'redirectAddr' => $response['Account']['redirectAddr'] |
501
|
|
|
); |
502
|
|
|
|
503
|
|
|
return false; |
504
|
|
|
case 'settings': |
505
|
|
|
default: |
506
|
|
|
return $response; |
507
|
|
|
} |
508
|
|
|
} |
509
|
|
|
|
510
|
|
|
/** |
511
|
|
|
* Get a top level domain based on an email address |
512
|
|
|
* |
513
|
|
|
* @param $email |
514
|
|
|
* @return bool|string |
515
|
|
|
*/ |
516
|
|
|
protected function getTopLevelDomainFromEmail($email) |
517
|
|
|
{ |
518
|
|
|
$pos = strpos($email, '@'); |
519
|
|
|
if ($pos !== false) { |
520
|
|
|
return trim(substr($email, $pos + 1)); |
521
|
|
|
} |
522
|
|
|
|
523
|
|
|
return false; |
524
|
|
|
} |
525
|
|
|
|
526
|
|
|
/** |
527
|
|
|
* Return the generated Autodiscover XML request body. |
528
|
|
|
* |
529
|
|
|
* @param string $email |
530
|
|
|
* @return string |
531
|
|
|
*/ |
532
|
|
|
protected function getAutoDiscoverXML($email) |
533
|
|
|
{ |
534
|
|
|
if (!empty($this->requestXML)) { |
535
|
|
|
return $this->requestXML; |
536
|
|
|
} |
537
|
|
|
|
538
|
|
|
$xml = new XMLWriter; |
539
|
|
|
$xml->openMemory(); |
540
|
|
|
$xml->setIndent(true); |
541
|
|
|
$xml->startDocument('1.0', 'UTF-8'); |
542
|
|
|
$xml->startElementNS( |
543
|
|
|
null, |
544
|
|
|
'Autodiscover', |
545
|
|
|
'http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006' |
546
|
|
|
); |
547
|
|
|
|
548
|
|
|
$xml->startElement('Request'); |
549
|
|
|
$xml->writeElement('EMailAddress', $email); |
550
|
|
|
$xml->writeElement( |
551
|
|
|
'AcceptableResponseSchema', |
552
|
|
|
'http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a' |
553
|
|
|
); |
554
|
|
|
$xml->endElement(); |
555
|
|
|
$xml->endElement(); |
556
|
|
|
|
557
|
|
|
$this->requestXML = $xml->outputMemory(); |
558
|
|
|
|
559
|
|
|
return $this->requestXML; |
560
|
|
|
} |
561
|
|
|
|
562
|
|
|
/** |
563
|
|
|
* Utility function to parse XML payloads from the response into easier |
564
|
|
|
* to manage associative arrays. |
565
|
|
|
* |
566
|
|
|
* @param string $xml XML to parse |
567
|
|
|
* @return array |
568
|
|
|
*/ |
569
|
|
|
protected function responseToArray($xml) |
570
|
|
|
{ |
571
|
|
|
$doc = new \DOMDocument(); |
572
|
|
|
$doc->loadXML($xml); |
573
|
|
|
$out = $this->nodeToArray($doc->documentElement); |
574
|
|
|
|
575
|
|
|
return $out['Response']; |
576
|
|
|
} |
577
|
|
|
|
578
|
|
|
/** |
579
|
|
|
* Recursive method for parsing DOM nodes. |
580
|
|
|
* |
581
|
|
|
* @link https://github.com/gaarf/XML-string-to-PHP-array |
582
|
|
|
* @param object $node DOMNode object |
583
|
|
|
* @return mixed |
584
|
|
|
*/ |
585
|
|
|
protected function nodeToArray($node) |
586
|
|
|
{ |
587
|
|
|
$output = array(); |
588
|
|
|
switch ($node->nodeType) { |
589
|
|
|
case XML_CDATA_SECTION_NODE: |
590
|
|
|
case XML_TEXT_NODE: |
591
|
|
|
$output = trim($node->textContent); |
592
|
|
|
break; |
593
|
|
|
case XML_ELEMENT_NODE: |
594
|
|
|
for ($i = 0, $m = $node->childNodes->length; $i < $m; $i++) { |
595
|
|
|
$child = $node->childNodes->item($i); |
596
|
|
|
$v = $this->nodeToArray($child); |
597
|
|
|
if (isset($child->tagName)) { |
598
|
|
|
$t = $child->tagName; |
599
|
|
|
if (!isset($output[$t])) { |
600
|
|
|
$output[$t] = array(); |
601
|
|
|
} |
602
|
|
|
$output[$t][] = $v; |
603
|
|
|
} elseif ($v || $v === '0') { |
604
|
|
|
$output = (string)$v; |
605
|
|
|
} |
606
|
|
|
} |
607
|
|
|
|
608
|
|
|
// Edge case of a node containing a text node, which also has |
609
|
|
|
// attributes. this way we'll retain text and attributes for |
610
|
|
|
// this node. |
611
|
|
|
if (is_string($output) && $node->attributes->length) { |
612
|
|
|
$output = array('@text' => $output); |
613
|
|
|
} |
614
|
|
|
|
615
|
|
|
if (is_array($output)) { |
616
|
|
|
if ($node->attributes->length) { |
617
|
|
|
$a = array(); |
618
|
|
|
foreach ($node->attributes as $attrName => $attrNode) { |
619
|
|
|
$a[$attrName] = (string)$attrNode->value; |
620
|
|
|
} |
621
|
|
|
$output['@attributes'] = $a; |
622
|
|
|
} |
623
|
|
|
foreach ($output as $t => $v) { |
624
|
|
|
if (is_array($v) && count($v) == 1 && $t != '@attributes') { |
625
|
|
|
$output[$t] = $v[0]; |
626
|
|
|
} |
627
|
|
|
} |
628
|
|
|
} |
629
|
|
|
break; |
630
|
|
|
} |
631
|
|
|
|
632
|
|
|
return $output; |
633
|
|
|
} |
634
|
|
|
} |
635
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.