Passed
Push — 1.11.x ( bce6cd...c146d9 )
by Angel Fernando Quiroz
12:25
created

main/gradebook/lib/be/exerciselink.class.php (1 issue)

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
/**
6
 * Class ExerciseLink
7
 * Defines a gradebook ExerciseLink object.
8
 *
9
 * @author Bert Steppé
10
 */
11
class ExerciseLink extends AbstractLink
12
{
13
    // This variable is used in the WSGetGradebookUserItemScore service, to check base course tests.
14
    public $checkBaseExercises = false;
15
    private $course_info;
16
    private $exercise_table;
17
    private $exercise_data = [];
18
    private $is_hp;
19
20
    /**
21
     * @param int $hp
22
     */
23
    public function __construct($hp = 0)
24
    {
25
        parent::__construct();
26
        $this->set_type(LINK_EXERCISE);
27
        $this->is_hp = $hp;
28
        if (1 == $this->is_hp) {
29
            $this->set_type(LINK_HOTPOTATOES);
30
        }
31
    }
32
33
    /**
34
     * Generate an array of all exercises available.
35
     *
36
     * @param bool $getOnlyHotPotatoes
37
     *
38
     * @return array 2-dimensional array - every element contains 2 subelements (id, name)
39
     */
40
    public function get_all_links($getOnlyHotPotatoes = false)
41
    {
42
        $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
43
        $tableItemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
44
        $exerciseTable = $this->get_exercise_table();
45
        $lpItemTable = Database::get_course_table(TABLE_LP_ITEM);
46
        $lpTable = Database::get_course_table(TABLE_LP_MAIN);
47
48
        $documentPath = api_get_path(SYS_COURSE_PATH).$this->course_code.'/document';
49
        if (empty($this->course_code)) {
50
            return [];
51
        }
52
        $sessionId = $this->get_session_id();
53
        if (empty($sessionId)) {
54
            $session_condition = api_get_session_condition(0, true, false, 'e.session_id');
55
        } else {
56
            $session_condition = api_get_session_condition($sessionId, true, true, 'e.session_id');
57
        }
58
59
        // @todo
60
        $uploadPath = null;
61
        $courseId = $this->course_id;
62
63
        $sql = "SELECT iid, title FROM $exerciseTable e
64
				WHERE
65
				    c_id = $courseId AND
66
				    active = 1
67
				    $session_condition ";
68
69
        $sqlLp = "SELECT e.iid, e.title, lp.name lp_name
70
                  FROM $exerciseTable e
71
                  INNER JOIN $lpItemTable i
72
                  ON (e.c_id = i.c_id AND e.iid = i.path)
73
                  INNER JOIN $lpTable lp
74
                  ON (lp.c_id = e.c_id AND lp.id = i.lp_id)
75
				  WHERE
76
				    e.c_id = $courseId AND
77
				    active = 0 AND
78
				    item_type = 'quiz'
79
				    $session_condition";
80
81
        $sql2 = "SELECT d.path as path, d.comment as comment, ip.visibility as visibility, d.id
82
                FROM $TBL_DOCUMENT d
83
                INNER JOIN $tableItemProperty ip
84
                ON (d.id = ip.ref AND d.c_id = ip.c_id)
85
                WHERE
86
                    d.c_id = $courseId AND
87
                    ip.c_id = $courseId AND
88
                    ip.tool = '".TOOL_DOCUMENT."' AND
89
                    (d.path LIKE '%htm%') AND
90
                    (d.path LIKE '%HotPotatoes_files%') AND
91
                    d.path  LIKE '".Database::escape_string($uploadPath.'/%/%')."' AND
92
                    ip.visibility = '1'
93
                ";
94
95
        require_once api_get_path(SYS_CODE_PATH).'exercise/hotpotatoes.lib.php';
96
97
        $exerciseInLP = [];
98
        if (!$this->is_hp) {
99
            $result = Database::query($sql);
100
            $resultLp = Database::query($sqlLp);
101
            $exerciseInLP = Database::store_result($resultLp);
102
        } else {
103
            $result2 = Database::query($sql2);
104
        }
105
106
        $cats = [];
107
        $exerciseList = [];
108
        if (isset($result)) {
109
            if (Database::num_rows($result) > 0) {
110
                while ($data = Database::fetch_array($result)) {
111
                    $cats[] = [$data['iid'], $data['title']];
112
                    $exerciseList[] = $data;
113
                }
114
            }
115
        }
116
        $hotPotatoes = [];
117
        if (isset($result2)) {
118
            if (Database::num_rows($result2) > 0) {
119
                while ($row = Database::fetch_array($result2)) {
120
                    $attribute['path'][] = $row['path'];
121
                    $attribute['visibility'][] = $row['visibility'];
122
                    $attribute['comment'][] = $row['comment'];
123
                    $attribute['id'] = $row['id'];
124
125
                    if (isset($attribute['path']) && is_array($attribute['path'])) {
126
                        foreach ($attribute['path'] as $path) {
127
                            $title = GetQuizName($path, $documentPath);
128
                            if ($title == '') {
129
                                $title = basename($path);
130
                            }
131
                            $element = [$attribute['id'], $title.'(HP)'];
132
                            $cats[] = $element;
133
                            $hotPotatoes[] = $element;
134
                        }
135
                    }
136
                }
137
            }
138
        }
139
140
        if ($getOnlyHotPotatoes) {
141
            return $hotPotatoes;
142
        }
143
144
        if (!empty($exerciseInLP)) {
145
            $allExercises = array_column($exerciseList, 'iid');
146
147
            foreach ($exerciseInLP as $exercise) {
148
                if (in_array($exercise['iid'], $allExercises)) {
149
                    continue;
150
                }
151
                $allExercises[] = $exercise['iid'];
152
                //$lpName = strip_tags($exercise['lp_name']);
153
                /*$cats[] = [
154
                    $exercise['iid'],
155
                    strip_tags(Exercise::get_formated_title_variable($exercise['title'])).
156
                    ' ('.get_lang('ToolLearnpath').' - '.$lpName.')',
157
                ];*/
158
                $cats[] = [
159
                    $exercise['iid'],
160
                    strip_tags(Exercise::get_formated_title_variable($exercise['title'])),
161
                ];
162
            }
163
        }
164
165
        return $cats;
166
    }
167
168
    /**
169
     * Has anyone done this exercise yet ?
170
     */
171
    public function has_results()
172
    {
173
        $tbl_stats = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
174
        $sessionId = $this->get_session_id();
175
        $course_id = api_get_course_int_id($this->get_course_code());
176
        $sql = "SELECT count(exe_id) AS number
177
                FROM $tbl_stats
178
                WHERE
179
                    session_id = $sessionId AND
180
                    c_id = $course_id AND
181
                    exe_exo_id = ".$this->get_ref_id();
182
        $result = Database::query($sql);
183
        $number = Database::fetch_row($result);
184
185
        return $number[0] != 0;
186
    }
187
188
    /**
189
     * Get the score of this exercise. Only the first attempts are taken into account.
190
     *
191
     * @param int    $stud_id student id (default: all students who have results -
192
     *                        then the average is returned)
193
     * @param string $type
194
     *
195
     * @return array (score, max) if student is given
196
     *               array (sum of scores, number of scores) otherwise
197
     *               or null if no scores available
198
     */
199
    public function calc_score($stud_id = null, $type = null)
200
    {
201
        $allowStats = api_get_configuration_value('allow_gradebook_stats');
202
        if ($allowStats) {
203
            $link = $this->entity;
204
            if (!empty($link)) {
205
                $weight = $link->getScoreWeight();
206
207
                switch ($type) {
208
                    case 'best':
209
                        $bestResult = $link->getBestScore();
210
211
                        return [$bestResult, $weight];
212
213
                        break;
214
                    case 'average':
215
                        $count = count($link->getUserScoreList());
216
                        //$count = count($this->getStudentList());
217
                        if (empty($count)) {
218
                            return [0, $weight];
219
                        }
220
                        $sumResult = array_sum($link->getUserScoreList());
221
222
                        return [$sumResult / $count, $weight];
223
224
                        break;
225
                    case 'ranking':
226
                        return [null, null];
227
                        break;
228
                    default:
229
                        if (!empty($stud_id)) {
230
                            $scoreList = $link->getUserScoreList();
231
                            $result = [0, $weight];
232
                            if (isset($scoreList[$stud_id])) {
233
                                $result = [$scoreList[$stud_id], $weight];
234
                            }
235
236
                            return $result;
237
                        } else {
238
                            $studentCount = count($this->getStudentList());
239
                            $sumResult = array_sum($link->getUserScoreList());
240
                            $result = [$sumResult, $studentCount];
241
                        }
242
243
                        return $result;
244
                        break;
245
                }
246
            }
247
        }
248
249
        $tblStats = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
250
        $tblHp = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTPOTATOES);
251
        $tblDoc = Database::get_course_table(TABLE_DOCUMENT);
252
253
        /* the following query should be similar (in conditions) to the one used
254
        in exercise/exercise.php, look for note-query-exe-results marker*/
255
        $sessionId = $this->get_session_id();
256
        $courseId = $this->getCourseId();
257
        $exerciseData = $this->get_exercise_data();
258
        $exerciseId = isset($exerciseData['iid']) ? (int) $exerciseData['iid'] : 0;
259
        $stud_id = (int) $stud_id;
260
261
        if (empty($exerciseId)) {
262
            return null;
263
        }
264
265
        $key = 'exercise_link_id:'.
266
            $this->get_id().
267
            'exerciseId:'.$exerciseId.'student:'.$stud_id.'session:'.$sessionId.'courseId:'.$courseId.'type:'.$type;
268
269
        $useCache = api_get_configuration_value('gradebook_use_apcu_cache');
270
        $cacheAvailable = api_get_configuration_value('apc') && $useCache;
271
        $cacheDriver = null;
272
        if ($cacheAvailable) {
273
            $cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
274
            if ($cacheDriver->contains($key)) {
275
                return $cacheDriver->fetch($key);
276
            }
277
        }
278
279
        $exercise = new Exercise($courseId);
280
        $exercise->read($exerciseId);
281
282
        if (!$this->is_hp) {
283
            if (false == $exercise->exercise_was_added_in_lp) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
284
                $sql = "SELECT * FROM $tblStats
285
                        WHERE
286
                            exe_exo_id = $exerciseId AND
287
                            orig_lp_id = 0 AND
288
                            orig_lp_item_id = 0 AND
289
                            status <> 'incomplete' AND
290
                            session_id = $sessionId AND
291
                            c_id = $courseId
292
                        ";
293
            } else {
294
                $lpCondition = null;
295
                if (!empty($exercise->lpList)) {
296
                    //$lpId = $exercise->getLpBySession($sessionId);
297
                    $lpList = [];
298
                    foreach ($exercise->lpList as $lpData) {
299
                        if ($this->checkBaseExercises) {
300
                            if ((int) $lpData['session_id'] == 0) {
301
                                $lpList[] = $lpData['lp_id'];
302
                            }
303
                        } else {
304
                            if ((int) $lpData['session_id'] == $sessionId) {
305
                                $lpList[] = $lpData['lp_id'];
306
                            }
307
                        }
308
                    }
309
310
                    if (empty($lpList) && !empty($sessionId)) {
311
                        // Check also if an LP was added in the base course.
312
                        foreach ($exercise->lpList as $lpData) {
313
                            if ((int) $lpData['session_id'] == 0) {
314
                                $lpList[] = $lpData['lp_id'];
315
                            }
316
                        }
317
                    }
318
319
                    $lpCondition = ' (orig_lp_id = 0 OR (orig_lp_id IN ("'.implode('", "', $lpList).'"))) AND ';
320
                }
321
322
                $sql = "SELECT *
323
                        FROM $tblStats
324
                        WHERE
325
                            exe_exo_id = $exerciseId AND
326
                            $lpCondition
327
                            status <> 'incomplete' AND
328
                            session_id = $sessionId AND
329
                            c_id = $courseId ";
330
            }
331
332
            if (!empty($stud_id) && 'ranking' !== $type) {
333
                $sql .= " AND exe_user_id = $stud_id ";
334
            }
335
            $sql .= ' ORDER BY exe_id DESC ';
336
        } else {
337
            $sql = "SELECT * FROM $tblHp hp
338
                    INNER JOIN $tblDoc doc
339
                    ON (hp.exe_name = doc.path AND doc.c_id = hp.c_id)
340
                    WHERE
341
                        hp.c_id = $courseId AND
342
                        doc.id = $exerciseId";
343
344
            if (!empty($stud_id)) {
345
                $sql .= " AND hp.exe_user_id = $stud_id ";
346
            }
347
        }
