Completed
Push — master ( 41eae6...33f93b )
by Gareth
01:53 queued 10s
created

handleNonSuccessfulResponses()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 7.3329

Importance

Changes 0
Metric Value
cc 6
nc 5
nop 2
dl 0
loc 22
ccs 8
cts 12
cp 0.6667
crap 7.3329
rs 8.9457
c 0
b 0
f 0
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\ResponseMessageType;
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
    const VERSION_2016 = 'Exchange2016';
103
104
    /**
105
     * Password to use when connecting to the Exchange server.
106
     *
107
     * @var string
108
     */
109
    protected $password = null;
110
111
    /**
112
     * Location of the Exchange server.
113
     *
114
     * @var string
115
     */
116
    protected $server = null;
117
118
    /**
119
     * SOAP client used to make the request
120
     *
121
     * @var NTLMSoapClient
122
     */
123
    protected $soap = null;
124
125
    /**
126
     * Username to use when connecting to the Exchange server.
127
     *
128
     * @var string
129
     */
130
    protected $username = null;
131
132
    /**
133
     * @var EmailAddressType
134
     */
135
    protected $primarySmtpMailbox = null;
136
137
    /**
138
     * @var Callable[]
139
     */
140
    protected static $middlewareStack = false;
141
142
    /**
143
     * A setting to check whether or not responses should be drilled down before being
144
     * returned. Setting this to false
145
     * will return the raw responses without any filtering
146
     *
147
     * @var bool
148
     */
149
    protected $drillDownResponses = true;
150
151
    /**
152
     * Miscrosoft Exchange version that we are going to connect to
153
     *
154
     * @var string
155
     */
156
    protected $version = null;
157
158
    protected $options = null;
159
160
    /**
161
     * The timezone for the client
162
     *
163
     * @var bool
164
     */
165
    protected $timezone = false;
166
167
    /**
168
     * @return EmailAddressType
169 28
     */
170
    public function getPrimarySmtpMailbox()
171 28
    {
172
        return $this->primarySmtpMailbox;
173
    }
174 1
175
    public function getPrimarySmtpEmailAddress()
176 1
    {
177 1
        if ($this->primarySmtpMailbox == null) {
178
            return null;
179
        }
180 1
181
        return $this->primarySmtpMailbox->getEmailAddress();
182
    }
183 2
184
    public function setPrimarySmtpEmailAddress($emailAddress)
185 2
    {
186 2
        $mailbox = new EmailAddressType();
187 2
        $mailbox->setEmailAddress($emailAddress);
188
        $this->primarySmtpMailbox = $mailbox;
189 2
190
        return $this;
191
    }
192
193
    /**
194
     * @param boolean $timezone
195
     */
196
    public function setTimezone($timezone)
197
    {
198
        $this->timezone = $timezone;
199
    }
200
201
    /**
202
     * @return string
203
     */
204
    public function getVersion()
205
    {
206
        return $this->version;
207
    }
208
209
    /**
210
     * @return string
211
     */
212
    public function getServer()
213
    {
214
        return $this->server;
215
    }
216
217
    /**
218
     * Constructor for the ExchangeWebServices class
219
     *
220
     * @param string $server
221
     * @param string $username
222
     * @param string $password
223
     * @param array $options
224 36
     */
225
    protected function __construct($server = null, $username = null, $password = null, $options = array())
226 36
    {
227
        if ($server !== null) {
228
            $this->createClient(
229
                $server,
230
                ExchangeWebServicesAuth::fromUsernameAndPassword($username, $password),
231
                $options
232
            );
233
        }
234 36
235 36
        $this->buildMiddlewareStack();
236
    }
237 35
238
    public static function fromUsernameAndPassword($server, $username, $password, $options)
239 35
    {
240 35
        $self = new self();
241 35
        $self->createClient($server, ExchangeWebServicesAuth::fromUsernameAndPassword($username, $password), $options);
242
        $self->options = $options;
243 35
244
        return $self;
245
    }
246 1
247
    public static function fromCallbackToken($server, $token, $options)
