Passed
Push — 1.11.x ( 2a32a0...4d82fe )
by Julito
08:58
created

ExerciseLink   F

Complexity

Total Complexity 95

Size/Duplication

Total Lines 646
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 337
c 1
b 0
f 0
dl 0
loc 646
rs 2
wmc 95

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 2
A has_results() 0 15 1
A get_name() 0 18 4
A needs_results() 0 3 1
A needs_name_and_description() 0 3 1
A is_valid_link() 0 5 1
A setHp() 0 3 1
A getLpListToString() 0 13 3
A get_type_name() 0 7 2
A getStats() 0 5 2
A get_link() 0 16 2
A get_exercise_table() 0 5 1
F calc_score() 0 244 44
A needs_max() 0 3 1
A getBestScore() 0 3 1
B get_exercise_data() 0 54 6
D get_all_links() 0 126 18
A get_icon_name() 0 3 1
A get_description() 0 5 2
A is_allowed_to_change_name() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like ExerciseLink often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ExerciseLink, and based on these observations, apply Extract Interface, too.

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
    private $course_info;
0 ignored issues
show
introduced by
The private property $course_info is not used, and could be removed.
Loading history...
14
    private $exercise_table;
15
    private $exercise_data = [];
16
    private $is_hp;
17
18
    /**
19
     * @param int $hp
20
     */
21
    public function __construct($hp = 0)
22
    {
23
        parent::__construct();
24
        $this->set_type(LINK_EXERCISE);
25
        $this->is_hp = $hp;
26
        if (1 == $this->is_hp) {
27
            $this->set_type(LINK_HOTPOTATOES);
28
        }
29
    }
30
31
    /**
32
     * Generate an array of all exercises available.
33
     *
34
     * @param bool $getOnlyHotPotatoes
35
     *
36
     * @return array 2-dimensional array - every element contains 2 subelements (id, name)
37
     */
38
    public function get_all_links($getOnlyHotPotatoes = false)
39
    {
40
        $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
41
        $tableItemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
42
        $exerciseTable = $this->get_exercise_table();
43
        $lpItemTable = Database::get_course_table(TABLE_LP_ITEM);
44
        $lpTable = Database::get_course_table(TABLE_LP_MAIN);
45
46
        $documentPath = api_get_path(SYS_COURSE_PATH).$this->course_code.'/document';
47
        if (empty($this->course_code)) {
48
            return [];
49
        }
50
        $sessionId = $this->get_session_id();
51
        if (empty($sessionId)) {
52
            $session_condition = api_get_session_condition(0, true, false, 'e.session_id');
53
        } else {
54
            $session_condition = api_get_session_condition($sessionId, true, true, 'e.session_id');
55
        }
56
57
        // @todo
58
        $uploadPath = null;
59
        $courseId = $this->course_id;
60
61
        $sql = "SELECT iid, title FROM $exerciseTable e
62
				WHERE
63
				    c_id = $courseId AND
64
				    active = 1
65
				    $session_condition ";
66
67
        $sqlLp = "SELECT e.iid, e.title, lp.name lp_name
68
                  FROM $exerciseTable e
69
                  INNER JOIN $lpItemTable i
70
                  ON (e.c_id = i.c_id AND e.id = i.path)
71
                  INNER JOIN $lpTable lp
72
                  ON (lp.c_id = e.c_id AND lp.id = i.lp_id)
73
				  WHERE
74
				    e.c_id = $courseId AND
75
				    active = 0 AND
76
				    item_type = 'quiz'
77
				    $session_condition";
78
79
        $sql2 = "SELECT d.path as path, d.comment as comment, ip.visibility as visibility, d.id
80
                FROM $TBL_DOCUMENT d
81
                INNER JOIN $tableItemProperty ip
82
                ON (d.id = ip.ref AND d.c_id = ip.c_id)
83
                WHERE
84
                    d.c_id = $courseId AND
85
                    ip.c_id = $courseId AND
86
                    ip.tool = '".TOOL_DOCUMENT."' AND
87
                    (d.path LIKE '%htm%') AND
88
                    (d.path LIKE '%HotPotatoes_files%') AND
89
                    d.path  LIKE '".Database::escape_string($uploadPath.'/%/%')."' AND
90
                    ip.visibility = '1'
91
                ";
92
93
        require_once api_get_path(SYS_CODE_PATH).'exercise/hotpotatoes.lib.php';
94
95
        $exerciseInLP = [];
96
        if (!$this->is_hp) {
97
            $result = Database::query($sql);
98
            $resultLp = Database::query($sqlLp);
99
            $exerciseInLP = Database::store_result($resultLp);
100
        } else {
101
            $result2 = Database::query($sql2);
102
        }
103
104
        $cats = [];
105
        $exerciseList = [];
106
        if (isset($result)) {
107
            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...
108
                while ($data = Database::fetch_array($result)) {
109
                    $cats[] = [$data['iid'], $data['title']];
110
                    $exerciseList[] = $data;
111
                }
112
            }
113
        }