348
349
        $scores = Database::query($sql);
350
351
        if (isset($stud_id) && empty($type)) {
352
            // for 1 student
353
            if ($data = Database::fetch_array($scores, 'ASSOC')) {
354
                $attempts = Database::query($sql);
355
                $counter = 0;
356
                while ($attempt = Database::fetch_array($attempts)) {
357
                    $counter++;
358
                }
359
                $result = [$data['exe_result'], $data['exe_weighting'], $data['exe_date'], $counter];
360
                if ($cacheAvailable) {
361
                    $cacheDriver->save($key, $result);
362
                }
363
364
                return $result;
365
            } else {
366
                if ($cacheAvailable) {
367
                    $cacheDriver->save($key, null);
368
                }
369
370
                return null;
371
            }
372
        } else {
373
            // all students -> get average
374
            // normal way of getting the info
375
            $students = []; // user list, needed to make sure we only
376
            // take first attempts into account
377
            $student_count = 0;
378
            $sum = 0;
379
            $bestResult = 0;
380
            $weight = 0;
381
            $sumResult = 0;
382
            $studentList = $this->getStudentList();
383
            $studentIdList = [];
384
            if (!empty($studentList)) {
385
                $studentIdList = array_column($studentList, 'user_id');
386
            }
387
388
            while ($data = Database::fetch_array($scores, 'ASSOC')) {
389
                // Only take into account users in the current student list.
390
                if (!empty($studentIdList)) {
391
                    if (!in_array($data['exe_user_id'], $studentIdList)) {
392
                        continue;
393
                    }
394
                }
395
396
                if (!isset($students[$data['exe_user_id']])) {
397
                    if ($data['exe_weighting'] != 0) {
398
                        $students[$data['exe_user_id']] = $data['exe_result'];
399
                        $student_count++;
400
                        if ($data['exe_result'] > $bestResult) {
401
                            $bestResult = $data['exe_result'];
402
                        }
403
                        $sum += $data['exe_result'] / $data['exe_weighting'];
404
                        $sumResult += $data['exe_result'];
405
                        $weight = $data['exe_weighting'];
406
                    }
407
                }
408
            }
409
410
            if ($student_count == 0) {
411
                if ($cacheAvailable) {
412
                    $cacheDriver->save($key, null);
413
                }
414
415
                return null;
416
            } else {
417
                switch ($type) {
418
                    case 'best':
419
                        $result = [$bestResult, $weight];
420
                        if ($cacheAvailable) {
421
                            $cacheDriver->save($key, $result);
422
                        }
423
424
                        return $result;
425
                        break;
426
                    case 'average':
427
                        $count = count($this->getStudentList());
428
                        if (empty($count)) {
429
                            $result = [0, $weight];
430
                            if ($cacheAvailable) {
431
                                $cacheDriver->save($key, $result);
432
                            }
433
434
                            return $result;
435
                        }
436
437
                        $result = [$sumResult / $count, $weight];
438
439
                        if ($cacheAvailable) {
440
                            $cacheDriver->save($key, $result);
441
                        }
442
443
                        return $result;
444
                        break;
445
                    case 'ranking':
446
                        $ranking = AbstractLink::getCurrentUserRanking($stud_id, $students);
447
                        if ($cacheAvailable) {
448
                            $cacheDriver->save($key, $ranking);
449
                        }
450
451
                        return $ranking;
452
                        break;
453
                    default:
454
                        $result = [$sum, $student_count];
455
                        if ($cacheAvailable) {
456
                            $cacheDriver->save($key, $result);
457
                        }
458
459
                        return $result;
460
                        break;
461
                }
462
            }
463
        }