248 1
    {
249 1
        $self = new self();
250 1
        $self->createClient($server, ExchangeWebServicesAuth::fromCallbackToken($token), $options);
251
        $self->options = $options;
252 1
253
        return $self;
254
    }
255 36
256
    protected function createClient($server, $auth, $options)
257 36
    {
258
        $location = 'https://' . $this->cleanServerUrl($server) . '/EWS/Exchange.asmx';
259 36
260 36
        $options = array_replace_recursive([
261 36
            'version' => self::VERSION_2007,
262 36
            'trace' => 1,
263 36
            'exceptions' => true,
264
            'classmap' => ClassMap::getClassMap(),
265 36
            'drillDownResponses' => true
266
        ], $options);
267 36
268 36
        $this->server = $server;
269
        $this->version = $options['version'];
270 36
271 36
        $backup = libxml_disable_entity_loader(false);
272 36
        $this->soap = new NTLMSoapClient(
273 36
            $location,
274 36
            $auth,
275
            dirname(__FILE__) . '/../../Resources/wsdl/services.wsdl',
276 36
            $options
277 36
        );
278
        libxml_disable_entity_loader($backup);
279 36
280 1
        if (isset($options['primarySmtpEmailAddress'])) {
281 1
            $this->setPrimarySmtpEmailAddress($options['primarySmtpEmailAddress']);
282
        }
283 36
284 1
        if (isset($options['impersonation'])) {
285 1
            $this->setPrimarySmtpEmailAddress($options['impersonation']);
286
        }
287 36
288 36
        $this->drillDownResponses = $options['drillDownResponses'];
289
    }
290
291
    /**
292
     * @codeCoverageIgnore
293
     *
294
     * @param $name
295
     * @param $arguments
296
     * @return Type
297
     * @throws \garethp\ews\API\Exception
298
     */
299
    public function __call($name, $arguments)
300
    {
301
        $request = MiddlewareRequest::newRequest($name, $arguments, $this->options);
302
        $response = $this->executeMiddlewareStack(self::$middlewareStack, $request);
303
        $response = $response->getResponse();
304
        return $response;
305
    }
306
307
    /**
308
     * Returns the SOAP Client that may be used to make calls against the server
309
     *
310
     * @return NTLMSoapClient
311 32
     */
312
    public function getClient()
313 32
    {
314
        return $this->soap;
315
    }
316
317
    /**
318
     * Cleans the server URL for usage
319
     *
320
     * @param $server
321
     * @return string
322 43
     */
323
    public function cleanServerUrl($server)
324 43
    {
325 43
        $url = parse_url($server);
326 38
        if (!isset($url['host']) && isset($url['path'])) {
327 38
            $url['host'] = $url['path'];
328 38
            unset($url['path']);
329
        }
330 43
331 43
        $server = $url['host'];
332 2
        if (isset($url['port'])) {
333 2
            $server .= ':' . $url['port'];
334
        }
335 43
336 4
        if (isset($url['path'])) {
337 4
            $server .= $url['path'];
338
        }
339 43
340
        $server = rtrim($server, "/");
341 43
342
        return $server;
343
    }
344
345
    /**
346
     * Process a response to verify that it succeeded and take the appropriate
347
     * action
348
     *
349
     * @param \garethp\ews\API\Message\BaseResponseMessageType $response
350
     * @return Type[]
351
     * @throws \garethp\ews\API\Exception
352 30
     */
353
    protected function processResponse($response)
354
    {
355 30
        // If the soap call failed then we need to thow an exception.
356 30
        $code = $this->getClient()->getResponseCode();
357
        $this->handleNonSuccessfulResponses($response, $code);
358 30
359
        if (!$this->drillDownResponses) {
360
            return $response;
361
        }
362 30
363
        if (!$response->exists('responseMessages')) {
364
            return $response;
365
        }
366 30
367 30
        $response = $response->getResponseMessages();
368
        $response = $this->drillDownResponseLevels($response);
369 30
370
        return $response;
371
    }
372
373
    /**
374
     * @param $response
375
     * @return array
376
     * @throws \garethp\ews\API\Exception
377 30
     */
378
    public static function drillDownResponseLevels($response)
