Completed
Pull Request — master (#66)
by marijn
03:01
created

ExchangeWebServices::createClient()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 34
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 3

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 34
ccs 25
cts 25
cp 1
rs 8.8571
cc 3
eloc 23
nc 4
nop 3
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 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);
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...
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) {
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...
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) {
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...
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