464
    }
465
466
    /**
467
     * Get URL where to go to if the user clicks on the link.
468
     * First we go to exercise_jump.php and then to the result page.
469
     * Check this php file for more info.
470
     */
471
    public function get_link()
472
    {
473
        $sessionId = $this->get_session_id();
474
        $data = $this->get_exercise_data();
475
        $exerciseId = $data['iid'];
476
        $path = isset($data['path']) ? $data['path'] : '';
477
478
        return api_get_path(WEB_CODE_PATH).'gradebook/exercise_jump.php?'
479
            .http_build_query(
480
                [
481
                    'path' => $path,
482
                    'session_id' => $sessionId,
483
                    'cidReq' => $this->get_course_code(),
484
                    'gradebook' => 'view',
485
                    'exerciseId' => $exerciseId,
486
                    'type' => $this->get_type(),
487
                ]
488
            );
489
    }
490
491
    /**
492
     * Get name to display: same as exercise title.
493
     */
494
    public function get_name()
495
    {
496
        $documentPath = api_get_path(SYS_COURSE_PATH).$this->course_code.'/document';
497
        require_once api_get_path(SYS_CODE_PATH).'exercise/hotpotatoes.lib.php';
498
        $data = $this->get_exercise_data();
499
500
        if ($this->is_hp == 1) {
501
            if (isset($data['path'])) {
502
                $title = GetQuizName($data['path'], $documentPath);
503
                if ($title == '') {
504
                    $title = basename($data['path']);
505
                }
506
507
                return $title;
508
            }
509
        }
510
511
        return strip_tags(Exercise::get_formated_title_variable($data['title']));
512
    }
