Completed
Push — master ( b57115...36326e )
by rugk
07:27
created

saveProviderData()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 14
nc 3
nop 2
1
<?php
2
/**
3
 * 2FA callback actions.
4
 *
5
 * @package ThreemaGateway
6
 * @author rugk
7
 * @copyright Copyright (c) 2015-2016 rugk
8
 * @license MIT
9
 */
10
11
abstract class ThreemaGateway_Handler_Action_TfaCallback_Abstract extends ThreemaGateway_Handler_Action_Abstract
12
{
13
    /**
14
     * @var array Cache of models
15
     */
16
    protected $modelCache = [];
17
18
    /**
19
     * @var XenForo_Session|null Imitiated session
20
     */
21
    protected $session = null;
22
23
    /**
24
     * @var array user data cache
25
     */
26
    protected $user = [];
27
28
    /**
29
     * @var string how the provider data has been fetched
30
     */
31
    protected $dataFetchMode;
32
33
    /**
34
     * @var ThreemaGateway_Handler_Action_Callback
35
     */
36
    protected $callback;
37
38
    /**
39
     * @var Threema\MsgApi\Helpers\ReceiveMessageResult
40
     */
41
    protected $receiveResult;
42
43
    /**
44
     * @var Threema\MsgApi\Messages\ThreemaMessage
45
     */
46
    protected $threemaMsg;
47
48
    /**
49
     * @var array|string
50
     */
51
    protected $log;
52
53
    /**
54
     * @var bool
55
     */
56
    protected $saveMessage;
57
58
    /**
59
     * @var bool
60
     */
61
    protected $debugMode;
62
63
    /**
64
     * @var string name of the processed message
65
     */
66
    protected $name = 'message';
67
68
    /**
69
     * @var string name of the secret contained in a message
70
     */
71
    protected $nameSecret = 'data';
72
73
    /**
74
     * @var int the type of the request
75
     */
76
    protected $pendingRequestType;
77
78
    /**
79
     * @var array filters/conditions applied to the message
80
     */
81
    protected $filters;
82
83
    /**
84
     * @var array all pending request messages found for the current message
85
     */
86
    protected $pendingRequests;
87
88
   /**
89
    * Checks whether text messages contain code used for the receiver 2FA.
90
    *
91
    * You should set the "event hint" to "1" to only pass text messages to the
92
    * listener. Otherwise errors may happen.
93
    *
94
    * @param ThreemaGateway_Handler_Action_Callback      $handler
95
    * @param Threema\MsgApi\Helpers\ReceiveMessageResult $receiveResult
96
    * @param Threema\MsgApi\Messages\ThreemaMessage      $threemaMsg
97
    * @param array|string                                $output        [$logType, $debugLog, $publicLog]
98
    * @param bool                                        $saveMessage
99
    * @param bool                                        $debugMode
100
    *
101
    * @throws XenForo_Exception
102
    */
103
   public function __construct(ThreemaGateway_Handler_Action_Callback $handler,
104
                               Threema\MsgApi\Helpers\ReceiveMessageResult $receiveResult,
105
                               Threema\MsgApi\Messages\ThreemaMessage $threemaMsg,
106
                               &$output,
107
                               &$saveMessage,
108
                               $debugMode)
109
   {
110
       $this->callback              = $handler;
111
       $this->log                   = $output;
112
       $this->receiveResult         = $receiveResult;
113
       $this->threemaMsg            = $threemaMsg;
114
       $this->saveMessage           = $saveMessage;
115
       $this->debugMode             = $debugMode;
116
   }
117
118
    /**
119
     * Prepare the message handling.
120
     *
121
     * Returns "false" if the process should be canceled. Otherwise "true".
122
     *
123
     * @throws XenForo_Exception
124
     * @return bool
125
     */
126
    abstract public function prepareProcessing();
127
128
    /**
129
     * Filters the passed data/message.
130
     *
131
     * Returns "false" if the process should be canceled. Otherwise "true".
132
     * The filters have had to be set by [@see addFilter()] before.
133
     *
134
     * @return bool
135
     */
136
    public function applyFilters()
137
    {
138
        // skip check if there are no filters
139
        if (!$this->filters) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->filters of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
140
            return true;
141
        }
142
143
        foreach ($this->filters as $filter) {
144
            if (!$this->applyFilter($filter['type'], $filter['data'], $filter['fail'])) {
145
                return false;
146
            }
147
        }
148
149
        return true;
150
    }
