Completed
Push — master ( 88d422...a6b19b )
by Gareth
01:56
created

ExchangeWebServices::fromCustomAuthentication()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 3
dl 0
loc 8
ccs 6
cts 6
cp 1
crap 1
rs 10
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
     */
170 28
    public function getPrimarySmtpMailbox()
171
    {
172 28
        return $this->primarySmtpMailbox;
173
    }
174
175 1
    public function getPrimarySmtpEmailAddress()
176
    {
177 1
        if ($this->primarySmtpMailbox == null) {
178 1
            return null;
179
        }
180
181 1
        return $this->primarySmtpMailbox->getEmailAddress();
182
    }
183
184 2
    public function setPrimarySmtpEmailAddress($emailAddress)
185
    {
186 2
        $mailbox = new EmailAddressType();
187 2
        $mailbox->setEmailAddress($emailAddress);
188 2
        $this->primarySmtpMailbox = $mailbox;
189
190 2
        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
     */
225 36
    protected function __construct($server = null, $username = null, $password = null, $options = array())
226
    {
227 36
        if ($server !== null) {
228
            $this->createClient(
229
                $server,
230
                ExchangeWebServicesAuth::fromUsernameAndPassword($username, $password),
231
                $options
232
            );
233
        }
234
235 36
        $this->buildMiddlewareStack();
236 36
    }
237
238 35
    public static function fromUsernameAndPassword($server, $username, $password, $options)
239
    {
240 35
        $self = new self();
241 35
        $self->createClient($server, ExchangeWebServicesAuth::fromUsernameAndPassword($username, $password), $options);
242 35
        $self->options = $options;
243
244 35
        return $self;
245
    }
246
247 1
    public static function fromCallbackToken($server, $token, $options)
248
    {
249 1
        $self = new self();
250 1
        $self->createClient($server, ExchangeWebServicesAuth::fromCallbackToken($token), $options);
251 1
        $self->options = $options;
252
253 1
        return $self;
254
    }
255
256 36
    public static function fromCustomAuthentication($server, $authentication, $options)
257
    {
258 36
        $self = new self();
259
        $self->createClient($server, $authentication, $options);
260 36
        $self->options = $options;
261 36
262 36
        return $self;
263 36
    }
264 36
265
    protected function createClient($server, $auth, $options)
266 36
    {
267
        $location = 'https://' . $this->cleanServerUrl($server) . '/EWS/Exchange.asmx';
268 36
269 36
        $options = array_replace_recursive([
270
            'version' => self::VERSION_2007,
271 36
            'trace' => 1,
272 36
            'exceptions' => true,
273 36
            'classmap' => ClassMap::getClassMap(),
274 36
            'drillDownResponses' => true
275 36
        ], $options);
276
277 36
        $this->server = $server;
278 36
        $this->version = $options['version'];
279
280 36
        $backup = libxml_disable_entity_loader(false);
281 1
        $this->soap = new NTLMSoapClient(
282 1
            $location,
283
            $auth,
284 36
            dirname(__FILE__) . '/../../Resources/wsdl/services.wsdl',
285 1
            $options
286 1
        );
287
        libxml_disable_entity_loader($backup);
288 36
289 36
        if (isset($options['primarySmtpEmailAddress'])) {
290
            $this->setPrimarySmtpEmailAddress($options['primarySmtpEmailAddress']);
291
        }
292
293
        if (isset($options['impersonation'])) {
294
            $this->setPrimarySmtpEmailAddress($options['impersonation']);
295
        }
296
297
        $this->drillDownResponses = $options['drillDownResponses'];
298
    }
299
300
    /**
301
     * @codeCoverageIgnore
302
     *
303
     * @param $name
304
     * @param $arguments
305
     * @return Type
306
     * @throws \garethp\ews\API\Exception
307
     */
308
    public function __call($name, $arguments)
309
    {
310
        $request = MiddlewareRequest::newRequest($name, $arguments, $this->options);
311
        $response = $this->executeMiddlewareStack(self::$middlewareStack, $request);
312 32
        $response = $response->getResponse();
313
        return $response;
314 32
    }
315
316
    /**
317
     * Returns the SOAP Client that may be used to make calls against the server
318
     *
319
     * @return NTLMSoapClient
320
     */
321
    public function getClient()
322
    {
323 43
        return $this->soap;
324
    }
325 43
326 43
    /**
327 38
     * Cleans the server URL for usage
328 38
     *
329 38
     * @param $server
330
     * @return string
331 43
     */
332 43
    public function cleanServerUrl($server)
333 2
    {
334 2
        $url = parse_url($server);
335
        if (!isset($url['host']) && isset($url['path'])) {
336 43
            $url['host'] = $url['path'];
337 4
            unset($url['path']);
338 4
        }
339
340 43
        $server = $url['host'];
341
        if (isset($url['port'])) {
342 43
            $server .= ':' . $url['port'];
343
        }
344
345
        if (isset($url['path'])) {
346
            $server .= $url['path'];
347
        }
348
349
        $server = rtrim($server, "/");
350
351
        return $server;
352
    }
353 30
354
    /**
355
     * Process a response to verify that it succeeded and take the appropriate
356 30
     * action
357 30
     *
358
     * @param \garethp\ews\API\Message\BaseResponseMessageType $response
359 30
     * @return Type[]
360
     * @throws \garethp\ews\API\Exception
361
     */
362
    protected function processResponse($response)
363 30
    {
364
        // If the soap call failed then we need to thow an exception.
365
        $code = $this->getClient()->getResponseCode();
366
        $this->handleNonSuccessfulResponses($response, $code);
367 30
368 30
        if (!$this->drillDownResponses) {
369
            return $response;
370 30
        }
371
372
        if (!$response->exists('responseMessages')) {
373
            return $response;
374
        }
375
376
        $response = $response->getResponseMessages();
377
        $response = $this->drillDownResponseLevels($response);
378 30
379
        return $response;
380 30
    }
381
382 30
    /**
383 30
     * @param $response
384 30
     * @return array
385 30
     * @throws \garethp\ews\API\Exception
386 30
     */
387
    public static function drillDownResponseLevels($response)
388 30
    {
389
        $items = self::getItemsFromResponse($response);
390
391 30
        if (count($items) === 1) {
392
            reset($items);
393 3
            $key = key($items);
394 3
            $methodName = "get$key";
395
            $response = $response->$methodName();
396
397 30
            return self::drillDownResponseLevels($response);
398
        }
399
400
        if (is_array($items) && isset($items[1]) && $items[1] instanceof Message\ResponseMessageType) {
401
            return array_map(function ($responseItem) {
402
                return self::drillDownResponseLevels($responseItem);
403
            }, $items);
404
        }
405 30
406
        return $response;
407 30
    }
408 30
409 30
    /**
410 30
     * @param $response
411
     * @return array
412 30
     * @throws ExchangeException
413 3
     */
414 3
    protected static function getItemsFromResponse($response)
415
    {
416 30
        $items = array();
417 30
        if ($response instanceof Type) {
418 1
            $items = $response->getNonNullItems();
419
        }
420
421 30
        if (is_array($response)) {
422 30
            $items = $response;
423 30
        }
424
425 30
        if ($response instanceof Message\ResponseMessageType) {
426
            if ($response->getResponseClass() !== "Success") {
427
                throw new ExchangeException($response);
428
            }
429
430
            unset($items['responseClass']);
431
            unset($items['responseCode']);
432
        }
433
434
        return $items;
435
    }
436 30
437
    /**
438 30
     * @param Message\BaseResponseMessageType $response
439
     * @param $code
440
     * @throws ExchangeException
441
     * @throws NoResponseReturnedException
442 30
     * @throws ServiceUnavailableException
443
     * @throws UnauthorizedException
444
     */
445
    protected function handleNonSuccessfulResponses($response, $code)
446 30
    {
447
        if ($code == 401) {
448
            throw new UnauthorizedException();
449
        }
450
451
        if ($code == 503) {
452
            throw new ServiceUnavailableException();
453 30
        }
454
455
        if ($code >= 300) {
456 30
            $response = new ResponseMessageType();
457
            $response->setMessageText('SOAP client returned status of ' . $code);
458 36
459
            throw new ExchangeException($response, $code);
460 36
        }
461 1
462
        if (empty($response) || empty($response->getNonNullResponseMessages())) {
463 1
            throw new NoResponseReturnedException();
464
        }
465 1
    }
466
467
    protected function buildMiddlewareStack()
468 1
    {
469
        if (self::$middlewareStack === false) {
470
            $factory = new MiddlewareFactory();
471 1
472
            self::$middlewareStack = [
473
                //Make the actual SOAP call
474 1
                $factory->getSoapCall(),
475
476
                //Transform an object of type Type to an XML Object
477 1
                $factory->getTypeToXMLObject(),
478 1
479 1
                //The SyncScope option isn't available for Exchange 2007 SP1 and below
480 36
                $factory->getStripSyncScopeForExchange2007(),
481
482
                //Add response processing
483
                $factory->getProcessResponse(),
484
485
                //Adds last request to FindFolder and FindItem responses
486
                $factory->getAddLastRequestToPagedResults()
487 30
            ];
488
        }
489 30
    }
490 30
491
    /**
492
     * @param array $middlewareStack
493 30
     * @param MiddlewareRequest $request
494
     * @return MiddlewareResponse
495 30
     */
496 30
    protected function executeMiddlewareStack(array $middlewareStack, MiddlewareRequest $request)
497 30
    {
498
        $newStack = [];
499 30
        foreach ($middlewareStack as $key => $current) {
500 30
            /** @var $current callable */
501 30
            $last = function () {
502
            };
503 30
504
            if ($key != 0) {
505
                $last = $newStack[$key - 1];
506 30
            }
507
508 30
            $current = Closure::bind($current, $this, $this);
509
            $newStack[] = function (MiddlewareRequest $request) use ($current, $last) {
510
                return $current($request, $last);
511 30
            };
512
        }
513
514
        /** @var $newStack callable[] */
515
        $newStack = array_reverse($newStack);
516
517
        $top = $newStack[0];
518
519
        /** @var $top callable */
520
        return $top($request);
521
    }
522
}
523