114
        $hotPotatoes = [];
115
        if (isset($result2)) {
116
            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...
117
                while ($row = Database::fetch_array($result2)) {
118
                    $attribute['path'][] = $row['path'];
119
                    $attribute['visibility'][] = $row['visibility'];
120
                    $attribute['comment'][] = $row['comment'];
121
                    $attribute['id'] = $row['id'];
122
123
                    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...
124
                        foreach ($attribute['path'] as $path) {
125
                            $title = GetQuizName($path, $documentPath);
126
                            if ($title == '') {
127
                                $title = basename($path);
128
                            }
129
                            $element = [$attribute['id'], $title.'(HP)'];
130
                            $cats[] = $element;
131
                            $hotPotatoes[] = $element;
132
                        }
133
                    }
134
                }
135
            }
136
        }
137
138
        if ($getOnlyHotPotatoes) {
139
            return $hotPotatoes;
140
        }
141
142
        if (!empty($exerciseInLP)) {
143
            $allExercises = array_column($exerciseList, 'iid');
144
145
            foreach ($exerciseInLP as $exercise) {
146
                if (in_array($exercise['iid'], $allExercises)) {
147
                   continue;
148
                }
149
                $allExercises[] = $exercise['iid'];
150
                //$lpName = strip_tags($exercise['lp_name']);
151
                /*$cats[] = [
152
                    $exercise['iid'],
153
                    strip_tags(Exercise::get_formated_title_variable($exercise['title'])).
154
                    ' ('.get_lang('ToolLearnpath').' - '.$lpName.')',
155
                ];*/
156
                $cats[] = [
157
                    $exercise['iid'],
158
                    strip_tags(Exercise::get_formated_title_variable($exercise['title']))
159
                ];
160
            }
161
        }
162
163
        return $cats;
164
    }
165
166
    /**
167
     * Has anyone done this exercise yet ?
168
     */
169
    public function has_results()
170
    {
171
        $tbl_stats = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
172
        $sessionId = $this->get_session_id();
173
        $course_id = api_get_course_int_id($this->get_course_code());
174
        $sql = "SELECT count(exe_id) AS number
175
                FROM $tbl_stats
176
                WHERE
177
                    session_id = $sessionId AND
178
                    c_id = $course_id AND
179
                    exe_exo_id = ".$this->get_ref_id();
180
        $result = Database::query($sql);
181
        $number = Database::fetch_row($result);
182
183
        return $number[0] != 0;
184
    }
185
186
    /**
187
     * Get the score of this exercise. Only the first attempts are taken into account.
188
     *
189
     * @param int    $stud_id student id (default: all students who have results -
190
     *                        then the average is returned)
191
     * @param string $type
192
     *
193
     * @return array (score, max) if student is given
194
     *               array (sum of scores, number of scores) otherwise
195
     *               or null if no scores available
196
     */
197
    public function calc_score($stud_id = null, $type = null)