151
152
    /**
153
     * Processes the pending requests, i.e. iterates all confirm requests and handles
154
     * them.
155
     *
156
     * Returns "false" if the process should be canceled. Otherwise "true".
157
     *
158
     * @param  array             $processOptions options, which are passed to {@link processConfirmRequest()}.
159
     * @throws XenForo_Exception
160
     *
161
     * @return bool
162
     */
163
    public function processPending(array $processOptions = [])
164
    {
165
        if (!$this->pendingRequests) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->pendingRequests of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
166
            $this->preProcessPending();
167
        }
168
        if (!$this->pendingRequests) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->pendingRequests of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
169
            if (isset($processOptions['requirePendingRequests']) && $processOptions['requirePendingRequests']) {
170
                throw new XenForo_Exception('preProcessPending() could not get any pending request data.');
171
            }
172
            return false;
173
        }
174
175
        // handle all requests
176
        /** @var bool $success whether the request has been successfully processed */
177
        $success = false;
178
179
        foreach ($this->pendingRequests as $confirmRequest) {
180
            // now confirm request
181
            if (!$this->processConfirmRequest($confirmRequest, $processOptions)) {
0 ignored issues
show
Unused Code introduced by
The call to ThreemaGateway_Handler_A...processConfirmRequest() has too many arguments starting with $processOptions.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
182
                // in case of error, just skip the message
183
                continue;
184
            }
185
186
            $success = true;
187
        }
188
189
        if (!$this->postProcessPending($success)) {
190
            return false;
191
        }
192
193
        return $success;
194
    }
195
196
    /**
197
     * Verifies & saves data for one confirm request.
198
     *
199
     * Returns "false" if the process should be canceled. Otherwise "true".
200
     * childs should call the parent here as the things done in this class are
201
     * essential!
202
     *
203
     * @throws XenForo_Exception
204
     * @return bool
205
     */
206
    protected function processConfirmRequest($confirmRequest)
207
    {
208
        // first verify send time
209
        if ($this->messageIsExpired($confirmRequest)) {
210
            continue;
211
        }
212
213
        // other processing should be done by child
214
215
        return true;
216
    }
217
218
    /**
219
     * Sets the name of the processed message.
220
     *
221
     * @param string $name       what message this is, e.g.: XY message
222
     * @param string $nameSecret what secrets etc. conmtains the message: XY code
223
     */
224
    final public function setMessageTypeName($name, $nameSecret)
225
    {
226
        $this->name       = $name;
227
        $this->nameSecret = $nameSecret;
228
    }
229
230
    /**
231
     * Sets the type of the request.
232
     *
233
     * Use one of the constants in
234
     * ThreemaGateway_Model_TfaPendingMessagesConfirmation::PENDING_*
235
     *
236
     * @param int $pendingRequestType
237
     */
238
    final public function setPrendingRequestType($pendingRequestType)
239
    {
240
        $this->pendingRequestType = $pendingRequestType;
241
    }
242
243
    /**
244
     * Returns the log and save message data you passed to this class when initiating.
245
     *
246
     * This should be called at least once at the end as this is the only way
247
     * to update the log and save message values.
248
     *
249
     * @param array|string $output      [$logType, $debugLog, $publicLog]
250
     * @param bool         $saveMessage
251
     */
252
    final public function getReferencedData(&$output, &$saveMessage)
253
    {
254
        $output      = $this->log;
255
        $saveMessage = $this->saveMessage;
256
    }
257
258
    /**
259
     * Adds a filter, which is applied to the message (data) when
260
     * {@see applyFilter()} is called.
261
     *
262
     * @param int   $filterType  please use the constants FILTER_*
263
     * @param mixed $filterData  any data the filter uses
264
     * @param bool  $failOnError whether the filter should fail on errors (true)
265
     *                           or silently ignore them (false)
266
     * @param bool  $saveMessage
0 ignored issues
show
Bug introduced by
There is no parameter named $saveMessage. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
267
     */
268
    public function addFilter($filterType, $filterData, $failOnError = true)
269
    {
270
        $this->filters[] = [
271
            'type' => $filterType,
272
            'data' => $filterData,
273
            'fail' => $failOnError
274
        ];
275
    }
276
277
    /**
278
     * Filters the passed data/message.
279
     *
280
     * Returns "false" if the process should be canceled. Otherwise "true".
281
     *
282
     * @param int   $filterType  please use the constants FILTER_*
283
     * @param mixed $filterData  any data the filter uses
284
     * @param bool  $failOnError whether the filter should fail on errors (true)
285
     *                           or silently ignore them (false)
286
     *
287
     * @throws XenForo_Exception
288
     * @return bool
289
     */