379 30
    {
380
        $items = self::getItemsFromResponse($response);
381 30
382 30
        if (count($items) === 1) {
383 30
            reset($items);
384 30
            $key = key($items);
385 30
            $methodName = "get$key";
386
            $response = $response->$methodName();
387 30
388
            return self::drillDownResponseLevels($response);
389
        }
390 30
391
        if (is_array($items) && isset($items[1]) && $items[1] instanceof Message\ResponseMessageType) {
392 3
            return array_map(function ($responseItem) {
393 3
                return self::drillDownResponseLevels($responseItem);
394
            }, $items);
395
        }
396 30
397
        return $response;
398
    }
399
400
    /**
401
     * @param $response
402
     * @return array
403
     * @throws ExchangeException
404 30
     */
405
    protected static function getItemsFromResponse($response)
406 30
    {
407 30
        $items = array();
408 30
        if ($response instanceof Type) {
409 30
            $items = $response->getNonNullItems();
410
        }
411 30
412 3
        if (is_array($response)) {
413 3
            $items = $response;
414
        }
415 30
416 30
        if ($response instanceof Message\ResponseMessageType) {
417 1
            if ($response->getResponseClass() !== "Success") {
418
                throw new ExchangeException($response);
419
            }
420 30
421 30
            unset($items['responseClass']);
422 30
            unset($items['responseCode']);
423
        }
424 30
425
        return $items;
426
    }
427
428
    /**
429
     * @param Message\BaseResponseMessageType $response
430
     * @param $code
431
     * @throws ExchangeException
432
     * @throws NoResponseReturnedException
433
     * @throws ServiceUnavailableException
434
     * @throws UnauthorizedException
435 30
     */
436
    protected function handleNonSuccessfulResponses($response, $code)
437 30
    {
438
        if ($code == 401) {
439
            throw new UnauthorizedException();
440
        }
441 30
442
        if ($code == 503) {
443
            throw new ServiceUnavailableException();
444
        }
445 30
446
        if ($code >= 300) {
447
            $response = new ResponseMessageType;
448
449 30
            $response->setMessageText('SOAP client returned status of ' . $code);
450
451
            throw new ExchangeException($response, $code);
452 30
        }
453
454 36
        if (empty($response) || empty($response->getNonNullResponseMessages())) {
455
            throw new NoResponseReturnedException();
456 36
        }
457 1
    }
458
459 1
    protected function buildMiddlewareStack()
460
    {
461 1
        if (self::$middlewareStack === false) {
462
            $factory = new MiddlewareFactory();
463
464 1
            self::$middlewareStack = [
465
                //Make the actual SOAP call
466
                $factory->getSoapCall(),
467 1
468
                //Transform an object of type Type to an XML Object
469
                $factory->getTypeToXMLObject(),
470 1
471
                //The SyncScope option isn't available for Exchange 2007 SP1 and below
472
                $factory->getStripSyncScopeForExchange2007(),
473 1
474 1
                //Add response processing
475 1
                $factory->getProcessResponse(),
476 36
477
                //Adds last request to FindFolder and FindItem responses
478
                $factory->getAddLastRequestToPagedResults()
479
            ];
480
        }
481
    }
482
483 30
    /**
484
     * @param array $middlewareStack
485 30
     * @param MiddlewareRequest $request
486 30
     * @return MiddlewareResponse
487
     */
488
    protected function executeMiddlewareStack(array $middlewareStack, MiddlewareRequest $request)
489 30
    {
490
        $newStack = [];
491 30
        foreach ($middlewareStack as $key => $current) {
492 30
            /** @var $current callable */
493 30
            $last = function () {
494
            };
495 30
496 30
            if ($key != 0) {
497 30
                $last = $newStack[$key - 1];
498
            }
499 30
500
            $current = Closure::bind($current, $this, $this);
501
            $newStack[] = function (MiddlewareRequest $request) use ($current, $last) {
502 30
                return $current($request, $last);
503
            };
504 30
        }
505
506
        /** @var $newStack callable[] */
507 30
        $newStack = array_reverse($newStack);
508
509
        $top = $newStack[0];
510
511
        /** @var $top callable */
512
        return $top($request);
513
    }
514
}
515