Completed
Push — master ( fd5d01...725272 )
by Gareth
03:16
created

ExchangeWebServices::executeMiddlewareStack()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 26
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3

Importance

Changes 8
Bugs 1 Features 0
Metric Value
c 8
b 1
f 0
dl 0
loc 26
ccs 14
cts 14
cp 1
rs 8.8571
cc 3
eloc 12
nc 3
nop 2
crap 3
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 26
    public function getPrimarySmtpMailbox()
169
    {
170 26
        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 34
    protected function __construct($server = null, $username = null, $password = null, $options = array())
224
    {
225 34
        if ($server !== null) {
226
            $this->createClient(
227
                $server,
228
                ExchangeWebServicesAuth::fromUsernameAndPassword($username, $password),
229
                $options
230
            );
231
        }
232
233 34
        $this->buildMiddlewareStack();
234 34
    }
235
236 33
    public static function fromUsernameAndPassword($server, $username, $password, $options)
237
    {
238 33
        $self = new self();
239 33
        $self->createClient($server, ExchangeWebServicesAuth::fromUsernameAndPassword($username, $password), $options);
240 33
        $self->options = $options;
241
242 33
        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 34
    protected function createClient($server, $auth, $options)
255
    {
256 34
        $location = 'https://' . $this->cleanServerUrl($server) . '/EWS/Exchange.asmx';
257
258 34
        $options = array_replace_recursive([
259 34
            'version' => self::VERSION_2007,
260 34
            'trace' => 1,
261 34
            'exceptions' => true,
262 34
            'classmap' => ClassMap::getClassMap(),
263
            'drillDownResponses' => true
264 34
        ], $options);
265
266 34
        $this->server = $server;
267 34
        $this->version = $options['version'];
268
269 34
        $this->soap = new NTLMSoapClient(
270 34
            $location,
271 34
            $auth,
272 34
            dirname(__FILE__) . '/../../Resources/wsdl/services.wsdl',
273
            $options
274 34
        );
275
276 34
        if (isset($options['primarySmtpEmailAddress'])) {
277 1
            $this->setPrimarySmtpEmailAddress($options['primarySmtpEmailAddress']);
278 1
        }
279
280 34
        if (isset($options['impersonation'])) {
281 1
            $this->setPrimarySmtpEmailAddress($options['impersonation']);
282 1
        }
283
284 34
        $this->drillDownResponses = $options['drillDownResponses'];
285 34
    }
286
287
    /**
288
     * @codeCoverageIgnore
289
     *
290
     * @param $name
291
     * @param $arguments
292
     * @return Type
293
     * @throws \garethp\ews\API\Exception
294
     */
295
    public function __call($name, $arguments)
296
    {
297
        $request = MiddlewareRequest::newRequest($name, $arguments, $this->options);
298
        $response = $this->executeMiddlewareStack(self::$middlewareStack, $request);
299
        $response = $response->getResponse();
300
        return $response;
301
        return $this->processResponse($response);
0 ignored issues
show
Unused Code introduced by
return $this->processResponse($response); does not seem to be reachable.

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 or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

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.

Loading history...
302
    }
303
304
    /**
305
     * Returns the SOAP Client that may be used to make calls against the server
306
     *
307
     * @return NTLMSoapClient
308
     */
309 30
    public function getClient()
310
    {
311 30
        return $this->soap;
312
    }
313
314
    /**
315
     * Cleans the server URL for usage
316
     *
317
     * @param $server
318
     * @return string
319
     */
320 41
    public function cleanServerUrl($server)
321
    {
322 41
        $url = parse_url($server);
323 41
        if (!isset($url['host']) && isset($url['path'])) {
324 36
            $url['host'] = $url['path'];
325 36
            unset($url['path']);
326 36
        }
327
328 41
        $server = $url['host'];
329 41
        if (isset($url['port'])) {
330 2
            $server .= ':' . $url['port'];
331 2
        }
332
333 41
        if (isset($url['path'])) {
334 4
            $server .= $url['path'];
335 4
        }
336
337 41
        $server = rtrim($server, "/");
338
339 41
        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 28
    protected function processResponse($response)
351
    {
352
        // If the soap call failed then we need to thow an exception.
353 28
        $code = $this->getClient()->getResponseCode();
354 28
        $this->handleNonSuccessfulResponses($response, $code);
355
356 28
        if (!$this->drillDownResponses) {
357
            return $response;
358
        }
359
360 28
        if (!$response->exists('responseMessages')) {
361
            return $response;
362
        }
363
364 28
        $response = $response->getResponseMessages();
365 28
        $response = $this->drillDownResponseLevels($response);
366
367 28
        return $response;
368
    }
369
370
    /**
371
     * @param $response
372
     * @return array
373
     * @throws \garethp\ews\API\Exception
374
     */
375 28
    public function drillDownResponseLevels($response)
376
    {
377 28
        $items = $this->getItemsFromResponse($response);
378
379 28
        if (count($items) == 1) {
380 28
            reset($items);
381 28
            $key = key($items);
382 28
            $methodName = "get$key";
383 28
            $response = $response->$methodName();
384
385 28
            return $this->drillDownResponseLevels($response);
386
        }
387
388 28
        if (is_array($items) && isset($items[1]) && $items[1] instanceof Message\ResponseMessageType) {
0 ignored issues
show
Bug introduced by
The class garethp\ews\API\Message\ResponseMessageType does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
389 2
            $response = array();
390 2
            foreach ($items as $responseItem) {
391 2
                $response[] = $this->drillDownResponseLevels($responseItem);
392 2
            }
393
394 2
            return $response;
395
        }
396
397 28
        return $response;
398
    }
399
400
    /**
401
     * @param $response
402
     * @return array
403
     * @throws ExchangeException
404
     */
405 28
    protected function getItemsFromResponse($response)
406
    {
407 28
        $items = array();
408 28
        if ($response instanceof Type) {
409 28
            $items = $response->getNonNullItems();
410 28
        }
411
412 28
        if (is_array($response)) {
413 2
            $items = $response;
414 2
        }
415
416 28
        if ($response instanceof Message\ResponseMessageType) {
0 ignored issues
show
Bug introduced by
The class garethp\ews\API\Message\ResponseMessageType does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
417 28
            if ($response->getResponseClass() !== "Success") {
418 1
                throw new ExchangeException($response->getMessageText());
419
            }
420
421 28
            unset($items['responseClass']);
422 28
            unset($items['responseCode']);
423 28
        }
424
425 28
        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
     */
436 28
    protected function handleNonSuccessfulResponses($response, $code)
437
    {
438 28
        if ($code == 401) {
439
            throw new UnauthorizedException();
440
        }
441
442 28
        if ($code == 503) {
443
            throw new ServiceUnavailableException();
444
        }
445
446 28
        if ($code >= 300) {
447
            throw new ExchangeException('SOAP client returned status of ' . $code, $code);
448
        }
449
450 28
        if (empty($response) || empty($response->getNonNullResponseMessages())) {
451
            throw new NoResponseReturnedException();
452
        }
453 28
    }
454
455 34
    protected function buildMiddlewareStack()
456
    {
457 34
        if (self::$middlewareStack === false) {
458 1
            $factory = new MiddlewareFactory();
459
460 1
            self::$middlewareStack = [
461
                //Make the actual SOAP call
462 1
                $factory->getSoapCall(),
463
464
                //Transform an object of type Type to an XML Object
465 1
                $factory->getTypeToXMLObject(),
466
467
                //The SyncScope option isn't available for Exchange 2007 SP1 and below
468 1
                $factory->getStripSyncScopeForExchange2007(),
469
470
                //Add response processing
471 1
                $factory->getProcessResponse(),
472
473
                //Adds last request to FindFolder and FindItem responses
474 1
                $factory->getAddLastRequestToPagedResults()
475 1
            ];
476 1
        }
477 34
    }
478
479
    /**
480
     * @param array $middlewareStack
481
     * @param MiddlewareRequest $request
482
     * @return MiddlewareResponse
483
     */
484 28
    protected function executeMiddlewareStack(array $middlewareStack, MiddlewareRequest $request)
485
    {
486 28
        $newStack = [];
487 28
        foreach ($middlewareStack as $key => $current) {
488
            /** @var $current callable */
489
            $last = function () {
490 28
            };
491
492 28
            if ($key != 0) {
493 28
                $last = $newStack[$key - 1];
494 28
            }
495
496 28
            $current = Closure::bind($current, $this, $this);
497 28
            $newStack[] = function (MiddlewareRequest $request) use ($current, $last) {
498 28
                return $current($request, $last);
499
            };
500 28
        }
501
502
        /** @var $newStack callable[] */
503 28
        $newStack = array_reverse($newStack);
504
505 28
        $top = $newStack[0];
506
507
        /** @var $top callable */
508 28
        return $top($request);
509
    }
510
}
511