290
    abstract protected function applyFilter($filterType, $filterData, $failOnError = true);
291
292
    /**
293
     * Analyses/filters/validates the existing old provider data e.g. to
294
     * compare it with the new data to set.
295
     *
296
     * Returns "false" if the process should be canceled. Otherwise "true".
297
     *
298
     * @param array $confirmRequest  the confirm request
299
     * @param array $oldProviderData old data read
300
     * @param array $setData         new data to set
301
     * @param array $processOptions  custom options (optional)
302
     *
303
     * @throws XenForo_Exception
304
     * @return bool
305
     */
306
    protected function preSaveData(array $confirmRequest, array &$oldProviderData, array &$setData, array $processOptions = [])
0 ignored issues
show
Unused Code introduced by
The parameter $confirmRequest is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
307
    {
308
        return true;
309
    }
310
311
    /**
312
     * Handles the already merged provider data.
313
     *
314
     * Returns "false" if the process should be canceled. Otherwise "true".
315
     *
316
     * @param array $confirmRequest the confirm request
317
     * @param array $providerData   merged provider data
318
     * @param array $processOptions custom options (optional)
319
     *
320
     * @throws XenForo_Exception
321
     * @return bool
322
     */
323
    protected function preSaveDataMerged(array $confirmRequest, array &$providerData, array $processOptions = [])
324
    {
325
        return true;
326
    }
327
328
    /**
329
     * Does some stuff with the data after it has been saved.
330
     *
331
     * Returns "false" if the process should be canceled. Otherwise "true".
332
     *
333
     * @param array $confirmRequest the confirm request
334
     * @param array $providerData   old data read
335
     * @param array $setData        new data to set
0 ignored issues
show
Bug introduced by
There is no parameter named $setData. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
336
     * @param array $processOptions custom options (optional)
337
     *
338
     * @throws XenForo_Exception
339
     * @return bool
340
     */
341
    protected function postSaveData(array $confirmRequest, array &$providerData, array $processOptions = [])
0 ignored issues
show
Unused Code introduced by
The parameter $confirmRequest is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $providerData is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $processOptions is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
342
    {
343
        return true;
344
    }
345
346
    /**
347
     * Does stuff needed to be done before processing the actual requests.
348
     *
349
     * Returns "false" if the process should be canceled. Otherwise "true".
350
     * Childs should call the parent here as the things done in this class are
351
     * essential!
352
     *
353
     * @throws XenForo_Exception
354
     * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be false|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
355
     */
356
    protected function preProcessPending()
357
    {
358
        // check whether message has already been saved to prevent replay attacks
359
        $this->callback->assertNoReplayAttack($this->receiveResult->getMessageId());
360
361
        /** @var array|false $this->pendingRequests all pending requets (or false if there are none) */
362
        if (!$this->pendingRequests = $this->getPendingRequests()) {
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getPendingRequests() can also be of type false. However, the property $pendingRequests is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
363
            return false;
364
        }
365
    }
366
367
    /**
368
     * Does stuff, which needs to be done after processing the requests.
369
     *
370
     * Returns "false" if the process should be canceled. Otherwise "true".
371
     * Childs should call the parent here as the things done in this class are
372
     * essential!
373
     *
374
     * @param  bool              $success whether the data was processed successfully
375
     * @throws XenForo_Exception
376
     * @return bool
377
     */
378
    protected function postProcessPending($success)
379
    {
380
        if ($success) {
381
            // do not save message as it already has been processed
382
            $this->saveMessage = false;
383
        }
384
385
        return true;
386
    }
387
388
    /**
389
     * Returns the pending messages for a given.
390
     *
391
     * @return array|false
392
     */
393
    protected function getPendingRequests()
394
    {
395
        /** @var ThreemaGateway_Model_TfaPendingMessagesConfirmation $pendingRequestsModel */
396
        $pendingRequestsModel = $this->getModelFromCache('ThreemaGateway_Model_TfaPendingMessagesConfirmation');
397
398
        /** @var array|null $pendingRequests all pending requests if there are some */
399
        $pendingRequests = $pendingRequestsModel->getPending(
400
            $this->callback->getRequest('from'),
401
            null,
402
            $this->pendingRequestType
403
        );
404
405
        if (!$pendingRequests) {
406
            $this->log('No confirmation requests registered. Abort.');
407
            return false;
408
        }
409
410
        return $pendingRequests;
411
    }
