Passed
Push — master ( 78cd0a...f41061 )
by Julito
10:08
created

ExerciseLink::getStats()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
/**
5
 * Class ExerciseLink
6
 * Defines a gradebook ExerciseLink object.
7
 *
8
 * @author Bert Steppé
9
 *
10
 * @package chamilo.gradebook
11
 */
12
class ExerciseLink extends AbstractLink
13
{
14
    private $course_info = null;
0 ignored issues
show
introduced by
The private property $course_info is not used, and could be removed.
Loading history...
15
    private $exercise_table = null;
16
    private $exercise_data = [];
17
    private $is_hp;
18
19
    /**
20
     * @param int $hp
21
     */
22
    public function __construct($hp = 0)
23
    {
24
        parent::__construct();
25
        $this->set_type(LINK_EXERCISE);
26
        $this->is_hp = $hp;
27
        if ($this->is_hp == 1) {
28
            $this->set_type(LINK_HOTPOTATOES);
29
        }
30
    }
31
32
    /**
33
     * Generate an array of all exercises available.
34
     *
35
     * @param bool $getOnlyHotPotatoes
36
     *
37
     * @return array 2-dimensional array - every element contains 2 subelements (id, name)
38
     */
39
    public function get_all_links($getOnlyHotPotatoes = false)
40
    {
41
        $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
42
        $tableItemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
43
        $exerciseTable = $this->get_exercise_table();
44
        $lpItemTable = Database::get_course_table(TABLE_LP_ITEM);
45
46
        $documentPath = api_get_path(SYS_COURSE_PATH).$this->course_code."/document";
47
        if (empty($this->course_code)) {
48
            return [];
49
        }
50
        $sessionId = api_get_session_id();
51
        if (empty($sessionId)) {
52
            $session_condition = api_get_session_condition(0, true);
53
        } else {
54
            $session_condition = api_get_session_condition($sessionId, true, true);
55
        }
56
57
        // @todo
58
        $uploadPath = null;
59
60
        $sql = 'SELECT id,title FROM '.$exerciseTable.'
61
				WHERE c_id = '.$this->course_id.' AND active=1  '.$session_condition;
62
63
        $sqlLp = "SELECT e.id, e.title 
64
                  FROM $exerciseTable e 
65
                  INNER JOIN $lpItemTable i
66
                  ON (e.c_id = i.c_id AND e.id = i.path)
67
				  WHERE 
68
				    e.c_id = $this->course_id AND 
69
				    active = 0 AND 
70
				    item_type = 'quiz'
71
				  $session_condition";
72
73
        $sql2 = "SELECT d.path as path, d.comment as comment, ip.visibility as visibility, d.id
74
                FROM $TBL_DOCUMENT d 
75
                INNER JOIN $tableItemProperty ip
76
                ON (d.id = ip.ref AND d.c_id = ip.c_id)
77
                WHERE
78
                    d.c_id = $this->course_id AND
79
                    ip.c_id = $this->course_id AND                
80
                    ip.tool = '".TOOL_DOCUMENT."' AND
81
                    (d.path LIKE '%htm%') AND 
82
                    (d.path LIKE '%HotPotatoes_files%') AND
83
                    d.path  LIKE '".Database::escape_string($uploadPath.'/%/%')."' AND
84
                    ip.visibility = '1'
85
                ";
86
87
        require_once api_get_path(SYS_CODE_PATH).'exercise/hotpotatoes.lib.php';
88
        $exerciseInLP = [];
89
90
        if (!$this->is_hp) {
91
            $result = Database::query($sql);
92
            $resultLp = Database::query($sqlLp);
93
            $exerciseInLP = Database::store_result($resultLp);
94
        } else {
95
            $result2 = Database::query($sql2);
96
        }
97
98
        $cats = [];
99
        if (isset($result)) {
100
            if (Database::num_rows($result) > 0) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.
Loading history...
101
                while ($data = Database::fetch_array($result)) {
102
                    $cats[] = [$data['id'], $data['title']];
103
                }
104
            }
105
        }
106
        $hotPotatoes = [];
107
        if (isset($result2)) {
108
            if (Database::num_rows($result2) > 0) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result2 does not seem to be defined for all execution paths leading up to this point.
Loading history...
109
                while ($row = Database::fetch_array($result2)) {
110
                    $attribute['path'][] = $row['path'];
111
                    $attribute['visibility'][] = $row['visibility'];
112
                    $attribute['comment'][] = $row['comment'];
113
                    $attribute['id'] = $row['id'];
114
115
                    if (isset($attribute['path']) && is_array($attribute['path'])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $attribute does not seem to be defined for all execution paths leading up to this point.
Loading history...
116
                        foreach ($attribute['path'] as $path) {
117
                            $title = GetQuizName($path, $documentPath);
118
                            if ($title == '') {
119
                                $title = basename($path);
120
                            }
121
                            $element = [$attribute['id'], $title.'(HP)'];
122
                            $cats[] = $element;
123
                            $hotPotatoes[] = $element;
124
                        }
125
                    }
126
                }
127
            }
128
        }
