Passed
Pull Request — master (#57)
by Raúl
03:22 queued 26s
created

PagantisNotifyModuleFrontController::postProcess()   B

Complexity

Conditions 6
Paths 28

Size

Total Lines 45
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 34
c 1
b 0
f 0
nc 28
nop 0
dl 0
loc 45
rs 8.7537
1
<?php
2
/**
3
 * This file is part of the official Pagantis module for PrestaShop.
4
 *
5
 * @author    Pagantis <[email protected]>
6
 * @copyright 2019 Pagantis
7
 * @license   proprietary
8
 */
9
10
require_once('AbstractController.php');
11
12
use Pagantis\OrdersApiClient\Client as PagantisClient;
13
use Pagantis\OrdersApiClient\Model\Order as PagantisModelOrder;
14
use Pagantis\ModuleUtils\Exception\AmountMismatchException;
15
use Pagantis\ModuleUtils\Exception\ConcurrencyException;
16
use Pagantis\ModuleUtils\Exception\MerchantOrderNotFoundException;
17
use Pagantis\ModuleUtils\Exception\NoIdentificationException;
18
use Pagantis\ModuleUtils\Exception\OrderNotFoundException;
19
use Pagantis\ModuleUtils\Exception\QuoteNotFoundException;
20
use Pagantis\ModuleUtils\Exception\ConfigurationNotFoundException;
21
use Pagantis\ModuleUtils\Exception\UnknownException;
22
use Pagantis\ModuleUtils\Exception\WrongStatusException;
23
use Pagantis\ModuleUtils\Model\Response\JsonSuccessResponse;
24
use Pagantis\ModuleUtils\Model\Response\JsonExceptionResponse;
25
26
/**
27
 * Class PagantisNotifyModuleFrontController
28
 */
29
class PagantisNotifyModuleFrontController extends AbstractController
30
{
31
    /**
32
     * Seconds to expire a locked request
33
     */
34
    const CONCURRENCY_TIMEOUT = 60;
35
36
    /**
37
     * @var string $merchantOrderId
38
     */
39
    protected $merchantOrderId;
40
41
    /**
42
     * @var \Cart $merchantOrder
0 ignored issues
show
Bug introduced by
The type Cart was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
43
     */
44
    protected $merchantOrder;
45
46
    /**
47
     * @var string $pagantisOrderId
48
     */
49
    protected $pagantisOrderId;
50
51
    /**
52
     * @var string $amountMismatchError
53
     */
54
    protected $amountMismatchError = '';
55
56
    /**
57
     * @var \Pagantis\OrdersApiClient\Model\Order $pagantisOrder
58
     */
59
    protected $pagantisOrder;
60
61
    /**
62
     * @var Pagantis\OrdersApiClient\Client $orderClient
63
     */
64
    protected $orderClient;
65
66
    /**
67
     * @var mixed $config
68
     */
69
    protected $config;
70
71
    /**
72
     * @var Object $jsonResponse
73
     */
74
    protected $jsonResponse;
75
76
    /**
77
     * @throws Exception
78
     */
79
    public function postProcess()
80
    {
81
        try {
82
            $this->prepareVariables();
83
            $this->getPagantisOrderId();
84
            $this->getPagantisOrder();
85
            $this->checkConcurrency();
86
            $this->getMerchantOrder();
87
            $this->checkOrderStatus();
88
            $this->checkMerchantOrderStatus();
89
            $this->validateAmount();
90
            $this->processMerchantOrder();
91
        } catch (\Exception $exception) {
92
            if ($_SERVER['REQUEST_METHOD'] == 'POST') {
93
                $this->jsonResponse = new JsonExceptionResponse();
94
                $this->jsonResponse->setMerchantOrderId($this->merchantOrderId);
95
                $this->jsonResponse->setPagantisOrderId($this->pagantisOrderId);
96
                $this->jsonResponse->setException($exception);
97
            }
98
            return $this->cancelProcess($exception);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->cancelProcess($exception) targeting PagantisNotifyModuleFron...roller::cancelProcess() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
99
        }
100
101
        try {
102
            $this->jsonResponse = new JsonSuccessResponse();
103
            $this->jsonResponse->setMerchantOrderId($this->merchantOrderId);
104
            $this->jsonResponse->setPagantisOrderId($this->pagantisOrderId);
105
            $this->confirmPagantisOrder();
106
        } catch (\Exception $exception) {
107
            $this->rollbackMerchantOrder();
108
            if ($_SERVER['REQUEST_METHOD'] == 'POST') {
109
                $this->jsonResponse = new JsonExceptionResponse();
110
                $this->jsonResponse->setMerchantOrderId($this->merchantOrderId);
111
                $this->jsonResponse->setPagantisOrderId($this->pagantisOrderId);
112
                $this->jsonResponse->setException($exception);
113
            }
114
            return $this->cancelProcess($exception);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->cancelProcess($exception) targeting PagantisNotifyModuleFron...roller::cancelProcess() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
115
        }
116
117
        try {
118
            $this->unblockConcurrency();
119
        } catch (\Exception $exception) {
120
            // Do nothing
121
        }
122
123
        return $this->finishProcess(false);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->finishProcess(false) targeting PagantisNotifyModuleFron...roller::finishProcess() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
124
    }
125
126
    /**
127
     * Check the concurrency of the purchase
128
     *
129
     * @throws Exception
130
     */
131
    public function checkConcurrency()
132
    {
133
        $this->unblockConcurrency();
134
        $this->blockConcurrency($this->merchantOrderId);
135
    }
136
137
    /**
138
     * Find and init variables needed to process payment
139
     *
140
     * @throws Exception
141
     */
142
    public function prepareVariables()
143
    {
144
        $callbackOkUrl = $this->context->link->getPageLink(
145
            'order-confirmation',
146
            null,
147
            null
148
        );
149
        $callbackKoUrl = $this->context->link->getPageLink(
150
            'order',
151
            null,
152
            null,
153
            array('step'=>3)
154
        );
155
        try {
156
            $this->config = array(
157
                'urlOK' => (Pagantis::getExtraConfig('PAGANTIS_URL_OK') !== '') ?
158
                    Pagantis::getExtraConfig('PAGANTIS_URL_OK') : $callbackOkUrl,
159
                'urlKO' => (Pagantis::getExtraConfig('PAGANTIS_URL_KO') !== '') ?
160
                    Pagantis::getExtraConfig('PAGANTIS_URL_KO') : $callbackKoUrl,
161
                'publicKey' => Configuration::get('pagantis_public_key'),
0 ignored issues
show
Bug introduced by
The type Configuration was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
162
                'privateKey' => Configuration::get('pagantis_private_key'),
163
                'secureKey' => Tools::getValue('key'),
0 ignored issues
show
Bug introduced by
The type Tools was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
164
            );
165
        } catch (\Exception $exception) {
166
            throw new ConfigurationNotFoundException();
167
        }
168
169
        $this->merchantOrderId = Tools::getValue('id_cart');
170
        if ($this->merchantOrderId == '') {
171
            throw new QuoteNotFoundException();
172
        }
173
174
175
        if (!($this->config['secureKey'] && $this->merchantOrderId && Module::isEnabled(self::PAGANTIS_CODE))) {
0 ignored issues
show
Bug introduced by
The type Module was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
176
            // This exception is only for Prestashop
177
            throw new UnknownException('Module may not be enabled');
178
        }
179
    }
180
181
    /**
182
     * Retrieve the merchant order by id
183
     *
184
     * @throws Exception
185
     */
186
    public function getMerchantOrder()
187
    {
188
        try {
189
            $this->merchantOrder = new Cart($this->merchantOrderId);
190
            if (!Validate::isLoadedObject($this->merchantOrder)) {
0 ignored issues
show
Bug introduced by
The type Validate was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
191
                // This exception is only for Prestashop
192
                throw new UnknownException('Unable to load cart');
193
            }
194
        } catch (\Exception $exception) {
195
            throw new MerchantOrderNotFoundException();
196
        }
197
    }
198
199
    /**
200
     * Find PAGANTIS Order Id in AbstractController::PAGANTIS_ORDERS_TABLE
201
     *
202
     * @throws Exception
203
     */
204
    private function getPagantisOrderId()
205
    {
206
        try {
207
            $this->pagantisOrderId= Db::getInstance()->getValue(
0 ignored issues
show
Bug introduced by
The type Db was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
208
                'select order_id from '._DB_PREFIX_.'pagantis_order where id = '.$this->merchantOrderId
0 ignored issues
show
Bug introduced by
The constant _DB_PREFIX_ was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
209
            );
210
211
            if (is_null($this->pagantisOrderId)) {
212
                throw new NoIdentificationException();
213
            }
214
        } catch (\Exception $exception) {
215
            throw new NoIdentificationException();
216
        }
217
    }
218
219
    /**
220
     * Find PAGANTIS Order in Orders Server using Pagantis\OrdersApiClient
221
     *
222
     * @throws Exception
223
     */
224
    private function getPagantisOrder()
225
    {
226
        $this->orderClient = new PagantisClient($this->config['publicKey'], $this->config['privateKey']);
227
        $this->pagantisOrder = $this->orderClient->getOrder($this->pagantisOrderId);
228
        if (!($this->pagantisOrder instanceof PagantisModelOrder)) {
0 ignored issues
show
introduced by
$this->pagantisOrder is always a sub-type of Pagantis\OrdersApiClient\Model\Order.
Loading history...
229
            throw new OrderNotFoundException();
230
        }
231
    }
232
233
    /**
234
     * Compare statuses of merchant order and PAGANTIS order, witch have to be the same.
235
     *
236
     * @throws Exception
237
     */
238
    public function checkOrderStatus()
239
    {
240
        if ($this->pagantisOrder->getStatus() === PagantisModelOrder::STATUS_CONFIRMED) {
241
            $this->jsonResponse = new JsonSuccessResponse();
242
            $this->jsonResponse->setMerchantOrderId($this->merchantOrderId);
243
            $this->jsonResponse->setPagantisOrderId($this->pagantisOrderId);
244
            return $this->finishProcess(false);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->finishProcess(false) targeting PagantisNotifyModuleFron...roller::finishProcess() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
245
        }
246
247
        if ($this->pagantisOrder->getStatus() !== PagantisModelOrder::STATUS_AUTHORIZED) {
248
            $status = '-';
249
            if ($this->pagantisOrder instanceof \Pagantis\OrdersApiClient\Model\Order) {
0 ignored issues
show
introduced by
$this->pagantisOrder is always a sub-type of Pagantis\OrdersApiClient\Model\Order.
Loading history...
250
                $status = $this->pagantisOrder->getStatus();
251
            }
252
            throw new WrongStatusException($status);
253
        }
254
    }
255
256
    /**
257
     * Check that the merchant order was not previously processes and is ready to be paid
258
     *
259
     * @throws Exception
260
     */
261
    public function checkMerchantOrderStatus()
262
    {
263
        if ($this->merchantOrder->orderExists() !== false) {
264
            throw new WrongStatusException('already_processed');
265
        }
266
    }
267
268
    /**
269
     * Check that the merchant order and the order in PAGANTIS have the same amount to prevent hacking
270
     *
271
     * @throws Exception
272
     */
273
    public function validateAmount()
274
    {
275
        $totalAmount = (string) $this->pagantisOrder->getShoppingCart()->getTotalAmount();
276
        $merchantAmount = (string) (100 * $this->merchantOrder->getOrderTotal(true));
277
        $merchantAmount = explode('.', explode(',', $merchantAmount)[0])[0];
278
        if ($totalAmount != $merchantAmount) {
279
            try {
280
                $psTotalAmount = substr_replace($merchantAmount, '.', (Tools::strlen($merchantAmount) -2), 0);
281
282
                $pgTotalAmountInCents = (string) $this->pagantisOrder->getShoppingCart()->getTotalAmount();
283
                $pgTotalAmount = substr_replace(
284
                    $pgTotalAmountInCents,
285
                    '.',
286
                    (Tools::strlen($pgTotalAmountInCents) -2),
287
                    0
288
                );
289
290
                $this->amountMismatchError = '. Amount mismatch in PrestaShop Order #'. $this->merchantOrderId .
291
                    ' compared with Pagantis Order: ' . $this->pagantisOrderId .
292
                    '. The order in PrestaShop has an amount of ' . $psTotalAmount . ' and in Pagantis ' .
293
                    $pgTotalAmount . ' PLEASE REVIEW THE ORDER';
294
                $this->saveLog(array(
295
                    'message' => $this->amountMismatchError
296
                ));
297
            } catch (\Exception $e) {
298
                // Do nothing
299
            }
300
        }
301
    }
302
303
    /**
304
     * Process the merchant order and notify client
305
     *
306
     * @throws Exception
307
     */
308
    public function processMerchantOrder()
309
    {
310
        try {
311
            $metadataOrder = $this->pagantisOrder->getMetadata();
312
            $metadataInfo = '';
313
            foreach ($metadataOrder as $metadataKey => $metadataValue) {
314
                if ($metadataKey == 'promotedProduct') {
315
                    $metadataInfo .= $metadataValue;
316
                }
317
            }
318
319
            $this->module->validateOrder(
320
                $this->merchantOrderId,
321
                Configuration::get('PS_OS_PAYMENT'),
322
                $this->merchantOrder->getOrderTotal(true),
323
                $this->module->displayName,
324
                'pagantisOrderId: ' . $this->pagantisOrder->getId() . ' ' .
325
                'pagantisOrderStatus: '. $this->pagantisOrder->getStatus() .
326
                $this->amountMismatchError .
327
                $metadataInfo,
328
                array('transaction_id' => $this->pagantisOrderId),
329
                null,
330
                false,
331
                $this->config['secureKey']
332
            );
333
        } catch (\Exception $exception) {
334
            throw new UnknownException($exception->getMessage());
335
        }
336
    }
337
338
    /**
339
     * Confirm the order in PAGANTIS
340
     *
341
     * @throws Exception
342
     */
343
    private function confirmPagantisOrder()
344
    {
345
        try {
346
            $this->orderClient->confirmOrder($this->pagantisOrderId);
347
            try {
348
                $mode = ($_SERVER['REQUEST_METHOD'] == 'POST') ? 'NOTIFICATION' : 'REDIRECTION';
349
                $message = 'Order CONFIRMED. The order was confirmed by a ' . $mode .
350
                    '. Pagantis OrderId=' . $this->pagantisOrderId .
351
                    '. Prestashop OrderId=' . $this->merchantOrderId;
352
                $this->saveLog(array(
353
                    'message' => $message
354
                ));
355
            } catch (\Exception $e) {
356
                // Do nothing
357
            }
358
        } catch (\Exception $exception) {
359
            throw new UnknownException($exception->getMessage());
360
        }
361
    }
362
363
    /**
364
     * Leave the merchant order as it was previously
365
     *
366
     * @throws Exception
367
     */
368
    public function rollbackMerchantOrder()
369
    {
370
        // Do nothing because the order is created only when the purchase was successfully
371
    }
372
373
    /**
374
     * Lock the concurrency to prevent duplicated inputs
375
     *
376
     * @param $orderId
377
     * @return bool|void
378
     * @throws ConcurrencyException
379
     */
380
    protected function blockConcurrency($orderId)
381
    {
382
        try {
383
            if ($this->pagantisOrder->getStatus() === PagantisModelOrder::STATUS_CONFIRMED) {
384
                $this->jsonResponse = new JsonSuccessResponse();
385
                $this->jsonResponse->setMerchantOrderId($this->merchantOrderId);
386
                $this->jsonResponse->setPagantisOrderId($this->pagantisOrderId);
387
                return $this->finishProcess(false);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->finishProcess(false) targeting PagantisNotifyModuleFron...roller::finishProcess() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
388
            }
389
390
            $table = 'pagantis_cart_process';
391
            return Db::getInstance()->insert($table, array('id' => $orderId, 'timestamp' => (time())));
392
            //if insert fails throw an exception
393
        } catch (\Exception $exception) {
394
            if ($_SERVER['REQUEST_METHOD'] == 'POST') {
395
                throw new ConcurrencyException();
396
            }
397
398
            $query = sprintf(
399
                "SELECT TIMESTAMPDIFF(SECOND,NOW()-INTERVAL %s SECOND, FROM_UNIXTIME(timestamp)) as rest FROM %s WHERE %s",
400
                self::CONCURRENCY_TIMEOUT,
401
                _DB_PREFIX_.$table,
0 ignored issues
show
Bug introduced by
The constant _DB_PREFIX_ was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
402
                "id=$orderId"
403
            );
404
            $resultSeconds = Db::getInstance()->getValue($query);
405
            $restSeconds = isset($resultSeconds) ? ($resultSeconds) : 0;
406
            $secondsToExpire = ($restSeconds>self::CONCURRENCY_TIMEOUT) ? self::CONCURRENCY_TIMEOUT : $restSeconds;
407
408
            $logMessage = sprintf(
409
                "Redirect concurrency, User have to wait %s seconds, default seconds %s, bd time to expire %s seconds",
410
                $secondsToExpire,
411
                self::CONCURRENCY_TIMEOUT,
412
                $restSeconds
413
            );
414
            $this->saveLog(array(
415
                'message' => $logMessage
416
            ));
417
            sleep($secondsToExpire+1);
418
            // After waiting...user continue the confirmation, hoping that previous call have finished.
419
            return true;
420
        }
421
    }
422
423
    /**
424
     * Unlock the concurrency
425
     *
426
     * @throws Exception
427
     */
428
    protected function unblockConcurrency()
429
    {
430
        try {
431
            Db::getInstance()->delete('pagantis_cart_process', 'timestamp < ' . (time() - self::CONCURRENCY_TIMEOUT));
432
        } catch (\Exception $exception) {
433
            throw new ConcurrencyException();
434
        }
435
    }
436
437
    /**
438
     * Do all the necessary actions to cancel the confirmation process in case of error
439
     * 1. Unblock concurrency
440
     * 2. Save log
441
     *
442
     * @param \Exception $exception
443
     *
444
     */
445
    public function cancelProcess($exception = null)
446
    {
447
        $debug = debug_backtrace();
448
        $method = $debug[1]['function'];
449
        $line = $debug[1]['line'];
450
        $data = array(
451
            'merchantOrderId' => $this->merchantOrderId,
452
            'pagantisOrderId' => $this->pagantisOrderId,
453
            'message' => ($exception)? $exception->getMessage() : 'Unable to get Exception message',
454
            'statusCode' => ($exception)? $exception->getCode() : 'Unable to get Exception statusCode',
455
            'method' => $method,
456
            'file' => __FILE__,
457
            'line' => $line,
458
        );
459
        $this->saveLog($data);
460
        return $this->finishProcess(true);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->finishProcess(true) targeting PagantisNotifyModuleFron...roller::finishProcess() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
461
    }
462
463
    /**
464
     * Redirect the request to the e-commerce or show the output in json
465
     *
466
     * @param bool $error
467
     */
468
    public function finishProcess($error = true)
469
    {
470
        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
471
            $this->jsonResponse->printResponse();
472
        }
473
474
        $parameters = array(
475
            'id_cart' => $this->merchantOrderId,
476
            'key' => $this->config['secureKey'],
477
            'id_module' => $this->module->id,
478
            'id_order' => ($this->pagantisOrder)?$this->pagantisOrder->getId(): null,
479
        );
480
        $url = ($error)? $this->config['urlKO'] : $this->config['urlOK'];
481
        return $this->redirect($url, $parameters);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->redirect($url, $parameters) targeting AbstractController::redirect() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
482
    }
483
}