513
514
    public function getLpListToString()
515
    {
516
        $data = $this->get_exercise_data();
517
        $lpList = Exercise::getLpListFromExercise($data['iid'], $this->getCourseId());
518
        $lpListToString = '';
519
        if (!empty($lpList)) {
520
            foreach ($lpList as &$list) {
521
                $list['name'] = Display::label($list['name'], 'warning');
522
            }
523
            $lpListToString = implode('&nbsp;', array_column($lpList, 'name'));
524
        }
525
526
        return $lpListToString;
527
    }
528
529
    /**
530
     * Get description to display: same as exercise description.
531
     */
532
    public function get_description()
533
    {
534
        $data = $this->get_exercise_data();
535
536
        return isset($data['description']) ? $data['description'] : null;
537
    }
538
539
    /**
540
     * Check if this still links to an exercise.
541
     */
542
    public function is_valid_link()
543
    {
544
        $exerciseData = $this->get_exercise_data();
545
546
        return !empty($exerciseData);
547
    }
548
549
    /**
550
     * @return string
551
     */
552
    public function get_type_name()
553
    {
554
        if ($this->is_hp == 1) {
555
            return 'HotPotatoes';
556
        }
557
558
        return get_lang('Quiz');
559
    }
560
561
    public function needs_name_and_description()