129
130
        if ($getOnlyHotPotatoes) {
131
            return $hotPotatoes;
132
        }
133
134
        if (!empty($exerciseInLP)) {
135
            foreach ($exerciseInLP as $exercise) {
136
                $cats[] = [
137
                    $exercise['id'],
138
                    $exercise['title'].' ('.get_lang('ToolLearnpath').')',
139
                ];
140
            }
141
        }
142
143
        return $cats;
144
    }
145
146
    /**
147
     * Has anyone done this exercise yet ?
148
     */
149
    public function has_results()
150
    {
151
        $tbl_stats = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
152
        $sessionId = api_get_session_id();
153
        $course_id = api_get_course_int_id($this->get_course_code());
154
        $sql = "SELECT count(exe_id) AS number 
155
                FROM $tbl_stats
156
                WHERE
157
                    session_id = $sessionId AND
158
                    c_id = $course_id AND
159
                    exe_exo_id = ".$this->get_ref_id();
160
        $result = Database::query($sql);
161
        $number = Database::fetch_row($result);
162
163
        return $number[0] != 0;
164
    }
165
166
    /**
167
     * Get the score of this exercise. Only the first attempts are taken into account.
168
     *
169
     * @param int    $stud_id student id (default: all students who have results -
170
     *                        then the average is returned)
171
     * @param string $type
172
     *
173
     * @return array (score, max) if student is given
174
     *               array (sum of scores, number of scores) otherwise
175
     *               or null if no scores available
176
     */
177
    public function calc_score($stud_id = null, $type = null)
178
    {
179
        $allowStats = api_get_configuration_value('allow_gradebook_stats');
180
181
        if ($allowStats) {
182
            $link = $this->entity;
183
            if (!empty($link)) {
184
                $weight = $link->getScoreWeight();
0 ignored issues
show
Bug introduced by
The method getScoreWeight() does not exist on Chamilo\CoreBundle\Entity\GradebookLink. ( Ignorable by Annotation )

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

184
                /** @scrutinizer ignore-call */ 
185
                $weight = $link->getScoreWeight();

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...
185
186
                switch ($type) {
187
                    case 'best':
188
                        $bestResult = $link->getBestScore();
0 ignored issues
show
Bug introduced by
The method getBestScore() does not exist on Chamilo\CoreBundle\Entity\GradebookLink. ( Ignorable by Annotation )

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

188
                        /** @scrutinizer ignore-call */ 
189
                        $bestResult = $link->getBestScore();

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...
189
                        $result = [$bestResult, $weight];
190
191
                        return $result;
192
                        break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
193
                    case 'average':
194
                        $count = count($this->getStudentList());
195
                        if (empty($count)) {
196
                            $result = [0, $weight];
197
198
                            return $result;
199
                        }
200
                        $sumResult = array_sum($link->getUserScoreList());
0 ignored issues
show
Bug introduced by
The method getUserScoreList() does not exist on Chamilo\CoreBundle\Entity\GradebookLink. ( Ignorable by Annotation )

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

200
                        $sumResult = array_sum($link->/** @scrutinizer ignore-call */ getUserScoreList());

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...
201
                        $result = [$sumResult / $count, $weight];
202
203
                        return $result;
204
                        break;
205
                    case 'ranking':
206
                        return '';
0 ignored issues
show
Bug Best Practice introduced by
The expression return '' returns the type string which is incompatible with the documented return type array.
Loading history...
207
                        /*
208
                        $newList = [];
209
                        $ranking = AbstractLink::getCurrentUserRanking($stud_id, $link->getUserScoreList());
210
                        return $ranking;*/
211
                        break;
212
                    default:
213
                        if (!empty($stud_id)) {
214
                            $scoreList = $link->getUserScoreList();
215
                            $result = [0, $weight];
216
                            if (isset($scoreList[$stud_id])) {
217
                                $result = [$scoreList[$stud_id], $weight];
218
                            }
219
220
                            return $result;
221
                        } else {
222
                            $studentCount = count($this->getStudentList());
223
                            $sumResult = array_sum($link->getUserScoreList());
224
                            $result = [$sumResult, $studentCount];
225
                        }
226
227
                        return $result;
228
                        break;
229
                }
230
            }
231
        }
232
233
        $tblStats = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
234
        $tblHp = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTPOTATOES);
