Completed
Push — master ( b1daba...543fa7 )
by Gareth
02:38
created

handleNonSuccessfulResponses()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 8.304

Importance

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