412
413
    /**
414
     * Checks whether a message is expired.
415
     *
416
     * This uses the date given by the Threema Gateway server (this is the
417
     * send date) to verify that the message is not expired.
418
     * Thus the current date is not used for this comparison as this should
419
     * be done in the 2FA provider directly when verifying the data
420
     * (verifyFromInput).
421
     *
422
     * @param  array             $confirmRequest the confirmation message request
423
     * @throws XenForo_Exception
424
     *
425
     * @return bool
426
     */
427
    protected function messageIsExpired(array $confirmRequest)
428
    {
429
        if ($this->callback->getRequest('date') > $confirmRequest['expiry_date']) {
430
            $this->log(
431
                'Message is too old.',
432
                'Message is too old, already expired. Maximum: ' . date('Y-m-d H:i:s', $confirmRequest['expiry_date'])
433
            );
434
            return true;
435
        }
436
437
        return false;
438
    }
439
440
    /**
441
     * Saves data for a confirm request (as the provider data of the 2FA method).
442
     *
443
     * @param  array             $confirmRequest the confirmation message request
444
     * @param  array             $setData        an array of the data to set
445
     * @param  array             $processOptions custom options (optional)
446
     * @throws XenForo_Exception
447
     */
448
    protected function setDataForRequest(
449
        array $confirmRequest,
450
        array $setData,
451
        array $processOptions = []
452
    ) {
453
        /** @var array $providerData provider data of session */
454
        $providerData = [];
455
456
        $this->log('', 'Request #' .
457
            $confirmRequest['request_id'] . ' from ' .
458
            $confirmRequest['provider_id'] . ' for user ' .
459
            $confirmRequest['user_id'] . ' for session ' .
460
            $confirmRequest['session_id']);
461
462
        // clear potentially old session data
463
        $this->session = null;
464
465
        try {
466
            $providerData = $this->getProviderDataBySession($confirmRequest);
467
468
            $this->log(
469
                '',
470
                'Got provider data from session.'
471
            );
472
        } catch (Exception $e) {
473
            $this->log(
474
                '',
475
                $e->getMessage() . ' Try 2FA model.'
476
            );
477
478
            // second try via model
479
            try {
480
                $providerData = $this->getProviderDataByModel($confirmRequest);
481
482
                $this->log(
483
                    '',
484
                    'Got provider data from user model.'
485
                );
486
            } catch (Exception $e) {
487
                $this->log(
488
                    'Could not get provider data.',
489
                    $e->getMessage() . ' Abort.'
490
                );
491
492
                // re-throw exception
493
                throw $e;
494
            }
495
        }
496
497
        if (!$this->preSaveData($confirmRequest, $providerData, $setData, $processOptions)) {
498
            throw new Exception('preSaveData() returned an error and prevented data saving.');
499
        }
500
501
        // merge the data with the original provider data
502
        $providerData = array_merge($providerData, $setData);
503
504
        if (!$this->preSaveDataMerged($confirmRequest, $providerData, $processOptions)) {
505
            throw new Exception('preSaveDataMerged() returned an error and prevented data saving.');
506
        }
507
508
        // and save the data
509
        try {
510
            $this->saveProviderData($providerData, $confirmRequest);
511
        } catch (Exception $e) {
512
            $this->log('Could not save provider data.', $e->getMessage());
513
514
            // re-throw exception
515
            throw $e;
516
        }
517
518
        $this->log($this->nameSecret . ' saved.', 'Saved ' . $this->nameSecret . ' for request #' .
519
            $confirmRequest['request_id'] . ' from ' .
520
            $confirmRequest['provider_id'] . ' for user ' .
521
            $confirmRequest['user_id'] . ' for session ' .
522
            $confirmRequest['session_id']);
523
524
        if (!$this->postSaveData($confirmRequest, $providerData, $processOptions)) {
525
            throw new Exception('postSaveData() returned an error.');
526
        }
527
    }
528
529
    /**
530
     * Fetches and returns the provider data using the session.
531
     *
532
     * @param  array             $confirmRequest the confirmation message request
533
     * @throws XenForo_Exception
534
     * @return array
535
     */
536
    protected function getProviderDataBySession(array $confirmRequest)
537
    {
538
        /** @var XenForo_Session $session */
539
        $session = $this->getSession();
540
        $session->threemagwSetupRaw($confirmRequest['session_id'], false);
541
542
        /** @var string $sessionKey session key identifying */
543
        $sessionKey = 'tfaData_' . $confirmRequest['provider_id'];
544
        /** @var array $providerData provider data of session */
545
        $providerData = $session->get($sessionKey);
546
547
        if (empty($providerData)) {
548
            throw new XenForo_Exception('Could not get provider data from session using key ' . $sessionKey . '.');
549
        }
550
551
        $this->dataFetchMode = 'session';
552
        return $providerData;
553
    }