235
        $tblDoc = Database::get_course_table(TABLE_DOCUMENT);
236
237
        /* the following query should be similar (in conditions) to the one used
238
        in exercise/exercise.php, look for note-query-exe-results marker*/
239
        $sessionId = $this->get_session_id();
240
        $courseId = $this->getCourseId();
241
        $exerciseData = $this->get_exercise_data();
242
243
        $exerciseId = isset($exerciseData['id']) ? $exerciseData['id'] : 0;
244
        $stud_id = (int) $stud_id;
245
246
        if (empty($exerciseId)) {
247
            return null;
248
        }
249
250
        $key = 'exercise_link_id:'.
251
            $this->get_id().
252
            'exerciseId:'.$exerciseId.'student:'.$stud_id.'session:'.$sessionId.'courseId:'.$courseId.'type:'.$type;
253
254
        $useCache = api_get_configuration_value('gradebook_use_apcu_cache');
255
        $cacheAvailable = api_get_configuration_value('apc') && $useCache;
256
        $cacheDriver = null;
257
        if ($cacheAvailable) {
258
            $cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
259
            if ($cacheDriver->contains($key)) {
260
                return $cacheDriver->fetch($key);
261
            }
262
        }
263
264
        $exercise = new Exercise($courseId);
265
        $exercise->read($exerciseData['id']);
266
267
        if (!$this->is_hp) {
268
            if ($exercise->exercise_was_added_in_lp == false) {
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...
269
                $sql = "SELECT * FROM $tblStats
270
                        WHERE
271
                            exe_exo_id = $exerciseId AND
272
                            orig_lp_id = 0 AND
273
                            orig_lp_item_id = 0 AND
274
                            status <> 'incomplete' AND
275
                            session_id = $sessionId AND
276
                            c_id = $courseId
277
                        ";
278
            } else {
279
                $lpId = null;
280
                if (!empty($exercise->lpList)) {
281
                    // Taking only the first LP
282
                    $lpId = current($exercise->lpList);
283
                    $lpId = $lpId['lp_id'];
284
                }
285
286
                $sql = "SELECT * 
287
                        FROM $tblStats
288
                        WHERE
289
                            exe_exo_id = $exerciseId AND
290
                            orig_lp_id = $lpId AND
291
                            status <> 'incomplete' AND
292
                            session_id = $sessionId AND
293
                            c_id = $courseId ";
294
            }
295
296
            if (!empty($stud_id) && $type != 'ranking') {
297
                $sql .= " AND exe_user_id = $stud_id ";
298
            }
299
            $sql .= ' ORDER BY exe_id DESC';
300
        } else {
301
            $sql = "SELECT * FROM $tblHp hp 
302
                    INNER JOIN $tblDoc doc
303
                    ON (hp.exe_name = doc.path AND doc.c_id = hp.c_id)
304
                    WHERE
305
                        hp.c_id = $courseId AND                        
306
                        doc.id = ".$exerciseId;
307
308
            if (!empty($stud_id)) {
309
                $sql .= " AND hp.exe_user_id = $stud_id ";
310
            }
311
        }
312
313
        $scores = Database::query($sql);
