GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

QuizActivityParser::getResponsesValidAsNo()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 2
1
<?php
2
/*
3
 * This file is part of the trefoil application.
4
 *
5
 * (c) Miguel Angel Gabriel <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Trefoil\Helpers;
12
13
use Symfony\Component\DomCrawler\Crawler;
14
use Trefoil\Util\CrawlerTools;
15
16
/**
17
 * Parse an HTML representation of an activity into a QuizActivity object.
18
 *
19
 * Example activity:
20
 *
21
 * <div class="activity" data-id="1-1">
22
 *     <h5>Optional heading</h5>
23
 *     <h6>Optional subheading</h6>
24
 *     <p>free text</p>
25
 *     <ul><li>can have unordered lists</li></ul>
26
 *     <p>more free text</p>
27
 *
28
 *     [activity questions]
29
 * </div>
30
 *
31
 * Where [activity questions]:
32
 *
33
 *  <ol>
34
 *    <li><p>Text of question 1</p>
35
 *      <ol>
36
 *        <li>Text of response 1</li>
37
 *        <li>Text of response 2
38
 *            <p>Optional explanation</p>
39
 *        </li>
40
 *        <li>Text of response 3</li>
41
 *      </ol>
42
 *    </li>
43
 *    <li><p>Text of question 2</p>
44
 *      <ol>
45
 *        <li>Text of response 1</li>
46
 *        ...
47
 *      </ol>
48
 *    </li>
49
 *  </ol>
50
 */