554
555
    /**
556
     * Fetches and returns the provider data.
557
     *
558
     * @param  array             $confirmRequest the confirmation message request
559
     * @throws XenForo_Exception
560
     * @return array
561
     */
562
    protected function getProviderDataByModel(array $confirmRequest)
563
    {
564
        /** @var XenForo_Model_Tfa $tfaModel */
565
        $tfaModel = $this->getModelFromCache('XenForo_Model_Tfa');
566
567
        /** @var array $userTfa */
568
        $userTfa = $tfaModel->getUserTfaEntries($confirmRequest['user_id']);
569
        if (!$userTfa) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $userTfa of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
570
            throw new XenForo_Exception('Could not get user 2FA data.');
571
        }
572
573
        try {
574
            /** @var array $providerData provider data of session */
575
            $providerData = unserialize($userTfa[$confirmRequest['provider_id']]['provider_data']);
576
        } catch (Exception $e) {
577
            throw new XenForo_Exception('Could not get provider data. (error: ' . $e->getMessage() . ')');
578
        }
579
580
        if (empty($providerData)) {
581
            throw new XenForo_Exception('Could not get provider data.');
582
        }
583
584
        $this->dataFetchMode = 'tfa_model';
585
        return $providerData;
586
    }
587
588
    /**
589
     * Gets model from cache or initializes a new model if needed.
590
     *
591
     * @param array $newProviderData provider data to save
592
     * @param array $confirmRequest  the confirmation message request
593
     *
594
     * @throws XenForo_Exception
595
     */
596
    protected function saveProviderData(array $newProviderData, array $confirmRequest)
597
    {
598
        switch ($this->dataFetchMode) {
599
            case 'session':
600
                /** @var string $sessionKey session key identifying */
601
                $sessionKey = 'tfaData_' . $confirmRequest['provider_id'];
602
603
                /** @var XenForo_Session $session */
604
                $session = $this->getSession();
605
606
                $session->set($sessionKey, $newProviderData);
607
                $session->save();
608
                break;
609
610
            case 'tfa_model':
611
                /** @var XenForo_Model_Tfa $tfaModel */
612
                $tfaModel = $this->getModelFromCache('XenForo_Model_Tfa');
613
                $tfaModel->updateUserProvider($confirmRequest['user_id'], $confirmRequest['provider_id'], $newProviderData, false);
614
                break;
615
616
            default:
617
                // if all fails, we can only throw an exception
618
                throw new XenForo_Exception('Invalid provider data fetch method: ' . $this->dataFetchMode);
619
        }
620
    }
621
622
    /**
623
     * Gets model from cache or initializes a new model if needed.
624
     *
625
     * @param string $class Name of class to load
626
     *
627
     * @return XenForo_Model
628
     */
629
    final protected function getModelFromCache($class)
630
    {
631
        if (!isset($this->modelCache[$class])) {
632
            $this->modelCache[$class] = XenForo_Model::create($class);
633
        }
634
635
        return $this->modelCache[$class];
636
    }
637
638
    /**
639
     * Returns the XenForo session.
640
     *
641
     * @return XenForo_Session
642
     */
643
    final protected function getSession()
644
    {
645
        if (!$this->session) {
646
            $class         = XenForo_Application::resolveDynamicClass('XenForo_Session');
647
            $this->session = new $class;
648
        }
649
650
        return $this->session;
651
    }
652
653
    /**
654
     * Adds some data to the log.
655
     *
656
     * @param  string          $logDetailed
657
     * @param  string|null     $logGeneral
658
     * @return XenForo_Session
0 ignored issues
show
Documentation introduced by
Should the return type not be XenForo_Session|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
659
     */
660
    final protected function log($logDetailed, $logGeneral = null)
661
    {
662
        return $this->callback->addLog($this->log, $logDetailed, $logGeneral);
663
    }
664
665
    /**
666
     * Returns the user array.
667
     *
668
     * @param  int   $userId
669
     * @return array
670
     */
671
    final protected function getUserData($userId)
672
    {
673
        if (!isset($this->user[$userId])) {
674
            /** @var XenForo_Model_User $userModel */
675
            $userModel = $this->getModelFromCache('XenForo_Model_User');
676
            /** @var array $user */
677
            $user = $userModel->getFullUserById($userId);
678
            if (!$user) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $user of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
679
                throw new XenForo_Exception('Could not get user data data.');
680
            }
681
682
            $this->user[$userId] = $user;
683
        }
684
685
        return $this->user[$userId];
686
    }
687
}
688