314
315
        if (isset($stud_id) && empty($type)) {
316
            // for 1 student
317
            if ($data = Database::fetch_array($scores)) {
318
                $result = [$data['score'], $data['max_score']];
319
                if ($cacheAvailable) {
320
                    $cacheDriver->save($key, $result);
321
                }
322
323
                return $result;
324
            } else {
325
                if ($cacheAvailable) {
326
                    $cacheDriver->save($key, null);
327
                }
328
329
                return null;
330
            }
331
        } else {
332
            // all students -> get average
333
            // normal way of getting the info
334
            $students = []; // user list, needed to make sure we only
335
            // take first attempts into account
336
            $student_count = 0;
337
            $sum = 0;
338
            $bestResult = 0;
339
            $weight = 0;
340
            $sumResult = 0;
341
342
            $studentList = $this->getStudentList();
343
            $studentIdList = [];
344
            if (!empty($studentList)) {
345
                $studentIdList = array_column($studentList, 'user_id');
346
            }
347
348
            while ($data = Database::fetch_array($scores, 'ASSOC')) {
349
                // Only take into account users in the current student list.
350
                if (!empty($studentIdList)) {
351
                    if (!in_array($data['exe_user_id'], $studentIdList)) {
352
                        continue;
353
                    }
354
                }
355
356
                if (!isset($students[$data['exe_user_id']])) {
357
                    if ($data['max_score'] != 0) {
358
                        $students[$data['exe_user_id']] = $data['score'];
359
                        $student_count++;
360
                        if ($data['score'] > $bestResult) {
361
                            $bestResult = $data['score'];
362
                        }
363
                        $sum += $data['score'] / $data['max_score'];
364
                        $sumResult += $data['score'];
365
                        $weight = $data['max_score'];
366
                    }
367
                }
368
            }
369
370
            if ($student_count == 0) {
371
                if ($cacheAvailable) {
372
                    $cacheDriver->save($key, null);
373
                }
374
375
                return null;
376
            } else {
377
                switch ($type) {
378
                    case 'best':
379
                        $result = [$bestResult, $weight];
380
                        if ($cacheAvailable) {
381
                            $cacheDriver->save($key, $result);
382
                        }
383
384
                        return $result;
385
                        break;
386
                    case 'average':
387
                        $count = count($this->getStudentList());
388
                        if (empty($count)) {
389
                            $result = [0, $weight];
390
                            if ($cacheAvailable) {
391
                                $cacheDriver->save($key, $result);
392
                            }
393
394
                            return $result;
395
                        }
396
397
                        $result = [$sumResult / $count, $weight];
398
399
                        if ($cacheAvailable) {
400
                            $cacheDriver->save($key, $result);
401
                        }
402
403
                        return $result;
404
                        break;
405
                    case 'ranking':
406
                        $ranking = AbstractLink::getCurrentUserRanking($stud_id, $students);
407
                        if ($cacheAvailable) {
408
                            $cacheDriver->save($key, $ranking);
409
                        }
410
411
                        return $ranking;
412
                        break;
413
                    default:
414
                        $result = [$sum, $student_count];
415
                        if ($cacheAvailable) {
416
                            $cacheDriver->save($key, $result);
417
                        }
418
419
                        return $result;
420
                        break;
421
                }
422
            }
423
        }
424
    }
425
426
    /**
427
     * Get URL where to go to if the user clicks on the link.
428
     * First we go to exercise_jump.php and then to the result page.
429
     * Check this php file for more info.
430
     */
431
    public function get_link()
432
    {
433
        // Status student
434
        $user_id = api_get_user_id();
435
        $sessionId = api_get_session_id();
436
        $course_code = $this->get_course_code();
437
        $courseInfo = api_get_course_info($course_code);
438
        $courseId = $courseInfo['real_id'];
439
        $status_user = api_get_status_of_user_in_course($user_id, $courseId);
440
441
        $data = $this->get_exercise_data();
442
        $exerciseId = $data['id'];
443
        $path = isset($data['path']) ? $data['path'] : '';
444
445
        $url = api_get_path(WEB_CODE_PATH).'gradebook/exercise_jump.php?'
446
            .http_build_query(
447
                [
448
                    'path' => $path,
449
                    'session_id' => $sessionId,
450
                    'cidReq' => $this->get_course_code(),
451
                    'gradebook' => 'view',
452
                    'exerciseId' => $exerciseId,
453
                    'type' => $this->get_type(),
454
                ]
455
            );
456
457
        return $url;
458
    }
459
460
    /**
461
     * Get name to display: same as exercise title.
462
     */
463
    public function get_name()
464
    {
465
        $documentPath = api_get_path(SYS_COURSE_PATH).$this->course_code.'/document';
466
        require_once api_get_path(SYS_CODE_PATH).'exercise/hotpotatoes.lib.php';
467
        $data = $this->get_exercise_data();
468
469
        if ($this->is_hp == 1) {
470
            if (isset($data['path'])) {
471
                $title = GetQuizName($data['path'], $documentPath);
472
                if ($title == '') {
473
                    $title = basename($data['path']);
474
                }
475
476
                return $title;
477
            }
478
        }
479
480
        return $data['title'];
481
    }
