Passed
Push — master ( b0b4c3...57268f )
by Yannick
09:19 queued 13s
created

LtiScoresResource::validate()   B

Complexity

Conditions 9
Paths 9

Size

Total Lines 33
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 17
c 0
b 0
f 0
nc 9
nop 0
dl 0
loc 33
rs 8.0555
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Entity\GradebookResult;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, GradebookResult. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
5
use Chamilo\CoreBundle\Entity\GradebookResultLog;
6
use Chamilo\PluginBundle\Entity\ImsLti\LineItem;
7
use Chamilo\UserBundle\Entity\User;
8
use Doctrine\ORM\OptimisticLockException;
9
use Doctrine\ORM\ORMException;
10
use Doctrine\ORM\TransactionRequiredException;
11
use Symfony\Component\HttpFoundation\Request;
12
use Symfony\Component\HttpFoundation\Response;
13
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
14
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
15
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
16
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
17
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
18
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
19
20
/**
21
 * Class LtiScoresResource.
22
 */
23
class LtiScoresResource extends LtiAdvantageServiceResource
24
{
25
    const URL_TEMPLATE = '/context_id/lineitems/line_item_id/results';
26
27
    const ACTIVITY_INITIALIZED = 'Initialized';
28
    const ACTIVITY_STARTED = 'Started';
29
    const ACTIVITY_IN_PROGRESS = 'InProgress';
30
    const ACTIVITY_SUBMITTED = 'Submitted';
31
    const ACTIVITY_COMPLETED = 'Completed';
32
33
    const GRADING_FULLY_GRADED = 'FullyGraded';
34
    const GRADING_PENDING = 'Pending';
35
    const GRADING_PENDING_MANUAL = 'PendingManual';
36
    const GRADING_FAILED = 'Failed';
37
    const GRADING_NOT_READY = 'NotReady';
38
39
    /**
40
     * @var LineItem|null
41
     */
42
    private $lineItem;
43
44
    /**
45
     * LtiScoresResource constructor.
46
     *
47
     * @param int $toolId
48
     * @param int $courseId
49
     * @param int $lineItemId
50
     *
51
     * @throws ORMException
52
     * @throws OptimisticLockException
53
     * @throws TransactionRequiredException
54
     */
55
    public function __construct($toolId, $courseId, $lineItemId)
56
    {
57
        parent::__construct($toolId, $courseId);
58
59
        $this->lineItem = Database::getManager()->find('ChamiloPluginBundle:ImsLti\LineItem', (int) $lineItemId);
60
    }
61
62
    /**
63
     * {@inheritDoc}
64
     */
65
    public function validate()
66
    {
67
        if (!$this->course) {
68
            throw new NotFoundHttpException('Course not found.');
69
        }
70
71
        if (!$this->lineItem) {
72
            throw new NotFoundHttpException('Line item not found');
73
        }
74
75
        if ($this->lineItem->getTool()->getId() !== $this->tool->getId()) {
76
            throw new AccessDeniedHttpException('Line item not found for the tool.');
77
        }
78
79
        if (!$this->tool) {
80
            throw new BadRequestHttpException('Tool not found.');
81
        }
82
83
        if ($this->tool->getCourse()->getId() !== $this->course->getId()) {
84
            throw new AccessDeniedHttpException('Tool not found in course.');
85
        }
86
87
        if ($this->request->server->get('HTTP_ACCEPT') !== LtiAssignmentGradesService::TYPE_SCORE) {
88
            throw new UnsupportedMediaTypeHttpException('Unsupported media type.');
89
        }
90
91
        $parentTool = $this->tool->getParent();
92
93
        if ($parentTool) {
94
            $advServices = $parentTool->getAdvantageServices();
95
96
            if (LtiAssignmentGradesService::AGS_NONE === $advServices['ags']) {
97
                throw new AccessDeniedHttpException('Assigment and grade service is not enabled for this tool.');
98
            }
99
        }
100
    }
101
102
    public function process()
103
    {
104
        switch ($this->request->getMethod()) {
105
            case Request::METHOD_POST:
106
                $this->validateToken(
107
                    [LtiAssignmentGradesService::SCOPE_SCORE_WRITE]
108
                );
109
                $this->processPost();
110
                break;
111
            default:
112
                throw new MethodNotAllowedHttpException([Request::METHOD_POST]);
113
        }
114
    }
115
116
    /**
117
     * @throws Exception
118
     */
119
    private function processPost()
120
    {
121
        $data = json_decode($this->request->getContent(), true);
122
123
        if (empty($data) ||
124
            !isset($data['userId']) ||
125
            !isset($data['gradingProgress']) ||
126
            !isset($data['activityProgress']) ||
127
            !isset($data['timestamp']) ||
128
            (isset($data['timestamp']) && !ImsLti::validateFormatDateIso8601($data['timestamp'])) ||
129
            (isset($data['scoreGiven']) && !is_numeric($data['scoreGiven'])) ||
130
            (isset($data['scoreGiven']) && !isset($data['scoreMaximum'])) ||
131
            (isset($data['scoreMaximum']) && !is_numeric($data['scoreMaximum']))
132
        ) {
133
            throw new BadRequestHttpException('Missing data to create score.');
134
        }
135
136
        $student = api_get_user_entity($data['userId']);
137
138
        if (!$student) {
139
            throw new BadRequestHttpException("User (id: {$data['userId']}) not found.");
140
        }
141
142
        $data['scoreMaximum'] = isset($data['scoreMaximum']) ? $data['scoreMaximum'] : 1;
143
144
        $evaluation = $this->lineItem->getEvaluation();
0 ignored issues
show
Bug introduced by
The method getEvaluation() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

144
        /** @scrutinizer ignore-call */ 
145
        $evaluation = $this->lineItem->getEvaluation();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
145
146
        $result = Database::getManager()
147
            ->getRepository('ChamiloCoreBundle:GradebookResult')
148
            ->findOneBy(
149
                [
150
                    'userId' => $data['userId'],
151
                    'evaluationId' => $evaluation->getId(),
152
                ]
153
            );
154
155
        if ($result && $result->getCreatedAt() >= new DateTime($data['timestamp'])) {
156
            throw new ConflictHttpException('The timestamp on record is later than the incoming score.');
157
        }
158
159
        if (isset($data['scoreGiven'])) {
160
            if (self::GRADING_FULLY_GRADED !== $data['gradingProgress']) {
161
                $data['scoreGiven'] = null;
162
            } else {
163
                $data['scoreGiven'] = (float) $data['scoreGiven'];
164
165
                if ($data['scoreMaximum'] > 0 && $data['scoreMaximum'] != $evaluation->getMax()) {
166
                    $data['scoreGiven'] = $data['scoreGiven'] * $evaluation->getMax() / $data['scoreMaximum'];
167
                }
168
            }
169
        }
170
171
        if (!$result) {
172
            $this->response->setStatusCode(Response::HTTP_CREATED);
173
        }
174
175
        $this->saveScore($data, $student, $result);
176
    }
177
178
    /**
179
     * @param GradebookResult $result
180
     *
181
     * @throws OptimisticLockException
182
     */
183
    private function saveScore(array $data, User $student, GradebookResult $result = null)
184
    {
185
        $em = Database::getManager();
186
187
        $evaluation = $this->lineItem->getEvaluation();
188
189
        if ($result) {
190
            $resultLog = new GradebookResultLog();
191
            $resultLog
192
                ->setCreatedAt(api_get_utc_datetime(null, false, true))
193
                ->setUserId($student->getId())
0 ignored issues
show
Bug introduced by
The method setUserId() does not exist on Chamilo\CoreBundle\Entity\GradebookResultLog. Did you maybe mean setUser()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

193
                ->/** @scrutinizer ignore-call */ setUserId($student->getId())

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
194
                ->setEvaluationId($evaluation->getId())
195
                ->setIdResult($result->getId())
196
                ->setScore($result->getScore());
197
198
            $em->persist($resultLog);
199
        } else {
200
            $result = new GradebookResult();
201
            $result
202
                ->setUserId($student->getId())
0 ignored issues
show
Bug introduced by
The method setUserId() does not exist on Chamilo\CoreBundle\Entity\GradebookResult. Did you maybe mean setUser()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

202
                ->/** @scrutinizer ignore-call */ 
203
                  setUserId($student->getId())

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
203
                ->setEvaluationId($evaluation->getId());
204
        }
205
206
        $result
207
            ->setCreatedAt(new DateTime($data['timestamp']))
208
            ->setScore($data['scoreGiven']);
209
210
        $em->persist($result);
211
212
        $em->flush();
213
    }
214
}
215