Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 264
0.00% covered (danger)
0.00%
0 / 27
CRAP
0.00% covered (danger)
0.00%
0 / 1
DeliveryExecutionStateService
0.00% covered (danger)
0.00%
0 / 264
0.00% covered (danger)
0.00%
0 / 27
4422
0.00% covered (danger)
0.00%
0 / 1
 getDeliveriesStates
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getInitialStatus
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 waitExecution
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 resumeExecution
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 run
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
12
 authoriseExecution
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
30
 terminate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 terminateExecution
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
42
 pauseExecution
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 pause
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
30
 finishExecution
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 finish
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 cancelExecution
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
6
 isCancelable
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 reportExecution
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
2
 legacyTransition
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
90
 canBeAuthorised
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
20
 getDeliveryLogService
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTestSessionService
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getCurrentItemId
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 catchSessionPause
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 getContext
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 reactivateExecution
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
6
 getBrowserDetector
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOsDetector
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 lockExecution
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 releaseExecution
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2016 (original work) Open Assessment Technologies SA;
19 *
20 */
21
22namespace oat\taoProctoring\model\implementation;
23
24use common_session_SessionManager as SessionManager;
25use Context;
26use oat\oatbox\event\EventManager;
27use oat\oatbox\log\LoggerAwareTrait;
28use oat\oatbox\mutex\LockTrait;
29use oat\oatbox\user\User;
30use oat\taoDelivery\model\execution\AbstractStateService;
31use oat\taoDelivery\model\execution\DeliveryExecution;
32use oat\taoDelivery\model\execution\ServiceProxy;
33use oat\taoDelivery\models\classes\execution\event\DeliveryExecutionReactivated;
34use oat\taoDeliveryRdf\model\guest\GuestTestUser;
35use oat\taoProctoring\model\authorization\AuthorizationGranted;
36use oat\taoProctoring\model\authorization\TestTakerAuthorizationService;
37use oat\taoProctoring\model\deliveryLog\DeliveryLog;
38use oat\taoProctoring\model\deliveryLog\event\DeliveryLogEvent;
39use oat\taoProctoring\model\event\DeliveryExecutionFinished;
40use oat\taoProctoring\model\event\DeliveryExecutionIrregularityReport;
41use oat\taoProctoring\model\event\DeliveryExecutionTerminated;
42use oat\taoProctoring\model\execution\DeliveryExecution as ProctoredDeliveryExecution;
43use oat\taoQtiTest\models\ExtendedStateService;
44use oat\taoTests\models\event\TestExecutionPausedEvent;
45use qtism\runtime\tests\AssessmentTestSessionState;
46use Sinergi\BrowserDetector\Browser;
47use Sinergi\BrowserDetector\Os;
48use Symfony\Component\Lock\Lock;
49
50/**
51 * Class DeliveryExecutionStateService
52 * @package oat\taoProctoring\model
53 * @author Aleh Hutnikau <hutnikau@1pt.com>
54 */
55class DeliveryExecutionStateService extends AbstractStateService implements
56    \oat\taoProctoring\model\DeliveryExecutionStateService
57{
58    use LoggerAwareTrait;
59    use LockTrait;
60
61    public const OPTION_TERMINATION_DELAY_AFTER_PAUSE = 'termination_delay_after_pause';
62    /**
63     * @var string lifetime delivery executions in awaiting state
64     */
65    public const OPTION_CANCELLATION_DELAY = 'cancellation_delay';
66    public const OPTION_TIME_HANDLING = 'time_handling';
67
68    public const TIME_HANDLING_EXTRA_TIME = 'extra_time';
69    public const TIME_HANDLING_TIMER_ADJUSTMENT = 'timer_adjustment';
70
71    /**
72     * @var TestSessionService
73     */
74    private $testSessionService;
75
76    /** @var Lock[]  */
77    private $executionLocks = [];
78
79    /**
80     * @return array
81     */
82    public function getDeliveriesStates()
83    {
84        return [
85            ProctoredDeliveryExecution::STATE_FINISHED,
86            ProctoredDeliveryExecution::STATE_ACTIVE,
87            ProctoredDeliveryExecution::STATE_PAUSED,
88            ProctoredDeliveryExecution::STATE_TERMINATED,
89        ];
90    }
91
92    /**
93     * (non-PHPdoc)
94     * @see \oat\taoDelivery\model\execution\AbstractStateService::getInitialStatus()
95     */
96    public function getInitialStatus($deliveryId, User $user)
97    {
98        $service = $this->getServiceLocator()->get(TestTakerAuthorizationService::SERVICE_ID);
99        return $service->isProctored($deliveryId, $user)
100            ? DeliveryExecution::STATE_PAUSED
101            : DeliveryExecution::STATE_ACTIVE;
102    }
103
104    /**
105     * @param DeliveryExecution $deliveryExecution
106     * @return bool
107     * @throws \common_exception_NotFound
108     * @throws \oat\oatbox\service\exception\InvalidServiceManagerException
109     */
110    public function waitExecution(DeliveryExecution $deliveryExecution)
111    {
112        $result = false;
113        $this->lockExecution($deliveryExecution);
114        $executionState = $deliveryExecution->getState()->getUri();
115        if (
116            ProctoredDeliveryExecution::STATE_TERMINATED !== $executionState &&
117            ProctoredDeliveryExecution::STATE_FINISHED !== $executionState
118        ) {
119            $this->setState($deliveryExecution, ProctoredDeliveryExecution::STATE_AWAITING);
120            $this->getDeliveryLogService()->log($deliveryExecution->getIdentifier(), 'TEST_AWAITING_AUTHORISATION', [
121                'timestamp' => microtime(true),
122                'context' => $this->getContext($deliveryExecution),
123            ]);
124            $result = true;
125        }
126
127        $this->releaseExecution($deliveryExecution);
128        return $result;
129    }
130
131    /**
132     * Alias for self::run() (for backward capability).
133     *
134     * @param DeliveryExecution $deliveryExecution
135     * @return bool
136     */
137    public function resumeExecution(DeliveryExecution $deliveryExecution)
138    {
139        return $this->run($deliveryExecution);
140    }
141
142    /**
143     * @param DeliveryExecution $deliveryExecution
144     * @return bool
145     */
146    public function run(DeliveryExecution $deliveryExecution)
147    {
148        $this->lockExecution($deliveryExecution);
149        $session = $this->getTestSessionService()->getTestSession($deliveryExecution);
150        $logData = [
151            'web_browser_name' => $this->getBrowserDetector()->getName(),
152            'web_browser_version' => $this->getBrowserDetector()->getVersion(),
153            'os_name' => $this->getOsDetector()->getName(),
154            'os_version' => $this->getOsDetector()->getVersion(),
155            'context' => $this->getContext($deliveryExecution),
156        ];
157
158        $this->setState($deliveryExecution, ProctoredDeliveryExecution::STATE_ACTIVE);
159
160        if ($session && $session->getState() !== AssessmentTestSessionState::INITIAL) {
161            $session->resume();
162            $this->getTestSessionService()->persist($session);
163            $logData['timestamp'] = microtime(true);
164            $this->getDeliveryLogService()->log(
165                $deliveryExecution->getIdentifier(),
166                DeliveryLogEvent::EVENT_ID_TEST_RESUME,
167                $logData
168            );
169        } else {
170            $logData['timestamp'] = microtime(true);
171            $this->getDeliveryLogService()->log(
172                $deliveryExecution->getIdentifier(),
173                DeliveryLogEvent::EVENT_ID_TEST_RUN,
174                $logData
175            );
176        }
177
178        $this->releaseExecution($deliveryExecution);
179        return true;
180    }
181
182    /**
183     * @param DeliveryExecution $deliveryExecution
184     * @param null $reason
185     * @param null $testCenter
186     * @return bool
187     * @throws \common_exception_Error
188     * @throws \common_exception_NotFound
189     * @throws \oat\oatbox\service\exception\InvalidServiceManagerException
190     */
191    public function authoriseExecution(DeliveryExecution $deliveryExecution, $reason = null, $testCenter = null)
192    {
193        $result = false;
194        $this->lockExecution($deliveryExecution);
195        if ($this->canBeAuthorised($deliveryExecution)) {
196            $proctor = SessionManager::getSession()->getUser();
197            $logData = [
198                'proctorUri' => $proctor->getIdentifier(),
199                'timestamp' => microtime(true),
200            ];
201            if (!empty($reason) && is_array($reason)) {
202                $logData = array_merge($logData, $reason);
203            }
204            if ($testCenter !== null) {
205                $logData['test_center'] = $testCenter;
206            }
207            $logData['itemId'] = $this->getCurrentItemId($deliveryExecution);
208            $logData['context'] = $this->getContext($deliveryExecution);
209            $this->getDeliveryLogService()->log(
210                $deliveryExecution->getIdentifier(),
211                DeliveryLogEvent::EVENT_ID_TEST_AUTHORISE,
212                $logData
213            );
214            $this->setState($deliveryExecution, ProctoredDeliveryExecution::STATE_AUTHORIZED);
215            $eventManager = $this->getServiceLocator()->get(EventManager::SERVICE_ID);
216            $eventManager->trigger(new AuthorizationGranted($deliveryExecution, $proctor));
217            $result = true;
218        }
219        $this->releaseExecution($deliveryExecution);
220        return $result;
221    }
222
223    /**
224     * {@inheritDoc}
225     * @see \oat\taoDelivery\model\execution\StateServiceInterface::terminate()
226     */
227    public function terminate(DeliveryExecution $deliveryExecution)
228    {
229        $this->terminateExecution($deliveryExecution);
230    }
231
232    /**
233     * Terminates a delivery execution
234     *
235     * @param DeliveryExecution $deliveryExecution
236     * @param null $reason
237     * @return bool
238     * @throws \common_exception_Error
239     * @throws \common_exception_MissingParameter
240     * @throws \common_exception_NotFound
241     * @throws \oat\oatbox\service\exception\InvalidServiceManagerException
242     * @throws \qtism\runtime\storage\common\StorageException
243     * @throws \qtism\runtime\tests\AssessmentTestSessionException
244     */
245    public function terminateExecution(DeliveryExecution $deliveryExecution, $reason = null)
246    {
247        $this->lockExecution($deliveryExecution);
248        $executionState = $deliveryExecution->getState()->getUri();
249        $result = false;
250
251        if (
252            ProctoredDeliveryExecution::STATE_TERMINATED !== $executionState
253            && ProctoredDeliveryExecution::STATE_FINISHED !== $executionState
254        ) {
255            $proctor = SessionManager::getSession()->getUser();
256            $eventManager = $this->getServiceManager()->get(EventManager::CONFIG_ID);
257
258            $session = $this->getTestSessionService()->getTestSession($deliveryExecution);
259            $logData = [
260                'reason' => $reason,
261                'timestamp' => microtime(true),
262                'context' => $this->getContext($deliveryExecution),
263                'itemId' => $session ? $this->getCurrentItemId($deliveryExecution) : null,
264            ];
265
266            $this->getDeliveryLogService()->log(
267                $deliveryExecution->getIdentifier(),
268                DeliveryLogEvent::EVENT_ID_TEST_TERMINATE,
269                $logData
270            );
271
272            if ($session) {
273                if ($session->isRunning()) {
274                    $session->endTestSession();
275                }
276                $this->getTestSessionService()->persist($session);
277                $this->getServiceLocator()->get(ExtendedStateService::SERVICE_ID)->persist($session->getSessionId());
278            }
279
280            // Delivery execution state changes after test session ends, in the same way as it happens
281            // when a human test taker takes the test.
282            $this->setState($deliveryExecution, ProctoredDeliveryExecution::STATE_TERMINATED);
283            $eventManager->trigger(new DeliveryExecutionTerminated($deliveryExecution, $proctor, $reason));
284            $result = true;
285        }
286        $this->releaseExecution($deliveryExecution);
287        return $result;
288    }
289
290    /**
291     * Alias for self::pause() (for backward capability).
292     *
293     * @param DeliveryExecution $deliveryExecution
294     * @param null $reason
295     * @return bool
296     * @throws \common_exception_Error
297     * @throws \common_exception_MissingParameter
298     * @throws \common_exception_NotFound
299     * @throws \oat\oatbox\service\exception\InvalidServiceManagerException
300     * @throws \qtism\runtime\storage\common\StorageException
301     */
302    public function pauseExecution(DeliveryExecution $deliveryExecution, $reason = null)
303    {
304        return $this->pause($deliveryExecution, $reason);
305    }
306
307    /**
308     * Pauses a delivery execution
309     *
310     * @param DeliveryExecution $deliveryExecution
311     * @param null $reason
312     * @return bool
313     * @throws \common_exception_Error
314     * @throws \common_exception_MissingParameter
315     * @throws \common_exception_NotFound
316     * @throws \oat\oatbox\service\exception\InvalidServiceManagerException
317     * @throws \qtism\runtime\storage\common\StorageException
318     */
319    public function pause(DeliveryExecution $deliveryExecution, $reason = null)
320    {
321        $this->lockExecution($deliveryExecution);
322        $executionState = $deliveryExecution->getState()->getUri();
323        $result = false;
324
325        if (
326            ProctoredDeliveryExecution::STATE_TERMINATED !== $executionState
327            && ProctoredDeliveryExecution::STATE_FINISHED !== $executionState
328        ) {
329            $session = $this->getTestSessionService()->getTestSession($deliveryExecution);
330            $data = [
331                'reason' => $reason,
332                'timestamp' => microtime(true),
333                'context' => $this->getContext($deliveryExecution),
334            ];
335            $this->setState($deliveryExecution, ProctoredDeliveryExecution::STATE_PAUSED);
336            if ($session) {
337                $data['itemId'] = $this->getCurrentItemId($deliveryExecution);
338                if ($session->getState() !== AssessmentTestSessionState::SUSPENDED) {
339                    $session->suspend();
340                    $this->getTestSessionService()->persist($session);
341                }
342                $this->getServiceLocator()->get(ExtendedStateService::SERVICE_ID)->persist($session->getSessionId());
343            }
344            $this->getDeliveryLogService()->log(
345                $deliveryExecution->getIdentifier(),
346                DeliveryLogEvent::EVENT_ID_TEST_PAUSE,
347                $data
348            );
349            $result = true;
350        }
351        $this->releaseExecution($deliveryExecution);
352        return $result;
353    }
354
355    /**
356     * Alias for self::finish() (for backward capability).
357     *
358     * @param DeliveryExecution $deliveryExecution
359     * @param null $reason
360     * @return bool
361     * @throws \common_exception_NotFound
362     * @throws \oat\oatbox\service\exception\InvalidServiceManagerException
363     */
364    public function finishExecution(DeliveryExecution $deliveryExecution, $reason = null)
365    {
366        return $this->finish($deliveryExecution, $reason);
367    }
368
369    /**
370     * @param DeliveryExecution $deliveryExecution
371     * @param null $reason
372     * @return bool
373     * @throws \common_exception_NotFound
374     * @throws \oat\oatbox\service\exception\InvalidServiceManagerException
375     */
376    public function finish(DeliveryExecution $deliveryExecution, $reason = null)
377    {
378        $this->lockExecution($deliveryExecution);
379        $result = $this->setState($deliveryExecution, ProctoredDeliveryExecution::STATE_FINISHED, $reason);
380        if ($result) {
381            $eventManager = $this->getServiceManager()->get(EventManager::SERVICE_ID);
382            $eventManager->trigger(new DeliveryExecutionFinished($deliveryExecution));
383        }
384        $this->releaseExecution($deliveryExecution);
385        return $result;
386    }
387
388    /**
389     * @param DeliveryExecution $deliveryExecution
390     * @param null $reason
391     * @return bool
392     * @throws \common_exception_Error
393     * @throws \common_exception_MissingParameter
394     * @throws \common_exception_NotFound
395     * @throws \oat\oatbox\service\exception\InvalidServiceManagerException
396     */
397    public function cancelExecution(DeliveryExecution $deliveryExecution, $reason = null)
398    {
399        $this->lockExecution($deliveryExecution);
400        $session = $this->getTestSessionService()->getTestSession($deliveryExecution);
401        if ($session === null) {
402            $data = [
403                'reason' => $reason,
404                'timestamp' => microtime(true),
405                'context' => $this->getContext($deliveryExecution),
406            ];
407            $this->getDeliveryLogService()->log(
408                $deliveryExecution->getIdentifier(),
409                DeliveryLogEvent::EVENT_ID_TEST_CANCEL,
410                $data
411            );
412            $result = $this->setState($deliveryExecution, ProctoredDeliveryExecution::STATE_CANCELED);
413        } else {
414            $this->logNotice(
415                'Attempt to cancel delivery execution ' . $deliveryExecution->getIdentifier()
416                    . ' with initialized test session.'
417            );
418            $result = false;
419        }
420
421        $this->releaseExecution($deliveryExecution);
422
423        return $result;
424    }
425
426    /**
427     * @param DeliveryExecution $deliveryExecution
428     * @return bool
429     * @throws \common_exception_Error
430     * @throws \common_exception_MissingParameter
431     * @throws \oat\oatbox\service\exception\InvalidServiceManagerException
432     */
433    public function isCancelable(DeliveryExecution $deliveryExecution)
434    {
435        return $this->getTestSessionService()->getTestSession($deliveryExecution) === null;
436    }
437
438    /**
439     * Report irregularity to a delivery execution
440     *
441     * @todo remove this method to separate service
442     * @param DeliveryExecution $deliveryExecution
443     * @param array $reason
444     * @return bool
445     */
446    public function reportExecution(DeliveryExecution $deliveryExecution, $reason)
447    {
448        $deliveryLog = $this->getDeliveryLogService();
449        $data = [
450            'reason' => $reason,
451            'timestamp' => microtime(true),
452            'itemId' => $this->getCurrentItemId($deliveryExecution),
453            'context' => $this->getContext($deliveryExecution)
454        ];
455        $returnValue = $deliveryLog->log(
456            $deliveryExecution->getIdentifier(),
457            DeliveryLogEvent::EVENT_ID_TEST_IRREGULARITY,
458            $data
459        );
460
461        // Trigger a report event.
462        /** @var EventManager $eventManager */
463        $eventManager = $this->getServiceManager()->get(EventManager::SERVICE_ID);
464        $eventManager->trigger(new DeliveryExecutionIrregularityReport($deliveryExecution));
465
466        return $returnValue;
467    }
468
469    /**
470     * @inheritdoc
471     */
472    public function legacyTransition(DeliveryExecution $deliveryExecution, $state)
473    {
474        $reason = null;
475        $testCenter = null;
476        switch ($state) {
477            case ProctoredDeliveryExecution::STATE_ACTIVE:
478                $result = $this->resumeExecution($deliveryExecution);
479                break;
480            case ProctoredDeliveryExecution::STATE_AUTHORIZED:
481                $result = $this->authoriseExecution($deliveryExecution, $reason, $testCenter);
482                break;
483            case ProctoredDeliveryExecution::STATE_AWAITING:
484                $result = $this->waitExecution($deliveryExecution);
485                break;
486            case ProctoredDeliveryExecution::STATE_CANCELED:
487                $result = $this->cancelExecution($deliveryExecution, $reason);
488                break;
489            case ProctoredDeliveryExecution::STATE_FINISHED:
490                $result = $this->finishExecution($deliveryExecution, $reason);
491                break;
492            case ProctoredDeliveryExecution::STATE_PAUSED:
493                $result = $this->pauseExecution($deliveryExecution, $reason);
494                break;
495            case ProctoredDeliveryExecution::STATE_TERMINATED:
496                $result = $this->terminateExecution($deliveryExecution, $reason);
497                break;
498            default:
499                $this->logWarning('Unrecognised state ' . $state);
500                $result = $this->setState($deliveryExecution, $state);
501        }
502
503        return $result;
504    }
505
506    /**
507     * Whether delivery execution can be moved to authorised state.
508     * @param DeliveryExecution $deliveryExecution
509     * @return bool
510     */
511    protected function canBeAuthorised(DeliveryExecution $deliveryExecution)
512    {
513        $result = false;
514        $user = SessionManager::getSession()->getUser();
515        $stateUri = $deliveryExecution->getState()->getUri();
516        if ($stateUri === ProctoredDeliveryExecution::STATE_AWAITING) {
517            $result = true;
518        }
519
520        if (
521            $user instanceof GuestTestUser &&
522            !in_array($stateUri, [
523                ProctoredDeliveryExecution::STATE_FINISHED,
524                ProctoredDeliveryExecution::STATE_TERMINATED,
525                ProctoredDeliveryExecution::STATE_CANCELED,
526            ])
527        ) {
528            $result = true;
529        }
530
531        return $result;
532    }
533
534    /**
535     * @return DeliveryLog
536     */
537    private function getDeliveryLogService()
538    {
539        /** @noinspection PhpIncompatibleReturnTypeInspection */
540        return $this->getServiceLocator()->get(DeliveryLog::SERVICE_ID);
541    }
542
543    /**
544     * Gets test session service
545     *
546     * @return TestSessionService
547     * @throws \oat\oatbox\service\exception\InvalidServiceManagerException
548     */
549    private function getTestSessionService()
550    {
551        if ($this->testSessionService === null) {
552            $this->testSessionService = $this->getServiceManager()->get(TestSessionService::SERVICE_ID);
553        }
554        return $this->testSessionService;
555    }
556
557    /**
558     * Get identifier of current item.
559     * @param DeliveryExecution $deliveryExecution
560     * @return null|string
561     */
562    protected function getCurrentItemId(DeliveryExecution $deliveryExecution)
563    {
564        $result = null;
565        $session = $this->getTestSessionService()->getTestSession($deliveryExecution);
566        if ($session) {
567            $item = $session->getCurrentAssessmentItemRef();
568            if ($item) {
569                $result = $item->getIdentifier();
570            }
571        }
572        return $result;
573    }
574
575    /**
576     * Pause delivery execution if test session was paused.
577     * @param TestExecutionPausedEvent $event
578     */
579    public function catchSessionPause(TestExecutionPausedEvent $event)
580    {
581        $deliveryExecution = ServiceProxy::singleton()->getDeliveryExecution($event->getTestExecutionId());
582        /** @var DeliveryExecutionStateService $service */
583        $requestParams = Context::getInstance()->getRequest()->getParameters();
584        $reason = null;
585        if (isset($requestParams['reason'])) {
586            $reason = $requestParams['reason'];
587        }
588        if ($deliveryExecution->getState()->getUri() !== DeliveryExecution::STATE_PAUSED) {
589            $this->pause($deliveryExecution, $reason);
590        }
591    }
592
593    /**
594     * @param DeliveryExecution $deliveryExecution
595     * @return string
596     */
597    protected function getContext(DeliveryExecution $deliveryExecution)
598    {
599        $result = 'cli' === php_sapi_name()
600            ? $_SERVER['PHP_SELF']
601            : Context::getInstance()->getRequest()->getRequestURI();
602
603        return $result;
604    }
605
606    /**
607     * @param DeliveryExecution $deliveryExecution
608     * @param null|string $reason
609     * @return bool
610     * @throws \common_exception_Error
611     * @throws \common_exception_NotFound
612     * @throws \oat\oatbox\service\exception\InvalidServiceManagerException
613     */
614    public function reactivateExecution(DeliveryExecution $deliveryExecution, $reason = null)
615    {
616        $this->lockExecution($deliveryExecution);
617        $executionState = $deliveryExecution->getState()->getUri();
618
619        $result = parent::reactivateExecution($deliveryExecution, $reason);
620
621        if (ProctoredDeliveryExecution::STATE_TERMINATED === $executionState) {
622            $logData = [
623                'reason' => $reason,
624                'timestamp' => microtime(true),
625                'context' => $this->getContext($deliveryExecution),
626            ];
627
628            $this->getDeliveryLogService()->log(
629                $deliveryExecution->getIdentifier(),
630                DeliveryExecutionReactivated::LOG_KEY,
631                $logData
632            );
633        }
634        $this->releaseExecution($deliveryExecution);
635        return $result;
636    }
637
638    /**
639     * Get the browser detector
640     *
641     * @return Browser
642     */
643    protected function getBrowserDetector()
644    {
645        return new Browser();
646    }
647
648    /**
649     * Get the operating system detector
650     *
651     * @return Os
652     */
653    protected function getOsDetector()
654    {
655        return new Os();
656    }
657
658    /**
659     * @param DeliveryExecution $deliveryExecution
660     */
661    protected function lockExecution(DeliveryExecution $deliveryExecution)
662    {
663        $deId = $deliveryExecution->getIdentifier();
664        $this->executionLocks[$deId] = $this->createLock(static::class . $deId, 30);
665        $this->executionLocks[$deId]->acquire(true);
666    }
667
668    /**
669     * @param DeliveryExecution $deliveryExecution
670     */
671    protected function releaseExecution(DeliveryExecution $deliveryExecution)
672    {
673        $deId = $deliveryExecution->getIdentifier();
674        if (isset($this->executionLocks[$deId])) {
675            $this->executionLocks[$deId]->release();
676            unset($this->executionLocks[$deId]);
677        }
678    }
679}