198
    {
199
        $allowStats = api_get_configuration_value('allow_gradebook_stats');
200
201
        if ($allowStats) {
202
            $link = $this->entity;
203
            if (!empty($link)) {
204
                $weight = $link->getScoreWeight();
205
206
                switch ($type) {
207
                    case 'best':
208
                        $bestResult = $link->getBestScore();
209
                        $result = [$bestResult, $weight];
210
211
                        return $result;
212
                        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...
213
                    case 'average':
214
                        $count = count($this->getStudentList());
215
                        if (empty($count)) {
216
                            $result = [0, $weight];
217
218
                            return $result;
219
                        }
220
                        $sumResult = array_sum($link->getUserScoreList());
221
                        $result = [$sumResult / $count, $weight];
222
223
                        return $result;
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
259
        $exerciseId = isset($exerciseData['id']) ? (int) $exerciseData['id'] : 0;
260
        $stud_id = (int) $stud_id;
261
262
        if (empty($exerciseId)) {
263
            return null;
264
        }
265
266
        $key = 'exercise_link_id:'.
267
            $this->get_id().
268
            'exerciseId:'.$exerciseId.'student:'.$stud_id.'session:'.$sessionId.'courseId:'.$courseId.'type:'.$type;
269
270
        $useCache = api_get_configuration_value('gradebook_use_apcu_cache');
271
        $cacheAvailable = api_get_configuration_value('apc') && $useCache;
272
        $cacheDriver = null;
273
        if ($cacheAvailable) {
274
            $cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
275
            if ($cacheDriver->contains($key)) {
276
                return $cacheDriver->fetch($key);
277
            }
278
        }
279
280
        $exercise = new Exercise($courseId);
281
        $exercise->read($exerciseId);
282
283
        if (!$this->is_hp) {
284
            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...
285
                $sql = "SELECT * FROM $tblStats
286
                        WHERE
287
                            exe_exo_id = $exerciseId AND
288
                            orig_lp_id = 0 AND
289
                            orig_lp_item_id = 0 AND
290
                            status <> 'incomplete' AND
291
                            session_id = $sessionId AND
292
                            c_id = $courseId
293
                        ";
294
            } else {
295
                $lpId = null;
296
                if (!empty($exercise->lpList)) {
297
                    $lpId = $exercise->getLpBySession($sessionId);
298
                    $lpId = $lpId['lp_id'];
299
                }
300
301
                $sql = "SELECT *
302
                        FROM $tblStats
303
                        WHERE
304
                            exe_exo_id = $exerciseId AND
305
                            orig_lp_id = $lpId AND
306
                            status <> 'incomplete' AND
307
                            session_id = $sessionId AND
308
                            c_id = $courseId ";
309
            }
310
311
            if (!empty($stud_id) && 'ranking' != $type) {
312
                $sql .= " AND exe_user_id = $stud_id ";
313
            }
314
            $sql .= ' ORDER BY exe_id DESC';
315
        } else {
316
            $sql = "SELECT * FROM $tblHp hp
317
                    INNER JOIN $tblDoc doc
318
                    ON (hp.exe_name = doc.path AND doc.c_id = hp.c_id)
319
                    WHERE
320
                        hp.c_id = $courseId AND
321
                        doc.id = $exerciseId";
322
323
            if (!empty($stud_id)) {
324
                $sql .= " AND hp.exe_user_id = $stud_id ";
325
            }
326
        }
327
328
        $scores = Database::query($sql);
329
330
        if (isset($stud_id) && empty($type)) {
331
            // for 1 student
332
            if ($data = Database::fetch_array($scores, 'ASSOC')) {
333
                $attempts = Database::query($sql);
334
                $counter = 0;
335
                while ($attempt = Database::fetch_array($attempts)) {
336
                    $counter++;
337
                }
338
                $result = [$data['exe_result'], $data['exe_weighting'], $data['exe_date'], $counter];
339
                if ($cacheAvailable) {
340
                    $cacheDriver->save($key, $result);
341
                }
342
343
                return $result;
344
            } else {
345
                if ($cacheAvailable) {
346
                    $cacheDriver->save($key, null);
347
                }
348
349
                return null;
350
            }
351
        } else {
352
            // all students -> get average
353
            // normal way of getting the info
354
            $students = []; // user list, needed to make sure we only
355
            // take first attempts into account
356
            $student_count = 0;
357
            $sum = 0;
358
            $bestResult = 0;
359
            $weight = 0;
360
            $sumResult = 0;
361
362
            $studentList = $this->getStudentList();
363
            $studentIdList = [];
364
            if (!empty($studentList)) {
365
                $studentIdList = array_column($studentList, 'user_id');
366
            }
367
368
            while ($data = Database::fetch_array($scores, 'ASSOC')) {
369
                // Only take into account users in the current student list.
370
                if (!empty($studentIdList)) {
371
                    if (!in_array($data['exe_user_id'], $studentIdList)) {
372
                        continue;
373
                    }
374
                }
375
376
                if (!isset($students[$data['exe_user_id']])) {
377
                    if ($data['exe_weighting'] != 0) {
378
                        $students[$data['exe_user_id']] = $data['exe_result'];
379
                        $student_count++;
380
                        if ($data['exe_result'] > $bestResult) {
381
                            $bestResult = $data['exe_result'];
382
                        }
383
                        $sum += $data['exe_result'] / $data['exe_weighting'];
384
                        $sumResult += $data['exe_result'];
385
                        $weight = $data['exe_weighting'];
386
                    }
387
                }
388
            }
389
390
            if ($student_count == 0) {
391
                if ($cacheAvailable) {
392
                    $cacheDriver->save($key, null);
393
                }
394
395
                return null;
396
            } else {
397
                switch ($type) {
398
                    case 'best':
399
                        $result = [$bestResult, $weight];
400
                        if ($cacheAvailable) {
401
                            $cacheDriver->save($key, $result);
402
                        }
403
404
                        return $result;
405
                        break;
406
                    case 'average':
407
                        $count = count($this->getStudentList());
408
                        if (empty($count)) {
409
                            $result = [0, $weight];
410
                            if ($cacheAvailable) {
411
                                $cacheDriver->save($key, $result);
412
                            }
413
414
                            return $result;
415
                        }
416
417
                        $result = [$sumResult / $count, $weight];
418
419
                        if ($cacheAvailable) {
420
                            $cacheDriver->save($key, $result);
421
                        }
422
423
                        return $result;
424
                        break;
425
                    case 'ranking':
426
                        $ranking = AbstractLink::getCurrentUserRanking($stud_id, $students);
427
                        if ($cacheAvailable) {
428
                            $cacheDriver->save($key, $ranking);
429
                        }
430
431
                        return $ranking;
432
                        break;
433
                    default:
434
                        $result = [$sum, $student_count];
435
                        if ($cacheAvailable) {
436
                            $cacheDriver->save($key, $result);
437
                        }
438
439
                        return $result;
440
                        break;
441
                }
442
            }
443
        }
444
    }
445
446
    /**
447
     * Get URL where to go to if the user clicks on the link.
448
     * First we go to exercise_jump.php and then to the result page.
449
     * Check this php file for more info.
450
     */
451
    public function get_link()
452
    {
453
        $sessionId = $this->get_session_id();
454
        $data = $this->get_exercise_data();
455
        $exerciseId = $data['id'];
456
        $path = isset($data['path']) ? $data['path'] : '';
457
458
        return api_get_path(WEB_CODE_PATH).'gradebook/exercise_jump.php?'
459
            .http_build_query(
460
                [
461
                    'path' => $path,
462
                    'session_id' => $sessionId,
463
                    'cidReq' => $this->get_course_code(),
464
                    'gradebook' => 'view',
465
                    'exerciseId' => $exerciseId,
466
                    'type' => $this->get_type(),
467
                ]
468
            );
469
    }
470
471
    /**
472
     * Get name to display: same as exercise title.
473
     */
474
    public function get_name()
475
    {
476
        $documentPath = api_get_path(SYS_COURSE_PATH).$this->course_code.'/document';
477
        require_once api_get_path(SYS_CODE_PATH).'exercise/hotpotatoes.lib.php';
478
        $data = $this->get_exercise_data();
479
480
        if ($this->is_hp == 1) {
481
            if (isset($data['path'])) {
482
                $title = GetQuizName($data['path'], $documentPath);
483
                if ($title == '') {
484
                    $title = basename($data['path']);
485
                }
486
487
                return $title;
488
            }
489
        }
490
491
        return strip_tags(Exercise::get_formated_title_variable($data['title']));
492
    }
493
494
    public function getLpListToString()
495
    {
496
        $data = $this->get_exercise_data();
497
        $lpList = Exercise::getLpListFromExercise($data['id'], $this->getCourseId());
498
        $lpListToString = '';
499
        if (!empty($lpList)) {
500
            foreach ($lpList as &$list) {
501
                $list['name'] = Display::label($list['name'], 'warning');
502
            }
503
            $lpListToString = implode('&nbsp;', array_column($lpList, 'name'));
504
        }
505
506
        return $lpListToString;
507
    }
508
509
    /**
510
     * Get description to display: same as exercise description.
511
     */
512
    public function get_description()
513
    {
514
        $data = $this->get_exercise_data();
515
516
        return isset($data['description']) ? $data['description'] : null;
517
    }
518
519
    /**
520
     * Check if this still links to an exercise.
521
     */
522
    public function is_valid_link()
523
    {
524
        $exerciseData = $this->get_exercise_data();
525
526
        return !empty($exerciseData);
527
    }
528
529
    /**
530
     * @return string
531
     */
532
    public function get_type_name()
533
    {
534
        if ($this->is_hp == 1) {
535
            return 'HotPotatoes';
536
        }
537
538
        return get_lang('Quiz');
539
    }
540
541
    public function needs_name_and_description()
542
    {
543
        return false;
544
    }
545
546
    public function needs_max()
547
    {
548
        return false;
549
    }
550
551
    public function needs_results()
552
    {
553
        return false;
554
    }
555
556
    public function is_allowed_to_change_name()
557
    {
558
        return false;
559
    }
560
561
    /**
562
     * @return string
563
     */
564
    public function get_icon_name()
565
    {
566
        return 'exercise';
567
    }
568
569
    /**
570
     * @param bool $hp
571
     */
572
    public function setHp($hp)
573
    {
574
        $this->hp = $hp;
575
    }
576
577
    public function getBestScore()
578
    {
579
        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...
580
    }
581
582
    public function getStats($type)
583
    {
584
        switch ($type) {
585
            case 'best':
586
                break;
587
        }
588
    }
589
590
    /**
591
     * Lazy load function to get the database contents of this exercise.
592
     */
593
    public function get_exercise_data()
594
    {
595
        $tableItemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
596
        if ($this->is_hp == 1) {
597
            $table = Database::get_course_table(TABLE_DOCUMENT);
598
        } else {
599
            $table = Database::get_course_table(TABLE_QUIZ_TEST);
600
        }
601
602
        $exerciseId = $this->get_ref_id();
603
604
        if (empty($this->exercise_data)) {
605
            if ($this->is_hp == 1) {
606
                $sql = "SELECT * FROM $table ex
607
                    INNER JOIN $tableItemProperty ip
608
                    ON (ip.ref = ex.id AND ip.c_id = ex.c_id)
609
                    WHERE
610
                        ip.c_id = $this->course_id AND
611
                        ex.c_id = $this->course_id AND
612
                        ip.ref = $exerciseId AND
613
                        ip.tool = '".TOOL_DOCUMENT."' AND
614
                        ex.path LIKE '%htm%' AND
615
                        ex.path LIKE '%HotPotatoes_files%' AND
616
                        ip.visibility = 1";
617
                $result = Database::query($sql);
618
                $this->exercise_data = Database::fetch_array($result);
619
            } else {
620
                // Try with iid
621
                $sql = 'SELECT * FROM '.$table.'
622
                        WHERE
623
                            c_id = '.$this->course_id.' AND
624
                            iid = '.$exerciseId;
625
                $result = Database::query($sql);
626
                $rows = Database::num_rows($result);
627
628
                if (!empty($rows)) {
629
                    $this->exercise_data = Database::fetch_array($result);
630
                } else {
631
                    // Try wit id
632
                    $sql = 'SELECT * FROM '.$table.'
633
                            WHERE
634
                                c_id = '.$this->course_id.' AND
635
                                id = '.$exerciseId;
636
                    $result = Database::query($sql);
637
                    $this->exercise_data = Database::fetch_array($result);
638
                }
639
            }
640
        }
641
642
        if (empty($this->exercise_data)) {
643
            return false;
644
        }
645
646
        return $this->exercise_data;
647
    }
648
649
    /**
650
     * Lazy load function to get the database table of the exercise.
651
     */
652
    private function get_exercise_table()
653
    {
654
        $this->exercise_table = Database::get_course_table(TABLE_QUIZ_TEST);
655
656
        return $this->exercise_table;
657
    }
658
}
659