51
class QuizActivityParser extends QuizItemParser
52
{
53
54
    /**
55
     * @var QuizActivity
56
     */
57
    protected $quizActivity;
58
59 1
    public function __construct($text)
60
    {
61 1
        $this->quizActivity = new QuizActivity();
62
63 1
        parent::__construct($text, $this->quizActivity);
64 1
    }
65
66
    /**
67
     * List of responses to be interpreted as "Yes"
68
     *
69
     * @var Array|string
70
     */
71
    protected $responsesValidAsYes = array('Yes', 'True');
72
73
    /**
74
     * List of responses to be interpreted as "No"
75
     *
76
     * @var Array|string
77
     */
78
    protected $responsesValidAsNo = array('No', 'False');
79
80
    /**
81
     * List of responses to be interpreted as "Both"
82
     *
83
     * @var Array|string
84
     */
85
    protected $responsesValidAsBoth = array('Both');
86
87
    public function getResponsesValidAsYes()
88
    {
89
        return $this->responsesValidAsYes;
90
    }
91
92 1
    public function setResponsesValidAsYes($responsesValidAsYes)
93
    {
94 1
        $this->responsesValidAsYes = $responsesValidAsYes;
95 1
    }
96
97
    public function getResponsesValidAsNo()
98
    {
99
        return $this->responsesValidAsNo;
100
    }
101
102 1
    public function setResponsesValidAsNo($responsesValidAsNo)
103
    {
104 1
        $this->responsesValidAsNo = $responsesValidAsNo;
105 1
    }
106
107
    public function getResponsesValidAsBoth()
108
    {
109
        return $this->responsesValidAsBoth;
110
    }
111
112 1
    public function setResponsesValidAsBoth($responsesValidAsBoth)
113
    {
114 1
        $this->responsesValidAsBoth = $responsesValidAsBoth;
115 1
    }
116
117
    /**
118
     * Example body:
119
     *
120
     *  <ol>
121
     *    <li><p>Text of question 1</p>
122
     *      <ol>
123
     *        <li>Text of response 1</li>
124
     *        <li>Text of response 2
125
     *            <p>Optional explanation</p>
126
     *        </li>
127
     *        <li>Text of response 3</li>
128
     *      </ol>
129
     *    </li>
130
     *    <li><p>Text of question 2</p>
131
     *      <ol>
132
     *        <li>Text of response 1</li>
133
     *        ...
134
     *      </ol>
135
     *    </li>
136
     *  </ol>
137
     *
138
     * @param Crawler $crawler
139
     *
140
     * @throws \RuntimeException
141
     * @return array             with activity values
142
     */
143 1
    protected function parseBody(Crawler $crawler)
144
    {
145
        // 'ol' node contains all the questions
146 1
        $olNode = $crawler->filter('div>ol');
147
148 1
        if (0 == $olNode->count()) {
149
            throw new \RuntimeException(
150
                sprintf(
151
                    'No questions found for activity id "%s" of type "%s"' . "\n"
152
                    . $this->quizActivity->getId(),
153
                    $this->quizActivity->getType()
154
                ));
155
        }
156
157
        // collect questions
158 1
        $questionsList = array();
159
160 1
        $qnodes = $olNode->children();
161
162
        // all the 1st level "li" nodes are questions
163 1
        foreach ($qnodes as $qIndex => $qDomNode) {
164 1
            $qnode = new Crawler($qDomNode);
165
166 1
            $question = new QuizActivityQuestion();
167
168
            // select all tags other than "ol"
169 1
            $question->setText(CrawlerTools::getNodeHtml($qnode->filter('* > :not(ol)')));
170
171
            // the 2nd level "li" nodes are responses to that question
172 1
            $responses = $qnode->filter('ol');
173 1
            if (0 == $responses->count()) {
174
                throw new \RuntimeException(
175
                    sprintf(
176
                        'No responses found for activity id "%s", question #%s of type "abc"',
177
                        $this->quizActivity->getId(),
178
                        $qIndex,
179
                        $this->quizActivity->getType()
180
                    ));
181
            }
182
183
            // collect responses and explanations
184 1
            $responsesList = array();
185 1
            $explanationsList = array();
186
187 1
            $rnodes = $responses->children();
188
189 1
            foreach ($rnodes as $rIndex => $rDomNode) {
190 1
                $rnode = new Crawler($rDomNode);
191
192
                // allowed tags inside a response
193 1
                $ps = $rnode->filter('p, ul, ol');
194
195 1
                $explanation = array();
196
197 1
                if ($ps->count() <= 1) {
198
                    // Response only
199 1
                    $responsesList[$rIndex] = $rnode->text();
200 1
                    $explanationsList[$rIndex] = null;
201 1
                } else {
202
                    // we have response (first p) and explanation (rest)
203 1
                    $responsesList[$rIndex] = $ps->eq(0)->text();
204
205
                    // get all the p's or li's
206 1
                    $count = $ps->count();
207 1
                    for ($i = 1; $i < $count; $i++) {
208 1
                        $nodeName = CrawlerTools::getNodeName($ps->eq($i));
209 1
                        if ('p' == $nodeName) {
210
                            // can contain HTML
211 1
                            $explanation[] = CrawlerTools::getNodeHtml($ps->eq($i));
212 1
                        } else {
213
                            // ul or ol, so take its li's
214
                            $liNodes = $ps->eq($i)->children();
215
                            $explanation[] = '<' . $nodeName . '>';
216
                            foreach ($liNodes as $liDomNode) {
217
                                $liNode = new Crawler($liDomNode);
218
219
                                // can contain HTML
220
                                $explanation[] = '<li>' . CrawlerTools::getNodeHtml($liNode) . '</li>';
221
                            }
222
                            $explanation[] = '</' . $nodeName . '>';
223
                        }
224 1
                    }
225
226
                    // Join everything
227 1
                    $explString = implode('', $explanation);
228
229 1
                    $explanationsList[$rIndex] = $explString;
230
                }
231
232 1
                if ($rnode->filter('strong')->count() > 0) {
233 1
                    $question->setSolution($rIndex);
234 1
                }
235 1
            }
236
237 1
            $question->setResponses($responsesList);
238 1
            $question->setExplanations($explanationsList);
239
240 1
            $questionsList[] = $question;
241 1
        }
242
243 1
        $this->quizActivity->setQuestions($questionsList);
244
245
        // Type ABC by default
246 1
        $this->quizActivity->setType(QuizActivity::QUIZ_ACTIVITY_TYPE_ABC);
247
248
        // Look if an ABC activity can be transformed bo YNB
249 1
        $this->transformAbcToYnb();
250 1
    }
251
252
    /**
253
     * Look if a ABC activity can be treated as a YNB activity.
254
     * - All responses for all questions must be the same.
255
     * - All responses for each question must be "True", "False" and (opt.) "Both"
256
     *   or equivalent values.
257
     */
258 1
    protected function transformAbcToYnb()
259
    {
260 1
        if ($this->quizActivity->getType() != QuizActivity::QUIZ_ACTIVITY_TYPE_ABC) {
261
            return;
262
        }
263
264
        // Look for each group of responses
265 1
        $responses = array();
266
267 1
        foreach ($this->quizActivity->getQuestions() as $question) {
268
            /** @var $question QuizActivityQuestion */
269 1
            $responsesClean = array();
270 1
            foreach ($question->getResponses() as $response) {
271
                // Remove ending dot if any
272 1
                $response = trim($response);
273 1
                $responsesClean[] = ('.' == substr($response, -1)) ? substr($response, 0, -1) : $response;
274 1
            }
275
276 1
            if (!$responses) {
277
                // the first set of responses is used as model
278 1
                $responses = $responsesClean;
279 1
            } else {
280
                // look if current set of responses is equal to the saved model
281 1
                if (array_merge(array_diff($responses, $responsesClean), array_diff($responsesClean, $responses))) {
282
                    // Different, cannot continue
283
                    return;
284
                }
285
            }
286
287
            // Look for Yes/No/Both type
288 1
            if (!$this->checkYNBTypeResponses($responsesClean)) {
289 1
                return;
290
            }
291 1
        }
292
293
        // OK for YNB type
294 1
        $this->quizActivity->setType(QuizActivity::QUIZ_ACTIVITY_TYPE_YNB);
295 1
    }
296
297
    /**
298
     * Check if a set of responses can be considered as a "Yes/No/Both" set.
299
     *
300
     * @param array $responses The responses to be checked
301
     *
302
     * @return boolean
303
     */
304 1
    protected function checkYNBTypeResponses(array $responses)
305
    {
306 1
        if (count($responses) > 3) {
307
            return false;
308
        }
309
310
        // Check that each response is a valid one
311 1
        foreach ($responses as $response) {
312 1
            $resp = strtolower($response);
313
            // note the case insensitive search in the arrays
314 1
            if (!in_array($resp, array_map('strtolower', $this->responsesValidAsYes)) &&
315 1
                !in_array($resp, array_map('strtolower', $this->responsesValidAsNo)) &&
316 1
                !in_array($resp, array_map('strtolower', $this->responsesValidAsBoth))
317 1
            ) {
318 1
                return false;
319
            }
320 1
        }
321
322 1
        return true;
323
    }
324
}
325