562
    {
563
        return false;
564
    }
565
566
    public function needs_max()
567
    {
568
        return false;
569
    }
570
571
    public function needs_results()
572
    {
573
        return false;
574
    }
575
576
    public function is_allowed_to_change_name()
577
    {
578
        return false;
579
    }
580
581
    /**
582
     * @return string
583
     */
584
    public function get_icon_name()
585
    {
586
        return 'exercise';
587
    }
588
589
    /**
590
     * @param bool $hp
591
     */
592
    public function setHp($hp)
593
    {
594
        $this->hp = $hp;
595
    }
596
597
    public function getBestScore()
598
    {
599
        return $this->getStats('best');
600
    }
601
602
    public function getStats($type)
603
    {
604
        switch ($type) {
605
            case 'best':
606
                break;
607
        }
608
    }
609
610
    /**
611
     * Lazy load function to get the database contents of this exercise.
612
     */
613
    public function get_exercise_data()
614
    {
615
        $tableItemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
616
        if ($this->is_hp == 1) {
617
            $table = Database::get_course_table(TABLE_DOCUMENT);
618
        } else {
619
            $table = Database::get_course_table(TABLE_QUIZ_TEST);
620
        }
621
622
        $exerciseId = $this->get_ref_id();
623
624
        if (empty($this->exercise_data)) {
625
            if ($this->is_hp == 1) {
626
                $sql = "SELECT * FROM $table ex
627
                    INNER JOIN $tableItemProperty ip
628
                    ON (ip.ref = ex.id AND ip.c_id = ex.c_id)
629
                    WHERE
630
                        ip.c_id = $this->course_id AND
631
                        ex.c_id = $this->course_id AND
632
                        ip.ref = $exerciseId AND
633
                        ip.tool = '".TOOL_DOCUMENT."' AND
634
                        ex.path LIKE '%htm%' AND
635
                        ex.path LIKE '%HotPotatoes_files%' AND
636
                        ip.visibility = 1";
637
                $result = Database::query($sql);
638
                $this->exercise_data = Database::fetch_array($result);
639
            } else {
640
                // Try with iid
641
                $sql = "SELECT * FROM $table
642
                    WHERE
643
                        iid = $exerciseId";
644
                $result = Database::query($sql);
645
                $rows = Database::num_rows($result);
646
647
                if (!empty($rows)) {
648
                    $this->exercise_data = Database::fetch_array($result);
649
                } else {
650
                    // Try wit id
651
                    $sql = "SELECT * FROM $table
652
                        WHERE
653
                            iid = $exerciseId";
654
                    $result = Database::query($sql);
655
                    $this->exercise_data = Database::fetch_array($result);
656
                }
657
            }
658
        }
659
660
        if (empty($this->exercise_data)) {
661
            return false;
662
        }
663
664
        return $this->exercise_data;
665
    }
666
667
    /**
668
     * Lazy load function to get the database table of the exercise.
669
     */
670
    private function get_exercise_table()
671
    {
672
        $this->exercise_table = Database::get_course_table(TABLE_QUIZ_TEST);
673
674
        return $this->exercise_table;
675
    }
676
}
677