482
483
    /**
484
     * Get description to display: same as exercise description.
485
     */
486
    public function get_description()
487
    {
488
        $data = $this->get_exercise_data();
489
490
        return isset($data['description']) ? $data['description'] : null;
491
    }
492
493
    /**
494
     * Check if this still links to an exercise.
495
     */
496
    public function is_valid_link()
497
    {
498
        $exerciseData = $this->get_exercise_data();
499
500
        return !empty($exerciseData);
501
    }
502
503
    /**
504
     * @return string
505
     */
506
    public function get_type_name()
507
    {
508
        if ($this->is_hp == 1) {
509
            return 'HotPotatoes';
510
        } else {
511
            return get_lang('Quiz');
512
        }
513
    }
514
515
    public function needs_name_and_description()
516
    {
517
        return false;
518
    }
519
520
    public function needs_max()
521
    {
522
        return false;
523
    }
524
525
    public function needs_results()
526
    {
527
        return false;
528
    }
529
530
    public function is_allowed_to_change_name()
531
    {
532
        return false;
533
    }
534
535
    /**
536
     * @return string
537
     */
538
    public function get_icon_name()
539
    {
540
        return 'exercise';
541
    }
542
543
    /**
544
     * @param bool $hp
545
     */
546
    public function setHp($hp)
547
    {
548
        $this->hp = $hp;
0 ignored issues
show
Bug Best Practice introduced by
The property hp does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
549
    }
550
551
    public function getBestScore()
552
    {
553
        return $this->getStats('best');
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getStats('best') targeting ExerciseLink::getStats() 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...
554
    }
555
556
    public function getStats($type)
557
    {
558
        switch ($type) {
559
            case 'best':
560
561
                break;
562
        }
563
    }
564
565
    /**
566
     * Lazy load function to get the database table of the exercise.
567
     */
568
    private function get_exercise_table()
569
    {
570
        $this->exercise_table = Database::get_course_table(TABLE_QUIZ_TEST);
571
572
        return $this->exercise_table;
573
    }
574
575
    /**
576
     * Lazy load function to get the database contents of this exercise.
577
     */
578
    private function get_exercise_data()
579
    {
580
        $tableItemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
581
        if ($this->is_hp == 1) {
582
            $table = Database::get_course_table(TABLE_DOCUMENT);
583
        } else {
584
            $table = $this->get_exercise_table();
585
        }
586
587
        $exerciseId = $this->get_ref_id();
588
589
        if ($table == '') {
590
            return false;
591
        } elseif (empty($this->exercise_data)) {
592
            if ($this->is_hp == 1) {
593
                $sql = "SELECT * FROM $table ex
594
                        INNER JOIN $tableItemProperty ip
595
                        ON (ip.ref = ex.id AND ip.c_id = ex.c_id)
596
                        WHERE
597
                            ip.c_id = $this->course_id AND
598
                            ex.c_id = $this->course_id AND
599
                            ip.ref = $exerciseId AND
600
                            ip.tool = '".TOOL_DOCUMENT."' AND
601
                            ex.path LIKE '%htm%' AND
602
                            ex.path LIKE '%HotPotatoes_files%' AND
603
                            ip.visibility = 1";
604
                $result = Database::query($sql);
605
                $this->exercise_data = Database::fetch_array($result);
606
            } else {
607
                // Try with iid
608
                $sql = 'SELECT * FROM '.$table.'
609
                        WHERE
610
                            c_id = '.$this->course_id.' AND
611
                            iid = '.$exerciseId;
612
                $result = Database::query($sql);
613
                $rows = Database::num_rows($result);
614
615
                if (!empty($rows)) {
616
                    $this->exercise_data = Database::fetch_array($result);
617
                } else {
618
                    // Try wit id
619
                    $sql = 'SELECT * FROM '.$table.'
620
                            WHERE
621
                                c_id = '.$this->course_id.' AND
622
                                id = '.$exerciseId;
623
                    $result = Database::query($sql);
624
                    $this->exercise_data = Database::fetch_array($result);
625
                }
626
            }
627
        }
628
629
        return $this->exercise_data;
630
    }
631
}
632