Test Failed
Push — master ( 482637...7bef58 )
by Julito
33:32
created

learnpath   F

Complexity

Total Complexity 1778

Size/Duplication

Total Lines 13061
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13061
rs 0.6314
c 0
b 0
f 0
wmc 1778

202 Methods

Rating   Name   Duplication   Size   Complexity  
F is_lp_visible_for_student() 0 123 21
D edit_item_prereq() 0 46 10
A getProgressBar() 0 5 1
D display_edit_item() 0 116 17
A get_total_items_count() 0 7 2
B getCalculateScore() 0 47 6
A get_first_item_id() 0 8 2
A get_update_queue() 0 7 2
B switch_attempt_mode() 0 21 5
A getAccumulateScormTime() 0 3 1
A get_course_int_id() 0 3 2
A previous() 0 14 2
A tree_array() 0 7 2
A createCategory() 0 8 1
B move_down() 0 54 8
B get_exercises() 0 104 5
A open() 0 12 2
A createForum() 0 21 1
C set_previous_step_as_prerequisite_for_all_items() 0 48 7
A updateCategory() 0 9 2
A get_author() 0 9 3
B display_lp_prerequisites_list() 0 31 5
A get_last() 0 13 3
A getHideTableOfContents() 0 3 1
B categoryIsPublished() 0 26 2
B getForum() 0 44 3
A get_progress_bar() 0 12 1
A getExercisesItems() 0 13 3
B getSiblingDirectories() 0 35 6
B get_brother_items() 0 31 6
C toggle_publish() 0 80 11
A getCountCategories() 0 10 2
A set_course_int_id() 0 3 1
A moveDownCategory() 0 11 2
A get_theme() 0 9 3
A get_js_lib() 0 8 2
A getItem() 0 7 3
A get_progress_bar_mode() 0 9 3
A toggle_visibility() 0 14 2
B getCurrentBuildingModeURL() 0 10 5
D rl_get_resource_name() 0 99 14
A getSavedFinalItem() 0 12 3
C get_preview_image_path() 0 28 7
A format_scorm_type_item() 0 3 1
A getCurrentItemParentNames() 0 14 3
A getSubscribeUsers() 0 3 1
A moveUpCategory() 0 11 2
A get_interactions_count_from_db() 0 16 2
C display_move_item() 0 55 11
C getCalculateStars() 0 80 12
A has_audio() 0 14 4
A get_id() 0 6 2
A getFinalEvaluationItem() 0 12 3
B get_view() 0 41 6
A getProgress() 0 22 2
A getSubscriptionSettings() 0 14 2
F display_manipulate() 0 115 15
A display_item_small_form() 0 13 1
A getTotalItemsCountWithoutDirs() 0 14 4
B delete_lp_image() 0 17 5
F add_lp() 0 147 14
A getFinalItem() 0 12 4
A getFinalItemTemplate() 0 3 1
F getListArrayToc() 0 79 12
A get_extension() 0 5 1
A getCourseCode() 0 3 1
A get_view_id() 0 9 3
C toggleCategoryPublish() 0 84 8
B generate_learning_path_folder() 0 28 4
A getCategories() 0 17 1
B get_attempt_mode() 0 21 9
A get_common_index_terms_by_prefix() 0 17 3
A get_previous_item_id() 0 8 2
A get_level_for_item() 0 7 2
A get_name() 0 9 3
B lpHasForum() 0 26 1
C save_item() 0 46 9
C update_default_view_mode() 0 40 8
B next() 0 22 5
B get_iv_objectives_array() 0 34 3
A getCategory() 0 7 1
B set_autolaunch() 0 27 2
F display_thread_form() 0 216 42
C save_last() 0 48 10
B copy() 0 26 1
A getCategoryFromCourseIntoSelect() 0 15 4
F get_link() 0 319 57
F create_document() 0 171 30
C prerequisites_match() 0 63 15
B fixBlockedLinks() 0 55 9
F edit_item() 0 237 24
A set_jslib() 0 17 3
B update_default_scorm_commit() 0 32 6
C getChildrenToc() 0 61 12
A set_error_msg() 0 9 3
C stop_previous_item() 0 53 17
C start_current_item() 0 40 15
B set_publicated_on() 0 30 5
D set_current_item() 0 33 10
A set_prerequisite() 0 16 3
A set_modified_on() 0 16 3
B get_type() 0 14 6
A get_maker() 0 9 3
F display_link_form() 0 207 39
F display_document_form() 0 411 84
A set_preview_image() 0 18 3
B getFinalItemForm() 0 91 4
A get_type_static() 0 16 3
A sort_tree_array() 0 12 3
F scorm_export() 0 917 107
A get_items_status_list() 0 13 3
F display_item_form() 0 221 36
B select_previous_item_id() 0 25 2
F add_item() 0 236 13
C get_previous_index() 0 24 7
B set_seriousgame_mode() 0 30 6
A get_preview_image() 0 9 3
C isBlockedByPrerequisite() 0 28 7
F get_progress_bar_text() 0 43 11
A get_teacher_toc_buttons() 0 21 4
D first() 0 68 20
C get_package_type() 0 75 18
A getCategoryId() 0 3 1
F create_tree_array() 0 42 12
B get_links() 0 109 6
A get_items_details_as_js() 0 14 4
C set_terms_by_prefix() 0 68 10
A get_user_id() 0 9 3
A set_use_max_score() 0 19 3
B create_path() 0 14 5
A get_current_item_id() 0 14 4
A set_previous_item() 0 6 2
A display_document() 0 20 2
A setSubscribeUsers() 0 13 2
B upload_image() 0 40 6
B save_current() 0 32 6
A set_theme() 0 17 3
A getChapterTypes() 0 4 1
F __construct() 0 336 54
B restart() 0 40 6
A set_author() 0 16 3
B get_iv_interactions_array() 0 42 4
B set_attempt_mode() 0 32 5
A close() 0 16 3
B deleteCategory() 0 32 4
B update_display_order() 0 28 5
B generate_lp_folder() 0 59 8
F display_quiz_form() 0 208 42
C get_mediaplayer() 0 71 10
A set_proximity() 0 21 4
D display_item() 0 97 15
C overview() 0 54 10
B set_expired_on() 0 31 5
F autocomplete_parents() 0 105 18
C move_up() 0 51 8
C get_scorm_prereq_string() 0 70 10
D display_item_prerequisites_form() 0 140 16
D print_recursive() 0 39 10
A clear_prerequisites() 0 17 2
C scorm_export_to_pdf() 0 63 12
C isFirstOrLastItem() 0 36 7
F display_hotpotatoes_form() 0 184 39
B get_scorm_xml_node() 0 19 7
B set_encoding() 0 23 5
B get_js_info() 0 44 6
B get_js_dropdown_array() 0 51 6
B build_action_menu() 0 117 5
B get_next_item_id() 0 20 6
A getCategoryByCourse() 0 8 1
B get_flat_ordered_items_list() 0 35 5
A display_resources() 0 49 1
A getCategoryLinkForTool() 0 12 1
B verify_document_size() 0 21 8
B get_complete_items_count() 0 27 6
A get_objectives_count_from_db() 0 16 2
B get_documents() 0 95 2
C delete_item() 0 73 9
C get_forums() 0 112 11
D edit_document() 0 67 14
F move_item() 0 153 27
F display_forum_form() 0 205 40
B update_scorm_debug() 0 30 6
B toggleCategoryVisibility() 0 29 3
C getParentToc() 0 67 14
A setAccumulateScormTime() 0 14 2
B update_reinit() 0 31 6
D rl_get_resource_link_for_learnpath() 0 160 30
B getLpFromSession() 0 28 5
C get_navigation_bar() 0 67 8
A set_hide_toc_frame() 0 20 4
D get_next_index() 0 32 10
A get_lp_session_id() 0 9 3
F return_new_tree() 0 412 49
A setCategoryId() 0 10 1
B get_toc() 0 27 5
C categoryIsVisibleForStudent() 0 63 12
D delete() 0 110 18
B get_student_publications() 0 44 4
A set_maker() 0 20 4
F display_student_publication_form() 0 199 38
B delete_children_items() 0 23 5
B set_name() 0 34 5

How to fix   Complexity   

Complex Class

Complex classes like learnpath 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 learnpath, and based on these observations, apply Extract Interface, too.

1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Entity\Repository\CourseRepository;
5
use Chamilo\CoreBundle\Entity\Repository\ItemPropertyRepository;
6
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
7
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
8
use Chamilo\CourseBundle\Entity\CItemProperty;
9
use Chamilo\CourseBundle\Entity\CLp;
10
use Chamilo\CourseBundle\Entity\CLpCategory;
11
use Chamilo\CourseBundle\Entity\CLpItem;
12
use Chamilo\CourseBundle\Entity\CLpItemView;
13
use Chamilo\CourseBundle\Entity\CTool;
14
use Chamilo\UserBundle\Entity\User;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, User. Consider defining an alias.

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

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

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

// Bar.php
namespace OtherDir;

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

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

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

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

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
15
use ChamiloSession as Session;
16
use Gedmo\Sortable\Entity\Repository\SortableRepository;
17
use Symfony\Component\Filesystem\Filesystem;
18
use Symfony\Component\Finder\Finder;
19
20
/**
21
 * Class learnpath
22
 * This class defines the parent attributes and methods for Chamilo learnpaths
23
 * and SCORM learnpaths. It is used by the scorm class.
24
 *
25
 * @todo decouple class
26
 *
27
 * @package chamilo.learnpath
28
 *
29
 * @author  Yannick Warnier <[email protected]>
30
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
31
 */
32
class learnpath
33
{
34
    public $attempt = 0; // The number for the current ID view.
35
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
36
    public $current; // Id of the current item the user is viewing.
37
    public $current_score; // The score of the current item.
38
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
39
    public $current_time_stop; // The time the user closed this resource.
40
    public $default_status = 'not attempted';
41
    public $encoding = 'UTF-8';
42
    public $error = '';
43
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
44
    public $index; // The index of the active learnpath_item in $ordered_items array.
45
    public $items = [];
46
    public $last; // item_id of last item viewed in the learning path.
47
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
48
    public $license; // Which license this course has been given - not used yet on 20060522.
49
    public $lp_id; // DB iid for this learnpath.
50
    public $lp_view_id; // DB ID for lp_view
51
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
52
    public $message = '';
53
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
54
    public $name; // Learnpath name (they generally have one).
55
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
56
    public $path = ''; // Path inside the scorm directory (if scorm).
57
    public $theme; // The current theme of the learning path.
58
    public $preview_image; // The current image of the learning path.
59
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
60
61
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
62
    public $prevent_reinit = 1;
63
64
    // Describes the mode of progress bar display.
65
    public $seriousgame_mode = 0;
66
    public $progress_bar_mode = '%';
67
68
    // Percentage progress as saved in the db.
69
    public $progress_db = 0;
70
    public $proximity; // Wether the content is distant or local or unknown.
71
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
72
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
73
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
74
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
75
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
76
    public $user_id; //ID of the user that is viewing/using the course
77
    public $update_queue = [];
78
    public $scorm_debug = 0;
79
    public $arrMenu = []; // Array for the menu items.
80
    public $debug = 0; // Logging level.
81
    public $lp_session_id = 0;
82
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
83
    public $prerequisite = 0;
84
    public $use_max_score = 1; // 1 or 0
85
    public $subscribeUsers = 0; // Subscribe users or not
86
    public $created_on = '';
87
    public $modified_on = '';
88
    public $publicated_on = '';
89
    public $expired_on = '';
90
    public $ref = null;
91
    public $course_int_id;
92
    public $course_info = [];
93
    public $categoryId;
94
95
    /**
96
     * Constructor.
97
     * Needs a database handler, a course code and a learnpath id from the database.
98
     * Also builds the list of items into $this->items.
99
     *
100
     * @param string $course Course code
101
     * @param int    $lp_id
102
     * @param int    $user_id
103
     *
104
     * @throws \Doctrine\DBAL\DBALException
105
     */
106
    public function __construct($course, $lp_id, $user_id)
107
    {
108
        $debug = $this->debug;
109
        $this->encoding = api_get_system_encoding();
110
        if ($debug) {
111
            error_log('In learnpath::__construct('.$course.','.$lp_id.','.$user_id.')');
112
        }
113
        if (empty($course)) {
114
            $course = api_get_course_id();
115
        }
116
        $course_info = api_get_course_info($course);
117
        if (!empty($course_info)) {
118
            $this->cc = $course_info['code'];
119
            $this->course_info = $course_info;
120
            $course_id = $course_info['real_id'];
121
        } else {
122
            $this->error = 'Course code does not exist in database.';
123
        }
124
125
        $lp_id = (int) $lp_id;
126
        $course_id = (int) $course_id;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $course_id does not seem to be defined for all execution paths leading up to this point.
Loading history...
127
        $this->set_course_int_id($course_id);
128
        // Check learnpath ID.
129
        if (empty($lp_id) || empty($course_id)) {
130
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
131
        } else {
132
            // TODO: Make it flexible to use any course_code (still using env course code here).
133
            $lp_table = Database::get_course_table(TABLE_LP_MAIN);
134
            $sql = "SELECT * FROM $lp_table
135
                    WHERE iid = $lp_id";
136
            if ($debug) {
137
                error_log('learnpath::__construct() '.__LINE__.' - Querying lp: '.$sql, 0);
138
            }
139
            $res = Database::query($sql);
140
            if (Database::num_rows($res) > 0) {
141
                $this->lp_id = $lp_id;
142
                $row = Database::fetch_array($res);
143
                $this->type = $row['lp_type'];
144
                $this->name = stripslashes($row['name']);
145
                $this->proximity = $row['content_local'];
146
                $this->theme = $row['theme'];
147
                $this->maker = $row['content_maker'];
148
                $this->prevent_reinit = $row['prevent_reinit'];
149
                $this->seriousgame_mode = $row['seriousgame_mode'];
150
                $this->license = $row['content_license'];
151
                $this->scorm_debug = $row['debug'];
152
                $this->js_lib = $row['js_lib'];
0 ignored issues
show
Bug Best Practice introduced by
The property js_lib does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
153
                $this->path = $row['path'];
154
                $this->preview_image = $row['preview_image'];
155
                $this->author = $row['author'];
0 ignored issues
show
Bug Best Practice introduced by
The property author does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
156
                $this->hide_toc_frame = $row['hide_toc_frame'];
0 ignored issues
show
Bug Best Practice introduced by
The property hide_toc_frame does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
157
                $this->lp_session_id = $row['session_id'];
158
                $this->use_max_score = $row['use_max_score'];
159
                $this->subscribeUsers = $row['subscribe_users'];
160
                $this->created_on = $row['created_on'];
161
                $this->modified_on = $row['modified_on'];
162
                $this->ref = $row['ref'];
163
                $this->categoryId = $row['category_id'];
164
                $this->accumulateScormTime = isset($row['accumulate_scorm_time']) ? $row['accumulate_scorm_time'] : 'true';
165
166
                if (!empty($row['publicated_on'])) {
167
                    $this->publicated_on = $row['publicated_on'];
168
                }
169
170
                if (!empty($row['expired_on'])) {
171
                    $this->expired_on = $row['expired_on'];
172
                }
173
                if ($this->type == 2) {
174
                    if ($row['force_commit'] == 1) {
175
                        $this->force_commit = true;
176
                    }
177
                }
178
                $this->mode = $row['default_view_mod'];
179
180
                // Check user ID.
181
                if (empty($user_id)) {
182
                    $this->error = 'User ID is empty';
183
                } else {
184
                    $userInfo = api_get_user_info($user_id);
185
                    if (!empty($userInfo)) {
186
                        $this->user_id = $userInfo['user_id'];
187
                    } else {
188
                        $this->error = 'User ID does not exist in database #'.$user_id;
189
                    }
190
                }
191
192
                // End of variables checking.
193
                $session_id = api_get_session_id();
194
                //  Get the session condition for learning paths of the base + session.
195
                $session = api_get_session_condition($session_id);
196
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
197
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
198
199
                // Selecting by view_count descending allows to get the highest view_count first.
200
                $sql = "SELECT * FROM $lp_table
201
                        WHERE 
202
                            c_id = $course_id AND 
203
                            lp_id = $lp_id AND 
204
                            user_id = $user_id 
205
                            $session
206
                        ORDER BY view_count DESC";
207
                $res = Database::query($sql);
208
                if ($debug) {
209
                    error_log('learnpath::__construct() '.__LINE__.' - querying lp_view: '.$sql, 0);
210
                }
211
212
                if (Database::num_rows($res) > 0) {
213
                    if ($debug) {
214
                        error_log('learnpath::__construct() '.__LINE__.' - Found previous view');
215
                    }
216
                    $row = Database::fetch_array($res);
217
                    $this->attempt = $row['view_count'];
218
                    $this->lp_view_id = $row['id'];
219
                    $this->last_item_seen = $row['last_item'];
220
                    $this->progress_db = $row['progress'];
221
                    $this->lp_view_session_id = $row['session_id'];
222
                } elseif (!api_is_invitee()) {
223
                    if ($debug) {
224
                        error_log('learnpath::__construct() '.__LINE__.' - NOT Found previous view');
225
                    }
226
                    $this->attempt = 1;
227
                    $params = [
228
                        'c_id' => $course_id,
229
                        'lp_id' => $lp_id,
230
                        'user_id' => $user_id,
231
                        'view_count' => 1,
232
                        'session_id' => $session_id,
233
                        'last_item' => 0,
234
                    ];
235
                    $this->last_item_seen = 0;
236
                    $this->lp_view_session_id = $session_id;
237
                    $this->lp_view_id = Database::insert($lp_table, $params);
238
                    if (!empty($this->lp_view_id)) {
239
                        $sql = "UPDATE $lp_table SET id = iid
240
                                WHERE iid = ".$this->lp_view_id;
241
                        Database::query($sql);
242
                    }
243
                }
244
245
                // Initialise items.
246
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
247
                $sql = "SELECT * FROM $lp_item_table
248
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
249
                        ORDER BY parent_item_id, display_order";
250
                $res = Database::query($sql);
251
252
                if ($debug) {
253
                    error_log('learnpath::__construct() '.__LINE__.' - query lp items: '.$sql);
254
                    error_log('-- Start while--');
255
                }
256
257
                $lp_item_id_list = [];
258
                while ($row = Database::fetch_array($res)) {
259
                    $lp_item_id_list[] = $row['iid'];
260
                    switch ($this->type) {
261
                        case 3: //aicc
262
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
263
                            if (is_object($oItem)) {
264
                                $my_item_id = $oItem->get_id();
265
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
266
                                $oItem->set_prevent_reinit($this->prevent_reinit);
267
                                // Don't use reference here as the next loop will make the pointed object change.
268
                                $this->items[$my_item_id] = $oItem;
269
                                $this->refs_list[$oItem->ref] = $my_item_id;
270
                                if ($debug) {
271
                                    error_log(
272
                                        'learnpath::__construct() - '.
273
                                        'aicc object with id '.$my_item_id.
274
                                        ' set in items[]',
275
                                        0
276
                                    );
277
                                }
278
                            }
279
                            break;
280
                        case 2:
281
                            $oItem = new scormItem('db', $row['iid'], $course_id);
282
                            if (is_object($oItem)) {
283
                                $my_item_id = $oItem->get_id();
284
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
285
                                $oItem->set_prevent_reinit($this->prevent_reinit);
286
                                // Don't use reference here as the next loop will make the pointed object change.
287
                                $this->items[$my_item_id] = $oItem;
288
                                $this->refs_list[$oItem->ref] = $my_item_id;
289
                                if ($debug) {
290
                                    error_log('object with id '.$my_item_id.' set in items[]');
291
                                }
292
                            }
293
                            break;
294
                        case 1:
295
                        default:
296
                            if ($debug) {
297
                                error_log('learnpath::__construct() '.__LINE__.' - calling learnpathItem');
298
                            }
299
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
300
301
                            if ($debug) {
302
                                error_log('learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
303
                            }
304
                            if (is_object($oItem)) {
305
                                $my_item_id = $oItem->get_id();
306
                                // Moved down to when we are sure the item_view exists.
307
                                //$oItem->set_lp_view($this->lp_view_id);
308
                                $oItem->set_prevent_reinit($this->prevent_reinit);
309
                                // Don't use reference here as the next loop will make the pointed object change.
310
                                $this->items[$my_item_id] = $oItem;
311
                                $this->refs_list[$my_item_id] = $my_item_id;
312
                                if ($debug) {
313
                                    error_log(
314
                                        'learnpath::__construct() '.__LINE__.
315
                                        ' - object with id '.$my_item_id.' set in items[]'
316
                                    );
317
                                }
318
                            }
319
                            break;
320
                    }
321
322
                    // Setting the object level with variable $this->items[$i][parent]
323
                    foreach ($this->items as $itemLPObject) {
324
                        $level = self::get_level_for_item(
325
                            $this->items,
326
                            $itemLPObject->db_id
327
                        );
328
                        $itemLPObject->level = $level;
329
                    }
330
331
                    // Setting the view in the item object.
332
                    if (is_object($this->items[$row['iid']])) {
333
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
334
                        if ($this->items[$row['iid']]->get_type() == TOOL_HOTPOTATOES) {
335
                            $this->items[$row['iid']]->current_start_time = 0;
336
                            $this->items[$row['iid']]->current_stop_time = 0;
337
                        }
338
                    }
339
                }
340
341
                if ($debug) {
342
                    error_log('learnpath::__construct() '.__LINE__.' ----- end while ----');
343
                }
344
345
                if (!empty($lp_item_id_list)) {
346
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
347
                    if (!empty($lp_item_id_list_to_string)) {
348
                        // Get last viewing vars.
349
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
350
                        // This query should only return one or zero result.
351
                        $sql = "SELECT lp_item_id, status
352
                                FROM $itemViewTable
353
                                WHERE
354
                                    c_id = $course_id AND
355
                                    lp_view_id = ".$this->lp_view_id." AND
356
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
357
                                ORDER BY view_count DESC ";
358
359
                        if ($debug) {
360
                            error_log(
361
                                'learnpath::__construct() - Selecting item_views: '.$sql,
362
                                0
363
                            );
364
                        }
365
366
                        $status_list = [];
367
                        $res = Database::query($sql);
368
                        while ($row = Database:: fetch_array($res)) {
369
                            $status_list[$row['lp_item_id']] = $row['status'];
370
                        }
371
372
                        foreach ($lp_item_id_list as $item_id) {
373
                            if (isset($status_list[$item_id])) {
374
                                $status = $status_list[$item_id];
375
                                if (is_object($this->items[$item_id])) {
376
                                    $this->items[$item_id]->set_status($status);
377
                                    if (empty($status)) {
378
                                        $this->items[$item_id]->set_status(
379
                                            $this->default_status
380
                                        );
381
                                    }
382
                                }
383
                            } else {
384
                                if (!api_is_invitee()) {
385
                                    if (is_object($this->items[$item_id])) {
386
                                        $this->items[$item_id]->set_status(
387
                                            $this->default_status
388
                                        );
389
                                    }
390
391
                                    if (!empty($this->lp_view_id)) {
392
                                        // Add that row to the lp_item_view table so that
393
                                        // we have something to show in the stats page.
394
                                        $params = [
395
                                            'c_id' => $course_id,
396
                                            'lp_item_id' => $item_id,
397
                                            'lp_view_id' => $this->lp_view_id,
398
                                            'view_count' => 1,
399
                                            'status' => 'not attempted',
400
                                            'start_time' => time(),
401
                                            'total_time' => 0,
402
                                            'score' => 0,
403
                                        ];
404
                                        $insertId = Database::insert($itemViewTable, $params);
405
406
                                        if ($insertId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $insertId of type integer|false is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
407
                                            $sql = "UPDATE $itemViewTable SET id = iid
408
                                                    WHERE iid = $insertId";
409
                                            Database::query($sql);
410
                                        }
411
412
                                        $this->items[$item_id]->set_lp_view(
413
                                            $this->lp_view_id,
414
                                            $course_id
415
                                        );
416
                                    }
417
                                }
418
                            }
419
                        }
420
                    }
421
                }
422
423
                $this->ordered_items = self::get_flat_ordered_items_list(
424
                    $this->get_id(),
425
                    0,
426
                    $course_id
427
                );
428
                $this->max_ordered_items = 0;
0 ignored issues
show
Bug Best Practice introduced by
The property max_ordered_items does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
429
                foreach ($this->ordered_items as $index => $dummy) {
430
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
431
                        $this->max_ordered_items = $index;
432
                    }
433
                }
434
                // TODO: Define the current item better.
435
                $this->first();
436
                if ($debug) {
437
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
438
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
439
                }
440
            } else {
441
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
442
            }
443
        }
444
    }
445
446
    /**
447
     * @return string
448
     */
449
    public function getCourseCode()
450
    {
451
        return $this->cc;
452
    }
453
454
    /**
455
     * @return int
456
     */
457
    public function get_course_int_id()
458
    {
459
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
460
    }
461
462
    /**
463
     * @param $course_id
464
     *
465
     * @return int
466
     */
467
    public function set_course_int_id($course_id)
468
    {
469
        return $this->course_int_id = (int) $course_id;
470
    }
471
472
    /**
473
     * Function rewritten based on old_add_item() from Yannick Warnier.
474
     * Due the fact that users can decide where the item should come, I had to overlook this function and
475
     * I found it better to rewrite it. Old function is still available.
476
     * Added also the possibility to add a description.
477
     *
478
     * @param int    $parent
479
     * @param int    $previous
480
     * @param string $type
481
     * @param int    $id resource ID (ref)
482
     * @param string $title
483
     * @param string $description
484
     * @param int    $prerequisites
485
     * @param int    $max_time_allowed
486
     * @param int    $userId
487
     *
488
     * @return int
489
     * @throws \Doctrine\DBAL\DBALException
490
     */
491
    public function add_item(
492
        $parent,
493
        $previous,
494
        $type = 'dir',
495
        $id,
496
        $title,
497
        $description,
498
        $prerequisites = 0,
499
        $max_time_allowed = 0,
500
        $userId = 0
501
    ) {
502
        $course_id = $this->course_info['real_id'];
503
        if ($this->debug > 0) {
504
            error_log('In learnpath::add_item('.$parent.','.$previous.','.$type.','.$id.','.$title.')');
505
        }
506
        if (empty($course_id)) {
507
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
508
            $this->course_info = api_get_course_info($this->cc);
509
            $course_id = $this->course_info['real_id'];
510
        }
511
        $userId = empty($userId) ? api_get_user_id() : $userId;
512
        $sessionId = api_get_session_id();
513
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
514
        $_course = $this->course_info;
515
        $parent = intval($parent);
516
        $previous = intval($previous);
517
        $id = intval($id);
518
        $max_time_allowed = htmlentities($max_time_allowed);
519
        if (empty($max_time_allowed)) {
520
            $max_time_allowed = 0;
521
        }
522
        $sql = "SELECT COUNT(iid) AS num
523
                FROM $tbl_lp_item
524
                WHERE
525
                    c_id = $course_id AND
526
                    lp_id = ".$this->get_id()." AND
527
                    parent_item_id = ".$parent;
528
529
        $res_count = Database::query($sql);
530
        $row = Database::fetch_array($res_count);
531
        $num = $row['num'];
532
533
        if ($num > 0) {
534
            if (empty($previous)) {
535
                $sql = "SELECT iid, next_item_id, display_order
536
                        FROM $tbl_lp_item
537
                        WHERE
538
                            c_id = $course_id AND
539
                            lp_id = ".$this->get_id()." AND
540
                            parent_item_id = $parent AND
541
                            previous_item_id = 0 OR
542
                            previous_item_id = $parent";
543
                $result = Database::query($sql);
544
                $row = Database::fetch_array($result);
545
                $tmp_previous = 0;
546
                $next = $row['iid'];
547
                $display_order = 0;
548
            } else {
549
                $previous = (int) $previous;
550
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
551
						FROM $tbl_lp_item
552
                        WHERE
553
                            c_id = $course_id AND
554
                            lp_id = ".$this->get_id()." AND
555
                            id = $previous";
556
                $result = Database::query($sql);
557
                $row = Database:: fetch_array($result);
558
                $tmp_previous = $row['iid'];
559
                $next = $row['next_item_id'];
560
                $display_order = $row['display_order'];
561
            }
562
        } else {
563
            $tmp_previous = 0;
564
            $next = 0;
565
            $display_order = 0;
566
        }
567
568
        $id = intval($id);
569
        $typeCleaned = Database::escape_string($type);
570
        $max_score = 100;
571
        if ($type == 'quiz') {
572
            $sql = 'SELECT SUM(ponderation)
573
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
574
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
575
                    ON
576
                        quiz_question.id = quiz_rel_question.question_id AND
577
                        quiz_question.c_id = quiz_rel_question.c_id
578
                    WHERE
579
                        quiz_rel_question.exercice_id = '.$id." AND
580
                        quiz_question.c_id = $course_id AND
581
                        quiz_rel_question.c_id = $course_id ";
582
            $rsQuiz = Database::query($sql);
583
            $max_score = Database::result($rsQuiz, 0, 0);
584
585
            // Disabling the exercise if we add it inside a LP
586
            $exercise = new Exercise($course_id);
587
            $exercise->read($id);
588
            $exercise->disable();
589
            $exercise->save();
590
        }
591
592
        $params = [
593
            "c_id" => $course_id,
594
            "lp_id" => $this->get_id(),
595
            "item_type" => $typeCleaned,
596
            "ref" => '',
597
            "title" => $title,
598
            "description" => $description,
599
            "path" => $id,
600
            "max_score" => $max_score,
601
            "parent_item_id" => $parent,
602
            "previous_item_id" => $previous,
603
            "next_item_id" => intval($next),
604
            "display_order" => $display_order + 1,
605
            "prerequisite" => $prerequisites,
606
            "max_time_allowed" => $max_time_allowed,
607
            'min_score' => 0,
608
            'launch_data' => '',
609
        ];
610
611
        if ($prerequisites != 0) {
612
            $params['prerequisite'] = $prerequisites;
613
        }
614
615
        $new_item_id = Database::insert($tbl_lp_item, $params);
616
617
        if ($this->debug > 2) {
618
            error_log('Inserting dir/chapter: '.$new_item_id, 0);
0 ignored issues
show
Bug introduced by
Are you sure $new_item_id of type integer|false can be used in concatenation? ( Ignorable by Annotation )

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

618
            error_log('Inserting dir/chapter: './** @scrutinizer ignore-type */ $new_item_id, 0);
Loading history...
619
        }
620
621
        if ($new_item_id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $new_item_id of type integer|false is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
622
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
623
            Database::query($sql);
624
625
            $sql = "UPDATE $tbl_lp_item
626
                    SET previous_item_id = $new_item_id 
627
                    WHERE c_id = $course_id AND id = $next";
628
            Database::query($sql);
629
630
            // Update the item that should be before the new item.
631
            $sql = "UPDATE $tbl_lp_item
632
                    SET next_item_id = $new_item_id
633
                    WHERE c_id = $course_id AND id = $tmp_previous";
634
            Database::query($sql);
635
636
            // Update all the items after the new item.
637
            $sql = "UPDATE $tbl_lp_item
638
                        SET display_order = display_order + 1
639
                    WHERE
640
                        c_id = $course_id AND
641
                        lp_id = ".$this->get_id()." AND
642
                        iid <> $new_item_id AND
643
                        parent_item_id = $parent AND
644
                        display_order > $display_order";
645
            Database::query($sql);
646
647
            // Update the item that should come after the new item.
648
            $sql = "UPDATE $tbl_lp_item
649
                    SET ref = $new_item_id
650
                    WHERE c_id = $course_id AND iid = $new_item_id";
651
            Database::query($sql);
652
653
            // Upload audio.
654
            if (!empty($_FILES['mp3']['name'])) {
655
                // Create the audio folder if it does not exist yet.
656
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
657
                if (!is_dir($filepath.'audio')) {
658
                    mkdir(
659
                        $filepath.'audio',
660
                        api_get_permissions_for_new_directories()
661
                    );
662
                    $audio_id = add_document(
663
                        $_course,
664
                        '/audio',
665
                        'folder',
666
                        0,
667
                        'audio',
668
                        '',
669
                        0,
670
                        true,
671
                        null,
672
                        $sessionId,
673
                        $userId
674
                    );
675
                    api_item_property_update(
676
                        $_course,
677
                        TOOL_DOCUMENT,
678
                        $audio_id,
679
                        'FolderCreated',
680
                        $userId,
681
                        null,
682
                        null,
683
                        null,
684
                        null,
685
                        $sessionId
686
                    );
687
                    api_item_property_update(
688
                        $_course,
689
                        TOOL_DOCUMENT,
690
                        $audio_id,
691
                        'invisible',
692
                        $userId,
693
                        null,
694
                        null,
695
                        null,
696
                        null,
697
                        $sessionId
698
                    );
699
                }
700
701
                $file_path = handle_uploaded_document(
702
                    $_course,
703
                    $_FILES['mp3'],
704
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
705
                    '/audio',
706
                    $userId,
707
                    '',
708
                    '',
709
                    '',
710
                    '',
711
                    false
712
                );
713
714
                // Getting the filename only.
715
                $file_components = explode('/', $file_path);
716
                $file = $file_components[count($file_components) - 1];
717
718
                // Store the mp3 file in the lp_item table.
719
                $sql = "UPDATE $tbl_lp_item SET
720
                          audio = '".Database::escape_string($file)."'
721
                        WHERE iid = '".intval($new_item_id)."'";
722
                Database::query($sql);
723
            }
724
        }
725
726
        return $new_item_id;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $new_item_id could also return false which is incompatible with the documented return type integer. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
727
    }
728
729
    /**
730
     * Static admin function allowing addition of a learnpath to a course.
731
     *
732
     * @param        $courseCode
733
     * @param        $name
734
     * @param string $description
735
     * @param string $learnpath
736
     * @param string $origin
737
     * @param string $zipname Zip file containing the learnpath or directory containing the learnpath
738
     * @param string $publicated_on
739
     * @param string $expired_on
740
     * @param int    $categoryId
741
     * @param int    $userId
742
     *
743
     * @return int The new learnpath ID on success, 0 on failure
744
     * @throws \Doctrine\DBAL\DBALException
745
     */
746
    public static function add_lp(
747
        $courseCode,
748
        $name,
749
        $description = '',
750
        $learnpath = 'guess',
751
        $origin = 'zip',
752
        $zipname = '',
753
        $publicated_on = '',
754
        $expired_on = '',
755
        $categoryId = 0,
756
        $userId = 0
757
    ) {
758
        global $charset;
759
760
        if (!empty($courseCode)) {
761
            $courseInfo = api_get_course_info($courseCode);
762
            $course_id = $courseInfo['real_id'];
763
        } else {
764
            $course_id = api_get_course_int_id();
765
            $courseInfo = api_get_course_info();
766
        }
767
768
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
769
        // Check course code exists.
770
        // Check lp_name doesn't exist, otherwise append something.
771
        $i = 0;
772
        $name = Database::escape_string($name);
773
        $categoryId = intval($categoryId);
774
775
        // Session id.
776
        $session_id = api_get_session_id();
777
        $userId = empty($userId) ? api_get_user_id() : $userId;
778
        $check_name = "SELECT * FROM $tbl_lp
779
                       WHERE c_id = $course_id AND name = '$name'";
780
781
        $res_name = Database::query($check_name);
782
783
        if (empty($publicated_on)) {
784
            $publicated_on = null;
785
        } else {
786
            $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
787
        }
788
789
        if (empty($expired_on)) {
790
            $expired_on = null;
791
        } else {
792
            $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
793
        }
794
795
        while (Database::num_rows($res_name)) {
796
            // There is already one such name, update the current one a bit.
797
            $i++;
798
            $name = $name.' - '.$i;
799
            $check_name = "SELECT * FROM $tbl_lp 
800
                           WHERE c_id = $course_id AND name = '$name'";
801
            $res_name = Database::query($check_name);
802
        }
803
        // New name does not exist yet; keep it.
804
        // Escape description.
805
        // Kevin: added htmlentities().
806
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
807
        $type = 1;
808
        switch ($learnpath) {
809
            case 'guess':
810
                break;
811
            case 'dokeos':
812
            case 'chamilo':
813
                $type = 1;
814
                break;
815
            case 'aicc':
816
                break;
817
        }
818
819
        switch ($origin) {
820
            case 'zip':
821
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
822
                break;
823
            case 'manual':
824
            default:
825
                $get_max = "SELECT MAX(display_order) 
826
                            FROM $tbl_lp WHERE c_id = $course_id";
827
                $res_max = Database::query($get_max);
828
                if (Database::num_rows($res_max) < 1) {
829
                    $dsp = 1;
830
                } else {
831
                    $row = Database::fetch_array($res_max);
832
                    $dsp = $row[0] + 1;
833
                }
834
835
                $params = [
836
                    'c_id' => $course_id,
837
                    'lp_type' => $type,
838
                    'name' => $name,
839
                    'description' => $description,
840
                    'path' => '',
841
                    'default_view_mod' => 'embedded',
842
                    'default_encoding' => 'UTF-8',
843
                    'display_order' => $dsp,
844
                    'content_maker' => 'Chamilo',
845
                    'content_local' => 'local',
846
                    'js_lib' => '',
847
                    'session_id' => $session_id,
848
                    'created_on' => api_get_utc_datetime(),
849
                    'modified_on' => api_get_utc_datetime(),
850
                    'publicated_on' => $publicated_on,
851
                    'expired_on' => $expired_on,
852
                    'category_id' => $categoryId,
853
                    'force_commit' => 0,
854
                    'content_license' => '',
855
                    'debug' => 0,
856
                    'theme' => '',
857
                    'preview_image' => '',
858
                    'author' => '',
859
                    'prerequisite' => 0,
860
                    'hide_toc_frame' => 0,
861
                    'seriousgame_mode' => 0,
862
                    'autolaunch' => 0,
863
                    'max_attempts' => 0,
864
                    'subscribe_users' => 0,
865
                    'accumulate_scorm_time' => 1,
866
                ];
867
                $id = Database::insert($tbl_lp, $params);
868
869
                if ($id > 0) {
870
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
871
                    Database::query($sql);
872
873
                    // Insert into item_property.
874
                    api_item_property_update(
875
                        $courseInfo,
876
                        TOOL_LEARNPATH,
877
                        $id,
0 ignored issues
show
Bug introduced by
It seems like $id can also be of type false; however, parameter $item_id of api_item_property_update() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

877
                        /** @scrutinizer ignore-type */ $id,
Loading history...
878
                        'LearnpathAdded',
879
                        $userId
880
                    );
881
                    api_set_default_visibility(
882
                        $id,
0 ignored issues
show
Bug introduced by
It seems like $id can also be of type false; however, parameter $item_id of api_set_default_visibility() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

882
                        /** @scrutinizer ignore-type */ $id,
Loading history...
883
                        TOOL_LEARNPATH,
884
                        0,
885
                        $courseInfo,
886
                        $session_id,
887
                        $userId
888
                    );
889
890
                    return $id;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $id could also return false which is incompatible with the documented return type integer. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
891
                }
892
                break;
893
        }
894
    }
895
896
    /**
897
     * Auto completes the parents of an item in case it's been completed or passed.
898
     *
899
     * @param int $item Optional ID of the item from which to look for parents
900
     */
901
    public function autocomplete_parents($item)
902
    {
903
        $debug = $this->debug;
904
905
        if ($debug) {
906
            error_log('Learnpath::autocomplete_parents()');
907
        }
908
909
        if (empty($item)) {
910
            $item = $this->current;
911
        }
912
913
        $currentItem = $this->getItem($item);
914
        if ($currentItem) {
915
            $parent_id = $currentItem->get_parent();
916
            $parent = $this->getItem($parent_id);
917
            if ($parent) {
918
                // if $item points to an object and there is a parent.
919
                if ($debug) {
920
                    error_log(
921
                        'Autocompleting parent of item '.$item.' '.
922
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
923
                        0
924
                    );
925
                }
926
927
                // New experiment including failed and browsed in completed status.
928
                //$current_status = $currentItem->get_status();
929
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
930
                // Fixes chapter auto complete
931
                if (true) {
0 ignored issues
show
Bug introduced by
Avoid IF statements that are always true or false
Loading history...
932
                    // If the current item is completed or passes or succeeded.
933
                    $updateParentStatus = true;
934
                    if ($debug) {
935
                        error_log('Status of current item is alright');
936
                    }
937
938
                    foreach ($parent->get_children() as $childItemId) {
939
                        $childItem = $this->getItem($childItemId);
940
941
                        // If children was not set try to get the info
942
                        if (empty($childItem->db_item_view_id)) {
943
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
944
                        }
945
946
                        // Check all his brothers (parent's children) for completion status.
947
                        if ($childItemId != $item) {
948
                            if ($debug) {
949
                                error_log(
950
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
951
                                    0
952
                                );
953
                            }
954
                            // Trying completing parents of failed and browsed items as well.
955
                            if ($childItem->status_is(
956
                                [
957
                                    'completed',
958
                                    'passed',
959
                                    'succeeded',
960
                                    'browsed',
961
                                    'failed',
962
                                ]
963
                            )
964
                            ) {
965
                                // Keep completion status to true.
966
                                continue;
967
                            } else {
968
                                if ($debug > 2) {
969
                                    error_log(
970
                                        'Found one incomplete child of parent #'.$parent_id.': child #'.$childItemId.' "'.$childItem->get_title().'", is '.$childItem->get_status().' db_item_view_id:#'.$childItem->db_item_view_id,
971
                                        0
972
                                    );
973
                                }
974
                                $updateParentStatus = false;
975
                                break;
976
                            }
977
                        }
978
                    }
979
980
                    if ($updateParentStatus) {
981
                        // If all the children were completed:
982
                        $parent->set_status('completed');
983
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
984
                        // Force the status to "completed"
985
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
986
                        $this->update_queue[$parent->get_id()] = 'completed';
987
                        if ($debug) {
988
                            error_log(
989
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
990
                                print_r($this->update_queue, 1),
991
                                0
992
                            );
993
                        }
994
                        // Recursive call.
995
                        $this->autocomplete_parents($parent->get_id());
996
                    }
997
                }
998
            } else {
999
                if ($debug) {
1000
                    error_log("Parent #$parent_id does not exists");
1001
                }
1002
            }
1003
        } else {
1004
            if ($debug) {
1005
                error_log("#$item is an item that doesn't have parents");
1006
            }
1007
        }
1008
    }
1009
1010
    /**
1011
     * Closes the current resource.
1012
     *
1013
     * Stops the timer
1014
     * Saves into the database if required
1015
     * Clears the current resource data from this object
1016
     *
1017
     * @return bool True on success, false on failure
1018
     */
1019
    public function close()
1020
    {
1021
        if ($this->debug > 0) {
1022
            error_log('In learnpath::close()', 0);
1023
        }
1024
        if (empty($this->lp_id)) {
1025
            $this->error = 'Trying to close this learnpath but no ID is set';
1026
1027
            return false;
1028
        }
1029
        $this->current_time_stop = time();
1030
        $this->ordered_items = [];
1031
        $this->index = 0;
1032
        unset($this->lp_id);
1033
        //unset other stuff
1034
        return true;
1035
    }
1036
1037
    /**
1038
     * Static admin function allowing removal of a learnpath.
1039
     *
1040
     * @param array  $courseInfo
1041
     * @param int    $id Learnpath ID
1042
     * @param string $delete Whether to delete data or keep it (default: 'keep', others: 'remove')
1043
     *
1044
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
1045
     * @throws \Doctrine\DBAL\DBALException
1046
     */
1047
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1048
    {
1049
        $course_id = api_get_course_int_id();
1050
        if (!empty($courseInfo)) {
1051
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1052
        }
1053
1054
        // TODO: Implement a way of getting this to work when the current object is not set.
1055
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1056
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1057
        if (!empty($id) && ($id != $this->lp_id)) {
1058
            return false;
1059
        }
1060
1061
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1062
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1063
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1064
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1065
1066
        // Delete lp item id.
1067
        foreach ($this->items as $id => $dummy) {
1068
            $sql = "DELETE FROM $lp_item_view
1069
                    WHERE c_id = $course_id AND lp_item_id = '".$id."'";
1070
            Database::query($sql);
1071
        }
1072
1073
        // Proposed by Christophe (nickname: clefevre)
1074
        $sql = "DELETE FROM $lp_item
1075
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1076
        Database::query($sql);
1077
1078
        $sql = "DELETE FROM $lp_view 
1079
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1080
        Database::query($sql);
1081
1082
        self::toggle_publish($this->lp_id, 'i');
1083
1084
        if ($this->type == 2 || $this->type == 3) {
1085
            // This is a scorm learning path, delete the files as well.
1086
            $sql = "SELECT path FROM $lp
1087
                    WHERE iid = ".$this->lp_id;
1088
            $res = Database::query($sql);
1089
            if (Database::num_rows($res) > 0) {
1090
                $row = Database::fetch_array($res);
1091
                $path = $row['path'];
1092
                $sql = "SELECT id FROM $lp
1093
                        WHERE 
1094
                            c_id = $course_id AND
1095
                            path = '$path' AND 
1096
                            iid != ".$this->lp_id;
1097
                $res = Database::query($sql);
1098
                if (Database::num_rows($res) > 0) {
1099
                    // Another learning path uses this directory, so don't delete it.
1100
                    if ($this->debug > 2) {
1101
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1102
                    }
1103
                } else {
1104
                    // No other LP uses that directory, delete it.
1105
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1106
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir; // The absolute system path for this course.
1107
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1108
                        if ($this->debug > 2) {
1109
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1110
                        }
1111
                        // Proposed by Christophe (clefevre).
1112
                        if (strcmp(substr($path, -2), "/.") == 0) {
1113
                            $path = substr($path, 0, -1); // Remove "." at the end.
1114
                        }
1115
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1116
                        rmdirr($course_scorm_dir.$path);
1117
                    }
1118
                }
1119
            }
1120
        }
1121
1122
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1123
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1124
        // Delete tools
1125
        $sql = "DELETE FROM $tbl_tool
1126
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1127
        Database::query($sql);
1128
1129
        $sql = "DELETE FROM $lp 
1130
                WHERE iid = ".$this->lp_id;
1131
        Database::query($sql);
1132
        // Updates the display order of all lps.
1133
        $this->update_display_order();
1134
1135
        api_item_property_update(
1136
            api_get_course_info(),
1137
            TOOL_LEARNPATH,
1138
            $this->lp_id,
1139
            'delete',
1140
            api_get_user_id()
1141
        );
1142
1143
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1144
            api_get_course_id(),
1145
            4,
1146
            $id,
1147
            api_get_session_id()
1148
        );
1149
1150
        if ($link_info !== false) {
1151
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1152
        }
1153
1154
        if (api_get_setting('search_enabled') == 'true') {
1155
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1156
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1157
        }
1158
    }
1159
1160
    /**
1161
     * Removes all the children of one item - dangerous!
1162
     *
1163
     * @param int $id Element ID of which children have to be removed
1164
     *
1165
     * @return int Total number of children removed
1166
     * @throws \Doctrine\DBAL\DBALException
1167
     */
1168
    public function delete_children_items($id)
1169
    {
1170
        $course_id = $this->course_info['real_id'];
1171
        if ($this->debug > 0) {
1172
            error_log('In learnpath::delete_children_items('.$id.')', 0);
1173
        }
1174
        $num = 0;
1175
        if (empty($id) || $id != strval(intval($id))) {
1176
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
1177
        }
1178
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1179
        $sql = "SELECT * FROM $lp_item 
1180
                WHERE c_id = ".$course_id." AND parent_item_id = $id";
1181
        $res = Database::query($sql);
1182
        while ($row = Database::fetch_array($res)) {
1183
            $num += $this->delete_children_items($row['iid']);
1184
            $sql = "DELETE FROM $lp_item 
1185
                    WHERE c_id = ".$course_id." AND iid = ".$row['iid'];
1186
            Database::query($sql);
1187
            $num++;
1188
        }
1189
1190
        return $num;
1191
    }
1192
1193
    /**
1194
     * Removes an item from the current learnpath.
1195
     *
1196
     * @param int    $id Elem ID (0 if first)
1197
     * @param string $remove Whether to remove the resource/data from the system
1198
     *                       or leave it (default: 'keep', others 'remove')
1199
     *
1200
     * @return int Number of elements moved
1201
     *
1202
     * @throws \Doctrine\DBAL\DBALException
1203
     * @todo implement resource removal
1204
     */
1205
    public function delete_item($id, $remove = 'keep')
1206
    {
1207
        $course_id = api_get_course_int_id();
1208
        if ($this->debug > 0) {
1209
            error_log('In learnpath::delete_item()', 0);
1210
        }
1211
        // TODO: Implement the resource removal.
1212
        if (empty($id) || $id != strval(intval($id))) {
1213
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
1214
        }
1215
        // First select item to get previous, next, and display order.
1216
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1217
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1218
        $res_sel = Database::query($sql_sel);
1219
        if (Database::num_rows($res_sel) < 1) {
1220
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
1221
        }
1222
        $row = Database::fetch_array($res_sel);
1223
        $previous = $row['previous_item_id'];
1224
        $next = $row['next_item_id'];
1225
        $display = $row['display_order'];
1226
        $parent = $row['parent_item_id'];
1227
        $lp = $row['lp_id'];
1228
        // Delete children items.
1229
        $num = $this->delete_children_items($id);
1230
        if ($this->debug > 2) {
1231
            error_log('learnpath::delete_item() - deleted '.$num.' children of element '.$id, 0);
1232
        }
1233
        // Now delete the item.
1234
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1235
        if ($this->debug > 2) {
1236
            error_log('Deleting item: '.$sql_del, 0);
1237
        }
1238
        Database::query($sql_del);
1239
        // Now update surrounding items.
1240
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1241
                    WHERE iid = $previous";
1242
        Database::query($sql_upd);
1243
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1244
                    WHERE iid = $next";
1245
        Database::query($sql_upd);
1246
        // Now update all following items with new display order.
1247
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1248
                    WHERE 
1249
                        c_id = $course_id AND 
1250
                        lp_id = $lp AND 
1251
                        parent_item_id = $parent AND 
1252
                        display_order > $display";
1253
        Database::query($sql_all);
1254
1255
        //Removing prerequisites since the item will not longer exist
1256
        $sql_all = "UPDATE $lp_item SET prerequisite = '' 
1257
                    WHERE c_id = $course_id AND prerequisite = $id";
1258
        Database::query($sql_all);
1259
1260
        // Remove from search engine if enabled.
1261
        if (api_get_setting('search_enabled') == 'true') {
1262
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1263
            $sql = 'SELECT * FROM %s 
1264
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1265
                    LIMIT 1';
1266
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1267
            $res = Database::query($sql);
1268
            if (Database::num_rows($res) > 0) {
1269
                $row2 = Database::fetch_array($res);
1270
                $di = new ChamiloIndexer();
1271
                $di->remove_document($row2['search_did']);
1272
            }
1273
            $sql = 'DELETE FROM %s 
1274
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1275
                    LIMIT 1';
1276
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1277
            Database::query($sql);
1278
        }
1279
    }
1280
1281
    /**
1282
     * Updates an item's content in place.
1283
     *
1284
     * @param int    $id Element ID
1285
     * @param int    $parent Parent item ID
1286
     * @param int    $previous Previous item ID
1287
     * @param string $title Item title
1288
     * @param string $description Item description
1289
     * @param string $prerequisites Prerequisites (optional)
1290
     * @param array  $audio The array resulting of the $_FILES[mp3] element
1291
     * @param int    $max_time_allowed
1292
     * @param string $url
1293
     *
1294
     * @return bool True on success, false on error
1295
     * @throws \Doctrine\DBAL\DBALException
1296
     */
1297
    public function edit_item(
1298
        $id,
1299
        $parent,
1300
        $previous,
1301
        $title,
1302
        $description,
1303
        $prerequisites = '0',
1304
        $audio = [],
1305
        $max_time_allowed = 0,
1306
        $url = ''
1307
    ) {
1308
        $course_id = api_get_course_int_id();
1309
        $_course = api_get_course_info();
1310
1311
        if ($this->debug > 0) {
1312
            error_log('In learnpath::edit_item()', 0);
1313
        }
1314
        if (empty($max_time_allowed)) {
1315
            $max_time_allowed = 0;
1316
        }
1317
        if (empty($id) || ($id != strval(intval($id))) || empty($title)) {
1318
            return false;
1319
        }
1320
1321
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1322
        $sql = "SELECT * FROM $tbl_lp_item 
1323
                WHERE iid = $id";
1324
        $res_select = Database::query($sql);
1325
        $row_select = Database::fetch_array($res_select);
1326
        $audio_update_sql = '';
1327
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1328
            // Create the audio folder if it does not exist yet.
1329
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1330
            if (!is_dir($filepath.'audio')) {
1331
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1332
                $audio_id = add_document(
1333
                    $_course,
1334
                    '/audio',
1335
                    'folder',
1336
                    0,
1337
                    'audio'
1338
                );
1339
                api_item_property_update(
1340
                    $_course,
1341
                    TOOL_DOCUMENT,
1342
                    $audio_id,
1343
                    'FolderCreated',
1344
                    api_get_user_id(),
1345
                    null,
1346
                    null,
1347
                    null,
1348
                    null,
1349
                    api_get_session_id()
1350
                );
1351
                api_item_property_update(
1352
                    $_course,
1353
                    TOOL_DOCUMENT,
1354
                    $audio_id,
1355
                    'invisible',
1356
                    api_get_user_id(),
1357
                    null,
1358
                    null,
1359
                    null,
1360
                    null,
1361
                    api_get_session_id()
1362
                );
1363
            }
1364
1365
            // Upload file in documents.
1366
            $pi = pathinfo($audio['name']);
1367
            if ($pi['extension'] == 'mp3') {
1368
                $c_det = api_get_course_info($this->cc);
1369
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1370
                $path = handle_uploaded_document(
1371
                    $c_det,
1372
                    $audio,
1373
                    $bp,
1374
                    '/audio',
1375
                    api_get_user_id(),
1376
                    0,
1377
                    null,
1378
                    0,
1379
                    'rename',
1380
                    false,
1381
                    0
1382
                );
1383
                $path = substr($path, 7);
1384
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1385
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1386
            }
1387
        }
1388
1389
        $same_parent = ($row_select['parent_item_id'] == $parent) ? true : false;
1390
        $same_previous = ($row_select['previous_item_id'] == $previous) ? true : false;
1391
1392
        // TODO: htmlspecialchars to be checked for encoding related problems.
1393
        if ($same_parent && $same_previous) {
1394
            // Only update title and description.
1395
            $sql = "UPDATE $tbl_lp_item
1396
                    SET title = '".Database::escape_string($title)."',
1397
                        prerequisite = '".$prerequisites."',
1398
                        description = '".Database::escape_string($description)."'
1399
                        ".$audio_update_sql.",
1400
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1401
                    WHERE iid = $id";
1402
            Database::query($sql);
1403
        } else {
1404
            $old_parent = $row_select['parent_item_id'];
1405
            $old_previous = $row_select['previous_item_id'];
1406
            $old_next = $row_select['next_item_id'];
1407
            $old_order = $row_select['display_order'];
1408
            $old_prerequisite = $row_select['prerequisite'];
1409
            $old_max_time_allowed = $row_select['max_time_allowed'];
1410
1411
            /* BEGIN -- virtually remove the current item id */
1412
            /* for the next and previous item it is like the current item doesn't exist anymore */
1413
            if ($old_previous != 0) {
1414
                // Next
1415
                $sql = "UPDATE $tbl_lp_item
1416
                        SET next_item_id = $old_next
1417
                        WHERE iid = $old_previous";
1418
                Database::query($sql);
1419
            }
1420
1421
            if ($old_next != 0) {
1422
                // Previous
1423
                $sql = "UPDATE $tbl_lp_item
1424
                        SET previous_item_id = $old_previous
1425
                        WHERE iid = $old_next";
1426
                Database::query($sql);
1427
            }
1428
1429
            // display_order - 1 for every item with a display_order
1430
            // bigger then the display_order of the current item.
1431
            $sql = "UPDATE $tbl_lp_item
1432
                    SET display_order = display_order - 1
1433
                    WHERE
1434
                        c_id = $course_id AND
1435
                        display_order > $old_order AND
1436
                        lp_id = ".$this->lp_id." AND
1437
                        parent_item_id = $old_parent";
1438
            Database::query($sql);
1439
            /* END -- virtually remove the current item id */
1440
1441
            /* BEGIN -- update the current item id to his new location */
1442
            if ($previous == 0) {
1443
                // Select the data of the item that should come after the current item.
1444
                $sql = "SELECT id, display_order
1445
                        FROM $tbl_lp_item
1446
                        WHERE
1447
                            c_id = $course_id AND
1448
                            lp_id = ".$this->lp_id." AND
1449
                            parent_item_id = $parent AND
1450
                            previous_item_id = $previous";
1451
                $res_select_old = Database::query($sql);
1452
                $row_select_old = Database::fetch_array($res_select_old);
1453
1454
                // If the new parent didn't have children before.
1455
                if (Database::num_rows($res_select_old) == 0) {
1456
                    $new_next = 0;
1457
                    $new_order = 1;
1458
                } else {
1459
                    $new_next = $row_select_old['id'];
1460
                    $new_order = $row_select_old['display_order'];
1461
                }
1462
            } else {
1463
                // Select the data of the item that should come before the current item.
1464
                $sql = "SELECT next_item_id, display_order
1465
                        FROM $tbl_lp_item
1466
                        WHERE iid = $previous";
1467
                $res_select_old = Database::query($sql);
1468
                $row_select_old = Database::fetch_array($res_select_old);
1469
                $new_next = $row_select_old['next_item_id'];
1470
                $new_order = $row_select_old['display_order'] + 1;
1471
            }
1472
1473
            // TODO: htmlspecialchars to be checked for encoding related problems.
1474
            // Update the current item with the new data.
1475
            $sql = "UPDATE $tbl_lp_item
1476
                    SET
1477
                        title = '".Database::escape_string($title)."',
1478
                        description = '".Database::escape_string($description)."',
1479
                        parent_item_id = $parent,
1480
                        previous_item_id = $previous,
1481
                        next_item_id = $new_next,
1482
                        display_order = $new_order
1483
                        $audio_update_sql
1484
                    WHERE iid = $id";
1485
            Database::query($sql);
1486
1487
            if ($previous != 0) {
1488
                // Update the previous item's next_item_id.
1489
                $sql = "UPDATE $tbl_lp_item
1490
                        SET next_item_id = $id
1491
                        WHERE iid = $previous";
1492
                Database::query($sql);
1493
            }
1494
1495
            if ($new_next != 0) {
1496
                // Update the next item's previous_item_id.
1497
                $sql = "UPDATE $tbl_lp_item
1498
                        SET previous_item_id = $id
1499
                        WHERE iid = $new_next";
1500
                Database::query($sql);
1501
            }
1502
1503
            if ($old_prerequisite != $prerequisites) {
1504
                $sql = "UPDATE $tbl_lp_item
1505
                        SET prerequisite = '$prerequisites'
1506
                        WHERE iid = $id";
1507
                Database::query($sql);
1508
            }
1509
1510
            if ($old_max_time_allowed != $max_time_allowed) {
1511
                // update max time allowed
1512
                $sql = "UPDATE $tbl_lp_item
1513
                        SET max_time_allowed = $max_time_allowed
1514
                        WHERE iid = $id";
1515
                Database::query($sql);
1516
            }
1517
1518
            // Update all the items with the same or a bigger display_order than the current item.
1519
            $sql = "UPDATE $tbl_lp_item
1520
                    SET display_order = display_order + 1
1521
                    WHERE
1522
                       c_id = $course_id AND
1523
                       lp_id = ".$this->get_id()." AND
1524
                       iid <> $id AND
1525
                       parent_item_id = $parent AND
1526
                       display_order >= $new_order";
1527
            Database::query($sql);
1528
        }
1529
1530
        if ($row_select['item_type'] == 'link') {
1531
            $link = new Link();
1532
            $linkId = $row_select['path'];
1533
            $link->updateLink($linkId, $url);
1534
        }
1535
    }
1536
1537
    /**
1538
     * Updates an item's prereq in place.
1539
     *
1540
     * @param int    $id Element ID
1541
     * @param string $prerequisite_id Prerequisite Element ID
1542
     * @param int    $mastery_score Prerequisite min score
1543
     * @param int    $max_score Prerequisite max score
1544
     *
1545
     * @return bool True on success, false on error
1546
     * @throws \Doctrine\DBAL\DBALException
1547
     */
1548
    public function edit_item_prereq(
1549
        $id,
1550
        $prerequisite_id,
1551
        $mastery_score = 0,
1552
        $max_score = 100
1553
    ) {
1554
        $course_id = api_get_course_int_id();
1555
        if ($this->debug > 0) {
1556
            error_log('In learnpath::edit_item_prereq('.$id.','.$prerequisite_id.','.$mastery_score.','.$max_score.')', 0);
1557
        }
1558
1559
        if (empty($id) || ($id != strval(intval($id))) || empty($prerequisite_id)) {
1560
            return false;
1561
        }
1562
1563
        $prerequisite_id = intval($prerequisite_id);
1564
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1565
1566
        if (!is_numeric($mastery_score) || $mastery_score < 0) {
1567
            $mastery_score = 0;
1568
        }
1569
1570
        if (!is_numeric($max_score) || $max_score < 0) {
1571
            $max_score = 100;
1572
        }
1573
1574
        /*if ($mastery_score > $max_score) {
1575
            $max_score = $mastery_score;
1576
        }*/
1577
1578
        if (!is_numeric($prerequisite_id)) {
1579
            $prerequisite_id = 'NULL';
1580
        }
1581
1582
        $mastery_score = floatval($mastery_score);
1583
        $max_score = floatval($max_score);
1584
1585
        $sql = " UPDATE $tbl_lp_item
1586
                 SET
1587
                    prerequisite = $prerequisite_id ,
1588
                    prerequisite_min_score = $mastery_score ,
1589
                    prerequisite_max_score = $max_score
1590
                 WHERE iid = $id";
1591
        Database::query($sql);
1592
        // TODO: Update the item object (can be ignored for now because refreshed).
1593
        return true;
1594
    }
1595
1596
    /**
1597
     * Gets all the chapters belonging to the same parent as the item/chapter given
1598
     * Can also be called as abstract method.
1599
     *
1600
     * @param int $id Item ID
1601
     *
1602
     * @return array A list of all the "brother items" (or an empty array on failure)
1603
     * @throws \Doctrine\DBAL\DBALException
1604
     */
1605
    public function getSiblingDirectories($id)
1606
    {
1607
        $course_id = api_get_course_int_id();
1608
        if ($this->debug > 0) {
1609
            error_log('In learnpath::getSiblingDirectories()', 0);
1610
        }
1611
1612
        if (empty($id) || $id != strval(intval($id))) {
1613
            return [];
1614
        }
1615
1616
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1617
        $sql_parent = "SELECT * FROM $lp_item
1618
                       WHERE iid = $id AND item_type='dir'";
1619
        $res_parent = Database::query($sql_parent);
1620
        if (Database::num_rows($res_parent) > 0) {
1621
            $row_parent = Database::fetch_array($res_parent);
1622
            $parent = $row_parent['parent_item_id'];
1623
            $sql = "SELECT * FROM $lp_item
1624
                    WHERE
1625
                        parent_item_id = $parent AND
1626
                        iid = $id AND
1627
                        item_type='dir'
1628
                    ORDER BY display_order";
1629
            $res_bros = Database::query($sql);
1630
1631
            $list = [];
1632
            while ($row_bro = Database::fetch_array($res_bros)) {
1633
                $list[] = $row_bro;
1634
            }
1635
1636
            return $list;
1637
        }
1638
1639
        return [];
1640
    }
1641
1642
    /**
1643
     * Gets all the items belonging to the same parent as the item given
1644
     * Can also be called as abstract method.
1645
     *
1646
     * @param int $id Item ID
1647
     *
1648
     * @return array A list of all the "brother items" (or an empty array on failure)
1649
     * @throws \Doctrine\DBAL\DBALException
1650
     */
1651
    public function get_brother_items($id)
1652
    {
1653
        $course_id = api_get_course_int_id();
1654
        if ($this->debug > 0) {
1655
            error_log('In learnpath::get_brother_items('.$id.')', 0);
1656
        }
1657
1658
        if (empty($id) || $id != strval(intval($id))) {
1659
            return [];
1660
        }
1661
1662
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1663
        $sql_parent = "SELECT * FROM $lp_item 
1664
                       WHERE iid = $id";
1665
        $res_parent = Database::query($sql_parent);
1666
        if (Database::num_rows($res_parent) > 0) {
1667
            $row_parent = Database::fetch_array($res_parent);
1668
            $parent = $row_parent['parent_item_id'];
1669
            $sql = "SELECT * FROM $lp_item 
1670
                    WHERE c_id = $course_id AND parent_item_id = $parent
1671
                    ORDER BY display_order";
1672
            $res_bros = Database::query($sql);
1673
            $list = [];
1674
            while ($row_bro = Database::fetch_array($res_bros)) {
1675
                $list[] = $row_bro;
1676
            }
1677
1678
            return $list;
1679
        }
1680
1681
        return [];
1682
    }
1683
1684
    /**
1685
     * Get the specific prefix index terms of this learning path.
1686
     *
1687
     * @param string $prefix
1688
     *
1689
     * @return array Array of terms
1690
     */
1691
    public function get_common_index_terms_by_prefix($prefix)
1692
    {
1693
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1694
        $terms = get_specific_field_values_list_by_prefix(
1695
            $prefix,
1696
            $this->cc,
1697
            TOOL_LEARNPATH,
1698
            $this->lp_id
1699
        );
1700
        $prefix_terms = [];
1701
        if (!empty($terms)) {
1702
            foreach ($terms as $term) {
1703
                $prefix_terms[] = $term['value'];
1704
            }
1705
        }
1706
1707
        return $prefix_terms;
1708
    }
1709
1710
    /**
1711
     * Gets the number of items currently completed.
1712
     *
1713
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1714
     *
1715
     * @return int The number of items currently completed
1716
     */
1717
    public function get_complete_items_count($failedStatusException = false)
1718
    {
1719
        if ($this->debug > 0) {
1720
            error_log('In learnpath::get_complete_items_count()', 0);
1721
        }
1722
        $i = 0;
1723
        $completedStatusList = [
1724
            'completed',
1725
            'passed',
1726
            'succeeded',
1727
            'browsed',
1728
        ];
1729
1730
        if (!$failedStatusException) {
1731
            $completedStatusList[] = 'failed';
1732
        }
1733
1734
        foreach ($this->items as $id => $dummy) {
1735
            // Trying failed and browsed considered "progressed" as well.
1736
            if ($this->items[$id]->status_is($completedStatusList) &&
1737
                $this->items[$id]->get_type() != 'dir'
1738
            ) {
1739
                $i++;
1740
            }
1741
        }
1742
1743
        return $i;
1744
    }
1745
1746
    /**
1747
     * Gets the current item ID.
1748
     *
1749
     * @return int The current learnpath item id
1750
     */
1751
    public function get_current_item_id()
1752
    {
1753
        $current = 0;
1754
        if ($this->debug > 0) {
1755
            error_log('In learnpath::get_current_item_id()', 0);
1756
        }
1757
        if (!empty($this->current)) {
1758
            $current = $this->current;
1759
        }
1760
        if ($this->debug > 2) {
1761
            error_log('In learnpath::get_current_item_id() - Returning '.$current, 0);
1762
        }
1763
1764
        return $current;
1765
    }
1766
1767
    /**
1768
     * Force to get the first learnpath item id.
1769
     *
1770
     * @return int The current learnpath item id
1771
     */
1772
    public function get_first_item_id()
1773
    {
1774
        $current = 0;
1775
        if (is_array($this->ordered_items)) {
1776
            $current = $this->ordered_items[0];
1777
        }
1778
1779
        return $current;
1780
    }
1781
1782
    /**
1783
     * Gets the total number of items available for viewing in this SCORM.
1784
     *
1785
     * @return int The total number of items
1786
     */
1787
    public function get_total_items_count()
1788
    {
1789
        if ($this->debug > 0) {
1790
            error_log('In learnpath::get_total_items_count()', 0);
1791
        }
1792
1793
        return count($this->items);
1794
    }
1795
1796
    /**
1797
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1798
     *
1799
     * @return int The total no-chapters number of items
1800
     */
1801
    public function getTotalItemsCountWithoutDirs()
1802
    {
1803
        if ($this->debug > 0) {
1804
            error_log('In learnpath::getTotalItemsCountWithoutDirs()', 0);
1805
        }
1806
        $total = 0;
1807
        $typeListNotToCount = self::getChapterTypes();
1808
        foreach ($this->items as $temp2) {
1809
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1810
                $total++;
1811
            }
1812
        }
1813
1814
        return $total;
1815
    }
1816
1817
    /**
1818
     *  Sets the first element URL.
1819
     */
1820
    public function first()
1821
    {
1822
        if ($this->debug > 0) {
1823
            error_log('In learnpath::first()', 0);
1824
            error_log('$this->last_item_seen '.$this->last_item_seen);
1825
        }
1826
1827
        // Test if the last_item_seen exists and is not a dir.
1828
        if (count($this->ordered_items) == 0) {
1829
            $this->index = 0;
1830
        }
1831
1832
        if (!empty($this->last_item_seen) &&
1833
            !empty($this->items[$this->last_item_seen]) &&
1834
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1835
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1836
            //&& !$this->items[$this->last_item_seen]->is_done()
1837
        ) {
1838
            if ($this->debug > 2) {
1839
                error_log('In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.$this->items[$this->last_item_seen]->get_type(), 0);
1840
            }
1841
            $index = -1;
1842
            foreach ($this->ordered_items as $myindex => $item_id) {
1843
                if ($item_id == $this->last_item_seen) {
1844
                    $index = $myindex;
1845
                    break;
1846
                }
1847
            }
1848
            if ($index == -1) {
1849
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1850
                if ($this->debug > 2) {
1851
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1852
                }
1853
1854
                return false;
1855
            } else {
1856
                $this->last = $this->last_item_seen;
1857
                $this->current = $this->last_item_seen;
1858
                $this->index = $index;
1859
            }
1860
        } else {
1861
            if ($this->debug > 2) {
1862
                error_log('In learnpath::first() - No last item seen', 0);
1863
            }
1864
            $index = 0;
1865
            // Loop through all ordered items and stop at the first item that is
1866
            // not a directory *and* that has not been completed yet.
1867
            while (!empty($this->ordered_items[$index]) &&
1868
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1869
                (
1870
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1871
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1872
                ) && $index < $this->max_ordered_items) {
1873
                $index++;
1874
            }
1875
1876
            $this->last = $this->current;
1877
            // current is
1878
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1879
            $this->index = $index;
1880
            if ($this->debug > 2) {
1881
                error_log('$index '.$index);
1882
                error_log('In learnpath::first() - No last item seen');
1883
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1884
            }
1885
        }
1886
        if ($this->debug > 2) {
1887
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1888
        }
1889
    }
1890
1891
    /**
1892
     * Gets the information about an item in a format usable as JavaScript to update
1893
     * the JS API by just printing this content into the <head> section of the message frame.
1894
     *
1895
     * @param int $item_id
1896
     *
1897
     * @return string
1898
     */
1899
    public function get_js_info($item_id = 0)
1900
    {
1901
        if ($this->debug > 0) {
1902
            error_log('In learnpath::get_js_info('.$item_id.')', 0);
1903
        }
1904
1905
        $info = '';
1906
        $item_id = intval($item_id);
1907
1908
        if (!empty($item_id) && is_object($this->items[$item_id])) {
1909
            //if item is defined, return values from DB
1910
            $oItem = $this->items[$item_id];
1911
            $info .= '<script language="javascript">';
1912
            $info .= "top.set_score(".$oItem->get_score().");\n";
1913
            $info .= "top.set_max(".$oItem->get_max().");\n";
1914
            $info .= "top.set_min(".$oItem->get_min().");\n";
1915
            $info .= "top.set_lesson_status('".$oItem->get_status()."');";
1916
            $info .= "top.set_session_time('".$oItem->get_scorm_time('js')."');";
1917
            $info .= "top.set_suspend_data('".$oItem->get_suspend_data()."');";
1918
            $info .= "top.set_saved_lesson_status('".$oItem->get_status()."');";
1919
            $info .= "top.set_flag_synchronized();";
1920
            $info .= '</script>';
1921
            if ($this->debug > 2) {
1922
                error_log('in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
1923
            }
1924
1925
            return $info;
1926
        } else {
1927
            // If item_id is empty, just update to default SCORM data.
1928
            $info .= '<script language="javascript">';
1929
            $info .= "top.set_score(".learnpathItem::get_score().");\n";
0 ignored issues
show
Bug Best Practice introduced by
The method learnpathItem::get_score() is not static, but was called statically. ( Ignorable by Annotation )

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

1929
            $info .= "top.set_score(".learnpathItem::/** @scrutinizer ignore-call */ get_score().");\n";
Loading history...
1930
            $info .= "top.set_max(".learnpathItem::get_max().");\n";
0 ignored issues
show
Bug Best Practice introduced by
The method learnpathItem::get_max() is not static, but was called statically. ( Ignorable by Annotation )

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

1930
            $info .= "top.set_max(".learnpathItem::/** @scrutinizer ignore-call */ get_max().");\n";
Loading history...
1931
            $info .= "top.set_min(".learnpathItem::get_min().");\n";
0 ignored issues
show
Bug Best Practice introduced by
The method learnpathItem::get_min() is not static, but was called statically. ( Ignorable by Annotation )

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

1931
            $info .= "top.set_min(".learnpathItem::/** @scrutinizer ignore-call */ get_min().");\n";
Loading history...
1932
            $info .= "top.set_lesson_status('".learnpathItem::get_status()."');";
0 ignored issues
show
Bug Best Practice introduced by
The method learnpathItem::get_status() is not static, but was called statically. ( Ignorable by Annotation )

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

1932
            $info .= "top.set_lesson_status('".learnpathItem::/** @scrutinizer ignore-call */ get_status()."');";
Loading history...
1933
            $info .= "top.set_session_time('".learnpathItem::getScormTimeFromParameter('js')."');";
1934
            $info .= "top.set_suspend_data('".learnpathItem::get_suspend_data()."');";
0 ignored issues
show
Bug Best Practice introduced by
The method learnpathItem::get_suspend_data() is not static, but was called statically. ( Ignorable by Annotation )

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

1934
            $info .= "top.set_suspend_data('".learnpathItem::/** @scrutinizer ignore-call */ get_suspend_data()."');";
Loading history...
1935
            $info .= "top.set_saved_lesson_status('".learnpathItem::get_status()."');";
1936
            $info .= "top.set_flag_synchronized();";
1937
            $info .= '</script>';
1938
            if ($this->debug > 2) {
1939
                error_log('in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
1940
            }
1941
1942
            return $info;
1943
        }
1944
    }
1945
1946
    /**
1947
     * Gets the js library from the database.
1948
     *
1949
     * @return string The name of the javascript library to be used
1950
     */
1951
    public function get_js_lib()
1952
    {
1953
        $lib = '';
1954
        if (!empty($this->js_lib)) {
1955
            $lib = $this->js_lib;
1956
        }
1957
1958
        return $lib;
1959
    }
1960
1961
    /**
1962
     * Gets the learnpath database ID.
1963
     *
1964
     * @return int Learnpath ID in the lp table
1965
     */
1966
    public function get_id()
1967
    {
1968
        if (!empty($this->lp_id)) {
1969
            return $this->lp_id;
1970
        } else {
1971
            return 0;
1972
        }
1973
    }
1974
1975
    /**
1976
     * Gets the last element URL.
1977
     *
1978
     * @return string URL to load into the viewer
1979
     */
1980
    public function get_last()
1981
    {
1982
        if ($this->debug > 0) {
1983
            error_log('In learnpath::get_last()', 0);
1984
        }
1985
        //This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1986
        if (count($this->ordered_items) > 0) {
1987
            $this->index = count($this->ordered_items) - 1;
1988
1989
            return $this->ordered_items[$this->index];
1990
        }
1991
1992
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
1993
    }
1994
1995
    /**
1996
     * Gets the navigation bar for the learnpath display screen.
1997
     *
1998
     * @return string The HTML string to use as a navigation bar
1999
     */
2000
    public function get_navigation_bar($idBar = null, $display = null)
2001
    {
2002
        if ($this->debug > 0) {
2003
            error_log('In learnpath::get_navigation_bar()', 0);
2004
        }
2005
        if (empty($idBar)) {
2006
            $idBar = 'control-top';
2007
        }
2008
        $lpId = $this->lp_id;
2009
        $mycurrentitemid = $this->get_current_item_id();
2010
2011
        $reportingText = get_lang('Reporting');
2012
        $previousText = get_lang('ScormPrevious');
2013
        $nextText = get_lang('ScormNext');
2014
        $fullScreenText = get_lang('ScormExitFullScreen');
2015
2016
        $settings = api_get_configuration_value('lp_view_settings');
2017
        $display = isset($settings['display']) ? $settings['display'] : false;
2018
        $reportingIcon = '
2019
            <a class="icon-toolbar" 
2020
                id="stats_link"
2021
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'" 
2022
                onclick="window.parent.API.save_asset(); return true;" 
2023
                target="content_name" title="'.$reportingText.'">
2024
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
2025
            </a>';
2026
2027
        if (!empty($display)) {
2028
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
2029
            if ($showReporting == false) {
2030
                $reportingIcon = '';
2031
            }
2032
        }
2033
2034
        $previousIcon = '
2035
            <a class="icon-toolbar" id="scorm-previous" href="#" 
2036
                onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
2037
                <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
2038
            </a>';
2039
2040
        $nextIcon = '
2041
            <a class="icon-toolbar" id="scorm-next" href="#" 
2042
                onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
2043
                <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
2044
            </a>';
2045
2046
        if ($this->mode == 'fullscreen') {
2047
            $navbar = '
2048
                  <span id="'.$idBar.'" class="buttons">
2049
                    '.$reportingIcon.'
2050
                    '.$previousIcon.'                    
2051
                    '.$nextIcon.'
2052
                    <a class="icon-toolbar" id="view-embedded" 
2053
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
2054
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
2055
                    </a>
2056
                  </span>';
2057
        } else {
2058
            $navbar = '
2059
            <span id="'.$idBar.'" class="buttons text-right">
2060
                '.$reportingIcon.'
2061
                '.$previousIcon.'
2062
                '.$nextIcon.'               
2063
            </span>';
2064
        }
2065
2066
        return $navbar;
2067
    }
2068
2069
    /**
2070
     * Gets the next resource in queue (url).
2071
     *
2072
     * @return string URL to load into the viewer
2073
     */
2074
    public function get_next_index()
2075
    {
2076
        if ($this->debug > 0) {
2077
            error_log('In learnpath::get_next_index()', 0);
2078
        }
2079
        // TODO
2080
        $index = $this->index;
2081
        $index++;
2082
        if ($this->debug > 2) {
2083
            error_log('Now looking at ordered_items['.($index).'] - type is '.$this->items[$this->ordered_items[$index]]->type, 0);
2084
        }
2085
        while (
2086
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
2087
            $index < $this->max_ordered_items
2088
        ) {
2089
            $index++;
2090
            if ($index == $this->max_ordered_items) {
2091
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
2092
                    return $this->index;
2093
                } else {
2094
                    return $index;
2095
                }
2096
            }
2097
        }
2098
        if (empty($this->ordered_items[$index])) {
2099
            return $this->index;
2100
        }
2101
        if ($this->debug > 2) {
2102
            error_log('index is now '.$index, 0);
2103
        }
2104
2105
        return $index;
2106
    }
2107
2108
    /**
2109
     * Gets item_id for the next element.
2110
     *
2111
     * @return int Next item (DB) ID
2112
     */
2113
    public function get_next_item_id()
2114
    {
2115
        if ($this->debug > 0) {
2116
            error_log('In learnpath::get_next_item_id()', 0);
2117
        }
2118
        $new_index = $this->get_next_index();
2119
        if (!empty($new_index)) {
2120
            if (isset($this->ordered_items[$new_index])) {
2121
                if ($this->debug > 2) {
2122
                    error_log('In learnpath::get_next_index() - Returning '.$this->ordered_items[$new_index], 0);
2123
                }
2124
2125
                return $this->ordered_items[$new_index];
2126
            }
2127
        }
2128
        if ($this->debug > 2) {
2129
            error_log('In learnpath::get_next_index() - Problem - Returning 0', 0);
2130
        }
2131
2132
        return 0;
2133
    }
2134
2135
    /**
2136
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
2137
     *
2138
     * Generally, the package provided is in the form of a zip file, so the function
2139
     * has been written to test a zip file. If not a zip, the function will return the
2140
     * default return value: ''
2141
     *
2142
     * @param	string	the path to the file
2143
     * @param	string 	the original name of the file
2144
     *
2145
     * @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
2146
     */
2147
    public static function get_package_type($file_path, $file_name)
2148
    {
2149
        // Get name of the zip file without the extension.
2150
        $file_info = pathinfo($file_name);
2151
        $filename = $file_info['basename']; // Name including extension.
2152
        $extension = $file_info['extension']; // Extension only.
2153
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
2154
                'dll',
2155
                'exe',
2156
            ])) {
2157
            return 'oogie';
2158
        }
2159
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
2160
                'dll',
2161
                'exe',
2162
            ])) {
2163
            return 'woogie';
2164
        }
2165
2166
        $zipFile = new PclZip($file_path);
2167
        // Check the zip content (real size and file extension).
2168
        $zipContentArray = $zipFile->listContent();
2169
        $package_type = '';
2170
        $at_root = false;
2171
        $manifest = '';
2172
        $aicc_match_crs = 0;
2173
        $aicc_match_au = 0;
2174
        $aicc_match_des = 0;
2175
        $aicc_match_cst = 0;
2176
2177
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
2178
        if (is_array($zipContentArray) && count($zipContentArray) > 0) {
2179
            foreach ($zipContentArray as $thisContent) {
2180
                if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
2181
                    // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
2182
                } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
2183
                    $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
2184
                    $package_type = 'scorm';
2185
                    break; // Exit the foreach loop.
2186
                } elseif (
2187
                    preg_match('/aicc\//i', $thisContent['filename']) ||
2188
                    in_array(
2189
                        strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
2190
                        ['crs', 'au', 'des', 'cst']
2191
                    )
2192
                ) {
2193
                    $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
2194
                    switch ($ext) {
2195
                        case 'crs':
2196
                            $aicc_match_crs = 1;
2197
                            break;
2198
                        case 'au':
2199
                            $aicc_match_au = 1;
2200
                            break;
2201
                        case 'des':
2202
                            $aicc_match_des = 1;
2203
                            break;
2204
                        case 'cst':
2205
                            $aicc_match_cst = 1;
2206
                            break;
2207
                        default:
2208
                            break;
2209
                    }
2210
                    //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
2211
                } else {
2212
                    $package_type = '';
2213
                }
2214
            }
2215
        }
2216
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
2217
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
2218
            $package_type = 'aicc';
2219
        }
2220
2221
        return $package_type;
2222
    }
2223
2224
    /**
2225
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
2226
     *
2227
     * @return string URL to load into the viewer
2228
     */
2229
    public function get_previous_index()
2230
    {
2231
        if ($this->debug > 0) {
2232
            error_log('In learnpath::get_previous_index()', 0);
2233
        }
2234
        $index = $this->index;
2235
        if (isset($this->ordered_items[$index - 1])) {
2236
            $index--;
2237
            while (isset($this->ordered_items[$index]) &&
2238
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2239
            ) {
2240
                $index--;
2241
                if ($index < 0) {
2242
                    return $this->index;
2243
                }
2244
            }
2245
        } else {
2246
            if ($this->debug > 2) {
2247
                error_log('get_previous_index() - there was no previous index available, reusing '.$index, 0);
2248
            }
2249
            // There is no previous item.
2250
        }
2251
2252
        return $index;
2253
    }
2254
2255
    /**
2256
     * Gets item_id for the next element.
2257
     *
2258
     * @return int Previous item (DB) ID
2259
     */
2260
    public function get_previous_item_id()
2261
    {
2262
        if ($this->debug > 0) {
2263
            error_log('In learnpath::get_previous_item_id()', 0);
2264
        }
2265
        $new_index = $this->get_previous_index();
2266
2267
        return $this->ordered_items[$new_index];
2268
    }
2269
2270
    /**
2271
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2272
     *
2273
     * @param int    $lpItemId
2274
     * @param string $autostart
2275
     *
2276
     * @return string The mediaplayer HTML
2277
     * @throws \Doctrine\DBAL\DBALException
2278
     */
2279
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2280
    {
2281
        $course_id = api_get_course_int_id();
2282
        $_course = api_get_course_info();
2283
        if (empty($_course)) {
2284
            return '';
2285
        }
2286
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2287
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2288
        $lpItemId = (int) $lpItemId;
2289
2290
        // Getting all the information about the item.
2291
        $sql = "SELECT * FROM $tbl_lp_item as lpi
2292
                INNER JOIN $tbl_lp_item_view as lp_view
2293
                ON (lpi.iid = lp_view.lp_item_id)
2294
                WHERE
2295
                    lpi.iid = $lpItemId AND
2296
                    lp_view.c_id = $course_id";
2297
        $result = Database::query($sql);
2298
        $row = Database::fetch_assoc($result);
2299
        $output = '';
2300
2301
        if (!empty($row['audio'])) {
2302
            $list = $_SESSION['oLP']->get_toc();
2303
            $type_quiz = false;
2304
2305
            foreach ($list as $toc) {
2306
                if ($toc['id'] == $_SESSION['oLP']->current && ($toc['type'] == 'quiz')) {
2307
                    $type_quiz = true;
2308
                }
2309
            }
2310
2311
            if ($type_quiz) {
2312
                if ($_SESSION['oLP']->prevent_reinit == 1) {
2313
                    $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2314
                } else {
2315
                    $autostart_audio = $autostart;
2316
                }
2317
            } else {
2318
                $autostart_audio = 'true';
2319
            }
2320
2321
            $courseInfo = api_get_course_info();
2322
            $audio = $row['audio'];
2323
2324
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
2325
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio.'?'.api_get_cidreq();
2326
2327
            if (!file_exists($file)) {
2328
                $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info());
2329
                $file = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio;
2330
                $url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio.'?'.api_get_cidreq();
2331
            }
2332
2333
            $player = Display::getMediaPlayer(
2334
                $file,
2335
                [
2336
                    'id' => 'lp_audio_media_player',
2337
                    'url' => $url,
2338
                    'autoplay' => $autostart_audio,
2339
                    'width' => '100%',
2340
                ]
2341
            );
2342
2343
            // The mp3 player.
2344
            $output = '<div id="container">';
2345
            $output .= $player;
2346
            $output .= '</div>';
2347
        }
2348
2349
        return $output;
2350
    }
2351
2352
    /**
2353
     * @param int   $studentId
2354
     * @param int   $prerequisite
2355
     * @param array $courseInfo
2356
     * @param int   $sessionId
2357
     *
2358
     * @return bool
2359
     */
2360
    public static function isBlockedByPrerequisite(
2361
        $studentId,
2362
        $prerequisite,
2363
        $courseInfo,
2364
        $sessionId
2365
    ) {
2366
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2367
        if ($allow) {
2368
            if (api_is_allowed_to_edit() || api_is_platform_admin() || api_is_drh()) {
2369
                return false;
2370
            }
2371
        }
2372
2373
        $isBlocked = false;
2374
2375
        if (!empty($prerequisite)) {
2376
            $progress = self::getProgress(
2377
                $prerequisite,
2378
                $studentId,
2379
                $courseInfo['real_id'],
2380
                $sessionId
2381
            );
2382
            if ($progress < 100) {
2383
                $isBlocked = true;
2384
            }
2385
        }
2386
2387
        return $isBlocked;
2388
    }
2389
2390
    /**
2391
     * Checks if the learning path is visible for student after the progress
2392
     * of its prerequisite is completed, considering the time availability and
2393
     * the LP visibility.
2394
     *
2395
     * @param int  $lp_id
2396
     * @param int  $student_id
2397
     * @param null $courseCode
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $courseCode is correct as it would always require null to be passed?
Loading history...
2398
     * @param int  $sessionId
2399
     *
2400
     * @return bool
2401
     * @throws \Doctrine\DBAL\DBALException
2402
     */
2403
    public static function is_lp_visible_for_student(
2404
        $lp_id,
2405
        $student_id,
2406
        $courseCode = null,
2407
        $sessionId = 0
2408
    ) {
2409
        $courseInfo = api_get_course_info($courseCode);
2410
        $lp_id = (int) $lp_id;
2411
        $sessionId = (int) $sessionId;
2412
2413
        if (empty($courseInfo)) {
2414
            return false;
2415
        }
2416
2417
        if (empty($sessionId)) {
2418
            $sessionId = api_get_session_id();
2419
        }
2420
2421
        $itemInfo = api_get_item_property_info(
2422
            $courseInfo['real_id'],
2423
            TOOL_LEARNPATH,
2424
            $lp_id,
2425
            $sessionId
2426
        );
2427
2428
        // If the item was deleted.
2429
        if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
2430
            return false;
2431
        }
2432
2433
        // @todo remove this query and load the row info as a parameter
2434
        $tbl_learnpath = Database::get_course_table(TABLE_LP_MAIN);
2435
        // Get current prerequisite
2436
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on
2437
                FROM $tbl_learnpath
2438
                WHERE iid = $lp_id";
2439
        $rs = Database::query($sql);
2440
        $now = time();
2441
        if (Database::num_rows($rs) > 0) {
2442
            $row = Database::fetch_array($rs, 'ASSOC');
2443
            $prerequisite = $row['prerequisite'];
2444
            $is_visible = true;
2445
2446
            $isBlocked = self::isBlockedByPrerequisite(
2447
                $student_id,
2448
                $prerequisite,
2449
                $courseInfo,
2450
                $sessionId
2451
            );
2452
2453
            if ($isBlocked) {
2454
                $is_visible = false;
2455
            }
2456
2457
            // Also check the time availability of the LP
2458
            if ($is_visible) {
2459
                // Adding visibility restrictions
2460
                if (!empty($row['publicated_on'])) {
2461
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2462
                        $is_visible = false;
2463
                    }
2464
                }
2465
                // Blocking empty start times see BT#2800
2466
                global $_custom;
2467
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2468
                    $_custom['lps_hidden_when_no_start_date']
2469
                ) {
2470
                    if (empty($row['publicated_on'])) {
2471
                        $is_visible = false;
2472
                    }
2473
                }
2474
2475
                if (!empty($row['expired_on'])) {
2476
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2477
                        $is_visible = false;
2478
                    }
2479
                }
2480
            }
2481
2482
            // Check if the subscription users/group to a LP is ON
2483
            if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1) {
2484
                // Try group
2485
                $is_visible = false;
2486
                // Checking only the user visibility
2487
                $userVisibility = api_get_item_visibility(
2488
                    $courseInfo,
2489
                    'learnpath',
2490
                    $row['id'],
2491
                    $sessionId,
2492
                    $student_id,
2493
                    'LearnpathSubscription'
2494
                );
2495
2496
                if ($userVisibility == 1) {
2497
                    $is_visible = true;
2498
                } else {
2499
                    $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id);
2500
                    if (!empty($userGroups)) {
2501
                        foreach ($userGroups as $groupInfo) {
2502
                            $groupId = $groupInfo['iid'];
2503
                            $userVisibility = api_get_item_visibility(
2504
                                $courseInfo,
2505
                                'learnpath',
2506
                                $row['id'],
2507
                                $sessionId,
2508
                                null,
2509
                                'LearnpathSubscription',
2510
                                $groupId
2511
                            );
2512
2513
                            if ($userVisibility == 1) {
2514
                                $is_visible = true;
2515
                                break;
2516
                            }
2517
                        }
2518
                    }
2519
                }
2520
            }
2521
2522
            return $is_visible;
2523
        }
2524
2525
        return false;
2526
    }
2527
2528
    /**
2529
     * @param int $lpId
2530
     * @param int $userId
2531
     * @param int $courseId
2532
     * @param int $sessionId
2533
     *
2534
     * @return int
2535
     * @throws \Doctrine\DBAL\DBALException
2536
     */
2537
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2538
    {
2539
        $lpId = intval($lpId);
2540
        $userId = intval($userId);
2541
        $courseId = intval($courseId);
2542
        $sessionId = intval($sessionId);
2543
        $progress = 0;
2544
2545
        $sessionCondition = api_get_session_condition($sessionId);
2546
        $table = Database::get_course_table(TABLE_LP_VIEW);
2547
        $sql = "SELECT * FROM $table
2548
                WHERE
2549
                    c_id = $courseId AND
2550
                    lp_id = $lpId AND
2551
                    user_id = $userId $sessionCondition ";
2552
        $res = Database::query($sql);
2553
        if (Database::num_rows($res) > 0) {
2554
            $row = Database:: fetch_array($res);
2555
            $progress = $row['progress'];
2556
        }
2557
2558
        return (int) $progress;
2559
    }
2560
2561
    /**
2562
     * Displays a progress bar
2563
     * completed so far.
2564
     *
2565
     * @param int    $percentage Progress value to display
2566
     * @param string $text_add   Text to display near the progress value
2567
     *
2568
     * @return string HTML string containing the progress bar
2569
     */
2570
    public static function get_progress_bar($percentage = -1, $text_add = '')
2571
    {
2572
        $text = $percentage.$text_add;
2573
        $output = '<div class="progress">
2574
            <div id="progress_bar_value" 
2575
                class="progress-bar progress-bar-success" role="progressbar" 
2576
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2577
            '.$text.'
2578
            </div>
2579
        </div>';
2580
2581
        return $output;
2582
    }
2583
2584
    /**
2585
     * @param string $mode can be '%' or 'abs'
2586
     *                     otherwise this value will be used $this->progress_bar_mode
2587
     *
2588
     * @return string
2589
     */
2590
    public function getProgressBar($mode = null)
2591
    {
2592
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2593
2594
        return self::get_progress_bar($percentage, $text_add);
2595
    }
2596
2597
    /**
2598
     * Gets the progress bar info to display inside the progress bar.
2599
     * Also used by scorm_api.php.
2600
     *
2601
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2602
     *                     we display a number of completed elements per total elements
2603
     * @param int    $add  Additional steps to fake as completed
2604
     *
2605
     * @return array Percentage or number and symbol (% or /xx)
2606
     */
2607
    public function get_progress_bar_text($mode = '', $add = 0)
2608
    {
2609
        if ($this->debug > 0) {
2610
            error_log('In learnpath::get_progress_bar_text()', 0);
2611
        }
2612
        if (empty($mode)) {
2613
            $mode = $this->progress_bar_mode;
2614
        }
2615
        $total_items = $this->getTotalItemsCountWithoutDirs();
2616
        if ($this->debug > 2) {
2617
            error_log('Total items available in this learnpath: '.$total_items, 0);
2618
        }
2619
        $completeItems = $this->get_complete_items_count();
2620
        if ($this->debug > 2) {
2621
            error_log('Items completed so far: '.$completeItems, 0);
2622
        }
2623
        if ($add != 0) {
2624
            $completeItems += $add;
2625
            if ($this->debug > 2) {
2626
                error_log('Items completed so far (+modifier): '.$completeItems, 0);
2627
            }
2628
        }
2629
        $text = '';
2630
        if ($completeItems > $total_items) {
2631
            $completeItems = $total_items;
2632
        }
2633
        $percentage = 0;
2634
        if ($mode == '%') {
2635
            if ($total_items > 0) {
2636
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2637
            } else {
2638
                $percentage = 0;
2639
            }
2640
            $percentage = number_format($percentage, 0);
2641
            $text = '%';
2642
        } elseif ($mode == 'abs') {
2643
            $percentage = $completeItems;
2644
            $text = '/'.$total_items;
2645
        }
2646
2647
        return [
2648
            $percentage,
2649
            $text,
2650
        ];
2651
    }
2652
2653
    /**
2654
     * Gets the progress bar mode.
2655
     *
2656
     * @return string The progress bar mode attribute
2657
     */
2658
    public function get_progress_bar_mode()
2659
    {
2660
        if ($this->debug > 0) {
2661
            error_log('In learnpath::get_progress_bar_mode()', 0);
2662
        }
2663
        if (!empty($this->progress_bar_mode)) {
2664
            return $this->progress_bar_mode;
2665
        } else {
2666
            return '%';
2667
        }
2668
    }
2669
2670
    /**
2671
     * Gets the learnpath theme (remote or local).
2672
     *
2673
     * @return string Learnpath theme
2674
     */
2675
    public function get_theme()
2676
    {
2677
        if ($this->debug > 0) {
2678
            error_log('In learnpath::get_theme()', 0);
2679
        }
2680
        if (!empty($this->theme)) {
2681
            return $this->theme;
2682
        } else {
2683
            return '';
2684
        }
2685
    }
2686
2687
    /**
2688
     * Gets the learnpath session id.
2689
     *
2690
     * @return int
2691
     */
2692
    public function get_lp_session_id()
2693
    {
2694
        if ($this->debug > 0) {
2695
            error_log('In learnpath::get_lp_session_id()', 0);
2696
        }
2697
        if (!empty($this->lp_session_id)) {
2698
            return (int) $this->lp_session_id;
2699
        } else {
2700
            return 0;
2701
        }
2702
    }
2703
2704
    /**
2705
     * Gets the learnpath image.
2706
     *
2707
     * @return string Web URL of the LP image
2708
     */
2709
    public function get_preview_image()
2710
    {
2711
        if ($this->debug > 0) {
2712
            error_log('In learnpath::get_preview_image()', 0);
2713
        }
2714
        if (!empty($this->preview_image)) {
2715
            return $this->preview_image;
2716
        } else {
2717
            return '';
2718
        }
2719
    }
2720
2721
    /**
2722
     * @param string $size
2723
     * @param string $path_type
2724
     *
2725
     * @return bool|string
2726
     */
2727
    public function get_preview_image_path($size = null, $path_type = 'web')
2728
    {
2729
        $preview_image = $this->get_preview_image();
2730
        if (isset($preview_image) && !empty($preview_image)) {
2731
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2732
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2733
2734
            if (isset($size)) {
2735
                $info = pathinfo($preview_image);
2736
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2737
2738
                if (file_exists($image_sys_path.$image_custom_size)) {
2739
                    if ($path_type == 'web') {
2740
                        return $image_path.$image_custom_size;
2741
                    } else {
2742
                        return $image_sys_path.$image_custom_size;
2743
                    }
2744
                }
2745
            } else {
2746
                if ($path_type == 'web') {
2747
                    return $image_path.$preview_image;
2748
                } else {
2749
                    return $image_sys_path.$preview_image;
2750
                }
2751
            }
2752
        }
2753
2754
        return false;
2755
    }
2756
2757
    /**
2758
     * Gets the learnpath author.
2759
     *
2760
     * @return string LP's author
2761
     */
2762
    public function get_author()
2763
    {
2764
        if ($this->debug > 0) {
2765
            error_log('In learnpath::get_author()', 0);
2766
        }
2767
        if (!empty($this->author)) {
2768
            return $this->author;
2769
        } else {
2770
            return '';
2771
        }
2772
    }
2773
2774
    /**
2775
     * Gets hide table of contents.
2776
     *
2777
     * @return int
2778
     */
2779
    public function getHideTableOfContents()
2780
    {
2781
        return (int) $this->hide_toc_frame;
2782
    }
2783
2784
    /**
2785
     * Generate a new prerequisites string for a given item. If this item was a sco and
2786
     * its prerequisites were strings (instead of IDs), then transform those strings into
2787
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2788
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2789
     * same rule as the scorm_export() method.
2790
     *
2791
     * @param    int        Item ID
2792
     *
2793
     * @return string Prerequisites string ready for the export as SCORM
2794
     */
2795
    public function get_scorm_prereq_string($item_id)
2796
    {
2797
        if ($this->debug > 0) {
2798
            error_log('In learnpath::get_scorm_prereq_string()');
2799
        }
2800
        if (!is_object($this->items[$item_id])) {
2801
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
2802
        }
2803
        /** @var learnpathItem $oItem */
2804
        $oItem = $this->items[$item_id];
2805
        $prereq = $oItem->get_prereq_string();
2806
2807
        if (empty($prereq)) {
2808
            return '';
2809
        }
2810
        if (preg_match('/^\d+$/', $prereq) && is_object($this->items[$prereq])) {
2811
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2812
            // then simply return it (with the ITEM_ prefix).
2813
            //return 'ITEM_' . $prereq;
2814
            return $this->items[$prereq]->ref;
2815
        } else {
2816
            if (isset($this->refs_list[$prereq])) {
2817
                // It's a simple string item from which the ID can be found in the refs list,
2818
                // so we can transform it directly to an ID for export.
2819
                return $this->items[$this->refs_list[$prereq]]->ref;
2820
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2821
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2822
            } else {
2823
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2824
                // and replace them, one by one, by the internal IDs (chamilo db)
2825
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2826
                // by a space as well.
2827
                $find = [
2828
                    '&',
2829
                    '|',
2830
                    '~',
2831
                    '=',
2832
                    '<>',
2833
                    '{',
2834
                    '}',
2835
                    '*',
2836
                    '(',
2837
                    ')',
2838
                ];
2839
                $replace = [
2840
                    ' ',
2841
                    ' ',
2842
                    ' ',
2843
                    ' ',
2844
                    ' ',
2845
                    ' ',
2846
                    ' ',
2847
                    ' ',
2848
                    ' ',
2849
                    ' ',
2850
                ];
2851
                $prereq_mod = str_replace($find, $replace, $prereq);
2852
                $ids = explode(' ', $prereq_mod);
2853
                foreach ($ids as $id) {
2854
                    $id = trim($id);
2855
                    if (isset($this->refs_list[$id])) {
2856
                        $prereq = preg_replace(
2857
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2858
                            'ITEM_'.$this->refs_list[$id],
2859
                            $prereq
2860
                        );
2861
                    }
2862
                }
2863
2864
                return $prereq;
2865
            }
2866
        }
2867
    }
2868
2869
    /**
2870
     * Returns the XML DOM document's node.
2871
     *
2872
     * @param    resource    Reference to a list of objects to search for the given ITEM_*
2873
     * @param    string        The identifier to look for
2874
     *
2875
     * @return mixed The reference to the element found with that identifier. False if not found
2876
     */
2877
    public function get_scorm_xml_node(&$children, $id)
2878
    {
2879
        for ($i = 0; $i < $children->length; $i++) {
2880
            $item_temp = $children->item($i);
2881
            if ($item_temp->nodeName == 'item') {
2882
                if ($item_temp->getAttribute('identifier') == $id) {
2883
                    return $item_temp;
2884
                }
2885
            }
2886
            $subchildren = $item_temp->childNodes;
2887
            if ($subchildren && $subchildren->length > 0) {
2888
                $val = $this->get_scorm_xml_node($subchildren, $id);
2889
                if (is_object($val)) {
2890
                    return $val;
2891
                }
2892
            }
2893
        }
2894
2895
        return false;
2896
    }
2897
2898
    /**
2899
     * Gets the status list for all LP's items.
2900
     *
2901
     * @return array Array of [index] => [item ID => current status]
2902
     */
2903
    public function get_items_status_list()
2904
    {
2905
        if ($this->debug > 0) {
2906
            error_log('In learnpath::get_items_status_list()', 0);
2907
        }
2908
        $list = [];
2909
        foreach ($this->ordered_items as $item_id) {
2910
            $list[] = [
2911
                $item_id => $this->items[$item_id]->get_status(),
2912
            ];
2913
        }
2914
2915
        return $list;
2916
    }
2917
2918
    /**
2919
     * Return the number of interactions for the given learnpath Item View ID.
2920
     * This method can be used as static.
2921
     *
2922
     * @param int $lp_iv_id Item View ID
2923
     * @param int $course_id course id
2924
     *
2925
     * @return int
2926
     * @throws \Doctrine\DBAL\DBALException
2927
     */
2928
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2929
    {
2930
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2931
        $lp_iv_id = intval($lp_iv_id);
2932
        $course_id = intval($course_id);
2933
2934
        $sql = "SELECT count(*) FROM $table
2935
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2936
        $res = Database::query($sql);
2937
        $num = 0;
2938
        if (Database::num_rows($res)) {
2939
            $row = Database::fetch_array($res);
2940
            $num = $row[0];
2941
        }
2942
2943
        return $num;
2944
    }
2945
2946
    /**
2947
     * Return the interactions as an array for the given lp_iv_id.
2948
     * This method can be used as static.
2949
     *
2950
     * @param int    Learnpath Item View ID
2951
     *
2952
     * @return array
2953
     *
2954
     * @throws \Doctrine\DBAL\DBALException
2955
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2956
     */
2957
    public static function get_iv_interactions_array($lp_iv_id)
2958
    {
2959
        $course_id = api_get_course_int_id();
2960
        $list = [];
2961
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2962
2963
        if (empty($lp_iv_id)) {
2964
            return [];
2965
        }
2966
2967
        $sql = "SELECT * FROM $table
2968
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2969
                ORDER BY order_id ASC";
2970
        $res = Database::query($sql);
2971
        $num = Database::num_rows($res);
2972
        if ($num > 0) {
2973
            $list[] = [
2974
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2975
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
2976
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2977
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
2978
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
2979
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
2980
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2981
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
2982
            ];
2983
            while ($row = Database::fetch_array($res)) {
2984
                $list[] = [
2985
                    'order_id' => ($row['order_id'] + 1),
2986
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2987
                    'type' => $row['interaction_type'],
2988
                    'time' => $row['completion_time'],
2989
                    //'correct_responses' => $row['correct_responses'],
2990
                    'correct_responses' => '', // Hide correct responses from students.
2991
                    'student_response' => $row['student_response'],
2992
                    'result' => $row['result'],
2993
                    'latency' => $row['latency'],
2994
                ];
2995
            }
2996
        }
2997
2998
        return $list;
2999
    }
3000
3001
    /**
3002
     * Return the number of objectives for the given learnpath Item View ID.
3003
     * This method can be used as static.
3004
     *
3005
     * @param int   Item View ID
3006
     *
3007
     * @return int Number of objectives
3008
     * @throws \Doctrine\DBAL\DBALException
3009
     */
3010
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
3011
    {
3012
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3013
        $course_id = intval($course_id);
3014
        $lp_iv_id = intval($lp_iv_id);
3015
        $sql = "SELECT count(*) FROM $table
3016
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
3017
        //@todo seems that this always returns 0
3018
        $res = Database::query($sql);
3019
        $num = 0;
3020
        if (Database::num_rows($res)) {
3021
            $row = Database::fetch_array($res);
3022
            $num = $row[0];
3023
        }
3024
3025
        return $num;
3026
    }
3027
3028
    /**
3029
     * Return the objectives as an array for the given lp_iv_id.
3030
     * This method can be used as static.
3031
     *
3032
     * @param int $lpItemViewId Learnpath Item View ID
3033
     *
3034
     * @return array
3035
     *
3036
     * @throws \Doctrine\DBAL\DBALException
3037
     * @todo    Translate labels
3038
     */
3039
    public static function get_iv_objectives_array($lpItemViewId = 0)
3040
    {
3041
        $course_id = api_get_course_int_id();
3042
        $lpItemViewId = (int) $lpItemViewId;
3043
3044
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3045
        $sql = "SELECT * FROM $table
3046
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
3047
                ORDER BY order_id ASC";
3048
        $res = Database::query($sql);
3049
        $num = Database::num_rows($res);
3050
        $list = [];
3051
        if ($num > 0) {
3052
            $list[] = [
3053
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3054
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
3055
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
3056
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
3057
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
3058
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
3059
            ];
3060
            while ($row = Database::fetch_array($res)) {
3061
                $list[] = [
3062
                    'order_id' => ($row['order_id'] + 1),
3063
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
3064
                    'score_raw' => $row['score_raw'],
3065
                    'score_max' => $row['score_max'],
3066
                    'score_min' => $row['score_min'],
3067
                    'status' => $row['status'],
3068
                ];
3069
            }
3070
        }
3071
3072
        return $list;
3073
    }
3074
3075
    /**
3076
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
3077
     * used by get_html_toc() to be ready to display.
3078
     *
3079
     * @return array TOC as a table with 4 elements per row: title, link, status and level
3080
     */
3081
    public function get_toc()
3082
    {
3083
        if ($this->debug > 0) {
3084
            error_log('learnpath::get_toc()', 0);
3085
        }
3086
        $toc = [];
3087
        foreach ($this->ordered_items as $item_id) {
3088
            if ($this->debug > 2) {
3089
                error_log('learnpath::get_toc(): getting info for item '.$item_id, 0);
3090
            }
3091
            // TODO: Change this link generation and use new function instead.
3092
            $toc[] = [
3093
                'id' => $item_id,
3094
                'title' => $this->items[$item_id]->get_title(),
3095
                'status' => $this->items[$item_id]->get_status(),
3096
                'level' => $this->items[$item_id]->get_level(),
3097
                'type' => $this->items[$item_id]->get_type(),
3098
                'description' => $this->items[$item_id]->get_description(),
3099
                'path' => $this->items[$item_id]->get_path(),
3100
                'parent' => $this->items[$item_id]->get_parent(),
3101
            ];
3102
        }
3103
        if ($this->debug > 2) {
3104
            error_log('In learnpath::get_toc() - TOC array: '.print_r($toc, true), 0);
3105
        }
3106
3107
        return $toc;
3108
    }
3109
3110
    /**
3111
     * Generate and return the table of contents for this learnpath. The JS
3112
     * table returned is used inside of scorm_api.php.
3113
     *
3114
     * @param string $varname
3115
     *
3116
     * @return string A JS array variable construction
3117
     */
3118
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3119
    {
3120
        if ($this->debug > 0) {
3121
            error_log('In learnpath::get_items_details_as_js()', 0);
3122
        }
3123
        $toc = $varname.' = new Array();';
3124
        foreach ($this->ordered_items as $item_id) {
3125
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3126
        }
3127
        if ($this->debug > 2) {
3128
            error_log('In learnpath::get_items_details_as_js() - TOC array: '.print_r($toc, true), 0);
3129
        }
3130
3131
        return $toc;
3132
    }
3133
3134
    /**
3135
     * Gets the learning path type.
3136
     *
3137
     * @param	bool		Return the name? If false, return the ID. Default is false.
3138
     *
3139
     * @return mixed Type ID or name, depending on the parameter
3140
     */
3141
    public function get_type($get_name = false)
3142
    {
3143
        $res = false;
3144
        if ($this->debug > 0) {
3145
            error_log('In learnpath::get_type()', 0);
3146
        }
3147
        if (!empty($this->type) && (!$get_name)) {
3148
            $res = $this->type;
3149
        }
3150
        if ($this->debug > 2) {
3151
            error_log('In learnpath::get_type() - Returning '.($res ? $res : 'false'), 0);
3152
        }
3153
3154
        return $res;
3155
    }
3156
3157
    /**
3158
     * Gets the learning path type as static method.
3159
     *
3160
     * @param int $lp_id
3161
     *
3162
     * @return mixed Type ID or name, depending on the parameter
3163
     * @throws \Doctrine\DBAL\DBALException
3164
     */
3165
    public static function get_type_static($lp_id = 0)
3166
    {
3167
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3168
        $lp_id = intval($lp_id);
3169
        $sql = "SELECT lp_type FROM $tbl_lp
3170
                WHERE iid = $lp_id";
3171
        $res = Database::query($sql);
3172
        if ($res === false) {
3173
            return null;
3174
        }
3175
        if (Database::num_rows($res) <= 0) {
3176
            return null;
3177
        }
3178
        $row = Database::fetch_array($res);
3179
3180
        return $row['lp_type'];
3181
    }
3182
3183
    /**
3184
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3185
     * This method can be used as abstract and is recursive.
3186
     *
3187
     * @param int $lp Learnpath ID
3188
     * @param int $parent Parent ID of the items to look for
3189
     * @param int $course_id
3190
     *
3191
     * @return array Ordered list of item IDs (empty array on error)
3192
     * @throws \Doctrine\DBAL\DBALException
3193
     */
3194
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3195
    {
3196
        if (empty($course_id)) {
3197
            $course_id = api_get_course_int_id();
3198
        } else {
3199
            $course_id = intval($course_id);
3200
        }
3201
        $list = [];
3202
3203
        if (empty($lp)) {
3204
            return $list;
3205
        }
3206
3207
        $lp = intval($lp);
3208
        $parent = intval($parent);
3209
3210
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3211
        $sql = "SELECT iid FROM $tbl_lp_item
3212
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3213
                ORDER BY display_order";
3214
3215
        $res = Database::query($sql);
3216
        while ($row = Database::fetch_array($res)) {
3217
            $sublist = self::get_flat_ordered_items_list(
3218
                $lp,
3219
                $row['iid'],
3220
                $course_id
3221
            );
3222
            $list[] = $row['iid'];
3223
            foreach ($sublist as $item) {
3224
                $list[] = $item;
3225
            }
3226
        }
3227
3228
        return $list;
3229
    }
3230
3231
    /**
3232
     * @return array
3233
     */
3234
    public static function getChapterTypes()
3235
    {
3236
        return [
3237
            'dir',
3238
        ];
3239
    }
3240
3241
    /**
3242
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3243
     *
3244
     * @param $tree
3245
     *
3246
     * @return array HTML TOC ready to display
3247
     * @throws \Doctrine\DBAL\DBALException
3248
     */
3249
    public function getParentToc($tree)
3250
    {
3251
        if ($this->debug > 0) {
3252
            error_log('In learnpath::get_html_toc()', 0);
3253
        }
3254
        if (empty($tree)) {
3255
            $tree = $this->get_toc();
3256
        }
3257
        $dirTypes = self::getChapterTypes();
3258
        $myCurrentId = $this->get_current_item_id();
3259
        $listParent = [];
3260
        $listChildren = [];
3261
        $listNotParent = [];
3262
        $list = [];
3263
        foreach ($tree as $subtree) {
3264
            if (in_array($subtree['type'], $dirTypes)) {
3265
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3266
                $subtree['children'] = $listChildren;
3267
                if (!empty($subtree['children'])) {
3268
                    foreach ($subtree['children'] as $subItem) {
3269
                        if ($subItem['id'] == $this->current) {
3270
                            $subtree['parent_current'] = 'in';
3271
                            $subtree['current'] = 'on';
3272
                        }
3273
                    }
3274
                }
3275
                $listParent[] = $subtree;
3276
            }
3277
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3278
                $classStatus = [
3279
                    'not attempted' => 'scorm_not_attempted',
3280
                    'incomplete' => 'scorm_not_attempted',
3281
                    'failed' => 'scorm_failed',
3282
                    'completed' => 'scorm_completed',
3283
                    'passed' => 'scorm_completed',
3284
                    'succeeded' => 'scorm_completed',
3285
                    'browsed' => 'scorm_completed',
3286
                ];
3287
3288
                if (isset($classStatus[$subtree['status']])) {
3289
                    $cssStatus = $classStatus[$subtree['status']];
3290
                }
3291
3292
                $title = Security::remove_XSS($subtree['title']);
3293
                unset($subtree['title']);
3294
3295
                if (empty($title)) {
3296
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3297
                }
3298
                $classStyle = null;
3299
                if ($subtree['id'] == $this->current) {
3300
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3301
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3302
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3303
                }
3304
                $subtree['title'] = $title;
3305
                $subtree['class'] = $cssStatus.' '.$classStyle;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $cssStatus does not seem to be defined for all execution paths leading up to this point.
Loading history...
3306
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3307
                $subtree['current_id'] = $myCurrentId;
3308
                $listNotParent[] = $subtree;
3309
            }
3310
        }
3311
3312
        $list['are_parents'] = $listParent;
3313
        $list['not_parents'] = $listNotParent;
3314
3315
        return $list;
3316
    }
3317
3318
    /**
3319
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3320
     *
3321
     * @param      $tree
3322
     * @param      $id
3323
     * @param bool $parent
3324
     *
3325
     * @return array HTML TOC ready to display
3326
     * @throws \Doctrine\DBAL\DBALException
3327
     */
3328
    public function getChildrenToc($tree, $id, $parent = true)
3329
    {
3330
        if ($this->debug > 0) {
3331
            error_log('In learnpath::get_html_toc()', 0);
3332
        }
3333
        if (empty($tree)) {
3334
            $tree = $this->get_toc();
3335
        }
3336
3337
        $dirTypes = self::getChapterTypes();
3338
        $mycurrentitemid = $this->get_current_item_id();
3339
        $list = [];
3340
        $classStatus = [
3341
            'not attempted' => 'scorm_not_attempted',
3342
            'incomplete' => 'scorm_not_attempted',
3343
            'failed' => 'scorm_failed',
3344
            'completed' => 'scorm_completed',
3345
            'passed' => 'scorm_completed',
3346
            'succeeded' => 'scorm_completed',
3347
            'browsed' => 'scorm_completed',
3348
        ];
3349
3350
        foreach ($tree as $subtree) {
3351
            $subtree['tree'] = null;
3352
3353
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3354
                if ($subtree['id'] == $this->current) {
3355
                    $subtree['current'] = 'active';
3356
                } else {
3357
                    $subtree['current'] = null;
3358
                }
3359
                if (isset($classStatus[$subtree['status']])) {
3360
                    $cssStatus = $classStatus[$subtree['status']];
3361
                }
3362
3363
                $title = Security::remove_XSS($subtree['title']);
3364
                unset($subtree['title']);
3365
                if (empty($title)) {
3366
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3367
                }
3368
3369
                $classStyle = null;
3370
                if ($subtree['id'] == $this->current) {
3371
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3372
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3373
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3374
                }
3375
3376
                if (in_array($subtree['type'], $dirTypes)) {
3377
                    $subtree['title'] = stripslashes($title);
3378
                } else {
3379
                    $subtree['title'] = $title;
3380
                    $subtree['class'] = $cssStatus.' '.$classStyle;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $cssStatus does not seem to be defined for all execution paths leading up to this point.
Loading history...
3381
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3382
                    $subtree['current_id'] = $mycurrentitemid;
3383
                }
3384
                $list[] = $subtree;
3385
            }
3386
        }
3387
3388
        return $list;
3389
    }
3390
3391
    /**
3392
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3393
     *
3394
     * @param array $toc_list
3395
     *
3396
     * @return array HTML TOC ready to display
3397
     * @throws \Doctrine\DBAL\DBALException
3398
     */
3399
    public function getListArrayToc($toc_list = [])
3400
    {
3401
        if ($this->debug > 0) {
3402
            error_log('In learnpath::get_html_toc()', 0);
3403
        }
3404
        if (empty($toc_list)) {
3405
            $toc_list = $this->get_toc();
3406
        }
3407
        // Temporary variables.
3408
        $mycurrentitemid = $this->get_current_item_id();
3409
        $list = [];
3410
        $arrayList = [];
3411
        $classStatus = [
3412
            'not attempted' => 'scorm_not_attempted',
3413
            'incomplete' => 'scorm_not_attempted',
3414
            'failed' => 'scorm_failed',
3415
            'completed' => 'scorm_completed',
3416
            'passed' => 'scorm_completed',
3417
            'succeeded' => 'scorm_completed',
3418
            'browsed' => 'scorm_completed',
3419
        ];
3420
3421
        foreach ($toc_list as $item) {
3422
            $list['id'] = $item['id'];
3423
            $list['status'] = $item['status'];
3424
            $cssStatus = null;
3425
3426
            if (isset($classStatus[$item['status']])) {
3427
                $cssStatus = $classStatus[$item['status']];
3428
            }
3429
3430
            $classStyle = ' ';
3431
            $dirTypes = self::getChapterTypes();
3432
3433
            if (in_array($item['type'], $dirTypes)) {
3434
                $classStyle = 'scorm_item_section ';
3435
            }
3436
            if ($item['id'] == $this->current) {
3437
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3438
            } elseif (!in_array($item['type'], $dirTypes)) {
3439
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3440
            }
3441
            $title = $item['title'];
3442
            if (empty($title)) {
3443
                $title = self::rl_get_resource_name(
3444
                    api_get_course_id(),
3445
                    $this->get_id(),
3446
                    $item['id']
3447
                );
3448
            }
3449
            $title = Security::remove_XSS($item['title']);
3450
3451
            if (empty($item['description'])) {
3452
                $list['description'] = $title;
3453
            } else {
3454
                $list['description'] = $item['description'];
3455
            }
3456
3457
            $list['class'] = $classStyle.' '.$cssStatus;
3458
            $list['level'] = $item['level'];
3459
            $list['type'] = $item['type'];
3460
3461
            if (in_array($item['type'], $dirTypes)) {
3462
                $list['css_level'] = 'level_'.$item['level'];
3463
            } else {
3464
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.learnpath::format_scorm_type_item($item['type']);
3465
            }
3466
3467
            if (in_array($item['type'], $dirTypes)) {
3468
                $list['title'] = stripslashes($title);
3469
            } else {
3470
                $list['title'] = stripslashes($title);
3471
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3472
                $list['current_id'] = $mycurrentitemid;
3473
            }
3474
            $arrayList[] = $list;
3475
        }
3476
3477
        return $arrayList;
3478
    }
3479
3480
    /**
3481
     * Returns an HTML-formatted string ready to display with teacher buttons
3482
     * in LP view menu.
3483
     *
3484
     * @return string HTML TOC ready to display
3485
     */
3486
    public function get_teacher_toc_buttons()
3487
    {
3488
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3489
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3490
        $html = '';
3491
        if ($isAllow && $hideIcons == false) {
3492
            if ($this->get_lp_session_id() == api_get_session_id()) {
3493
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3494
                $html .= '<div class="btn-group">';
3495
                $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=build&lp_id=".$this->lp_id."&isStudentView=false' target='_parent'>".
3496
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3497
                $html .= "<a class='btn btn-sm btn-default' href='lp_controller.php?".api_get_cidreq()."&action=add_item&type=step&lp_id=".$this->lp_id."&isStudentView=false' target='_parent'>".
3498
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3499
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3500
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3501
                $html .= '</div>';
3502
                $html .= '</div>';
3503
            }
3504
        }
3505
3506
        return $html;
3507
    }
3508
3509
    /**
3510
     * Gets the learnpath maker name - generally the editor's name.
3511
     *
3512
     * @return string Learnpath maker name
3513
     */
3514
    public function get_maker()
3515
    {
3516
        if ($this->debug > 0) {
3517
            error_log('In learnpath::get_maker()', 0);
3518
        }
3519
        if (!empty($this->maker)) {
3520
            return $this->maker;
3521
        } else {
3522
            return '';
3523
        }
3524
    }
3525
3526
    /**
3527
     * Gets the learnpath name/title.
3528
     *
3529
     * @return string Learnpath name/title
3530
     */
3531
    public function get_name()
3532
    {
3533
        if ($this->debug > 0) {
3534
            error_log('In learnpath::get_name()', 0);
3535
        }
3536
        if (!empty($this->name)) {
3537
            return $this->name;
3538
        } else {
3539
            return 'N/A';
3540
        }
3541
    }
3542
3543
    /**
3544
     * Gets a link to the resource from the present location, depending on item ID.
3545
     *
3546
     * @param string $type Type of link expected
3547
     * @param int    $item_id Learnpath item ID
3548
     *
3549
     * @param bool   $provided_toc
3550
     *
3551
     * @return string $provided_toc Link to the lp_item resource
3552
     * @throws \Doctrine\DBAL\DBALException
3553
     */
3554
    public function get_link($type = 'http', $item_id = null, $provided_toc = false)
3555
    {
3556
        $course_id = $this->get_course_int_id();
3557
        if ($this->debug > 0) {
3558
            error_log('In learnpath::get_link('.$type.','.$item_id.')', 0);
3559
        }
3560
        if (empty($item_id)) {
3561
            if ($this->debug > 2) {
3562
                error_log('In learnpath::get_link() - no item id given in learnpath::get_link()');
3563
                error_log('using current: '.$this->get_current_item_id(), 0);
3564
            }
3565
            $item_id = $this->get_current_item_id();
3566
        }
3567
3568
        if (empty($item_id)) {
3569
            if ($this->debug > 2) {
3570
                error_log('In learnpath::get_link() - no current item id found in learnpath object', 0);
3571
            }
3572
            //still empty, this means there was no item_id given and we are not in an object context or
3573
            //the object property is empty, return empty link
3574
            $this->first();
3575
3576
            return '';
3577
        }
3578
3579
        $file = '';
3580
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3581
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3582
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3583
        $item_id = intval($item_id);
3584
3585
        $sql = "SELECT
3586
                    l.lp_type as ltype,
3587
                    l.path as lpath,
3588
                    li.item_type as litype,
3589
                    li.path as lipath,
3590
                    li.parameters as liparams
3591
        		FROM $lp_table l
3592
                INNER JOIN $lp_item_table li
3593
                ON (li.lp_id = l.iid)
3594
        		WHERE 
3595
        		    li.iid = $item_id 
3596
        		";
3597
        if ($this->debug > 2) {
3598
            error_log('In learnpath::get_link() - selecting item '.$sql, 0);
3599
        }
3600
        $res = Database::query($sql);
3601
        if (Database::num_rows($res) > 0) {
3602
            $row = Database::fetch_array($res);
3603
            $lp_type = $row['ltype'];
3604
            $lp_path = $row['lpath'];
3605
            $lp_item_type = $row['litype'];
3606
            $lp_item_path = $row['lipath'];
3607
            $lp_item_params = $row['liparams'];
3608
3609
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3610
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3611
            }
3612
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3613
            if ($type == 'http') {
3614
                //web path
3615
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3616
            } else {
3617
                $course_path = $sys_course_path; //system path
3618
            }
3619
3620
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3621
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3622
            if (in_array(
3623
                $lp_item_type,
3624
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3625
            )
3626
            ) {
3627
                $lp_type = 1;
3628
            }
3629
3630
            if ($this->debug > 2) {
3631
                error_log('In learnpath::get_link() - $lp_type '.$lp_type, 0);
3632
                error_log('In learnpath::get_link() - $lp_item_type '.$lp_item_type, 0);
3633
            }
3634
3635
            // Now go through the specific cases to get the end of the path
3636
            // @todo Use constants instead of int values.
3637
3638
            switch ($lp_type) {
3639
                case 1:
3640
                    $file = self::rl_get_resource_link_for_learnpath(
3641
                        $course_id,
3642
                        $this->get_id(),
3643
                        $item_id,
3644
                        $this->get_view_id()
3645
                    );
3646
3647
                    if ($this->debug > 0) {
3648
                        error_log('rl_get_resource_link_for_learnpath - file: '.$file, 0);
3649
                    }
3650
3651
                    switch ($lp_item_type) {
3652
                        case 'dir':
3653
                            $file = 'lp_content.php?type=dir';
3654
                            break;
3655
                        case 'link':
3656
                            if (Link::is_youtube_link($file)) {
3657
                                $src = Link::get_youtube_video_id($file);
3658
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3659
                            } elseif (Link::isVimeoLink($file)) {
3660
                                $src = Link::getVimeoLinkId($file);
3661
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
0 ignored issues
show
Bug introduced by
Are you sure $src of type false|string can be used in concatenation? ( Ignorable by Annotation )

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

3661
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='./** @scrutinizer ignore-type */ $src;
Loading history...
3662
                            } else {
3663
                                // If the current site is HTTPS and the link is
3664
                                // HTTP, browsers will refuse opening the link
3665
                                $urlId = api_get_current_access_url_id();
3666
                                $url = api_get_access_url($urlId, false);
3667
                                $protocol = substr($url['url'], 0, 5);
3668
                                if ($protocol === 'https') {
3669
                                    $linkProtocol = substr($file, 0, 5);
3670
                                    if ($linkProtocol === 'http:') {
3671
                                        //this is the special intervention case
3672
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3673
                                    }
3674
                                }
3675
                            }
3676
                            break;
3677
                        case 'quiz':
3678
                            // Check how much attempts of a exercise exits in lp
3679
                            $lp_item_id = $this->get_current_item_id();
3680
                            $lp_view_id = $this->get_view_id();
3681
3682
                            $prevent_reinit = null;
3683
                            if (isset($this->items[$this->current])) {
3684
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3685
                            }
3686
3687
                            if (empty($provided_toc)) {
3688
                                if ($this->debug > 0) {
3689
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3690
                                }
3691
                                $list = $this->get_toc();
3692
                            } else {
3693
                                if ($this->debug > 0) {
3694
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3695
                                }
3696
                                $list = $provided_toc;
3697
                            }
3698
3699
                            $type_quiz = false;
3700
3701
                            foreach ($list as $toc) {
3702
                                if ($toc['id'] == $lp_item_id && ($toc['type'] == 'quiz')) {
3703
                                    $type_quiz = true;
3704
                                }
3705
                            }
3706
3707
                            if ($type_quiz) {
3708
                                $lp_item_id = intval($lp_item_id);
3709
                                $lp_view_id = intval($lp_view_id);
3710
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3711
                                        WHERE
3712
                                            c_id = $course_id AND
3713
                                            lp_item_id='".$lp_item_id."' AND
3714
                                            lp_view_id ='".$lp_view_id."' AND
3715
                                            status='completed'";
3716
                                $result = Database::query($sql);
3717
                                $row_count = Database:: fetch_row($result);
3718
                                $count_item_view = (int) $row_count[0];
3719
                                $not_multiple_attempt = 0;
3720
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3721
                                    $not_multiple_attempt = 1;
3722
                                }
3723
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3724
                            }
3725
                            break;
3726
                    }
3727
3728
                    $tmp_array = explode('/', $file);
3729
                    $document_name = $tmp_array[count($tmp_array) - 1];
3730
                    if (strpos($document_name, '_DELETED_')) {
3731
                        $file = 'blank.php?error=document_deleted';
3732
                    }
3733
3734
                    break;
3735
                case 2:
3736
                    if ($this->debug > 2) {
3737
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3738
                    }
3739
3740
                    if ($lp_item_type != 'dir') {
3741
                        // Quite complex here:
3742
                        // We want to make sure 'http://' (and similar) links can
3743
                        // be loaded as is (withouth the Chamilo path in front) but
3744
                        // some contents use this form: resource.htm?resource=http://blablabla
3745
                        // which means we have to find a protocol at the path's start, otherwise
3746
                        // it should not be considered as an external URL.
3747
                        // if ($this->prerequisites_match($item_id)) {
3748
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3749
                            if ($this->debug > 2) {
3750
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3751
                            }
3752
                            // Distant url, return as is.
3753
                            $file = $lp_item_path;
3754
                        } else {
3755
                            if ($this->debug > 2) {
3756
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3757
                            }
3758
                            // Prevent getting untranslatable urls.
3759
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3760
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3761
                            // Prepare the path.
3762
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3763
                            // TODO: Fix this for urls with protocol header.
3764
                            $file = str_replace('//', '/', $file);
3765
                            $file = str_replace(':/', '://', $file);
3766
                            if (substr($lp_path, -1) == '/') {
3767
                                $lp_path = substr($lp_path, 0, -1);
3768
                            }
3769
3770
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3771
                                // if file not found.
3772
                                $decoded = html_entity_decode($lp_item_path);
3773
                                list($decoded) = explode('?', $decoded);
3774
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3775
                                    $file = self::rl_get_resource_link_for_learnpath(
3776
                                        $course_id,
3777
                                        $this->get_id(),
3778
                                        $item_id,
3779
                                        $this->get_view_id()
3780
                                    );
3781
                                    if (empty($file)) {
3782
                                        $file = 'blank.php?error=document_not_found';
3783
                                    } else {
3784
                                        $tmp_array = explode('/', $file);
3785
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3786
                                        if (strpos($document_name, '_DELETED_')) {
3787
                                            $file = 'blank.php?error=document_deleted';
3788
                                        } else {
3789
                                            $file = 'blank.php?error=document_not_found';
3790
                                        }
3791
                                    }
3792
                                } else {
3793
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3794
                                }
3795
                            }
3796
                        }
3797
3798
                        // We want to use parameters if they were defined in the imsmanifest
3799
                        if (strpos($file, 'blank.php') === false) {
3800
                            $lp_item_params = ltrim($lp_item_params, '?');
3801
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3802
                        }
3803
                    } else {
3804
                        $file = 'lp_content.php?type=dir';
3805
                    }
3806
                    break;
3807
                case 3:
3808
                    if ($this->debug > 2) {
3809
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3810
                    }
3811
                    // Formatting AICC HACP append URL.
3812
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3813
                    if (!empty($lp_item_params)) {
3814
                        $aicc_append .= $lp_item_params.'&';
3815
                    }
3816
                    if ($lp_item_type != 'dir') {
3817
                        // Quite complex here:
3818
                        // We want to make sure 'http://' (and similar) links can
3819
                        // be loaded as is (withouth the Chamilo path in front) but
3820
                        // some contents use this form: resource.htm?resource=http://blablabla
3821
                        // which means we have to find a protocol at the path's start, otherwise
3822
                        // it should not be considered as an external URL.
3823
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3824
                            if ($this->debug > 2) {
3825
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3826
                            }
3827
                            // Distant url, return as is.
3828
                            $file = $lp_item_path;
3829
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3830
                            /*
3831
                            if (stristr($file,'<servername>') !== false) {
3832
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3833
                            }
3834
                            */
3835
                            if (stripos($file, '<servername>') !== false) {
3836
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3837
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3838
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3839
                            }
3840
3841
                            $file .= $aicc_append;
3842
                        } else {
3843
                            if ($this->debug > 2) {
3844
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3845
                            }
3846
                            // Prevent getting untranslatable urls.
3847
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3848
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3849
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3850
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3851
                            // TODO: Fix this for urls with protocol header.
3852
                            $file = str_replace('//', '/', $file);
3853
                            $file = str_replace(':/', '://', $file);
3854
                            $file .= $aicc_append;
3855
                        }
3856
                    } else {
3857
                        $file = 'lp_content.php?type=dir';
3858
                    }
3859
                    break;
3860
                case 4:
3861
                    break;
3862
                default:
3863
                    break;
3864
            }
3865
            // Replace &amp; by & because &amp; will break URL with params
3866
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3867
        }
3868
        if ($this->debug > 2) {
3869
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3870
        }
3871
3872
        return $file;
3873
    }
3874
3875
    /**
3876
     * Gets the latest usable view or generate a new one.
3877
     *
3878
     * @param    int    Optional attempt number. If none given, takes the highest from the lp_view table
3879
     *
3880
     * @return int DB lp_view id
3881
     * @throws \Doctrine\DBAL\DBALException
3882
     */
3883
    public function get_view($attempt_num = 0)
3884
    {
3885
        if ($this->debug > 0) {
3886
            error_log('In learnpath::get_view()', 0);
3887
        }
3888
        $search = '';
3889
        // Use $attempt_num to enable multi-views management (disabled so far).
3890
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3891
            $search = 'AND view_count = '.$attempt_num;
3892
        }
3893
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3894
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3895
3896
        $course_id = api_get_course_int_id();
3897
        $sessionId = api_get_session_id();
3898
3899
        $sql = "SELECT iid, view_count FROM $lp_view_table
3900
        		WHERE
3901
        		    c_id = $course_id AND
3902
        		    lp_id = ".$this->get_id()." AND
3903
        		    user_id = ".$this->get_user_id()." AND
3904
        		    session_id = $sessionId
3905
        		    $search
3906
                ORDER BY view_count DESC";
3907
        $res = Database::query($sql);
3908
        if (Database::num_rows($res) > 0) {
3909
            $row = Database::fetch_array($res);
3910
            $this->lp_view_id = $row['iid'];
3911
        } elseif (!api_is_invitee()) {
3912
            // There is no database record, create one.
3913
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3914
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3915
            Database::query($sql);
3916
            $id = Database::insert_id();
3917
            $this->lp_view_id = $id;
3918
3919
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3920
            Database::query($sql);
3921
        }
3922
3923
        return $this->lp_view_id;
3924
    }
3925
3926
    /**
3927
     * Gets the current view id.
3928
     *
3929
     * @return int View ID (from lp_view)
3930
     */
3931
    public function get_view_id()
3932
    {
3933
        if ($this->debug > 0) {
3934
            error_log('In learnpath::get_view_id()', 0);
3935
        }
3936
        if (!empty($this->lp_view_id)) {
3937
            return $this->lp_view_id;
3938
        } else {
3939
            return 0;
3940
        }
3941
    }
3942
3943
    /**
3944
     * Gets the update queue.
3945
     *
3946
     * @return array Array containing IDs of items to be updated by JavaScript
3947
     */
3948
    public function get_update_queue()
3949
    {
3950
        if ($this->debug > 0) {
3951
            error_log('In learnpath::get_update_queue()', 0);
3952
        }
3953
3954
        return $this->update_queue;
3955
    }
3956
3957
    /**
3958
     * Gets the user ID.
3959
     *
3960
     * @return int User ID
3961
     */
3962
    public function get_user_id()
3963
    {
3964
        if ($this->debug > 0) {
3965
            error_log('In learnpath::get_user_id()', 0);
3966
        }
3967
        if (!empty($this->user_id)) {
3968
            return $this->user_id;
3969
        } else {
3970
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type integer.
Loading history...
3971
        }
3972
    }
3973
3974
    /**
3975
     * Checks if any of the items has an audio element attached.
3976
     *
3977
     * @return bool True or false
3978
     */
3979
    public function has_audio()
3980
    {
3981
        if ($this->debug > 1) {
3982
            error_log('In learnpath::has_audio()', 0);
3983
        }
3984
        $has = false;
3985
        foreach ($this->items as $i => $item) {
3986
            if (!empty($this->items[$i]->audio)) {
3987
                $has = true;
3988
                break;
3989
            }
3990
        }
3991
3992
        return $has;
3993
    }
3994
3995
    /**
3996
     * Moves an item up and down at its level.
3997
     *
3998
     * @param int    $id Item to move up and down
3999
     * @param string $direction Direction 'up' or 'down'
4000
     *
4001
     * @return bool|int
4002
     * @throws \Doctrine\DBAL\DBALException
4003
     */
4004
    public function move_item($id, $direction)
4005
    {
4006
        $course_id = api_get_course_int_id();
4007
        if ($this->debug > 0) {
4008
            error_log('In learnpath::move_item('.$id.','.$direction.')', 0);
4009
        }
4010
        if (empty($id) || empty($direction)) {
4011
            return false;
4012
        }
4013
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
4014
        $sql_sel = "SELECT *
4015
                    FROM $tbl_lp_item
4016
                    WHERE  
4017
                        iid = $id
4018
                    ";
4019
        $res_sel = Database::query($sql_sel);
4020
        // Check if elem exists.
4021
        if (Database::num_rows($res_sel) < 1) {
4022
            return false;
4023
        }
4024
        // Gather data.
4025
        $row = Database::fetch_array($res_sel);
4026
        $previous = $row['previous_item_id'];
4027
        $next = $row['next_item_id'];
4028
        $display = $row['display_order'];
4029
        $parent = $row['parent_item_id'];
4030
        $lp = $row['lp_id'];
4031
        // Update the item (switch with previous/next one).
4032
        switch ($direction) {
4033
            case 'up':
4034
                if ($this->debug > 2) {
4035
                    error_log('Movement up detected', 0);
4036
                }
4037
                if ($display > 1) {
4038
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4039
                                 WHERE iid = $previous";
4040
4041
                    if ($this->debug > 2) {
4042
                        error_log('Selecting previous: '.$sql_sel2, 0);
4043
                    }
4044
                    $res_sel2 = Database::query($sql_sel2);
4045
                    if (Database::num_rows($res_sel2) < 1) {
4046
                        $previous_previous = 0;
4047
                    }
4048
                    // Gather data.
4049
                    $row2 = Database::fetch_array($res_sel2);
4050
                    $previous_previous = $row2['previous_item_id'];
4051
                    // Update previous_previous item (switch "next" with current).
4052
                    if ($previous_previous != 0) {
4053
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4054
                                        next_item_id = $id
4055
                                    WHERE iid = $previous_previous";
4056
                        if ($this->debug > 2) {
4057
                            error_log($sql_upd2, 0);
4058
                        }
4059
                        Database::query($sql_upd2);
4060
                    }
4061
                    // Update previous item (switch with current).
4062
                    if ($previous != 0) {
4063
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4064
                                    next_item_id = $next,
4065
                                    previous_item_id = $id,
4066
                                    display_order = display_order +1
4067
                                    WHERE iid = $previous";
4068
                        if ($this->debug > 2) {
4069
                            error_log($sql_upd2, 0);
4070
                        }
4071
                        Database::query($sql_upd2);
4072
                    }
4073
4074
                    // Update current item (switch with previous).
4075
                    if ($id != 0) {
4076
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4077
                                        next_item_id = $previous,
4078
                                        previous_item_id = $previous_previous,
4079
                                        display_order = display_order-1
4080
                                    WHERE c_id = ".$course_id." AND id = $id";
4081
                        if ($this->debug > 2) {
4082
                            error_log($sql_upd2, 0);
4083
                        }
4084
                        Database::query($sql_upd2);
4085
                    }
4086
                    // Update next item (new previous item).
4087
                    if ($next != 0) {
4088
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
4089
                                     WHERE iid = $next";
4090
                        if ($this->debug > 2) {
4091
                            error_log($sql_upd2, 0);
4092
                        }
4093
                        Database::query($sql_upd2);
4094
                    }
4095
                    $display = $display - 1;
4096
                }
4097
                break;
4098
            case 'down':
4099
                if ($this->debug > 2) {
4100
                    error_log('Movement down detected', 0);
4101
                }
4102
                if ($next != 0) {
4103
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item 
4104
                                 WHERE iid = $next";
4105
                    if ($this->debug > 2) {
4106
                        error_log('Selecting next: '.$sql_sel2, 0);
4107
                    }
4108
                    $res_sel2 = Database::query($sql_sel2);
4109
                    if (Database::num_rows($res_sel2) < 1) {
4110
                        $next_next = 0;
4111
                    }
4112
                    // Gather data.
4113
                    $row2 = Database::fetch_array($res_sel2);
4114
                    $next_next = $row2['next_item_id'];
4115
                    // Update previous item (switch with current).
4116
                    if ($previous != 0) {
4117
                        $sql_upd2 = "UPDATE $tbl_lp_item 
4118
                                     SET next_item_id = $next
4119
                                     WHERE iid = $previous";
4120
                        Database::query($sql_upd2);
4121
                    }
4122
                    // Update current item (switch with previous).
4123
                    if ($id != 0) {
4124
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4125
                                     previous_item_id = $next, 
4126
                                     next_item_id = $next_next, 
4127
                                     display_order = display_order + 1
4128
                                     WHERE iid = $id";
4129
                        Database::query($sql_upd2);
4130
                    }
4131
4132
                    // Update next item (new previous item).
4133
                    if ($next != 0) {
4134
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4135
                                     previous_item_id = $previous, 
4136
                                     next_item_id = $id, 
4137
                                     display_order = display_order-1
4138
                                     WHERE iid = $next";
4139
                        Database::query($sql_upd2);
4140
                    }
4141
4142
                    // Update next_next item (switch "previous" with current).
4143
                    if ($next_next != 0) {
4144
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4145
                                     previous_item_id = $id
4146
                                     WHERE iid = $next_next";
4147
                        Database::query($sql_upd2);
4148
                    }
4149
                    $display = $display + 1;
4150
                }
4151
                break;
4152
            default:
4153
                return false;
4154
        }
4155
4156
        return $display;
4157
    }
4158
4159
    /**
4160
     * Move a LP up (display_order).
4161
     *
4162
     * @param int $lp_id Learnpath ID
4163
     * @param int $categoryId
4164
     *
4165
     * @return bool
4166
     * @throws \Doctrine\DBAL\DBALException
4167
     */
4168
    public static function move_up($lp_id, $categoryId = 0)
4169
    {
4170
        $courseId = api_get_course_int_id();
4171
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4172
4173
        $categoryCondition = '';
4174
        if (!empty($categoryId)) {
4175
            $categoryId = (int) $categoryId;
4176
            $categoryCondition = " AND category_id = $categoryId";
4177
        }
4178
        $sql = "SELECT * FROM $lp_table
4179
                WHERE c_id = $courseId
4180
                $categoryCondition
4181
                ORDER BY display_order";
4182
        $res = Database::query($sql);
4183
        if ($res === false) {
4184
            return false;
4185
        }
4186
4187
        $lps = [];
4188
        $lp_order = [];
4189
        $num = Database::num_rows($res);
4190
        // First check the order is correct, globally (might be wrong because
4191
        // of versions < 1.8.4)
4192
        if ($num > 0) {
4193
            $i = 1;
4194
            while ($row = Database::fetch_array($res)) {
4195
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4196
                    $sql = "UPDATE $lp_table SET display_order = $i
4197
                            WHERE iid = ".$row['iid'];
4198
                    Database::query($sql);
4199
                }
4200
                $row['display_order'] = $i;
4201
                $lps[$row['iid']] = $row;
4202
                $lp_order[$i] = $row['iid'];
4203
                $i++;
4204
            }
4205
        }
4206
        if ($num > 1) { // If there's only one element, no need to sort.
4207
            $order = $lps[$lp_id]['display_order'];
4208
            if ($order > 1) { // If it's the first element, no need to move up.
4209
                $sql = "UPDATE $lp_table SET display_order = $order
4210
                        WHERE iid = ".$lp_order[$order - 1];
4211
                Database::query($sql);
4212
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4213
                        WHERE iid = $lp_id";
4214
                Database::query($sql);
4215
            }
4216
        }
4217
4218
        return true;
4219
    }
4220
4221
    /**
4222
     * Move a learnpath down (display_order).
4223
     *
4224
     * @param int $lp_id Learnpath ID
4225
     * @param int $categoryId
4226
     *
4227
     * @return bool
4228
     * @throws \Doctrine\DBAL\DBALException
4229
     */
4230
    public static function move_down($lp_id, $categoryId = 0)
4231
    {
4232
        $courseId = api_get_course_int_id();
4233
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4234
4235
        $categoryCondition = '';
4236
        if (!empty($categoryId)) {
4237
            $categoryId = (int) $categoryId;
4238
            $categoryCondition = " AND category_id = $categoryId";
4239
        }
4240
4241
        $sql = "SELECT * FROM $lp_table
4242
                WHERE c_id = $courseId
4243
                $categoryCondition
4244
                ORDER BY display_order";
4245
        $res = Database::query($sql);
4246
        if ($res === false) {
4247
            return false;
4248
        }
4249
        $lps = [];
4250
        $lp_order = [];
4251
        $num = Database::num_rows($res);
4252
        $max = 0;
4253
        // First check the order is correct, globally (might be wrong because
4254
        // of versions < 1.8.4).
4255
        if ($num > 0) {
4256
            $i = 1;
4257
            while ($row = Database::fetch_array($res)) {
4258
                $max = $i;
4259
                if ($row['display_order'] != $i) {
4260
                    // If we find a gap in the order, we need to fix it.
4261
                    $sql = "UPDATE $lp_table SET display_order = $i
4262
                              WHERE iid = ".$row['iid'];
4263
                    Database::query($sql);
4264
                }
4265
                $row['display_order'] = $i;
4266
                $lps[$row['iid']] = $row;
4267
                $lp_order[$i] = $row['iid'];
4268
                $i++;
4269
            }
4270
        }
4271
        if ($num > 1) { // If there's only one element, no need to sort.
4272
            $order = $lps[$lp_id]['display_order'];
4273
            if ($order < $max) { // If it's the first element, no need to move up.
4274
                $sql = "UPDATE $lp_table SET display_order = $order
4275
                        WHERE iid = ".$lp_order[$order + 1];
4276
                Database::query($sql);
4277
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4278
                        WHERE iid = $lp_id";
4279
                Database::query($sql);
4280
            }
4281
        }
4282
4283
        return true;
4284
    }
4285
4286
    /**
4287
     * Updates learnpath attributes to point to the next element
4288
     * The last part is similar to set_current_item but processing the other way around.
4289
     */
4290
    public function next()
4291
    {
4292
        if ($this->debug > 0) {
4293
            error_log('In learnpath::next()', 0);
4294
        }
4295
        $this->last = $this->get_current_item_id();
4296
        $this->items[$this->last]->save(
4297
            false,
4298
            $this->prerequisites_match($this->last)
4299
        );
4300
        $this->autocomplete_parents($this->last);
4301
        $new_index = $this->get_next_index();
4302
        if ($this->debug > 2) {
4303
            error_log('New index: '.$new_index, 0);
4304
        }
4305
        $this->index = $new_index;
4306
        if ($this->debug > 2) {
4307
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4308
        }
4309
        $this->current = $this->ordered_items[$new_index];
4310
        if ($this->debug > 2) {
4311
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4312
        }
4313
    }
4314
4315
    /**
4316
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4317
     * class, this might be redefined to allow several behaviours depending on the document type.
4318
     *
4319
     * @param int Resource ID
4320
     */
4321
    public function open($id)
4322
    {
4323
        if ($this->debug > 0) {
4324
            error_log('In learnpath::open()', 0);
4325
        }
4326
        // TODO:
4327
        // set the current resource attribute to this resource
4328
        // switch on element type (redefine in child class?)
4329
        // set status for this item to "opened"
4330
        // start timer
4331
        // initialise score
4332
        $this->index = 0; //or = the last item seen (see $this->last)
4333
    }
4334
4335
    /**
4336
     * Check that all prerequisites are fulfilled. Returns true and an
4337
     * empty string on success, returns false
4338
     * and the prerequisite string on error.
4339
     * This function is based on the rules for aicc_script language as
4340
     * described in the SCORM 1.2 CAM documentation page 108.
4341
     *
4342
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4343
     *
4344
     * @return bool true if prerequisites are matched, false otherwise -
4345
     *              Empty string if true returned, prerequisites string otherwise
4346
     */
4347
    public function prerequisites_match($itemId = null)
4348
    {
4349
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4350
        if ($allow) {
4351
            if (api_is_allowed_to_edit() || api_is_platform_admin() || api_is_drh()) {
4352
                return true;
4353
            }
4354
        }
4355
4356
        $debug = $this->debug;
4357
        if ($debug > 0) {
4358
            error_log('In learnpath::prerequisites_match()', 0);
4359
        }
4360
4361
        if (empty($itemId)) {
4362
            $itemId = $this->current;
4363
        }
4364
4365
        $currentItem = $this->getItem($itemId);
4366
4367
        if ($currentItem) {
4368
            if ($this->type == 2) {
4369
                // Getting prereq from scorm
4370
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4371
            } else {
4372
                $prereq_string = $currentItem->get_prereq_string();
4373
            }
4374
4375
            if (empty($prereq_string)) {
4376
                if ($debug > 0) {
4377
                    error_log('Found prereq_string is empty return true');
4378
                }
4379
4380
                return true;
4381
            }
4382
            // Clean spaces.
4383
            $prereq_string = str_replace(' ', '', $prereq_string);
4384
            if ($debug > 0) {
4385
                error_log('Found prereq_string: '.$prereq_string, 0);
4386
            }
4387
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4388
            $result = $currentItem->parse_prereq(
4389
                $prereq_string,
4390
                $this->items,
4391
                $this->refs_list,
4392
                $this->get_user_id()
4393
            );
4394
4395
            if ($result === false) {
4396
                $this->set_error_msg($currentItem->prereq_alert);
4397
            }
4398
        } else {
4399
            $result = true;
4400
            if ($debug > 1) {
4401
                error_log('$this->items['.$itemId.'] was not an object', 0);
4402
            }
4403
        }
4404
4405
        if ($debug > 1) {
4406
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4407
        }
4408
4409
        return $result;
4410
    }
4411
4412
    /**
4413
     * Updates learnpath attributes to point to the previous element
4414
     * The last part is similar to set_current_item but processing the other way around.
4415
     */
4416
    public function previous()
4417
    {
4418
        if ($this->debug > 0) {
4419
            error_log('In learnpath::previous()', 0);
4420
        }
4421
        $this->last = $this->get_current_item_id();
4422
        $this->items[$this->last]->save(
4423
            false,
4424
            $this->prerequisites_match($this->last)
4425
        );
4426
        $this->autocomplete_parents($this->last);
4427
        $new_index = $this->get_previous_index();
4428
        $this->index = $new_index;
4429
        $this->current = $this->ordered_items[$new_index];
4430
    }
4431
4432
    /**
4433
     * Publishes a learnpath. This basically means show or hide the learnpath
4434
     * to normal users.
4435
     * Can be used as abstract.
4436
     *
4437
     * @param int    Learnpath ID
4438
     * @param int   New visibility
4439
     *
4440
     * @return bool
4441
     */
4442
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4443
    {
4444
        $action = 'visible';
4445
        if ($set_visibility != 1) {
4446
            $action = 'invisible';
4447
            self::toggle_publish($lp_id, 'i');
4448
        }
4449
4450
        return api_item_property_update(
4451
            api_get_course_info(),
4452
            TOOL_LEARNPATH,
4453
            $lp_id,
4454
            $action,
4455
            api_get_user_id()
4456
        );
4457
    }
4458
4459
    /**
4460
     * Publishes a learnpath category.
4461
     * This basically means show or hide the learnpath category to normal users.
4462
     *
4463
     * @param int $id
4464
     * @param int $visibility
4465
     *
4466
     * @return bool
4467
     */
4468
    public static function toggleCategoryVisibility($id, $visibility = 1)
4469
    {
4470
        $action = 'visible';
4471
4472
        if ($visibility != 1) {
4473
            $action = 'invisible';
4474
            $list = new LearnpathList(
4475
                api_get_user_id(),
4476
                null,
4477
                null,
4478
                null,
4479
                false,
4480
                $id
4481
            );
4482
4483
            $lpList = $list->get_flat_list();
4484
            foreach ($lpList as $lp) {
4485
                learnpath::toggle_visibility($lp['iid'], 0);
4486
            }
4487
4488
            learnpath::toggleCategoryPublish($id, 0);
4489
        }
4490
4491
        return api_item_property_update(
4492
            api_get_course_info(),
4493
            TOOL_LEARNPATH_CATEGORY,
4494
            $id,
4495
            $action,
4496
            api_get_user_id()
4497
        );
4498
    }
4499
4500
    /**
4501
     * Publishes a learnpath. This basically means show or hide the learnpath
4502
     * on the course homepage
4503
     * Can be used as abstract.
4504
     *
4505
     * @param int    $lp_id Learnpath id
4506
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4507
     *
4508
     * @return bool
4509
     * @throws \Doctrine\DBAL\DBALException
4510
     */
4511
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4512
    {
4513
        $course_id = api_get_course_int_id();
4514
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4515
        $lp_id = (int) $lp_id;
4516
        $sql = "SELECT * FROM $tbl_lp
4517
                WHERE iid = $lp_id";
4518
        $result = Database::query($sql);
4519
        if (Database::num_rows($result)) {
4520
            $row = Database::fetch_array($result);
4521
            $name = Database::escape_string($row['name']);
4522
            if ($set_visibility == 'i') {
4523
                $v = 0;
4524
            }
4525
            if ($set_visibility == 'v') {
4526
                $v = 1;
4527
            }
4528
4529
            $session_id = api_get_session_id();
4530
            $session_condition = api_get_session_condition($session_id);
4531
4532
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4533
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4534
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4535
4536
            $sql = "SELECT * FROM $tbl_tool
4537
                    WHERE
4538
                        c_id = $course_id AND
4539
                        (link = '$link' OR link = '$oldLink') AND
4540
                        image = 'scormbuilder.gif' AND
4541
                        (
4542
                            link LIKE '$link%' OR
4543
                            link LIKE '$oldLink%'
4544
                        )
4545
                        $session_condition
4546
                    ";
4547
4548
            $result = Database::query($sql);
4549
            $num = Database::num_rows($result);
4550
            if ($set_visibility == 'i' && $num > 0) {
4551
                $sql = "DELETE FROM $tbl_tool
4552
                        WHERE 
4553
                            c_id = $course_id AND 
4554
                            (link = '$link' OR link = '$oldLink') AND 
4555
                            image='scormbuilder.gif' 
4556
                            $session_condition";
4557
                Database::query($sql);
4558
            } elseif ($set_visibility == 'v' && $num == 0) {
4559
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4560
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $v does not seem to be defined for all execution paths leading up to this point.
Loading history...
4561
                Database::query($sql);
4562
                $insertId = Database::insert_id();
4563
                if ($insertId) {
4564
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4565
                    Database::query($sql);
4566
                }
4567
            } elseif ($set_visibility == 'v' && $num > 0) {
4568
                $sql = "UPDATE $tbl_tool SET
4569
                            c_id = $course_id,
4570
                            name = '$name',
4571
                            link = '$link',
4572
                            image = 'scormbuilder.gif',
4573
                            visibility = '$v',
4574
                            admin = '0',
4575
                            address = 'pastillegris.gif',
4576
                            added_tool = 0,
4577
                            session_id = $session_id
4578
                        WHERE
4579
                            c_id = ".$course_id." AND
4580
                            (link = '$link' OR link = '$oldLink') AND 
4581
                            image='scormbuilder.gif' 
4582
                            $session_condition
4583
                        ";
4584
                Database::query($sql);
4585
            } else {
4586
                // Parameter and database incompatible, do nothing, exit.
4587
                return false;
4588
            }
4589
        } else {
4590
            return false;
4591
        }
4592
    }
4593
4594
    /**
4595
     * Publishes a learnpath.
4596
     * Show or hide the learnpath category on the course homepage.
4597
     *
4598
     * @param int $id
4599
     * @param int $setVisibility
4600
     *
4601
     * @return bool
4602
     * @throws \Doctrine\ORM\NonUniqueResultException
4603
     * @throws \Doctrine\ORM\ORMException
4604
     * @throws \Doctrine\ORM\OptimisticLockException
4605
     * @throws \Doctrine\ORM\TransactionRequiredException
4606
     */
4607
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4608
    {
4609
        $courseId = api_get_course_int_id();
4610
        $sessionId = api_get_session_id();
4611
        $sessionCondition = api_get_session_condition(
4612
            $sessionId,
4613
            true,
4614
            false,
4615
            't.sessionId'
4616
        );
4617
4618
        $em = Database::getManager();
4619
4620
        /** @var CLpCategory $category */
4621
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4622
4623
        if (!$category) {
4624
            return false;
4625
        }
4626
4627
        $link = self::getCategoryLinkForTool($id);
4628
4629
        /** @var CTool $tool */
4630
        $tool = $em->createQuery("
4631
                SELECT t FROM ChamiloCourseBundle:CTool t
4632
                WHERE
4633
                    t.cId = :course AND
4634
                    t.link = :link1 AND
4635
                    t.image = 'lp_category.gif' AND
4636
                    t.link LIKE :link2
4637
                    $sessionCondition
4638
            ")
4639
            ->setParameters([
4640
                'course' => (int) $courseId,
4641
                'link1' => $link,
4642
                'link2' => "$link%",
4643
            ])
4644
            ->getOneOrNullResult();
4645
4646
        if ($setVisibility == 0 && $tool) {
4647
            $em->remove($tool);
4648
            $em->flush();
4649
4650
            return true;
4651
        }
4652
4653
        if ($setVisibility == 1 && !$tool) {
4654
            $tool = new CTool();
4655
            $tool
4656
                ->setCategory('authoring')
4657
                ->setCId($courseId)
4658
                ->setName(strip_tags($category->getName()))
4659
                ->setLink($link)
4660
                ->setImage('lp_category.gif')
4661
                ->setVisibility(1)
4662
                ->setAdmin(0)
4663
                ->setAddress('pastillegris.gif')
4664
                ->setAddedTool(0)
4665
                ->setSessionId($sessionId)
4666
                ->setTarget('_self');
4667
4668
            $em->persist($tool);
4669
            $em->flush();
4670
4671
            $tool->setId($tool->getIid());
4672
4673
            $em->persist($tool);
4674
            $em->flush();
4675
4676
            return true;
4677
        }
4678
4679
        if ($setVisibility == 1 && $tool) {
4680
            $tool
4681
                ->setName(strip_tags($category->getName()))
4682
                ->setVisibility(1);
4683
4684
            $em->persist($tool);
4685
            $em->flush();
4686
4687
            return true;
4688
        }
4689
4690
        return false;
4691
    }
4692
4693
    /**
4694
     * Check if the learnpath category is visible for a user.
4695
     *
4696
     * @param CLpCategory $category
4697
     * @param User        $user
4698
     *
4699
     * @return bool
4700
     */
4701
    public static function categoryIsVisibleForStudent(
4702
        CLpCategory $category,
4703
        User $user
4704
    ) {
4705
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4706
4707
        if ($isAllowedToEdit) {
4708
            return true;
4709
        }
4710
4711
        if (empty($category)) {
4712
            return false;
4713
        }
4714
4715
        $users = $category->getUsers();
4716
4717
        if (empty($users) || !$users->count()) {
4718
            return true;
4719
        }
4720
4721
        if ($category->hasUserAdded($user)) {
4722
            return true;
4723
        }
4724
4725
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4726
        if (!empty($groups)) {
4727
            $em = Database::getManager();
4728
4729
            /** @var ItemPropertyRepository $itemRepo */
4730
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4731
4732
            /** @var CourseRepository $courseRepo */
4733
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4734
            $sessionId = api_get_session_id();
4735
            $session = null;
4736
            if (!empty($sessionId)) {
4737
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4738
            }
4739
4740
            $course = $courseRepo->find(api_get_course_int_id());
4741
4742
            // Subscribed groups to a LP
4743
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4744
                TOOL_LEARNPATH_CATEGORY,
4745
                $category->getId(),
4746
                $course,
4747
                $session
4748
            );
4749
4750
            if (!empty($subscribedGroupsInLp)) {
4751
                $groups = array_column($groups, 'iid');
4752
                /** @var CItemProperty $item */
4753
                foreach ($subscribedGroupsInLp as $item) {
4754
                    if ($item->getGroup() &&
4755
                        in_array($item->getGroup()->getId(), $groups)
4756
                    ) {
4757
                        return true;
4758
                    }
4759
                }
4760
            }
4761
        }
4762
4763
        return false;
4764
    }
4765
4766
    /**
4767
     * Check if a learnpath category is published as course tool.
4768
     *
4769
     * @param CLpCategory $category
4770
     * @param int         $courseId
4771
     *
4772
     * @return bool
4773
     */
4774
    public static function categoryIsPublished(
4775
        CLpCategory $category,
4776
        $courseId
4777
    ) {
4778
        $link = self::getCategoryLinkForTool($category->getId());
4779
        $em = Database::getManager();
4780
4781
        $tools = $em
4782
            ->createQuery("
4783
                SELECT t FROM ChamiloCourseBundle:CTool t
4784
                WHERE t.cId = :course AND 
4785
                    t.name = :name AND
4786
                    t.image = 'lp_category.gif' AND
4787
                    t.link LIKE :link
4788
            ")
4789
            ->setParameters([
4790
                'course' => $courseId,
4791
                'name' => strip_tags($category->getName()),
4792
                'link' => "$link%",
4793
            ])
4794
            ->getResult();
4795
4796
        /** @var CTool $tool */
4797
        $tool = current($tools);
4798
4799
        return $tool ? $tool->getVisibility() : false;
4800
    }
4801
4802
    /**
4803
     * Restart the whole learnpath. Return the URL of the first element.
4804
     * Make sure the results are saved with anoter method. This method should probably be
4805
     * redefined in children classes.
4806
     * To use a similar method  statically, use the create_new_attempt() method.
4807
     *
4808
     * @return bool
4809
     * @throws \Doctrine\DBAL\DBALException
4810
     */
4811
    public function restart()
4812
    {
4813
        if ($this->debug > 0) {
4814
            error_log('In learnpath::restart()', 0);
4815
        }
4816
        // TODO
4817
        // Call autosave method to save the current progress.
4818
        //$this->index = 0;
4819
        if (api_is_invitee()) {
4820
            return false;
4821
        }
4822
        $session_id = api_get_session_id();
4823
        $course_id = api_get_course_int_id();
4824
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4825
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4826
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4827
        if ($this->debug > 2) {
4828
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4829
        }
4830
        Database::query($sql);
4831
        $view_id = Database::insert_id();
4832
4833
        if ($view_id) {
4834
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4835
            Database::query($sql);
4836
            $this->lp_view_id = $view_id;
4837
            $this->attempt = $this->attempt + 1;
4838
        } else {
4839
            $this->error = 'Could not insert into item_view table...';
4840
4841
            return false;
4842
        }
4843
        $this->autocomplete_parents($this->current);
4844
        foreach ($this->items as $index => $dummy) {
4845
            $this->items[$index]->restart();
4846
            $this->items[$index]->set_lp_view($this->lp_view_id);
4847
        }
4848
        $this->first();
4849
4850
        return true;
4851
    }
4852
4853
    /**
4854
     * Saves the current item.
4855
     *
4856
     * @return bool
4857
     */
4858
    public function save_current()
4859
    {
4860
        $debug = $this->debug;
4861
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4862
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4863
        if ($debug) {
4864
            error_log('save_current() saving item '.$this->current, 0);
4865
            error_log(''.print_r($this->items, true), 0);
4866
        }
4867
        if (isset($this->items[$this->current]) &&
4868
            is_object($this->items[$this->current])
4869
        ) {
4870
            if ($debug) {
4871
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4872
            }
4873
4874
            $res = $this->items[$this->current]->save(
4875
                false,
4876
                $this->prerequisites_match($this->current)
4877
            );
4878
            $this->autocomplete_parents($this->current);
4879
            $status = $this->items[$this->current]->get_status();
4880
            $this->update_queue[$this->current] = $status;
4881
4882
            if ($debug) {
4883
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4884
            }
4885
4886
            return $res;
4887
        }
4888
4889
        return false;
4890
    }
4891
4892
    /**
4893
     * Saves the given item.
4894
     *
4895
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4896
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4897
     *
4898
     * @return bool
4899
     */
4900
    public function save_item($item_id = null, $from_outside = true)
4901
    {
4902
        $debug = $this->debug;
4903
        if ($debug) {
4904
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4905
        }
4906
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4907
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4908
        if (empty($item_id)) {
4909
            $item_id = intval($_REQUEST['id']);
4910
        }
4911
        if (empty($item_id)) {
4912
            $item_id = $this->get_current_item_id();
4913
        }
4914
        if (isset($this->items[$item_id]) &&
4915
            is_object($this->items[$item_id])
4916
        ) {
4917
            if ($debug) {
4918
                error_log('Object exists');
4919
            }
4920
4921
            // Saving the item.
4922
            $res = $this->items[$item_id]->save(
4923
                $from_outside,
4924
                $this->prerequisites_match($item_id)
4925
            );
4926
4927
            if ($debug) {
4928
                error_log('update_queue before:');
4929
                error_log(print_r($this->update_queue, 1));
4930
            }
4931
            $this->autocomplete_parents($item_id);
4932
4933
            $status = $this->items[$item_id]->get_status();
4934
            $this->update_queue[$item_id] = $status;
4935
4936
            if ($debug) {
4937
                error_log('get_status(): '.$status);
4938
                error_log('update_queue after:');
4939
                error_log(print_r($this->update_queue, 1));
4940
            }
4941
4942
            return $res;
4943
        }
4944
4945
        return false;
4946
    }
4947
4948
    /**
4949
     * Saves the last item seen's ID only in case.
4950
     */
4951
    public function save_last()
4952
    {
4953
        $course_id = api_get_course_int_id();
4954
        $debug = $this->debug;
4955
        if ($debug) {
4956
            error_log('In learnpath::save_last()', 0);
4957
        }
4958
        $session_condition = api_get_session_condition(
4959
            api_get_session_id(),
4960
            true,
4961
            false
4962
        );
4963
        $table = Database::get_course_table(TABLE_LP_VIEW);
4964
4965
        if (isset($this->current) && !api_is_invitee()) {
4966
            if ($debug) {
4967
                error_log('Saving current item ('.$this->current.') for later review', 0);
4968
            }
4969
            $sql = "UPDATE $table SET
4970
                        last_item = ".intval($this->get_current_item_id())."
4971
                    WHERE
4972
                        c_id = $course_id AND
4973
                        lp_id = ".$this->get_id()." AND
4974
                        user_id = ".$this->get_user_id()." ".$session_condition;
4975
4976
            if ($debug) {
4977
                error_log('Saving last item seen : '.$sql, 0);
4978
            }
4979
            Database::query($sql);
4980
        }
4981
4982
        if (!api_is_invitee()) {
4983
            // Save progress.
4984
            list($progress) = $this->get_progress_bar_text('%');
4985
            if ($progress >= 0 && $progress <= 100) {
4986
                $progress = (int) $progress;
4987
                $sql = "UPDATE $table SET
4988
                            progress = $progress
4989
                        WHERE
4990
                            c_id = $course_id AND
4991
                            lp_id = ".$this->get_id()." AND
4992
                            user_id = ".$this->get_user_id()." ".$session_condition;
4993
                // Ignore errors as some tables might not have the progress field just yet.
4994
                Database::query($sql);
4995
                if ($debug) {
4996
                    error_log($sql);
4997
                }
4998
                $this->progress_db = $progress;
4999
            }
5000
        }
5001
    }
5002
5003
    /**
5004
     * Sets the current item ID (checks if valid and authorized first).
5005
     *
5006
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
5007
     */
5008
    public function set_current_item($item_id = null)
5009
    {
5010
        $debug = $this->debug;
5011
        if ($debug) {
5012
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
5013
        }
5014
        if (empty($item_id)) {
5015
            if ($debug) {
5016
                error_log('No new current item given, ignore...', 0);
5017
            }
5018
            // Do nothing.
5019
        } else {
5020
            if ($debug) {
5021
                error_log('New current item given is '.$item_id.'...', 0);
5022
            }
5023
            if (is_numeric($item_id)) {
5024
                $item_id = intval($item_id);
5025
                // TODO: Check in database here.
5026
                $this->last = $this->current;
5027
                $this->current = $item_id;
5028
                // TODO: Update $this->index as well.
5029
                foreach ($this->ordered_items as $index => $item) {
5030
                    if ($item == $this->current) {
5031
                        $this->index = $index;
5032
                        break;
5033
                    }
5034
                }
5035
                if ($debug) {
5036
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
5037
                }
5038
            } else {
5039
                if ($debug) {
5040
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
5041
                }
5042
            }
5043
        }
5044
    }
5045
5046
    /**
5047
     * Sets the encoding.
5048
     *
5049
     * @param    string    New encoding
5050
     *
5051
     * @return bool
5052
     *              TODO (as of Chamilo 1.8.8): Check in the future whether this method is needed.
5053
     * @throws \Doctrine\DBAL\DBALException
5054
     */
5055
    public function set_encoding($enc = 'UTF-8')
5056
    {
5057
        if ($this->debug > 0) {
5058
            error_log('In learnpath::set_encoding()', 0);
5059
        }
5060
5061
        $enc = api_refine_encoding_id($enc);
5062
        if (empty($enc)) {
5063
            $enc = api_get_system_encoding();
5064
        }
5065
        if (api_is_encoding_supported($enc)) {
5066
            $lp = $this->get_id();
5067
            if ($lp != 0) {
5068
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5069
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc' 
5070
                        WHERE iid = ".$lp;
5071
                $res = Database::query($sql);
5072
5073
                return $res;
5074
            }
5075
        }
5076
5077
        return false;
5078
    }
5079
5080
    /**
5081
     * Sets the JS lib setting in the database directly.
5082
     * This is the JavaScript library file this lp needs to load on startup.
5083
     *
5084
     * @param    string    Proximity setting
5085
     *
5086
     * @return bool True on update success. False otherwise.
5087
     * @throws \Doctrine\DBAL\DBALException
5088
     */
5089
    public function set_jslib($lib = '')
5090
    {
5091
        if ($this->debug > 0) {
5092
            error_log('In learnpath::set_jslib()', 0);
5093
        }
5094
        $lp = $this->get_id();
5095
        $course_id = api_get_course_int_id();
5096
5097
        if ($lp != 0) {
5098
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5099
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib' 
5100
                    WHERE iid = $lp";
5101
            $res = Database::query($sql);
5102
5103
            return $res;
5104
        } else {
5105
            return false;
5106
        }
5107
    }
5108
5109
    /**
5110
     * Sets the name of the LP maker (publisher) (and save).
5111
     *
5112
     * @param    string    Optional string giving the new content_maker of this learnpath
5113
     *
5114
     * @return bool True
5115
     * @throws \Doctrine\DBAL\DBALException
5116
     */
5117
    public function set_maker($name = '')
5118
    {
5119
        if ($this->debug > 0) {
5120
            error_log('In learnpath::set_maker()', 0);
5121
        }
5122
        if (empty($name)) {
5123
            return false;
5124
        }
5125
        $this->maker = $name;
5126
        $table = Database::get_course_table(TABLE_LP_MAIN);
5127
        $lp_id = $this->get_id();
5128
        $sql = "UPDATE $table SET
5129
                content_maker = '".Database::escape_string($this->maker)."'
5130
                WHERE iid = $lp_id";
5131
        if ($this->debug > 2) {
5132
            error_log('lp updated with new content_maker : '.$this->maker, 0);
5133
        }
5134
        Database::query($sql);
5135
5136
        return true;
5137
    }
5138
5139
    /**
5140
     * Sets the name of the current learnpath (and save).
5141
     *
5142
     * @param string $name Optional string giving the new name of this learnpath
5143
     *
5144
     * @return bool True/False
5145
     * @throws \Doctrine\DBAL\DBALException
5146
     */
5147
    public function set_name($name = null)
5148
    {
5149
        if ($this->debug > 0) {
5150
            error_log('In learnpath::set_name()', 0);
5151
        }
5152
        if (empty($name)) {
5153
            return false;
5154
        }
5155
        $this->name = Database::escape_string($name);
5156
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5157
        $lp_id = $this->get_id();
5158
        $course_id = $this->course_info['real_id'];
5159
        $sql = "UPDATE $lp_table SET
5160
                name = '".Database::escape_string($this->name)."'
5161
                WHERE iid = $lp_id";
5162
        if ($this->debug > 2) {
5163
            error_log('lp updated with new name : '.$this->name, 0);
5164
        }
5165
        $result = Database::query($sql);
5166
        // If the lp is visible on the homepage, change his name there.
5167
        if (Database::affected_rows($result)) {
5168
            $session_id = api_get_session_id();
5169
            $session_condition = api_get_session_condition($session_id);
5170
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5171
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5172
            $sql = "UPDATE $tbl_tool SET name = '$this->name'
5173
            	    WHERE
5174
            	        c_id = $course_id AND
5175
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5176
            Database::query($sql);
5177
5178
            return true;
5179
        } else {
5180
            return false;
5181
        }
5182
    }
5183
5184
    /**
5185
     * Set index specified prefix terms for all items in this path.
5186
     *
5187
     * @param string $terms_string Comma-separated list of terms
5188
     * @param string $prefix Xapian term prefix
5189
     *
5190
     * @return bool False on error, true otherwise
5191
     * @throws \Doctrine\DBAL\DBALException
5192
     */
5193
    public function set_terms_by_prefix($terms_string, $prefix)
5194
    {
5195
        $course_id = api_get_course_int_id();
5196
        if (api_get_setting('search_enabled') !== 'true') {
5197
            return false;
5198
        }
5199
5200
        if (!extension_loaded('xapian')) {
5201
            return false;
5202
        }
5203
5204
        $terms_string = trim($terms_string);
5205
        $terms = explode(',', $terms_string);
5206
        array_walk($terms, 'trim_value');
5207
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5208
5209
        // Don't do anything if no change, verify only at DB, not the search engine.
5210
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5211
            return false;
5212
        }
5213
5214
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5215
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5216
5217
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5218
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5219
        $lp_id = intval($_POST['lp_id']);
5220
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5221
        $result = Database::query($sql);
5222
        $di = new ChamiloIndexer();
5223
5224
        while ($lp_item = Database::fetch_array($result)) {
5225
            // Get search_did.
5226
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5227
            $sql = 'SELECT * FROM %s
5228
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5229
                    LIMIT 1';
5230
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5231
5232
            //echo $sql; echo '<br>';
5233
            $res = Database::query($sql);
5234
            if (Database::num_rows($res) > 0) {
5235
                $se_ref = Database::fetch_array($res);
5236
                // Compare terms.
5237
                $doc = $di->get_document($se_ref['search_did']);
5238
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5239
                $xterms = [];
5240
                foreach ($xapian_terms as $xapian_term) {
5241
                    $xterms[] = substr($xapian_term['name'], 1);
5242
                }
5243
5244
                $dterms = $terms;
5245
                $missing_terms = array_diff($dterms, $xterms);
5246
                $deprecated_terms = array_diff($xterms, $dterms);
5247
5248
                // Save it to search engine.
5249
                foreach ($missing_terms as $term) {
5250
                    $doc->add_term($prefix.$term, 1);
5251
                }
5252
                foreach ($deprecated_terms as $term) {
5253
                    $doc->remove_term($prefix.$term);
5254
                }
5255
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5256
                $di->getDb()->flush();
5257
            }
5258
        }
5259
5260
        return true;
5261
    }
5262
5263
    /**
5264
     * Sets the theme of the LP (local/remote) (and save).
5265
     *
5266
     * @param    string    Optional string giving the new theme of this learnpath
5267
     *
5268
     * @return bool Returns true if theme name is not empty
5269
     * @throws \Doctrine\DBAL\DBALException
5270
     */
5271
    public function set_theme($name = '')
5272
    {
5273
        if ($this->debug > 0) {
5274
            error_log('In learnpath::set_theme()', 0);
5275
        }
5276
        $this->theme = $name;
5277
        $table = Database::get_course_table(TABLE_LP_MAIN);
5278
        $lp_id = $this->get_id();
5279
        $sql = "UPDATE $table 
5280
                SET theme = '".Database::escape_string($this->theme)."'
5281
                WHERE iid = $lp_id";
5282
        if ($this->debug > 2) {
5283
            error_log('lp updated with new theme : '.$this->theme, 0);
5284
        }
5285
        Database::query($sql);
5286
5287
        return true;
5288
    }
5289
5290
    /**
5291
     * Sets the image of an LP (and save).
5292
     *
5293
     * @param     string    Optional string giving the new image of this learnpath
5294
     *
5295
     * @return bool Returns true if theme name is not empty
5296
     * @throws \Doctrine\DBAL\DBALException
5297
     */
5298
    public function set_preview_image($name = '')
5299
    {
5300
        if ($this->debug > 0) {
5301
            error_log('In learnpath::set_preview_image()', 0);
5302
        }
5303
5304
        $this->preview_image = $name;
5305
        $table = Database::get_course_table(TABLE_LP_MAIN);
5306
        $lp_id = $this->get_id();
5307
        $sql = "UPDATE $table SET
5308
                preview_image = '".Database::escape_string($this->preview_image)."'
5309
                WHERE iid = $lp_id";
5310
        if ($this->debug > 2) {
5311
            error_log('lp updated with new preview image : '.$this->preview_image, 0);
5312
        }
5313
        Database::query($sql);
5314
5315
        return true;
5316
    }
5317
5318
    /**
5319
     * Sets the author of a LP (and save).
5320
     *
5321
     * @param    string    Optional string giving the new author of this learnpath
5322
     *
5323
     * @return bool Returns true if author's name is not empty
5324
     * @throws \Doctrine\DBAL\DBALException
5325
     */
5326
    public function set_author($name = '')
5327
    {
5328
        if ($this->debug > 0) {
5329
            error_log('In learnpath::set_author()', 0);
5330
        }
5331
        $this->author = $name;
0 ignored issues
show
Bug Best Practice introduced by
The property author does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
5332
        $table = Database::get_course_table(TABLE_LP_MAIN);
5333
        $lp_id = $this->get_id();
5334
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5335
                WHERE iid = $lp_id";
5336
        if ($this->debug > 2) {
5337
            error_log('lp updated with new preview author : '.$this->author, 0);
5338
        }
5339
        Database::query($sql);
5340
5341
        return true;
5342
    }
5343
5344
    /**
5345
     * Sets the hide_toc_frame parameter of a LP (and save).
5346
     *
5347
     * @param    int    1 if frame is hidden 0 then else
0 ignored issues
show
Documentation Bug introduced by
The doc comment 1 at position 0 could not be parsed: Unknown type name '1' at position 0 in 1.
Loading history...
5348
     *
5349
     * @return bool Returns true if author's name is not empty
5350
     * @throws \Doctrine\DBAL\DBALException
5351
     */
5352
    public function set_hide_toc_frame($hide)
5353
    {
5354
        if ($this->debug > 0) {
5355
            error_log('In learnpath::set_hide_toc_frame()', 0);
5356
        }
5357
        if (intval($hide) == $hide) {
5358
            $this->hide_toc_frame = $hide;
0 ignored issues
show
Bug Best Practice introduced by
The property hide_toc_frame does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
5359
            $table = Database::get_course_table(TABLE_LP_MAIN);
5360
            $lp_id = $this->get_id();
5361
            $sql = "UPDATE $table SET
5362
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5363
                    WHERE iid = $lp_id";
5364
            if ($this->debug > 2) {
5365
                error_log('lp updated with new preview hide_toc_frame : '.$this->author, 0);
5366
            }
5367
            Database::query($sql);
5368
5369
            return true;
5370
        } else {
5371
            return false;
5372
        }
5373
    }
5374
5375
    /**
5376
     * Sets the prerequisite of a LP (and save).
5377
     *
5378
     * @param    int        integer giving the new prerequisite of this learnpath
5379
     *
5380
     * @return bool returns true if prerequisite is not empty
5381
     * @throws \Doctrine\DBAL\DBALException
5382
     */
5383
    public function set_prerequisite($prerequisite)
5384
    {
5385
        if ($this->debug > 0) {
5386
            error_log('In learnpath::set_prerequisite()', 0);
5387
        }
5388
        $this->prerequisite = intval($prerequisite);
5389
        $table = Database::get_course_table(TABLE_LP_MAIN);
5390
        $lp_id = $this->get_id();
5391
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5392
                WHERE iid = $lp_id";
5393
        if ($this->debug > 2) {
5394
            error_log('lp updated with new preview requisite : '.$this->requisite, 0);
0 ignored issues
show
Bug introduced by
The property requisite does not exist on learnpath. Did you mean prerequisite?
Loading history...
5395
        }
5396
        Database::query($sql);
5397
5398
        return true;
5399
    }
5400
5401
    /**
5402
     * Sets the location/proximity of the LP (local/remote) (and save).
5403
     *
5404
     * @param    string    Optional string giving the new location of this learnpath
5405
     *
5406
     * @return bool True on success / False on error
5407
     * @throws \Doctrine\DBAL\DBALException
5408
     */
5409
    public function set_proximity($name = '')
5410
    {
5411
        if ($this->debug > 0) {
5412
            error_log('In learnpath::set_proximity()', 0);
5413
        }
5414
        if (empty($name)) {
5415
            return false;
5416
        }
5417
5418
        $this->proximity = $name;
5419
        $table = Database::get_course_table(TABLE_LP_MAIN);
5420
        $lp_id = $this->get_id();
5421
        $sql = "UPDATE $table SET
5422
                    content_local = '".Database::escape_string($name)."'
5423
                WHERE iid = $lp_id";
5424
        if ($this->debug > 2) {
5425
            error_log('lp updated with new proximity : '.$this->proximity, 0);
5426
        }
5427
        Database::query($sql);
5428
5429
        return true;
5430
    }
5431
5432
    /**
5433
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5434
     *
5435
     * @param	int	DB ID of the item
5436
     */
5437
    public function set_previous_item($id)
5438
    {
5439
        if ($this->debug > 0) {
5440
            error_log('In learnpath::set_previous_item()', 0);
5441
        }
5442
        $this->last = $id;
5443
    }
5444
5445
    /**
5446
     * Sets use_max_score.
5447
     *
5448
     * @param int $use_max_score Optional string giving the new location of this learnpath
5449
     *
5450
     * @return bool True on success / False on error
5451
     * @throws \Doctrine\DBAL\DBALException
5452
     */
5453
    public function set_use_max_score($use_max_score = 1)
5454
    {
5455
        if ($this->debug > 0) {
5456
            error_log('In learnpath::set_use_max_score()', 0);
5457
        }
5458
        $use_max_score = intval($use_max_score);
5459
        $this->use_max_score = $use_max_score;
5460
        $table = Database::get_course_table(TABLE_LP_MAIN);
5461
        $lp_id = $this->get_id();
5462
        $sql = "UPDATE $table SET
5463
                    use_max_score = '".$this->use_max_score."'
5464
                WHERE iid = $lp_id";
5465
5466
        if ($this->debug > 2) {
5467
            error_log('lp updated with new use_max_score : '.$this->use_max_score, 0);
5468
        }
5469
        Database::query($sql);
5470
5471
        return true;
5472
    }
5473
5474
    /**
5475
     * Sets and saves the expired_on date.
5476
     *
5477
     * @param string $expired_on Optional string giving the new author of this learnpath
5478
     *
5479
     * @return bool Returns true if author's name is not empty
5480
     * @throws \Doctrine\ORM\OptimisticLockException
5481
     */
5482
    public function set_expired_on($expired_on)
5483
    {
5484
        if ($this->debug > 0) {
5485
            error_log('In learnpath::set_expired_on()', 0);
5486
        }
5487
5488
        $em = Database::getManager();
5489
        /** @var CLp $lp */
5490
        $lp = $em
5491
            ->getRepository('ChamiloCourseBundle:CLp')
5492
            ->findOneBy(
5493
                [
5494
                    'iid' => $this->get_id(),
5495
                ]
5496
            );
5497
5498
        if (!$lp) {
5499
            return false;
5500
        }
5501
5502
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5503
5504
        $lp->setExpiredOn($this->expired_on);
0 ignored issues
show
Bug introduced by
It seems like $this->expired_on can also be of type string; however, parameter $expiredOn of Chamilo\CourseBundle\Entity\CLp::setExpiredOn() does only seem to accept DateTime, maybe add an additional type check? ( Ignorable by Annotation )

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

5504
        $lp->setExpiredOn(/** @scrutinizer ignore-type */ $this->expired_on);
Loading history...
5505
        $em->persist($lp);
5506
        $em->flush();
5507
5508
        if ($this->debug > 2) {
5509
            error_log('lp updated with new expired_on : '.$this->expired_on, 0);
5510
        }
5511
5512
        return true;
5513
    }
5514
5515
    /**
5516
     * Sets and saves the publicated_on date.
5517
     *
5518
     * @param string $publicated_on Optional string giving the new author of this learnpath
5519
     *
5520
     * @return bool Returns true if author's name is not empty
5521
     * @throws \Doctrine\ORM\OptimisticLockException
5522
     */
5523
    public function set_publicated_on($publicated_on)
5524
    {
5525
        if ($this->debug > 0) {
5526
            error_log('In learnpath::set_expired_on()', 0);
5527
        }
5528
5529
        $em = Database::getManager();
5530
        /** @var CLp $lp */
5531
        $lp = $em
5532
            ->getRepository('ChamiloCourseBundle:CLp')
5533
            ->findOneBy(
5534
                [
5535
                    'iid' => $this->get_id(),
5536
                ]
5537
            );
5538
5539
        if (!$lp) {
5540
            return false;
5541
        }
5542
5543
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5544
        $lp->setPublicatedOn($this->publicated_on);
0 ignored issues
show
Bug introduced by
It seems like $this->publicated_on can also be of type string; however, parameter $publicatedOn of Chamilo\CourseBundle\Entity\CLp::setPublicatedOn() does only seem to accept DateTime, maybe add an additional type check? ( Ignorable by Annotation )

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

5544
        $lp->setPublicatedOn(/** @scrutinizer ignore-type */ $this->publicated_on);
Loading history...
5545
        $em->persist($lp);
5546
        $em->flush();
5547
5548
        if ($this->debug > 2) {
5549
            error_log('lp updated with new publicated_on : '.$this->publicated_on, 0);
5550
        }
5551
5552
        return true;
5553
    }
5554
5555
    /**
5556
     * Sets and saves the expired_on date.
5557
     *
5558
     * @return bool Returns true if author's name is not empty
5559
     * @throws \Doctrine\DBAL\DBALException
5560
     */
5561
    public function set_modified_on()
5562
    {
5563
        if ($this->debug > 0) {
5564
            error_log('In learnpath::set_expired_on()', 0);
5565
        }
5566
        $this->modified_on = api_get_utc_datetime();
5567
        $table = Database::get_course_table(TABLE_LP_MAIN);
5568
        $lp_id = $this->get_id();
5569
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5570
                WHERE iid = $lp_id";
5571
        if ($this->debug > 2) {
5572
            error_log('lp updated with new expired_on : '.$this->modified_on, 0);
5573
        }
5574
        Database::query($sql);
5575
5576
        return true;
5577
    }
5578
5579
    /**
5580
     * Sets the object's error message.
5581
     *
5582
     * @param	string	Error message. If empty, reinits the error string
5583
     */
5584
    public function set_error_msg($error = '')
5585
    {
5586
        if ($this->debug > 0) {
5587
            error_log('In learnpath::set_error_msg()', 0);
5588
        }
5589
        if (empty($error)) {
5590
            $this->error = '';
5591
        } else {
5592
            $this->error .= $error;
5593
        }
5594
    }
5595
5596
    /**
5597
     * Launches the current item if not 'sco'
5598
     * (starts timer and make sure there is a record ready in the DB).
5599
     *
5600
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5601
     *
5602
     * @return bool
5603
     */
5604
    public function start_current_item($allow_new_attempt = false)
5605
    {
5606
        $debug = $this->debug;
5607
        if ($debug) {
5608
            error_log('In learnpath::start_current_item()');
5609
            error_log('current: '.$this->current);
5610
        }
5611
        if ($this->current != 0 && is_object($this->items[$this->current])) {
5612
            $type = $this->get_type();
5613
            $item_type = $this->items[$this->current]->get_type();
5614
            if (($type == 2 && $item_type != 'sco') ||
5615
                ($type == 3 && $item_type != 'au') ||
5616
                ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)
5617
            ) {
5618
                if ($debug) {
5619
                    error_log('item type: '.$item_type);
5620
                    error_log('lp type: '.$type);
5621
                }
5622
                $this->items[$this->current]->open($allow_new_attempt);
5623
                $this->autocomplete_parents($this->current);
5624
                $prereq_check = $this->prerequisites_match($this->current);
5625
                if ($debug) {
5626
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5627
                }
5628
                $this->items[$this->current]->save(false, $prereq_check);
5629
            }
5630
            // If sco, then it is supposed to have been updated by some other call.
5631
            if ($item_type == 'sco') {
5632
                $this->items[$this->current]->restart();
5633
            }
5634
        }
5635
        if ($debug) {
5636
            error_log('lp_view_session_id');
5637
            error_log($this->lp_view_session_id);
5638
            error_log('api session id');
5639
            error_log(api_get_session_id());
5640
            error_log('End of learnpath::start_current_item()');
5641
        }
5642
5643
        return true;
5644
    }
5645
5646
    /**
5647
     * Stops the processing and counters for the old item (as held in $this->last).
5648
     *
5649
     * @return bool True/False
5650
     */
5651
    public function stop_previous_item()
5652
    {
5653
        $debug = $this->debug;
5654
        if ($debug) {
5655
            error_log('In learnpath::stop_previous_item()', 0);
5656
        }
5657
5658
        if ($this->last != 0 && $this->last != $this->current && is_object($this->items[$this->last])) {
5659
            if ($debug) {
5660
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5661
            }
5662
            switch ($this->get_type()) {
5663
                case '3':
5664
                    if ($this->items[$this->last]->get_type() != 'au') {
5665
                        if ($debug) {
5666
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5667
                        }
5668
                        $this->items[$this->last]->close();
5669
                    } else {
5670
                        if ($debug) {
5671
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5672
                        }
5673
                    }
5674
                    break;
5675
                case '2':
5676
                    if ($this->items[$this->last]->get_type() != 'sco') {
5677
                        if ($debug) {
5678
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5679
                        }
5680
                        $this->items[$this->last]->close();
5681
                    } else {
5682
                        if ($debug) {
5683
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5684
                        }
5685
                    }
5686
                    break;
5687
                case '1':
5688
                default:
5689
                    if ($debug) {
5690
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5691
                    }
5692
                    $this->items[$this->last]->close();
5693
                    break;
5694
            }
5695
        } else {
5696
            if ($debug) {
5697
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5698
            }
5699
5700
            return false;
5701
        }
5702
5703
        return true;
5704
    }
5705
5706
    /**
5707
     * Updates the default view mode from fullscreen to embedded and inversely.
5708
     *
5709
     * @return string The current default view mode ('fullscreen' or 'embedded')
5710
     * @throws \Doctrine\DBAL\DBALException
5711
     */
5712
    public function update_default_view_mode()
5713
    {
5714
        if ($this->debug > 0) {
5715
            error_log('In learnpath::update_default_view_mode()', 0);
5716
        }
5717
        $table = Database::get_course_table(TABLE_LP_MAIN);
5718
        $sql = "SELECT * FROM $table
5719
                WHERE iid = ".$this->get_id();
5720
        $res = Database::query($sql);
5721
        if (Database::num_rows($res) > 0) {
5722
            $row = Database::fetch_array($res);
5723
            $default_view_mode = $row['default_view_mod'];
5724
            $view_mode = $default_view_mode;
5725
            switch ($default_view_mode) {
5726
                case 'fullscreen': // default with popup
5727
                    $view_mode = 'embedded';
5728
                    break;
5729
                case 'embedded': // default view with left menu
5730
                    $view_mode = 'embedframe';
5731
                    break;
5732
                case 'embedframe': //folded menu
5733
                    $view_mode = 'impress';
5734
                    break;
5735
                case 'impress':
5736
                    $view_mode = 'fullscreen';
5737
                    break;
5738
            }
5739
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5740
                    WHERE iid = ".$this->get_id();
5741
            Database::query($sql);
5742
            $this->mode = $view_mode;
5743
5744
            return $view_mode;
5745
        } else {
5746
            if ($this->debug > 2) {
5747
                error_log('Problem in update_default_view() - could not find LP '.$this->get_id().' in DB', 0);
5748
            }
5749
        }
5750
5751
        return -1;
5752
    }
5753
5754
    /**
5755
     * Updates the default behaviour about auto-commiting SCORM updates.
5756
     *
5757
     * @return bool True if auto-commit has been set to 'on', false otherwise
5758
     * @throws \Doctrine\DBAL\DBALException
5759
     */
5760
    public function update_default_scorm_commit()
5761
    {
5762
        if ($this->debug > 0) {
5763
            error_log('In learnpath::update_default_scorm_commit()', 0);
5764
        }
5765
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5766
        $sql = "SELECT * FROM $lp_table
5767
                WHERE iid = ".$this->get_id();
5768
        $res = Database::query($sql);
5769
        if (Database::num_rows($res) > 0) {
5770
            $row = Database::fetch_array($res);
5771
            $force = $row['force_commit'];
5772
            if ($force == 1) {
5773
                $force = 0;
5774
                $force_return = false;
5775
            } elseif ($force == 0) {
5776
                $force = 1;
5777
                $force_return = true;
5778
            }
5779
            $sql = "UPDATE $lp_table SET force_commit = $force
5780
                    WHERE iid = ".$this->get_id();
5781
            Database::query($sql);
5782
            $this->force_commit = $force_return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $force_return does not seem to be defined for all execution paths leading up to this point.
Loading history...
5783
5784
            return $force_return;
5785
        } else {
5786
            if ($this->debug > 2) {
5787
                error_log('Problem in update_default_scorm_commit() - could not find LP '.$this->get_id().' in DB', 0);
5788
            }
5789
        }
5790
5791
        return -1;
0 ignored issues
show
Bug Best Practice introduced by
The expression return -1 returns the type integer which is incompatible with the documented return type boolean.
Loading history...
5792
    }
5793
5794
    /**
5795
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5796
     *
5797
     * @return bool True on success, false on failure
5798
     * @throws \Doctrine\DBAL\DBALException
5799
     */
5800
    public function update_display_order()
5801
    {
5802
        $course_id = api_get_course_int_id();
5803
        $table = Database::get_course_table(TABLE_LP_MAIN);
5804
        $sql = "SELECT * FROM $table 
5805
                WHERE c_id = $course_id ORDER BY display_order";
5806
        $res = Database::query($sql);
5807
        if ($res === false) {
5808
            return false;
5809
        }
5810
5811
        $num = Database::num_rows($res);
5812
        // First check the order is correct, globally (might be wrong because
5813
        // of versions < 1.8.4).
5814
        if ($num > 0) {
5815
            $i = 1;
5816
            while ($row = Database::fetch_array($res)) {
5817
                if ($row['display_order'] != $i) {
5818
                    // If we find a gap in the order, we need to fix it.
5819
                    $sql = "UPDATE $table SET display_order = $i
5820
                            WHERE iid = ".$row['iid'];
5821
                    Database::query($sql);
5822
                }
5823
                $i++;
5824
            }
5825
        }
5826
5827
        return true;
5828
    }
5829
5830
    /**
5831
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5832
     *
5833
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5834
     * @throws \Doctrine\DBAL\DBALException
5835
     */
5836
    public function update_reinit()
5837
    {
5838
        $course_id = api_get_course_int_id();
5839
        if ($this->debug > 0) {
5840
            error_log('In learnpath::update_reinit()', 0);
5841
        }
5842
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5843
        $sql = "SELECT * FROM $lp_table
5844
                WHERE iid = ".$this->get_id();
5845
        $res = Database::query($sql);
5846
        if (Database::num_rows($res) > 0) {
5847
            $row = Database::fetch_array($res);
5848
            $force = $row['prevent_reinit'];
5849
            if ($force == 1) {
5850
                $force = 0;
5851
            } elseif ($force == 0) {
5852
                $force = 1;
5853
            }
5854
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5855
                    WHERE iid = ".$this->get_id();
5856
            Database::query($sql);
5857
            $this->prevent_reinit = $force;
5858
5859
            return $force;
5860
        } else {
5861
            if ($this->debug > 2) {
5862
                error_log('Problem in update_reinit() - could not find LP '.$this->get_id().' in DB', 0);
5863
            }
5864
        }
5865
5866
        return -1;
0 ignored issues
show
Bug Best Practice introduced by
The expression return -1 returns the type integer which is incompatible with the documented return type boolean.
Loading history...
5867
    }
5868
5869
    /**
5870
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5871
     *
5872
     * @return string 'single', 'multi' or 'seriousgame'
5873
     *
5874
     * @author ndiechburg <[email protected]>
5875
     */
5876
    public function get_attempt_mode()
5877
    {
5878
        //Set default value for seriousgame_mode
5879
        if (!isset($this->seriousgame_mode)) {
5880
            $this->seriousgame_mode = 0;
5881
        }
5882
        // Set default value for prevent_reinit
5883
        if (!isset($this->prevent_reinit)) {
5884
            $this->prevent_reinit = 1;
5885
        }
5886
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5887
            return 'seriousgame';
5888
        }
5889
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5890
            return 'single';
5891
        }
5892
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5893
            return 'multiple';
5894
        }
5895
5896
        return 'single';
5897
    }
5898
5899
    /**
5900
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5901
     *
5902
     * @param string 'seriousgame', 'single' or 'multiple'
0 ignored issues
show
Documentation Bug introduced by
The doc comment 'seriousgame', 'single' at position 0 could not be parsed: Unknown type name ''seriousgame'' at position 0 in 'seriousgame', 'single'.
Loading history...
5903
     *
5904
     * @return bool
5905
     *
5906
     * @throws \Doctrine\DBAL\DBALException
5907
     * @author ndiechburg <[email protected]>
5908
     */
5909
    public function set_attempt_mode($mode)
5910
    {
5911
        switch ($mode) {
5912
            case 'seriousgame':
5913
                $sg_mode = 1;
5914
                $prevent_reinit = 1;
5915
                break;
5916
            case 'single':
5917
                $sg_mode = 0;
5918
                $prevent_reinit = 1;
5919
                break;
5920
            case 'multiple':
5921
                $sg_mode = 0;
5922
                $prevent_reinit = 0;
5923
                break;
5924
            default:
5925
                $sg_mode = 0;
5926
                $prevent_reinit = 0;
5927
                break;
5928
        }
5929
        $this->prevent_reinit = $prevent_reinit;
5930
        $this->seriousgame_mode = $sg_mode;
5931
        $table = Database::get_course_table(TABLE_LP_MAIN);
5932
        $sql = "UPDATE $table SET
5933
                prevent_reinit = $prevent_reinit ,
5934
                seriousgame_mode = $sg_mode
5935
                WHERE iid = ".$this->get_id();
5936
        $res = Database::query($sql);
5937
        if ($res) {
5938
            return true;
5939
        } else {
5940
            return false;
5941
        }
5942
    }
5943
5944
    /**
5945
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5946
     *
5947
     * @author ndiechburg <[email protected]>
5948
     */
5949
    public function switch_attempt_mode()
5950
    {
5951
        if ($this->debug > 0) {
5952
            error_log('In learnpath::switch_attempt_mode()', 0);
5953
        }
5954
        $mode = $this->get_attempt_mode();
5955
        switch ($mode) {
5956
            case 'single':
5957
                $next_mode = 'multiple';
5958
                break;
5959
            case 'multiple':
5960
                $next_mode = 'seriousgame';
5961
                break;
5962
            case 'seriousgame':
5963
                $next_mode = 'single';
5964
                break;
5965
            default:
5966
                $next_mode = 'single';
5967
                break;
5968
        }
5969
        $this->set_attempt_mode($next_mode);
5970
    }
5971
5972
    /**
5973
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5974
     * but possibility to do again a completed item.
5975
     *
5976
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5977
     *
5978
     * @throws \Doctrine\DBAL\DBALException
5979
     * @author ndiechburg <[email protected]>
5980
     */
5981
    public function set_seriousgame_mode()
5982
    {
5983
        if ($this->debug > 0) {
5984
            error_log('In learnpath::set_seriousgame_mode()', 0);
5985
        }
5986
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5987
        $sql = "SELECT * FROM $lp_table 
5988
                WHERE iid = ".$this->get_id();
5989
        $res = Database::query($sql);
5990
        if (Database::num_rows($res) > 0) {
5991
            $row = Database::fetch_array($res);
5992
            $force = $row['seriousgame_mode'];
5993
            if ($force == 1) {
5994
                $force = 0;
5995
            } elseif ($force == 0) {
5996
                $force = 1;
5997
            }
5998
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5999
			        WHERE iid = ".$this->get_id();
6000
            Database::query($sql);
6001
            $this->seriousgame_mode = $force;
6002
6003
            return $force;
6004
        } else {
6005
            if ($this->debug > 2) {
6006
                error_log('Problem in set_seriousgame_mode() - could not find LP '.$this->get_id().' in DB', 0);
6007
            }
6008
        }
6009
6010
        return -1;
0 ignored issues
show
Bug Best Practice introduced by
The expression return -1 returns the type integer which is incompatible with the documented return type boolean.
Loading history...
6011
    }
6012
6013
    /**
6014
     * Updates the "scorm_debug" value that shows or hide the debug window.
6015
     *
6016
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
6017
     * @throws \Doctrine\DBAL\DBALException
6018
     */
6019
    public function update_scorm_debug()
6020
    {
6021
        if ($this->debug > 0) {
6022
            error_log('In learnpath::update_scorm_debug()', 0);
6023
        }
6024
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6025
        $sql = "SELECT * FROM $lp_table
6026
                WHERE iid = ".$this->get_id();
6027
        $res = Database::query($sql);
6028
        if (Database::num_rows($res) > 0) {
6029
            $row = Database::fetch_array($res);
6030
            $force = $row['debug'];
6031
            if ($force == 1) {
6032
                $force = 0;
6033
            } elseif ($force == 0) {
6034
                $force = 1;
6035
            }
6036
            $sql = "UPDATE $lp_table SET debug = $force
6037
                    WHERE iid = ".$this->get_id();
6038
            Database::query($sql);
6039
            $this->scorm_debug = $force;
6040
6041
            return $force;
6042
        } else {
6043
            if ($this->debug > 2) {
6044
                error_log('Problem in update_scorm_debug() - could not find LP '.$this->get_id().' in DB', 0);
6045
            }
6046
        }
6047
6048
        return -1;
0 ignored issues
show
Bug Best Practice introduced by
The expression return -1 returns the type integer which is incompatible with the documented return type boolean.
Loading history...
6049
    }
6050
6051
    /**
6052
     * Function that makes a call to the function sort_tree_array and create_tree_array.
6053
     *
6054
     * @author Kevin Van Den Haute
6055
     *
6056
     * @param  array
6057
     */
6058
    public function tree_array($array)
6059
    {
6060
        if ($this->debug > 1) {
6061
            error_log('In learnpath::tree_array()', 0);
6062
        }
6063
        $array = $this->sort_tree_array($array);
6064
        $this->create_tree_array($array);
6065
    }
6066
6067
    /**
6068
     * Creates an array with the elements of the learning path tree in it.
6069
     *
6070
     * @author Kevin Van Den Haute
6071
     *
6072
     * @param array $array
6073
     * @param int   $parent
6074
     * @param int   $depth
6075
     * @param array $tmp
6076
     */
6077
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
6078
    {
6079
        if ($this->debug > 1) {
6080
            error_log('In learnpath::create_tree_array())', 0);
6081
        }
6082
6083
        if (is_array($array)) {
6084
            for ($i = 0; $i < count($array); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
6085
                if ($array[$i]['parent_item_id'] == $parent) {
6086
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
6087
                        $tmp[] = $array[$i]['parent_item_id'];
6088
                        $depth++;
6089
                    }
6090
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
6091
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
6092
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
6093
6094
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
6095
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
6096
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
6097
                    $this->arrMenu[] = [
6098
                        'id' => $array[$i]['id'],
6099
                        'ref' => $ref,
6100
                        'item_type' => $array[$i]['item_type'],
6101
                        'title' => $array[$i]['title'],
6102
                        'path' => $path,
6103
                        'description' => $array[$i]['description'],
6104
                        'parent_item_id' => $array[$i]['parent_item_id'],
6105
                        'previous_item_id' => $array[$i]['previous_item_id'],
6106
                        'next_item_id' => $array[$i]['next_item_id'],
6107
                        'min_score' => $array[$i]['min_score'],
6108
                        'max_score' => $array[$i]['max_score'],
6109
                        'mastery_score' => $array[$i]['mastery_score'],
6110
                        'display_order' => $array[$i]['display_order'],
6111
                        'prerequisite' => $preq,
6112
                        'depth' => $depth,
6113
                        'audio' => $audio,
6114
                        'prerequisite_min_score' => $prerequisiteMinScore,
6115
                        'prerequisite_max_score' => $prerequisiteMaxScore,
6116
                    ];
6117
6118
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
6119
                }
6120
            }
6121
        }
6122
    }
6123
6124
    /**
6125
     * Sorts a multi dimensional array by parent id and display order.
6126
     *
6127
     * @author Kevin Van Den Haute
6128
     *
6129
     * @param array $array (array with al the learning path items in it)
6130
     *
6131
     * @return array
6132
     */
6133
    public function sort_tree_array($array)
6134
    {
6135
        foreach ($array as $key => $row) {
6136
            $parent[$key] = $row['parent_item_id'];
6137
            $position[$key] = $row['display_order'];
6138
        }
6139
6140
        if (count($array) > 0) {
6141
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $position seems to be defined by a foreach iteration on line 6135. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
6142
        }
6143
6144
        return $array;
6145
    }
6146
6147
    /**
6148
     * Function that creates a html list of learning path items so that we can add audio files to them.
6149
     *
6150
     * @author Kevin Van Den Haute
6151
     *
6152
     * @return string
6153
     */
6154
    public function overview()
6155
    {
6156
        if ($this->debug > 0) {
6157
            error_log('In learnpath::overview()', 0);
6158
        }
6159
6160
        $return = '';
6161
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
6162
6163
        // we need to start a form when we want to update all the mp3 files
6164
        if ($update_audio == 'true') {
6165
            $return .= '<form action="'.api_get_self().'?'.api_get_cidreq().'&updateaudio='.Security::remove_XSS($_GET['updateaudio']).'&action='.Security::remove_XSS($_GET['action']).'&lp_id='.$_SESSION['oLP']->lp_id.'" method="post" enctype="multipart/form-data" name="updatemp3" id="updatemp3">';
6166
        }
6167
        $return .= '<div id="message"></div>';
6168
        if (count($this->items) == 0) {
6169
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
6170
        } else {
6171
            $return_audio = '<table class="data_table">';
6172
            $return_audio .= '<tr>';
6173
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
6174
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
6175
            $return_audio .= '</tr>';
6176
6177
            if ($update_audio != 'true') {
6178
                $return .= '<div class="col-md-12">';
6179
                $return .= self::return_new_tree($update_audio);
0 ignored issues
show
Bug Best Practice introduced by
The method learnpath::return_new_tree() is not static, but was called statically. ( Ignorable by Annotation )

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

6179
                $return .= self::/** @scrutinizer ignore-call */ return_new_tree($update_audio);
Loading history...
6180
                $return .= '</div>';
6181
                $return .= Display::div(
6182
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
6183
                    ['style' => 'float:left; margin-top:15px;width:100%']
6184
                );
6185
            } else {
6186
                $return_audio .= self::return_new_tree($update_audio);
6187
                $return .= $return_audio.'</table>';
6188
            }
6189
6190
            // We need to close the form when we are updating the mp3 files.
6191
            if ($update_audio == 'true') {
6192
                $return .= '<div class="footer-audio">';
6193
                $return .= Display::button(
6194
                    'save_audio',
6195
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
6196
                    ['class' => 'btn btn-primary', 'type' => 'submit']
6197
                );
6198
                $return .= '</div>';
6199
            }
6200
        }
6201
6202
        // We need to close the form when we are updating the mp3 files.
6203
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
6204
            $return .= '</form>';
6205
        }
6206
6207
        return $return;
6208
    }
6209
6210
    /**
6211
     * @param string string $update_audio
6212
     * @param bool $drop_element_here
6213
     *
6214
     * @return string
6215
     * @throws \Doctrine\DBAL\DBALException
6216
     */
6217
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6218
    {
6219
        $return = '';
6220
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6221
        $course_id = api_get_course_int_id();
6222
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
6223
6224
        $sql = "SELECT * FROM $tbl_lp_item
6225
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
6226
6227
        $result = Database::query($sql);
6228
        $arrLP = [];
6229
        while ($row = Database::fetch_array($result)) {
6230
            $arrLP[] = [
6231
                'id' => $row['id'],
6232
                'item_type' => $row['item_type'],
6233
                'title' => Security::remove_XSS($row['title']),
6234
                'path' => $row['path'],
6235
                'description' => Security::remove_XSS($row['description']),
6236
                'parent_item_id' => $row['parent_item_id'],
6237
                'previous_item_id' => $row['previous_item_id'],
6238
                'next_item_id' => $row['next_item_id'],
6239
                'max_score' => $row['max_score'],
6240
                'min_score' => $row['min_score'],
6241
                'mastery_score' => $row['mastery_score'],
6242
                'prerequisite' => $row['prerequisite'],
6243
                'display_order' => $row['display_order'],
6244
                'audio' => $row['audio'],
6245
                'prerequisite_max_score' => $row['prerequisite_max_score'],
6246
                'prerequisite_min_score' => $row['prerequisite_min_score'],
6247
            ];
6248
        }
6249
6250
        $this->tree_array($arrLP);
6251
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6252
        unset($this->arrMenu);
6253
        $default_data = null;
6254
        $default_content = null;
6255
        $elements = [];
6256
        $return_audio = null;
6257
6258
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
6259
            $title = $arrLP[$i]['title'];
6260
            $title_cut = cut($arrLP[$i]['title'], 25);
6261
6262
            // Link for the documents
6263
            if ($arrLP[$i]['item_type'] == 'document') {
6264
                $url = api_get_self().'?'.api_get_cidreq().'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6265
                $title_cut = Display::url(
6266
                    $title_cut,
6267
                    $url,
6268
                    [
6269
                        'class' => 'ajax moved',
6270
                        'data-title' => $title,
6271
                        'title' => $title,
6272
                    ]
6273
                );
6274
            }
6275
6276
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6277
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6278
                Session::write('pathItem', $arrLP[$i]['path']);
6279
            }
6280
6281
            if (($i % 2) == 0) {
6282
                $oddClass = 'row_odd';
6283
            } else {
6284
                $oddClass = 'row_even';
6285
            }
6286
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6287
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6288
6289
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
6290
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6291
            } else {
6292
                if (file_exists('../img/lp_'.$icon_name.'.gif')) {
6293
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6294
                } else {
6295
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6296
                        $icon = Display::return_icon('certificate.png');
6297
                    } else {
6298
                        $icon = Display::return_icon('folder_document.gif');
6299
                    }
6300
                }
6301
            }
6302
6303
            // The audio column.
6304
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6305
            $audio = '';
6306
            if (!$update_audio || $update_audio != 'true') {
6307
                if (empty($arrLP[$i]['audio'])) {
6308
                    $audio .= '';
6309
                }
6310
            } else {
6311
                $types = self::getChapterTypes();
6312
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6313
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6314
                    if (!empty($arrLP[$i]['audio'])) {
6315
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6316
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6317
                    }
6318
                }
6319
            }
6320
6321
            $return_audio .= Display::span($icon.' '.$title).
6322
            Display::tag(
6323
                'td',
6324
                $audio,
6325
                ['style' => '']
6326
            );
6327
            $return_audio .= '</td>';
6328
            $move_icon = '';
6329
            $move_item_icon = '';
6330
            $edit_icon = '';
6331
            $delete_icon = '';
6332
            $audio_icon = '';
6333
            $prerequisities_icon = '';
6334
            $forumIcon = '';
6335
            $previewIcon = '';
6336
6337
            if ($is_allowed_to_edit) {
6338
                if (!$update_audio || $update_audio != 'true') {
6339
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6340
                        $move_icon .= '<a class="moved" href="#">';
6341
                        $move_icon .= Display::return_icon(
6342
                            'move_everywhere.png',
6343
                            get_lang('Move'),
6344
                            [],
6345
                            ICON_SIZE_TINY
6346
                        );
6347
                        $move_icon .= '</a>';
6348
                    }
6349
                }
6350
6351
                // No edit for this item types
6352
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6353
                    if ($arrLP[$i]['item_type'] != 'dir') {
6354
                        $edit_icon .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=edit_item&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'&path_item='.$arrLP[$i]['path'].'" class="btn btn-default">';
6355
                        $edit_icon .= Display::return_icon(
6356
                            'edit.png',
6357
                            get_lang('LearnpathEditModule'),
6358
                            [],
6359
                            ICON_SIZE_TINY
6360
                        );
6361
                        $edit_icon .= '</a>';
6362
6363
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6364
                            $forumThread = null;
6365
                            if (isset($this->items[$arrLP[$i]['id']])) {
6366
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6367
                                    $this->course_int_id,
6368
                                    $this->lp_session_id
6369
                                );
6370
                            }
6371
                            if ($forumThread) {
6372
                                $forumIconUrl = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
6373
                                    'action' => 'dissociate_forum',
6374
                                    'id' => $arrLP[$i]['id'],
6375
                                    'lp_id' => $this->lp_id,
6376
                                ]);
6377
                                $forumIcon = Display::url(
6378
                                    Display::return_icon(
6379
                                        'forum.png',
6380
                                        get_lang('DissociateForumToLPItem'),
6381
                                        [],
6382
                                        ICON_SIZE_TINY
6383
                                    ),
6384
                                    $forumIconUrl,
6385
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6386
                                );
6387
                            } else {
6388
                                $forumIconUrl = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
6389
                                    'action' => 'create_forum',
6390
                                    'id' => $arrLP[$i]['id'],
6391
                                    'lp_id' => $this->lp_id,
6392
                                ]);
6393
                                $forumIcon = Display::url(
6394
                                    Display::return_icon(
6395
                                        'forum.png',
6396
                                        get_lang('AssociateForumToLPItem'),
6397
                                        [],
6398
                                        ICON_SIZE_TINY
6399
                                    ),
6400
                                    $forumIconUrl,
6401
                                    ['class' => "btn btn-default lp-btn-associate-forum"]
6402
                                );
6403
                            }
6404
                        }
6405
                    } else {
6406
                        $edit_icon .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'&path_item='.$arrLP[$i]['path'].'" class="btn btn-default">';
6407
                        $edit_icon .= Display::return_icon(
6408
                            'edit.png',
6409
                            get_lang('LearnpathEditModule'),
6410
                            [],
6411
                            ICON_SIZE_TINY
6412
                        );
6413
                        $edit_icon .= '</a>';
6414
                    }
6415
                } else {
6416
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6417
                        $edit_icon .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6418
                        $edit_icon .= Display::return_icon(
6419
                            'edit.png',
6420
                            get_lang('Edit'),
6421
                            [],
6422
                            ICON_SIZE_TINY
6423
                        );
6424
                        $edit_icon .= '</a>';
6425
                    }
6426
                }
6427
6428
                $delete_icon .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" onclick="return confirmation(\''.addslashes($title).'\');" class="btn btn-default">';
6429
                $delete_icon .= Display::return_icon(
6430
                    'delete.png',
6431
                    get_lang('LearnpathDeleteModule'),
6432
                    [],
6433
                    ICON_SIZE_TINY
6434
                );
6435
                $delete_icon .= '</a>';
6436
6437
                $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6438
                $previewImage = Display::return_icon(
6439
                    'preview_view.png',
6440
                    get_lang('Preview'),
6441
                    [],
6442
                    ICON_SIZE_TINY
6443
                );
6444
6445
                switch ($arrLP[$i]['item_type']) {
6446
                    case TOOL_DOCUMENT:
6447
                    case TOOL_LP_FINAL_ITEM:
6448
                        $urlPreviewLink = api_get_self().'?'.api_get_cidreq().'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6449
                        $previewIcon = Display::url(
6450
                            $previewImage,
6451
                            $urlPreviewLink,
6452
                            [
6453
                                'target' => '_blank',
6454
                                'class' => 'btn btn-default',
6455
                                'data-title' => $arrLP[$i]['title'],
6456
                                'title' => $arrLP[$i]['title'],
6457
                            ]
6458
                        );
6459
                        break;
6460
                    case TOOL_THREAD:
6461
                    case TOOL_FORUM:
6462
                    case TOOL_QUIZ:
6463
                    case TOOL_STUDENTPUBLICATION:
6464
                    case TOOL_LP_FINAL_ITEM:
6465
                    case TOOL_LINK:
6466
                        //$target = '';
6467
                        //$class = 'btn btn-default ajax';
6468
                        //if ($arrLP[$i]['item_type'] == TOOL_LINK) {
6469
                        $class = 'btn btn-default';
6470
                        $target = '_blank';
6471
                        //}
6472
6473
                        $link = self::rl_get_resource_link_for_learnpath(
6474
                            $this->course_int_id,
6475
                            $this->lp_id,
6476
                            $arrLP[$i]['id'],
6477
                            0,
6478
                            ''
6479
                        );
6480
                        $previewIcon = Display::url(
6481
                            $previewImage,
6482
                            $link,
6483
                            [
6484
                                'class' => $class,
6485
                                'data-title' => $arrLP[$i]['title'],
6486
                                'title' => $arrLP[$i]['title'],
6487
                                'target' => $target,
6488
                            ]
6489
                        );
6490
                        break;
6491
                    default:
6492
                        $previewIcon = Display::url(
6493
                            $previewImage,
6494
                            $url.'&action=view_item',
6495
                            ['class' => 'btn btn-default', 'target' => '_blank']
6496
                        );
6497
                        break;
6498
                }
6499
6500
                if ($arrLP[$i]['item_type'] != 'dir') {
6501
                    $prerequisities_icon = Display::url(
6502
                        Display::return_icon(
6503
                            'accept.png',
6504
                            get_lang('LearnpathPrerequisites'),
6505
                            [],
6506
                            ICON_SIZE_TINY
6507
                        ),
6508
                        $url.'&action=edit_item_prereq',
6509
                        ['class' => 'btn btn-default']
6510
                    );
6511
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6512
                        $move_item_icon = Display::url(
6513
                            Display::return_icon(
6514
                                'move.png',
6515
                                get_lang('Move'),
6516
                                [],
6517
                                ICON_SIZE_TINY
6518
                            ),
6519
                            $url.'&action=move_item',
6520
                            ['class' => 'btn btn-default']
6521
                        );
6522
                    }
6523
                    $audio_icon = Display::url(
6524
                        Display::return_icon(
6525
                            'audio.png',
6526
                            get_lang('UplUpload'),
6527
                            [],
6528
                            ICON_SIZE_TINY
6529
                        ),
6530
                        $url.'&action=add_audio',
6531
                        ['class' => 'btn btn-default']
6532
                    );
6533
                }
6534
            }
6535
            if ($update_audio != 'true') {
6536
                $row = $move_icon.' '.$icon.
6537
                    Display::span($title_cut).
6538
                    Display::tag(
6539
                        'div',
6540
                        "<div class=\"btn-group btn-group-xs\">$previewIcon $audio $edit_icon $forumIcon $prerequisities_icon $move_item_icon $audio_icon $delete_icon</div>",
6541
                        ['class' => 'btn-toolbar button_actions']
6542
                    );
6543
            } else {
6544
                $row = Display::span($title.$icon).Display::span($audio, ['class' => 'button_actions']);
6545
            }
6546
6547
            $parent_id = $arrLP[$i]['parent_item_id'];
6548
            $default_data[$arrLP[$i]['id']] = $row;
6549
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6550
6551
            if (empty($parent_id)) {
6552
                $elements[$arrLP[$i]['id']]['data'] = $row;
6553
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6554
            } else {
6555
                $parent_arrays = [];
6556
                if ($arrLP[$i]['depth'] > 1) {
6557
                    //Getting list of parents
6558
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6559
                        foreach ($arrLP as $item) {
6560
                            if ($item['id'] == $parent_id) {
6561
                                if ($item['parent_item_id'] == 0) {
6562
                                    $parent_id = $item['id'];
6563
                                    break;
6564
                                } else {
6565
                                    $parent_id = $item['parent_item_id'];
6566
                                    if (empty($parent_arrays)) {
6567
                                        $parent_arrays[] = intval($item['id']);
6568
                                    }
6569
                                    $parent_arrays[] = $parent_id;
6570
                                    break;
6571
                                }
6572
                            }
6573
                        }
6574
                    }
6575
                }
6576
6577
                if (!empty($parent_arrays)) {
6578
                    $parent_arrays = array_reverse($parent_arrays);
6579
                    $val = '$elements';
6580
                    $x = 0;
6581
                    foreach ($parent_arrays as $item) {
6582
                        if ($x != count($parent_arrays) - 1) {
6583
                            $val .= '["'.$item.'"]["children"]';
6584
                        } else {
6585
                            $val .= '["'.$item.'"]["children"]';
6586
                        }
6587
                        $x++;
6588
                    }
6589
                    $val .= "";
6590
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6591
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6592
                } else {
6593
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6594
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6595
                }
6596
            }
6597
        }
6598
6599
        $list = '<ul id="lp_item_list">';
6600
        $tree = self::print_recursive(
0 ignored issues
show
Bug Best Practice introduced by
The method learnpath::print_recursive() is not static, but was called statically. ( Ignorable by Annotation )

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

6600
        /** @scrutinizer ignore-call */ 
6601
        $tree = self::print_recursive(
Loading history...
6601
            $elements,
6602
            $default_data,
6603
            $default_content
6604
        );
6605
6606
        if (!empty($tree)) {
6607
            $list .= $tree;
6608
        } else {
6609
            if ($drop_element_here) {
6610
                $list .= Display::return_message(get_lang("DragAndDropAnElementHere"));
6611
            }
6612
        }
6613
        $list .= '</ul>';
6614
6615
        $return .= Display::panelCollapse(
6616
            $this->name,
6617
            $list,
6618
            'scorm-list',
6619
            null,
6620
            'scorm-list-accordion',
6621
            'scorm-list-collapse'
6622
        );
6623
6624
        if ($update_audio == 'true') {
6625
            $return = $return_audio;
6626
        }
6627
6628
        return $return;
6629
    }
6630
6631
    /**
6632
     * @param array $elements
6633
     * @param array $default_data
6634
     * @param array $default_content
6635
     *
6636
     * @return string
6637
     */
6638
    public function print_recursive($elements, $default_data, $default_content)
6639
    {
6640
        $return = '';
6641
        foreach ($elements as $key => $item) {
6642
            if (isset($item['load_data']) || empty($item['data'])) {
6643
                $item['data'] = $default_data[$item['load_data']];
6644
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6645
            }
6646
            $sub_list = '';
6647
            if (isset($item['type']) && $item['type'] == 'dir') {
6648
                // empty value
6649
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6650
            }
6651
            if (empty($item['children'])) {
6652
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6653
                $active = null;
6654
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6655
                    $active = 'active';
6656
                }
6657
                $return .= Display::tag(
6658
                    'li',
6659
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6660
                    ['id' => $key, 'class' => 'record li_container']
6661
                );
6662
            } else {
6663
                // Sections
6664
                if (isset($item['children'])) {
6665
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
0 ignored issues
show
Bug Best Practice introduced by
The method learnpath::print_recursive() is not static, but was called statically. ( Ignorable by Annotation )

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

6665
                    /** @scrutinizer ignore-call */ 
6666
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
Loading history...
6666
                }
6667
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $data does not seem to be defined for all execution paths leading up to this point.
Loading history...
6668
                $return .= Display::tag(
6669
                    'li',
6670
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6671
                    ['id' => $key, 'class' => 'record li_container']
6672
                );
6673
            }
6674
        }
6675
6676
        return $return;
6677
    }
6678
6679
    /**
6680
     * This function builds the action menu.
6681
     *
6682
     * @param bool $returnContent          Optional
6683
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6684
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6685
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6686
     *
6687
     * @return string
6688
     */
6689
    public function build_action_menu(
6690
        $returnContent = false,
6691
        $showRequirementButtons = true,
6692
        $isConfigPage = false,
6693
        $allowExpand = true
6694
    ) {
6695
        $actionsLeft = '';
6696
        $actionsRight = '';
6697
6698
        $actionsLeft .= Display::url(
6699
            Display::return_icon(
6700
                'preview_view.png',
6701
                get_lang('Preview'),
6702
                '',
6703
                ICON_SIZE_MEDIUM
6704
            ),
6705
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6706
                'action' => 'view',
6707
                'lp_id' => $this->lp_id,
6708
                'isStudentView' => 'true',
6709
            ])
6710
        );
6711
        $actionsLeft .= Display::url(
6712
            Display::return_icon(
6713
                'upload_audio.png',
6714
                get_lang('UpdateAllAudioFragments'),
6715
                '',
6716
                ICON_SIZE_MEDIUM
6717
            ),
6718
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6719
                'action' => 'admin_view',
6720
                'lp_id' => $this->lp_id,
6721
                'updateaudio' => 'true',
6722
            ])
6723
        );
6724
6725
        if (!$isConfigPage) {
6726
            $actionsLeft .= Display::url(
6727
                Display::return_icon(
6728
                    'settings.png',
6729
                    get_lang('CourseSettings'),
6730
                    '',
6731
                    ICON_SIZE_MEDIUM
6732
                ),
6733
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6734
                    'action' => 'edit',
6735
                    'lp_id' => $this->lp_id,
6736
                ])
6737
            );
6738
        } else {
6739
            $actionsLeft .= Display::url(
6740
                Display::return_icon(
6741
                    'edit.png',
6742
                    get_lang('Edit'),
6743
                    '',
6744
                    ICON_SIZE_MEDIUM
6745
                ),
6746
                'lp_controller.php?'.http_build_query([
6747
                    'action' => 'build',
6748
                    'lp_id' => $this->lp_id,
6749
                ]).'&'.api_get_cidreq()
6750
            );
6751
        }
6752
6753
        if ($allowExpand) {
6754
            $actionsLeft .= Display::url(
6755
                Display::return_icon(
6756
                    'expand.png',
6757
                    get_lang('Expand'),
6758
                    ['id' => 'expand'],
6759
                    ICON_SIZE_MEDIUM
6760
                ).
6761
                Display::return_icon(
6762
                    'contract.png',
6763
                    get_lang('Collapse'),
6764
                    ['id' => 'contract', 'class' => 'hide'],
6765
                    ICON_SIZE_MEDIUM
6766
                ),
6767
                '#',
6768
                ['role' => 'button', 'id' => 'hide_bar_template']
6769
            );
6770
        }
6771
6772
        if ($showRequirementButtons) {
6773
            $buttons = [
6774
                [
6775
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6776
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6777
                        'action' => 'set_previous_step_as_prerequisite',
6778
                        'lp_id' => $this->lp_id,
6779
                    ]),
6780
                ],
6781
                [
6782
                    'title' => get_lang('ClearAllPrerequisites'),
6783
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6784
                        'action' => 'clear_prerequisites',
6785
                        'lp_id' => $this->lp_id,
6786
                    ]),
6787
                ],
6788
            ];
6789
            $actionsRight = Display::groupButtonWithDropDown(
6790
                get_lang('PrerequisitesOptions'),
6791
                $buttons,
6792
                true
6793
            );
6794
        }
6795
6796
        $toolbar = Display::toolbarAction(
6797
            'actions-lp-controller',
6798
            [$actionsLeft, $actionsRight]
6799
        );
6800
6801
        if ($returnContent) {
6802
            return $toolbar;
6803
        }
6804
6805
        echo $toolbar;
6806
    }
6807
6808
    /**
6809
     * Creates the default learning path folder.
6810
     *
6811
     * @param array $course
6812
     * @param int   $creatorId
6813
     *
6814
     * @return bool
6815
     */
6816
    public static function generate_learning_path_folder($course, $creatorId = 0)
6817
    {
6818
        // Creating learning_path folder
6819
        $dir = '/learning_path';
6820
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6821
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6822
6823
        $folder = false;
6824
        if (!is_dir($filepath.'/'.$dir)) {
6825
            $folderData = create_unexisting_directory(
6826
                $course,
6827
                $creatorId,
6828
                0,
6829
                null,
6830
                0,
6831
                $filepath,
6832
                $dir,
6833
                get_lang('LearningPaths'),
6834
                0
6835
            );
6836
            if (!empty($folderData)) {
6837
                $folder = true;
6838
            }
6839
        } else {
6840
            $folder = true;
6841
        }
6842
6843
        return $folder;
6844
    }
6845
6846
    /**
6847
     * @param array  $course
6848
     * @param string $lp_name
6849
     * @param int    $creatorId
6850
     *
6851
     * @return array
6852
     */
6853
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6854
    {
6855
        $filepath = '';
6856
        $dir = '/learning_path/';
6857
6858
        if (empty($lp_name)) {
6859
            $lp_name = $this->name;
6860
        }
6861
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6862
6863
        $folder = self::generate_learning_path_folder($course, $creatorId);
6864
6865
        // Limits title size
6866
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6867
        $dir = $dir.$title;
6868
6869
        // Creating LP folder
6870
        $documentId = null;
6871
6872
        if ($folder) {
6873
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6874
            if (!is_dir($filepath.'/'.$dir)) {
6875
                $folderData = create_unexisting_directory(
6876
                    $course,
6877
                    $creatorId,
6878
                    0,
6879
                    0,
6880
                    0,
6881
                    $filepath,
6882
                    $dir,
6883
                    $lp_name
6884
                );
6885
                if (!empty($folderData)) {
6886
                    $folder = true;
6887
                }
6888
6889
                $documentId = $folderData['id'];
6890
            } else {
6891
                $folder = true;
6892
            }
6893
            $dir = $dir.'/';
6894
            if ($folder) {
6895
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6896
            }
6897
        }
6898
6899
        if (empty($documentId)) {
6900
            $dir = api_remove_trailing_slash($dir);
6901
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6902
        }
6903
6904
        $array = [
6905
            'dir' => $dir,
6906
            'filepath' => $filepath,
6907
            'folder' => $folder,
6908
            'id' => $documentId,
6909
        ];
6910
6911
        return $array;
6912
    }
6913
6914
    /**
6915
     * Create a new document //still needs some finetuning.
6916
     *
6917
     * @param array  $courseInfo
6918
     * @param string $content
6919
     * @param string $title
6920
     * @param string $extension
6921
     * @param int    $parentId
6922
     * @param int    $creatorId creator id
6923
     *
6924
     * @return int
6925
     * @throws \Doctrine\DBAL\DBALException
6926
     */
6927
    public function create_document(
6928
        $courseInfo,
6929
        $content = '',
6930
        $title = '',
6931
        $extension = 'html',
6932
        $parentId = 0,
6933
        $creatorId = 0
6934
    ) {
6935
        if (!empty($courseInfo)) {
6936
            $course_id = $courseInfo['real_id'];
6937
        } else {
6938
            $course_id = api_get_course_int_id();
6939
        }
6940
6941
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6942
        $sessionId = api_get_session_id();
6943
6944
        // Generates folder
6945
        $result = $this->generate_lp_folder($courseInfo);
6946
        $dir = $result['dir'];
6947
6948
        if (empty($parentId) || $parentId == '/') {
6949
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6950
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6951
6952
            if ($parentId === '/') {
6953
                $dir = '/';
6954
            }
6955
6956
            // Please, do not modify this dirname formatting.
6957
            if (strstr($dir, '..')) {
6958
                $dir = '/';
6959
            }
6960
6961
            if (!empty($dir[0]) && $dir[0] == '.') {
6962
                $dir = substr($dir, 1);
6963
            }
6964
            if (!empty($dir[0]) && $dir[0] != '/') {
6965
                $dir = '/'.$dir;
6966
            }
6967
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6968
                $dir .= '/';
6969
            }
6970
        } else {
6971
            $parentInfo = DocumentManager::get_document_data_by_id(
6972
                $parentId,
6973
                $courseInfo['code']
6974
            );
6975
            if (!empty($parentInfo)) {
6976
                $dir = $parentInfo['path'].'/';
6977
            }
6978
        }
6979
6980
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6981
        if (!is_dir($filepath)) {
6982
            $dir = '/';
6983
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6984
        }
6985
6986
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6987
        // is already escaped twice when it gets here.
6988
6989
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6990
        if (!empty($title)) {
6991
            $title = api_replace_dangerous_char(stripslashes($title));
6992
        } else {
6993
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6994
        }
6995
6996
        $title = disable_dangerous_file($title);
6997
        $filename = $title;
6998
        $content = !empty($content) ? $content : $_POST['content_lp'];
6999
        $tmp_filename = $filename;
7000
7001
        $i = 0;
7002
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
7003
            $tmp_filename = $filename.'_'.++$i;
7004
        }
7005
7006
        $filename = $tmp_filename.'.'.$extension;
7007
        if ($extension == 'html') {
7008
            $content = stripslashes($content);
7009
            $content = str_replace(
7010
                api_get_path(WEB_COURSE_PATH),
7011
                api_get_path(REL_PATH).'courses/',
7012
                $content
7013
            );
7014
7015
            // Change the path of mp3 to absolute.
7016
7017
            // The first regexp deals with :// urls.
7018
            $content = preg_replace(
7019
                "|(flashvars=\"file=)([^:/]+)/|",
7020
                "$1".api_get_path(
7021
                    REL_COURSE_PATH
7022
                ).$courseInfo['path'].'/document/',
7023
                $content
7024
            );
7025
            // The second regexp deals with audio/ urls.
7026
            $content = preg_replace(
7027
                "|(flashvars=\"file=)([^/]+)/|",
7028
                "$1".api_get_path(
7029
                    REL_COURSE_PATH
7030
                ).$courseInfo['path'].'/document/$2/',
7031
                $content
7032
            );
7033
            // For flv player: To prevent edition problem with firefox,
7034
            // we have to use a strange tip (don't blame me please).
7035
            $content = str_replace(
7036
                '</body>',
7037
                '<style type="text/css">body{}</style></body>',
7038
                $content
7039
            );
7040
        }
7041
7042
        if (!file_exists($filepath.$filename)) {
7043
            if ($fp = @fopen($filepath.$filename, 'w')) {
7044
                fputs($fp, $content);
7045
                fclose($fp);
7046
7047
                $file_size = filesize($filepath.$filename);
7048
                $save_file_path = $dir.$filename;
7049
7050
                $document_id = add_document(
7051
                    $courseInfo,
7052
                    $save_file_path,
7053
                    'file',
7054
                    $file_size,
7055
                    $tmp_filename,
7056
                    '',
7057
                    0, //readonly
7058
                    true,
7059
                    null,
7060
                    $sessionId,
7061
                    $creatorId
7062
                );
7063
7064
                if ($document_id) {
7065
                    api_item_property_update(
7066
                        $courseInfo,
7067
                        TOOL_DOCUMENT,
7068
                        $document_id,
7069
                        'DocumentAdded',
7070
                        $creatorId,
7071
                        null,
7072
                        null,
7073
                        null,
7074
                        null,
7075
                        $sessionId
7076
                    );
7077
7078
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7079
                    $new_title = $originalTitle;
7080
7081
                    if ($new_comment || $new_title) {
7082
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7083
                        $ct = '';
7084
                        if ($new_comment) {
7085
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7086
                        }
7087
                        if ($new_title) {
7088
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7089
                        }
7090
7091
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7092
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7093
                        Database::query($sql);
7094
                    }
7095
                }
7096
7097
                return $document_id;
7098
            }
7099
        }
7100
    }
7101
7102
    /**
7103
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7104
     *
7105
     * @param array $_course array
7106
     *
7107
     * @throws \Doctrine\DBAL\DBALException
7108
     */
7109
    public function edit_document($_course)
7110
    {
7111
        $course_id = api_get_course_int_id();
7112
        $urlAppend = api_get_configuration_value('url_append');
7113
        // Please, do not modify this dirname formatting.
7114
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7115
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7116
7117
        if (strstr($dir, '..')) {
7118
            $dir = '/';
7119
        }
7120
7121
        if (isset($dir[0]) && $dir[0] == '.') {
7122
            $dir = substr($dir, 1);
7123
        }
7124
7125
        if (isset($dir[0]) && $dir[0] != '/') {
7126
            $dir = '/'.$dir;
7127
        }
7128
7129
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7130
            $dir .= '/';
7131
        }
7132
7133
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7134
7135
        if (!is_dir($filepath)) {
7136
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7137
        }
7138
7139
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7140
7141
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7142
            $document_id = intval($_POST['path']);
7143
            $sql = "SELECT path FROM ".$table_doc."
7144
                    WHERE c_id = $course_id AND id = ".$document_id;
7145
            $res = Database::query($sql);
7146
            $row = Database::fetch_array($res);
7147
            $content = stripslashes($_POST['content_lp']);
7148
            $file = $filepath.$row['path'];
7149
7150
            if ($fp = @fopen($file, 'w')) {
7151
                $content = str_replace(
7152
                    api_get_path(WEB_COURSE_PATH),
7153
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7154
                    $content
7155
                );
7156
                // Change the path of mp3 to absolute.
7157
                // The first regexp deals with :// urls.
7158
                $content = preg_replace(
7159
                    "|(flashvars=\"file=)([^:/]+)/|",
7160
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7161
                    $content
7162
                );
7163
                // The second regexp deals with audio/ urls.
7164
                $content = preg_replace(
7165
                    "|(flashvars=\"file=)([^:/]+)/|",
7166
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7167
                    $content
7168
                );
7169
                fputs($fp, $content);
7170
                fclose($fp);
7171
7172
                $sql = "UPDATE ".$table_doc." SET
7173
                            title='".Database::escape_string($_POST['title'])."'
7174
                        WHERE c_id = ".$course_id." AND id = ".$document_id;
7175
                Database::query($sql);
7176
            }
7177
        }
7178
    }
7179
7180
    /**
7181
     * Displays the selected item, with a panel for manipulating the item.
7182
     *
7183
     * @param int    $item_id
7184
     * @param string $msg
7185
     * @param bool   $show_actions
7186
     *
7187
     * @return string
7188
     * @throws \Doctrine\DBAL\DBALException
7189
     */
7190
    public function display_item($item_id, $msg = null, $show_actions = true)
7191
    {
7192
        $course_id = api_get_course_int_id();
7193
        $return = '';
7194
        if (is_numeric($item_id)) {
7195
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7196
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7197
                    WHERE lp.iid = ".intval($item_id);
7198
            $result = Database::query($sql);
7199
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7200
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7201
7202
                // Prevents wrong parent selection for document, see Bug#1251.
7203
                if ($row['item_type'] != 'dir') {
7204
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7205
                }
7206
7207
                if ($show_actions) {
7208
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7209
                }
7210
                $return .= '<div style="padding:10px;">';
7211
7212
                if ($msg != '') {
7213
                    $return .= $msg;
7214
                }
7215
7216
                $return .= '<h3>'.$row['title'].'</h3>';
7217
7218
                switch ($row['item_type']) {
7219
                    case TOOL_THREAD:
7220
                        $link = $this->rl_get_resource_link_for_learnpath(
7221
                            $course_id,
7222
                            $row['lp_id'],
7223
                            $item_id,
7224
                            0
7225
                        );
7226
                        $return .= Display::url(
7227
                            get_lang('GoToThread'),
7228
                            $link,
7229
                            ['class' => 'btn btn-primary']
7230
                        );
7231
                        break;
7232
                    case TOOL_FORUM:
7233
                        $return .= Display::url(
7234
                            get_lang('GoToForum'),
7235
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7236
                            ['class' => 'btn btn-primary']
7237
                        );
7238
                        break;
7239
                    case TOOL_QUIZ:
7240
                        if (!empty($row['path'])) {
7241
                            $exercise = new Exercise();
7242
                            $exercise->read($row['path']);
7243
                            $return .= $exercise->description.'<br />';
7244
                            $return .= Display::url(
7245
                                get_lang('GoToExercise'),
7246
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7247
                                ['class' => 'btn btn-primary']
7248
                            );
7249
                        }
7250
                        break;
7251
                    case TOOL_LP_FINAL_ITEM:
7252
                        $return .= $this->getSavedFinalItem();
7253
                        break;
7254
                    case TOOL_DOCUMENT:
7255
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7256
                        $sql_doc = "SELECT path FROM ".$tbl_doc."
7257
                                    WHERE c_id = ".$course_id." AND iid = ".intval($row['path']);
7258
                        $result = Database::query($sql_doc);
7259
                        $path_file = Database::result($result, 0, 0);
7260
                        $path_parts = pathinfo($path_file);
7261
                        // TODO: Correct the following naive comparisons.
7262
                        if (in_array($path_parts['extension'], [
7263
                            'html',
7264
                            'txt',
7265
                            'png',
7266
                            'jpg',
7267
                            'JPG',
7268
                            'jpeg',
7269
                            'JPEG',
7270
                            'gif',
7271
                            'swf',
7272
                            'pdf',
7273
                            'htm',
7274
                        ])) {
7275
                            $return .= $this->display_document($row['path'], true, true);
7276
                        }
7277
                        break;
7278
                    case TOOL_HOTPOTATOES:
7279
                        $return .= $this->display_document($row['path'], false, true);
7280
                        break;
7281
                }
7282
                $return .= '</div>';
7283
            }
7284
        }
7285
7286
        return $return;
7287
    }
7288
7289
    /**
7290
     * Shows the needed forms for editing a specific item.
7291
     *
7292
     * @param int $item_id
7293
     *
7294
     * @return string
7295
     * @throws \Doctrine\DBAL\DBALException
7296
     */
7297
    public function display_edit_item($item_id)
7298
    {
7299
        $course_id = api_get_course_int_id();
7300
        $return = '';
7301
        if (is_numeric($item_id)) {
7302
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7303
            $sql = "SELECT * FROM $tbl_lp_item
7304
                    WHERE iid = ".intval($item_id);
7305
            $res = Database::query($sql);
7306
            $row = Database::fetch_array($res);
7307
            switch ($row['item_type']) {
7308
                case 'dir':
7309
                case 'asset':
7310
                case 'sco':
7311
                    if (isset($_GET['view']) && $_GET['view'] == 'build') {
7312
                        $return .= $this->display_manipulate($item_id, $row['item_type']);
7313
                        $return .= $this->display_item_form(
7314
                            $row['item_type'],
7315
                            get_lang('EditCurrentChapter').' :',
7316
                            'edit',
7317
                            $item_id,
7318
                            $row
0 ignored issues
show
Bug introduced by
It seems like $row can also be of type array; however, parameter $extra_info of learnpath::display_item_form() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

7318
                            /** @scrutinizer ignore-type */ $row
Loading history...
7319
                        );
7320
                    } else {
7321
                        $return .= $this->display_item_small_form(
7322
                            $row['item_type'],
7323
                            get_lang('EditCurrentChapter').' :',
7324
                            $row
7325
                        );
7326
                    }
7327
                    break;
7328
                case TOOL_DOCUMENT:
7329
                    $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7330
                    $sql = "SELECT lp.*, doc.path as dir
7331
                            FROM $tbl_lp_item as lp
7332
                            LEFT JOIN $tbl_doc as doc
7333
                            ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7334
                            WHERE
7335
                                doc.c_id = $course_id AND
7336
                                lp.iid = ".intval($item_id);
7337
                    $res_step = Database::query($sql);
7338
                    $row_step = Database::fetch_array($res_step, 'ASSOC');
7339
                    $return .= $this->display_manipulate(
7340
                        $item_id,
7341
                        $row['item_type']
7342
                    );
7343
                    $return .= $this->display_document_form(
7344
                        'edit',
7345
                        $item_id,
7346
                        $row_step
7347
                    );
7348
                    break;
7349
                case TOOL_LINK:
7350
                    $link_id = (string) $row['path'];
7351
                    if (ctype_digit($link_id)) {
7352
                        $tbl_link = Database::get_course_table(TABLE_LINK);
7353
                        $sql_select = 'SELECT url FROM '.$tbl_link.'
7354
                                       WHERE c_id = '.$course_id.' AND iid = '.intval($link_id);
7355
                        $res_link = Database::query($sql_select);
7356
                        $row_link = Database::fetch_array($res_link);
7357
                        if (is_array($row_link)) {
7358
                            $row['url'] = $row_link['url'];
7359
                        }
7360
                    }
7361
                    $return .= $this->display_manipulate(
7362
                        $item_id,
7363
                        $row['item_type']
7364
                    );
7365
                    $return .= $this->display_link_form('edit', $item_id, $row);
7366
                    break;
7367
                case TOOL_LP_FINAL_ITEM:
7368
                    Session::write('finalItem', true);
7369
                    $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7370
                    $sql = "SELECT lp.*, doc.path as dir
7371
                            FROM $tbl_lp_item as lp
7372
                            LEFT JOIN $tbl_doc as doc
7373
                            ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7374
                            WHERE
7375
                                doc.c_id = $course_id AND
7376
                                lp.iid = ".intval($item_id);
7377
                    $res_step = Database::query($sql);
7378
                    $row_step = Database::fetch_array($res_step, 'ASSOC');
7379
                    $return .= $this->display_manipulate(
7380
                        $item_id,
7381
                        $row['item_type']
7382
                    );
7383
                    $return .= $this->display_document_form(
7384
                        'edit',
7385
                        $item_id,
7386
                        $row_step
7387
                    );
7388
                    break;
7389
                case TOOL_QUIZ:
7390
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7391
                    $return .= $this->display_quiz_form('edit', $item_id, $row);
7392
                    break;
7393
                case TOOL_HOTPOTATOES:
7394
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7395
                    $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
0 ignored issues
show
Bug introduced by
It seems like $row can also be of type array; however, parameter $extra_info of learnpath::display_hotpotatoes_form() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

7395
                    $return .= $this->display_hotpotatoes_form('edit', $item_id, /** @scrutinizer ignore-type */ $row);
Loading history...
7396
                    break;
7397
                case TOOL_STUDENTPUBLICATION:
7398
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7399
                    $return .= $this->display_student_publication_form('edit', $item_id, $row);
0 ignored issues
show
Bug introduced by
It seems like $row can also be of type array; however, parameter $extra_info of learnpath::display_student_publication_form() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

7399
                    $return .= $this->display_student_publication_form('edit', $item_id, /** @scrutinizer ignore-type */ $row);
Loading history...
7400
                    break;
7401
                case TOOL_FORUM:
7402
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7403
                    $return .= $this->display_forum_form('edit', $item_id, $row);
0 ignored issues
show
Bug introduced by
It seems like $row can also be of type array; however, parameter $extra_info of learnpath::display_forum_form() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

7403
                    $return .= $this->display_forum_form('edit', $item_id, /** @scrutinizer ignore-type */ $row);
Loading history...
7404
                    break;
7405
                case TOOL_THREAD:
7406
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7407
                    $return .= $this->display_thread_form('edit', $item_id, $row);
0 ignored issues
show
Bug introduced by
It seems like $row can also be of type array; however, parameter $extra_info of learnpath::display_thread_form() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

7407
                    $return .= $this->display_thread_form('edit', $item_id, /** @scrutinizer ignore-type */ $row);
Loading history...
7408
                    break;
7409
            }
7410
        }
7411
7412
        return $return;
7413
    }
7414
7415
    /**
7416
     * Function that displays a list with al the resources that
7417
     * could be added to the learning path.
7418
     *
7419
     * @return bool
7420
     * @throws Exception
7421
     * @throws HTML_QuickForm_Error
7422
     * @throws \Doctrine\DBAL\DBALException
7423
     */
7424
    public function display_resources()
7425
    {
7426
        $course_code = api_get_course_id();
7427
7428
        // Get all the docs.
7429
        $documents = $this->get_documents(true);
7430
7431
        // Get all the exercises.
7432
        $exercises = $this->get_exercises();
7433
7434
        // Get all the links.
7435
        $links = $this->get_links();
7436
7437
        // Get all the student publications.
7438
        $works = $this->get_student_publications();
7439
7440
        // Get all the forums.
7441
        $forums = $this->get_forums(null, $course_code);
7442
7443
        // Get the final item form (see BT#11048) .
7444
        $finish = $this->getFinalItemForm();
7445
7446
        $headers = [
7447
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7448
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7449
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7450
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7451
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7452
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7453
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7454
        ];
7455
7456
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7457
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7458
        echo Display::tabs(
7459
            $headers,
7460
            [
7461
                $documents,
7462
                $exercises,
7463
                $links,
7464
                $works,
7465
                $forums,
7466
                $dir,
7467
                $finish,
7468
            ],
7469
            'resource_tab'
7470
        );
7471
7472
        return true;
7473
    }
7474
7475
    /**
7476
     * Returns the extension of a document.
7477
     *
7478
     * @param string $filename
7479
     *
7480
     * @return string Extension (part after the last dot)
7481
     */
7482
    public function get_extension($filename)
7483
    {
7484
        $explode = explode('.', $filename);
7485
7486
        return $explode[count($explode) - 1];
7487
    }
7488
7489
    /**
7490
     * Displays a document by id.
7491
     *
7492
     * @param int  $id
7493
     *
7494
     * @param bool $show_title
7495
     * @param bool $iframe
7496
     * @param bool $edit_link
7497
     *
7498
     * @return string
7499
     * @throws \Doctrine\DBAL\DBALException
7500
     */
7501
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7502
    {
7503
        $_course = api_get_course_info();
7504
        $course_id = api_get_course_int_id();
7505
        $id = (int) $id;
7506
        $return = '';
7507
        $table = Database::get_course_table(TABLE_DOCUMENT);
7508
        $sql_doc = "SELECT * FROM $table
7509
                    WHERE c_id = $course_id AND iid = $id";
7510
        $res_doc = Database::query($sql_doc);
7511
        $row_doc = Database::fetch_array($res_doc);
7512
7513
        // TODO: Add a path filter.
7514
        if ($iframe) {
7515
            $return .= '<iframe id="learnpath_preview_frame" frameborder="0" height="400" width="100%" scrolling="auto" src="'.api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.str_replace('%2F', '/', urlencode($row_doc['path'])).'?'.api_get_cidreq().'"></iframe>';
7516
        } else {
7517
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7518
        }
7519
7520
        return $return;
7521
    }
7522
7523
    /**
7524
     * Return HTML form to add/edit a quiz.
7525
     *
7526
     * @param string $action Action (add/edit)
7527
     * @param int    $id Item ID if already exists
7528
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7529
     *
7530
     * @return string HTML form
7531
     * @throws Exception
7532
     * @throws \Doctrine\DBAL\DBALException
7533
     */
7534
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7535
    {
7536
        $course_id = api_get_course_int_id();
7537
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7538
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7539
7540
        if ($id != 0 && is_array($extra_info)) {
7541
            $item_title = $extra_info['title'];
7542
            $item_description = $extra_info['description'];
7543
        } elseif (is_numeric($extra_info)) {
7544
            $sql = "SELECT title, description
7545
                    FROM $tbl_quiz
7546
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7547
7548
            $result = Database::query($sql);
7549
            $row = Database::fetch_array($result);
7550
            $item_title = $row['title'];
7551
            $item_description = $row['description'];
7552
        } else {
7553
            $item_title = '';
7554
            $item_description = '';
7555
        }
7556
        $item_title = Security::remove_XSS($item_title);
7557
        $item_description = Security::remove_XSS($item_description);
7558
7559
        if ($id != 0 && is_array($extra_info)) {
7560
            $parent = $extra_info['parent_item_id'];
7561
        } else {
7562
            $parent = 0;
7563
        }
7564
7565
        $sql = "SELECT * FROM $tbl_lp_item 
7566
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7567
7568
        $result = Database::query($sql);
7569
        $arrLP = [];
7570
        while ($row = Database::fetch_array($result)) {
7571
            $arrLP[] = [
7572
                'id' => $row['id'],
7573
                'item_type' => $row['item_type'],
7574
                'title' => $row['title'],
7575
                'path' => $row['path'],
7576
                'description' => $row['description'],
7577
                'parent_item_id' => $row['parent_item_id'],
7578
                'previous_item_id' => $row['previous_item_id'],
7579
                'next_item_id' => $row['next_item_id'],
7580
                'display_order' => $row['display_order'],
7581
                'max_score' => $row['max_score'],
7582
                'min_score' => $row['min_score'],
7583
                'mastery_score' => $row['mastery_score'],
7584
                'prerequisite' => $row['prerequisite'],
7585
                'max_time_allowed' => $row['max_time_allowed'],
7586
            ];
7587
        }
7588
7589
        $this->tree_array($arrLP);
7590
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7591
        unset($this->arrMenu);
7592
7593
        $form = new FormValidator(
7594
            'quiz_form',
7595
            'POST',
7596
            $this->getCurrentBuildingModeURL()
7597
        );
7598
        $defaults = [];
7599
7600
        if ($action == 'add') {
7601
            $legend = get_lang('CreateTheExercise');
7602
        } elseif ($action == 'move') {
7603
            $legend = get_lang('MoveTheCurrentExercise');
7604
        } else {
7605
            $legend = get_lang('EditCurrentExecice');
7606
        }
7607
7608
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7609
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7610
        }
7611
7612
        $form->addHeader($legend);
7613
7614
        if ($action != 'move') {
7615
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle']);
7616
            $defaults['title'] = $item_title;
7617
        }
7618
7619
        // Select for Parent item, root or chapter
7620
        $selectParent = $form->addSelect(
7621
            'parent',
7622
            get_lang('Parent'),
7623
            [],
7624
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7625
        );
7626
        $selectParent->addOption($this->name, 0);
7627
7628
        $arrHide = [
7629
            $id,
7630
        ];
7631
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7632
            if ($action != 'add') {
7633
                if (
7634
                    ($arrLP[$i]['item_type'] == 'dir') &&
7635
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7636
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7637
                ) {
7638
                    $selectParent->addOption(
7639
                        $arrLP[$i]['title'],
7640
                        $arrLP[$i]['id'],
7641
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7642
                    );
7643
7644
                    if ($parent == $arrLP[$i]['id']) {
7645
                        $selectParent->setSelected($arrLP[$i]['id']);
7646
                    }
7647
                } else {
7648
                    $arrHide[] = $arrLP[$i]['id'];
7649
                }
7650
            } else {
7651
                if ($arrLP[$i]['item_type'] == 'dir') {
7652
                    $selectParent->addOption(
7653
                        $arrLP[$i]['title'],
7654
                        $arrLP[$i]['id'],
7655
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7656
                    );
7657
7658
                    if ($parent == $arrLP[$i]['id']) {
7659
                        $selectParent->setSelected($arrLP[$i]['id']);
7660
                    }
7661
                }
7662
            }
7663
        }
7664
        if (is_array($arrLP)) {
7665
            reset($arrLP);
7666
        }
7667
7668
        $selectPrevious = $form->addSelect(
7669
            'previous',
7670
            get_lang('Position'),
7671
            [],
7672
            ['id' => 'previous']
7673
        );
7674
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7675
7676
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7677
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7678
                $arrLP[$i]['id'] != $id
7679
            ) {
7680
                $selectPrevious->addOption(
7681
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7682
                    $arrLP[$i]['id']
7683
                );
7684
7685
                if (is_array($extra_info)) {
7686
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7687
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7688
                    }
7689
                } elseif ($action == 'add') {
7690
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7691
                }
7692
            }
7693
        }
7694
7695
        if ($action != 'move') {
7696
            $id_prerequisite = 0;
7697
            if (is_array($arrLP)) {
7698
                foreach ($arrLP as $key => $value) {
7699
                    if ($value['id'] == $id) {
7700
                        $id_prerequisite = $value['prerequisite'];
7701
                        break;
7702
                    }
7703
                }
7704
            }
7705
            $arrHide = [];
7706
            for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7707
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7708
                    if (is_array($extra_info)) {
7709
                        if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7710
                            $s_selected_position = $arrLP[$i]['id'];
7711
                        }
7712
                    } elseif ($action == 'add') {
7713
                        $s_selected_position = 0;
7714
                    }
7715
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7716
                }
7717
            }
7718
        }
7719
7720
        if ($action == 'add') {
7721
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7722
        } else {
7723
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7724
        }
7725
7726
        if ($action == 'move') {
7727
            $form->addHidden('title', $item_title);
7728
            $form->addHidden('description', $item_description);
7729
        }
7730
7731
        if (is_numeric($extra_info)) {
7732
            $form->addHidden('path', $extra_info);
7733
        } elseif (is_array($extra_info)) {
7734
            $form->addHidden('path', $extra_info['path']);
7735
        }
7736
7737
        $form->addHidden('type', TOOL_QUIZ);
7738
        $form->addHidden('post_time', time());
7739
        $form->setDefaults($defaults);
7740
7741
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7742
    }
7743
7744
    /**
7745
     * Addition of Hotpotatoes tests.
7746
     *
7747
     * @param string $action
7748
     * @param    int    Internal ID of the item
7749
     * @param string $extra_info
7750
     *
7751
     * @return string HTML structure to display the hotpotatoes addition formular
7752
     * @throws \Doctrine\DBAL\DBALException
7753
     */
7754
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7755
    {
7756
        $course_id = api_get_course_int_id();
7757
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
7758
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7759
7760
        if ($id != 0 && is_array($extra_info)) {
7761
            $item_title = stripslashes($extra_info['title']);
7762
            $item_description = stripslashes($extra_info['description']);
7763
        } elseif (is_numeric($extra_info)) {
7764
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7765
7766
            $sql = "SELECT * FROM ".$TBL_DOCUMENT."
7767
                    WHERE
7768
                        c_id = ".$course_id." AND
7769
                        path LIKE '".$uploadPath."/%/%htm%' AND
7770
                        iid = ".(int) $extra_info."
7771
                    ORDER BY iid ASC";
7772
7773
            $res_hot = Database::query($sql);
7774
            $row = Database::fetch_array($res_hot);
7775
7776
            $item_title = $row['title'];
7777
            $item_description = $row['description'];
7778
7779
            if (!empty($row['comment'])) {
7780
                $item_title = $row['comment'];
7781
            }
7782
        } else {
7783
            $item_title = '';
7784
            $item_description = '';
7785
        }
7786
7787
        if ($id != 0 && is_array($extra_info)) {
7788
            $parent = $extra_info['parent_item_id'];
7789
        } else {
7790
            $parent = 0;
7791
        }
7792
7793
        $sql = "SELECT * FROM $tbl_lp_item
7794
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7795
        $result = Database::query($sql);
7796
        $arrLP = [];
7797
        while ($row = Database::fetch_array($result)) {
7798
            $arrLP[] = [
7799
                'id' => $row['id'],
7800
                'item_type' => $row['item_type'],
7801
                'title' => $row['title'],
7802
                'path' => $row['path'],
7803
                'description' => $row['description'],
7804
                'parent_item_id' => $row['parent_item_id'],
7805
                'previous_item_id' => $row['previous_item_id'],
7806
                'next_item_id' => $row['next_item_id'],
7807
                'display_order' => $row['display_order'],
7808
                'max_score' => $row['max_score'],
7809
                'min_score' => $row['min_score'],
7810
                'mastery_score' => $row['mastery_score'],
7811
                'prerequisite' => $row['prerequisite'],
7812
                'max_time_allowed' => $row['max_time_allowed'],
7813
            ];
7814
        }
7815
7816
        $legend = '<legend>';
7817
        if ($action == 'add') {
7818
            $legend .= get_lang('CreateTheExercise');
7819
        } elseif ($action == 'move') {
7820
            $legend .= get_lang('MoveTheCurrentExercise');
7821
        } else {
7822
            $legend .= get_lang('EditCurrentExecice');
7823
        }
7824
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7825
            $legend .= Display:: return_message(
7826
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7827
            );
7828
        }
7829
        $legend .= '</legend>';
7830
7831
        $return = '<form method="POST">';
7832
        $return .= $legend;
7833
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7834
        $return .= '<tr>';
7835
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7836
        $return .= '<td class="input">';
7837
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7838
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7839
        $arrHide = [
7840
            $id,
7841
        ];
7842
7843
        if (count($arrLP) > 0) {
7844
            for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7845
                if ($action != 'add') {
7846
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7847
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7848
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7849
                    ) {
7850
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7851
                    } else {
7852
                        $arrHide[] = $arrLP[$i]['id'];
7853
                    }
7854
                } else {
7855
                    if ($arrLP[$i]['item_type'] == 'dir') {
7856
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7857
                    }
7858
                }
7859
            }
7860
            reset($arrLP);
7861
        }
7862
7863
        $return .= '</select>';
7864
        $return .= '</td>';
7865
        $return .= '</tr>';
7866
        $return .= '<tr>';
7867
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7868
        $return .= '<td class="input">';
7869
        $return .= '<select id="previous" name="previous" size="1">';
7870
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
7871
7872
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7873
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7874
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7875
                    $selected = 'selected="selected" ';
7876
                } elseif ($action == 'add') {
7877
                    $selected = 'selected="selected" ';
7878
                } else {
7879
                    $selected = '';
7880
                }
7881
7882
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7883
            }
7884
        }
7885
7886
        $return .= '</select>';
7887
        $return .= '</td>';
7888
        $return .= '</tr>';
7889
7890
        if ($action != 'move') {
7891
            $return .= '<tr>';
7892
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7893
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7894
            $return .= '</tr>';
7895
            $id_prerequisite = 0;
7896
            if (is_array($arrLP) && count($arrLP) > 0) {
7897
                foreach ($arrLP as $key => $value) {
7898
                    if ($value['id'] == $id) {
7899
                        $id_prerequisite = $value['prerequisite'];
7900
                        break;
7901
                    }
7902
                }
7903
7904
                $arrHide = [];
7905
                for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7906
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7907
                        if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7908
                            $s_selected_position = $arrLP[$i]['id'];
7909
                        } elseif ($action == 'add') {
7910
                            $s_selected_position = 0;
7911
                        }
7912
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7913
                    }
7914
                }
7915
            }
7916
        }
7917
7918
        $return .= '<tr>';
7919
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.get_lang('SaveHotpotatoes').'</button></td>';
7920
        $return .= '</tr>';
7921
        $return .= '</table>';
7922
7923
        if ($action == 'move') {
7924
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7925
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7926
        }
7927
7928
        if (is_numeric($extra_info)) {
7929
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7930
        } elseif (is_array($extra_info)) {
7931
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7932
        }
7933
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7934
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7935
        $return .= '</form>';
7936
7937
        return $return;
7938
    }
7939
7940
    /**
7941
     * Return the form to display the forum edit/add option.
7942
     *
7943
     * @param string $action
7944
     * @param    int    ID of the lp_item if already exists
7945
     * @param string $extra_info
7946
     *
7947
     * @return string HTML form
7948
     * @throws Exception
7949
     * @throws \Doctrine\DBAL\DBALException
7950
     */
7951
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
7952
    {
7953
        $course_id = api_get_course_int_id();
7954
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7955
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7956
7957
        if ($id != 0 && is_array($extra_info)) {
7958
            $item_title = stripslashes($extra_info['title']);
7959
        } elseif (is_numeric($extra_info)) {
7960
            $sql = "SELECT forum_title as title, forum_comment as comment
7961
                    FROM ".$tbl_forum."
7962
                    WHERE c_id = ".$course_id." AND forum_id = ".$extra_info;
7963
7964
            $result = Database::query($sql);
7965
            $row = Database::fetch_array($result);
7966
7967
            $item_title = $row['title'];
7968
            $item_description = $row['comment'];
7969
        } else {
7970
            $item_title = '';
7971
            $item_description = '';
7972
        }
7973
7974
        if ($id != 0 && is_array($extra_info)) {
7975
            $parent = $extra_info['parent_item_id'];
7976
        } else {
7977
            $parent = 0;
7978
        }
7979
7980
        $sql = "SELECT * FROM $tbl_lp_item
7981
                WHERE
7982
                    c_id = $course_id AND
7983
                    lp_id = ".$this->lp_id;
7984
        $result = Database::query($sql);
7985
        $arrLP = [];
7986
        while ($row = Database::fetch_array($result)) {
7987
            $arrLP[] = [
7988
                'id' => $row['iid'],
7989
                'item_type' => $row['item_type'],
7990
                'title' => $row['title'],
7991
                'path' => $row['path'],
7992
                'description' => $row['description'],
7993
                'parent_item_id' => $row['parent_item_id'],
7994
                'previous_item_id' => $row['previous_item_id'],
7995
                'next_item_id' => $row['next_item_id'],
7996
                'display_order' => $row['display_order'],
7997
                'max_score' => $row['max_score'],
7998
                'min_score' => $row['min_score'],
7999
                'mastery_score' => $row['mastery_score'],
8000
                'prerequisite' => $row['prerequisite'],
8001
            ];
8002
        }
8003
8004
        $this->tree_array($arrLP);
8005
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8006
        unset($this->arrMenu);
8007
8008
        if ($action == 'add') {
8009
            $legend = get_lang('CreateTheForum');
8010
        } elseif ($action == 'move') {
8011
            $legend = get_lang('MoveTheCurrentForum');
8012
        } else {
8013
            $legend = get_lang('EditCurrentForum');
8014
        }
8015
8016
        $form = new FormValidator(
8017
            'forum_form',
8018
            'POST',
8019
            $this->getCurrentBuildingModeURL()
8020
        );
8021
        $defaults = [];
8022
8023
        $form->addHeader($legend);
8024
8025
        if ($action != 'move') {
8026
            $form->addText(
8027
                'title',
8028
                get_lang('Title'),
8029
                true,
8030
                ['id' => 'idTitle', 'class' => 'learnpath_item_form']
8031
            );
8032
            $defaults['title'] = $item_title;
8033
        }
8034
8035
        $selectParent = $form->addSelect(
8036
            'parent',
8037
            get_lang('Parent'),
8038
            [],
8039
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
8040
        );
8041
        $selectParent->addOption($this->name, 0);
8042
        $arrHide = [
8043
            $id,
8044
        ];
8045
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8046
            if ($action != 'add') {
8047
                if ($arrLP[$i]['item_type'] == 'dir' &&
8048
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8049
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8050
                ) {
8051
                    $selectParent->addOption(
8052
                        $arrLP[$i]['title'],
8053
                        $arrLP[$i]['id'],
8054
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8055
                    );
8056
8057
                    if ($parent == $arrLP[$i]['id']) {
8058
                        $selectParent->setSelected($arrLP[$i]['id']);
8059
                    }
8060
                } else {
8061
                    $arrHide[] = $arrLP[$i]['id'];
8062
                }
8063
            } else {
8064
                if ($arrLP[$i]['item_type'] == 'dir') {
8065
                    $selectParent->addOption(
8066
                        $arrLP[$i]['title'],
8067
                        $arrLP[$i]['id'],
8068
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8069
                    );
8070
8071
                    if ($parent == $arrLP[$i]['id']) {
8072
                        $selectParent->setSelected($arrLP[$i]['id']);
8073
                    }
8074
                }
8075
            }
8076
        }
8077
8078
        if (is_array($arrLP)) {
8079
            reset($arrLP);
8080
        }
8081
8082
        $selectPrevious = $form->addSelect(
8083
            'previous',
8084
            get_lang('Position'),
8085
            [],
8086
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8087
        );
8088
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8089
8090
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8091
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8092
                $arrLP[$i]['id'] != $id
8093
            ) {
8094
                $selectPrevious->addOption(
8095
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8096
                    $arrLP[$i]['id']
8097
                );
8098
8099
                if (isset($extra_info['previous_item_id']) &&
8100
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8101
                ) {
8102
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8103
                } elseif ($action == 'add') {
8104
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8105
                }
8106
            }
8107
        }
8108
8109
        if ($action != 'move') {
8110
            $id_prerequisite = 0;
8111
            if (is_array($arrLP)) {
8112
                foreach ($arrLP as $key => $value) {
8113
                    if ($value['id'] == $id) {
8114
                        $id_prerequisite = $value['prerequisite'];
8115
                        break;
8116
                    }
8117
                }
8118
            }
8119
8120
            $arrHide = [];
8121
            for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8122
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8123
                    if (isset($extra_info['previous_item_id']) &&
8124
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8125
                    ) {
8126
                        $s_selected_position = $arrLP[$i]['id'];
8127
                    } elseif ($action == 'add') {
8128
                        $s_selected_position = 0;
8129
                    }
8130
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8131
                }
8132
            }
8133
        }
8134
8135
        if ($action == 'add') {
8136
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8137
        } else {
8138
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8139
        }
8140
8141
        if ($action == 'move') {
8142
            $form->addHidden('title', $item_title);
8143
            $form->addHidden('description', $item_description);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $item_description does not seem to be defined for all execution paths leading up to this point.
Loading history...
8144
        }
8145
8146
        if (is_numeric($extra_info)) {
8147
            $form->addHidden('path', $extra_info);
8148
        } elseif (is_array($extra_info)) {
8149
            $form->addHidden('path', $extra_info['path']);
8150
        }
8151
        $form->addHidden('type', TOOL_FORUM);
8152
        $form->addHidden('post_time', time());
8153
        $form->setDefaults($defaults);
8154
8155
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8156
    }
8157
8158
    /**
8159
     * Return HTML form to add/edit forum threads.
8160
     *
8161
     * @param string $action
8162
     * @param    int    Item ID if already exists in learning path
8163
     * @param string $extra_info
8164
     *
8165
     * @return string HTML form
8166
     * @throws Exception
8167
     * @throws \Doctrine\DBAL\DBALException
8168
     */
8169
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8170
    {
8171
        $course_id = api_get_course_int_id();
8172
        if (empty($course_id)) {
8173
            return null;
8174
        }
8175
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8176
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8177
8178
        if ($id != 0 && is_array($extra_info)) {
8179
            $item_title = stripslashes($extra_info['title']);
8180
        } elseif (is_numeric($extra_info)) {
8181
            $sql = "SELECT thread_title as title FROM $tbl_forum
8182
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8183
8184
            $result = Database::query($sql);
8185
            $row = Database::fetch_array($result);
8186
8187
            $item_title = $row['title'];
8188
            $item_description = '';
8189
        } else {
8190
            $item_title = '';
8191
            $item_description = '';
8192
        }
8193
8194
        if ($id != 0 && is_array($extra_info)) {
8195
            $parent = $extra_info['parent_item_id'];
8196
        } else {
8197
            $parent = 0;
8198
        }
8199
8200
        $sql = "SELECT * FROM $tbl_lp_item
8201
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8202
        $result = Database::query($sql);
8203
8204
        $arrLP = [];
8205
        while ($row = Database::fetch_array($result)) {
8206
            $arrLP[] = [
8207
                'id' => $row['iid'],
8208
                'item_type' => $row['item_type'],
8209
                'title' => $row['title'],
8210
                'path' => $row['path'],
8211
                'description' => $row['description'],
8212
                'parent_item_id' => $row['parent_item_id'],
8213
                'previous_item_id' => $row['previous_item_id'],
8214
                'next_item_id' => $row['next_item_id'],
8215
                'display_order' => $row['display_order'],
8216
                'max_score' => $row['max_score'],
8217
                'min_score' => $row['min_score'],
8218
                'mastery_score' => $row['mastery_score'],
8219
                'prerequisite' => $row['prerequisite'],
8220
            ];
8221
        }
8222
8223
        $this->tree_array($arrLP);
8224
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8225
        unset($this->arrMenu);
8226
8227
        $form = new FormValidator(
8228
            'thread_form',
8229
            'POST',
8230
            $this->getCurrentBuildingModeURL()
8231
        );
8232
        $defaults = [];
8233
8234
        if ($action == 'add') {
8235
            $legend = get_lang('CreateTheForum');
8236
        } elseif ($action == 'move') {
8237
            $legend = get_lang('MoveTheCurrentForum');
8238
        } else {
8239
            $legend = get_lang('EditCurrentForum');
8240
        }
8241
8242
        $form->addHeader($legend);
8243
        $selectParent = $form->addSelect(
8244
            'parent',
8245
            get_lang('Parent'),
8246
            [],
8247
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8248
        );
8249
        $selectParent->addOption($this->name, 0);
8250
8251
        $arrHide = [
8252
            $id,
8253
        ];
8254
8255
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8256
            if ($action != 'add') {
8257
                if (
8258
                    ($arrLP[$i]['item_type'] == 'dir') &&
8259
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8260
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8261
                ) {
8262
                    $selectParent->addOption(
8263
                        $arrLP[$i]['title'],
8264
                        $arrLP[$i]['id'],
8265
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8266
                    );
8267
8268
                    if ($parent == $arrLP[$i]['id']) {
8269
                        $selectParent->setSelected($arrLP[$i]['id']);
8270
                    }
8271
                } else {
8272
                    $arrHide[] = $arrLP[$i]['id'];
8273
                }
8274
            } else {
8275
                if ($arrLP[$i]['item_type'] == 'dir') {
8276
                    $selectParent->addOption(
8277
                        $arrLP[$i]['title'],
8278
                        $arrLP[$i]['id'],
8279
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8280
                    );
8281
8282
                    if ($parent == $arrLP[$i]['id']) {
8283
                        $selectParent->setSelected($arrLP[$i]['id']);
8284
                    }
8285
                }
8286
            }
8287
        }
8288
8289
        if ($arrLP != null) {
8290
            reset($arrLP);
8291
        }
8292
8293
        $selectPrevious = $form->addSelect(
8294
            'previous',
8295
            get_lang('Position'),
8296
            [],
8297
            ['id' => 'previous']
8298
        );
8299
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8300
8301
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8302
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8303
                $selectPrevious->addOption(
8304
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8305
                    $arrLP[$i]['id']
8306
                );
8307
8308
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8309
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8310
                } elseif ($action == 'add') {
8311
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8312
                }
8313
            }
8314
        }
8315
8316
        if ($action != 'move') {
8317
            $form->addText(
8318
                'title',
8319
                get_lang('Title'),
8320
                true,
8321
                ['id' => 'idTitle']
8322
            );
8323
            $defaults['title'] = $item_title;
8324
8325
            $id_prerequisite = 0;
8326
            if ($arrLP != null) {
8327
                foreach ($arrLP as $key => $value) {
8328
                    if ($value['id'] == $id) {
8329
                        $id_prerequisite = $value['prerequisite'];
8330
                        break;
8331
                    }
8332
                }
8333
            }
8334
8335
            $arrHide = [];
8336
            $s_selected_position = 0;
8337
            for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8338
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8339
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8340
                        $s_selected_position = $arrLP[$i]['id'];
8341
                    } elseif ($action == 'add') {
8342
                        $s_selected_position = 0;
8343
                    }
8344
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8345
                }
8346
            }
8347
8348
            $selectPrerequisites = $form->addSelect(
8349
                'prerequisites',
8350
                get_lang('LearnpathPrerequisites'),
8351
                [],
8352
                ['id' => 'prerequisites']
8353
            );
8354
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8355
8356
            foreach ($arrHide as $key => $value) {
8357
                $selectPrerequisites->addOption($value['value'], $key);
8358
8359
                if ($key == $s_selected_position && $action == 'add') {
8360
                    $selectPrerequisites->setSelected($key);
8361
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8362
                    $selectPrerequisites->setSelected($key);
8363
                }
8364
            }
8365
        }
8366
8367
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8368
8369
        if ($action == 'move') {
8370
            $form->addHidden('title', $item_title);
8371
            $form->addHidden('description', $item_description);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $item_description does not seem to be defined for all execution paths leading up to this point.
Loading history...
8372
        }
8373
8374
        if (is_numeric($extra_info)) {
8375
            $form->addHidden('path', $extra_info);
8376
        } elseif (is_array($extra_info)) {
8377
            $form->addHidden('path', $extra_info['path']);
8378
        }
8379
8380
        $form->addHidden('type', TOOL_THREAD);
8381
        $form->addHidden('post_time', time());
8382
        $form->setDefaults($defaults);
8383
8384
        return $form->returnForm();
8385
    }
8386
8387
    /**
8388
     * Return the HTML form to display an item (generally a dir item).
8389
     *
8390
     * @param string $item_type
8391
     * @param string $title
8392
     * @param string $action
8393
     * @param int    $id
8394
     * @param string $extra_info
8395
     *
8396
     * @return string HTML form
8397
     * @throws Exception
8398
     * @throws HTML_QuickForm_Error
8399
     * @throws \Doctrine\DBAL\DBALException
8400
     */
8401
    public function display_item_form(
8402
        $item_type,
8403
        $title = '',
8404
        $action = 'add_item',
8405
        $id = 0,
8406
        $extra_info = 'new'
8407
    ) {
8408
        $_course = api_get_course_info();
8409
8410
        global $charset;
8411
8412
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8413
8414
        if ($id != 0 && is_array($extra_info)) {
8415
            $item_title = $extra_info['title'];
8416
            $item_description = $extra_info['description'];
8417
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8418
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8419
        } else {
8420
            $item_title = '';
8421
            $item_description = '';
8422
            $item_path_fck = '';
8423
        }
8424
8425
        if ($id != 0 && is_array($extra_info)) {
8426
            $parent = $extra_info['parent_item_id'];
8427
        } else {
8428
            $parent = 0;
8429
        }
8430
8431
        $id = intval($id);
8432
        $sql = "SELECT * FROM $tbl_lp_item
8433
                WHERE
8434
                    lp_id = ".$this->lp_id." AND
8435
                    iid != $id";
8436
8437
        if ($item_type == 'dir') {
8438
            $sql .= " AND parent_item_id = 0";
8439
        }
8440
8441
        $result = Database::query($sql);
8442
        $arrLP = [];
8443
        while ($row = Database::fetch_array($result)) {
8444
            $arrLP[] = [
8445
                'id' => $row['iid'],
8446
                'item_type' => $row['item_type'],
8447
                'title' => $row['title'],
8448
                'path' => $row['path'],
8449
                'description' => $row['description'],
8450
                'parent_item_id' => $row['parent_item_id'],
8451
                'previous_item_id' => $row['previous_item_id'],
8452
                'next_item_id' => $row['next_item_id'],
8453
                'max_score' => $row['max_score'],
8454
                'min_score' => $row['min_score'],
8455
                'mastery_score' => $row['mastery_score'],
8456
                'prerequisite' => $row['prerequisite'],
8457
                'display_order' => $row['display_order'],
8458
            ];
8459
        }
8460
8461
        $this->tree_array($arrLP);
8462
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8463
        unset($this->arrMenu);
8464
8465
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8466
8467
        $form = new FormValidator('form', 'POST', $url);
8468
        $defaults['title'] = api_html_entity_decode(
0 ignored issues
show
Comprehensibility Best Practice introduced by
$defaults was never initialized. Although not strictly required by PHP, it is generally a good practice to add $defaults = array(); before regardless.
Loading history...
8469
            $item_title,
8470
            ENT_QUOTES,
8471
            $charset
8472
        );
8473
        $defaults['description'] = $item_description;
8474
8475
        $form->addElement('header', $title);
8476
8477
        //$arrHide = array($id);
8478
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$arrHide was never initialized. Although not strictly required by PHP, it is generally a good practice to add $arrHide = array(); before regardless.
Loading history...
8479
        $arrHide[0]['padding'] = 20;
8480
        $charset = api_get_system_encoding();
8481
8482
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8483
            if ($action != 'add') {
8484
                if ($arrLP[$i]['item_type'] == 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8485
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8486
                ) {
8487
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8488
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8489
                    if ($parent == $arrLP[$i]['id']) {
8490
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8491
                    }
8492
                }
8493
            } else {
8494
                if ($arrLP[$i]['item_type'] == 'dir') {
8495
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8496
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8497
                    if ($parent == $arrLP[$i]['id']) {
8498
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8499
                    }
8500
                }
8501
            }
8502
        }
8503
8504
        if ($action != 'move') {
8505
            $form->addElement('text', 'title', get_lang('Title'));
8506
            $form->applyFilter('title', 'html_filter');
8507
            $form->addRule('title', get_lang('ThisFieldIsRequired'), 'required');
8508
        } else {
8509
            $form->addElement('hidden', 'title');
8510
        }
8511
8512
        $parent_select = $form->addElement(
8513
            'select',
8514
            'parent',
8515
            get_lang('Parent'),
8516
            '',
8517
            [
8518
                'id' => 'idParent',
8519
                'onchange' => "javascript: load_cbo(this.value);",
8520
            ]
8521
        );
8522
8523
        foreach ($arrHide as $key => $value) {
8524
            $parent_select->addOption(
0 ignored issues
show
Bug introduced by
The method addOption() does not exist on HTML_QuickForm_element. It seems like you code against a sub-type of HTML_QuickForm_element such as HTML_QuickForm_select. ( Ignorable by Annotation )

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

8524
            $parent_select->/** @scrutinizer ignore-call */ 
8525
                            addOption(
Loading history...
8525
                $value['value'],
8526
                $key,
8527
                'style="padding-left:'.$value['padding'].'px;"'
8528
            );
8529
        }
8530
        if (!empty($s_selected_parent)) {
8531
            $parent_select->setSelected($s_selected_parent);
0 ignored issues
show
Bug introduced by
The method setSelected() does not exist on HTML_QuickForm_element. It seems like you code against a sub-type of HTML_QuickForm_element such as HTML_QuickForm_select. ( Ignorable by Annotation )

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

8531
            $parent_select->/** @scrutinizer ignore-call */ 
8532
                            setSelected($s_selected_parent);
Loading history...
8532
        }
8533
8534
        if (is_array($arrLP)) {
8535
            reset($arrLP);
8536
        }
8537
        $arrHide = [];
8538
        // POSITION
8539
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8540
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8541
                //this is the same!
8542
                if (isset($extra_info['previous_item_id']) &&
8543
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8544
                ) {
8545
                    $s_selected_position = $arrLP[$i]['id'];
8546
                } elseif ($action == 'add') {
8547
                    $s_selected_position = $arrLP[$i]['id'];
8548
                }
8549
8550
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8551
            }
8552
        }
8553
8554
        $position = $form->addElement(
8555
            'select',
8556
            'previous',
8557
            get_lang('Position'),
8558
            '',
8559
            ['id' => 'previous']
8560
        );
8561
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8562
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8563
8564
        foreach ($arrHide as $key => $value) {
8565
            $position->addOption($value['value'].'"', $key, 'style="padding-left:'.$padding.'px;"');
8566
        }
8567
8568
        if (!empty($s_selected_position)) {
8569
            $position->setSelected($s_selected_position);
8570
        }
8571
8572
        if (is_array($arrLP)) {
8573
            reset($arrLP);
8574
        }
8575
8576
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8577
8578
        //fix in order to use the tab
8579
        if ($item_type == 'dir') {
8580
            $form->addElement('hidden', 'type', 'dir');
8581
        }
8582
8583
        $extension = null;
8584
        if (!empty($item_path)) {
8585
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8586
        }
8587
8588
        //assets can't be modified
8589
        //$item_type == 'asset' ||
8590
        if (($item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) {
8591
            if ($item_type == 'sco') {
8592
                $form->addElement(
8593
                    'html',
8594
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8595
                );
8596
            }
8597
            $renderer = $form->defaultRenderer();
8598
            $renderer->setElementTemplate('<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}', 'content_lp');
8599
8600
            $relative_prefix = '';
8601
8602
            $editor_config = [
8603
                'ToolbarSet' => 'LearningPathDocuments',
8604
                'Width' => '100%',
8605
                'Height' => '500',
8606
                'FullPage' => true,
8607
                'CreateDocumentDir' => $relative_prefix,
8608
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8609
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8610
            ];
8611
8612
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8613
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8614
            $defaults['content_lp'] = file_get_contents($content_path);
8615
        }
8616
8617
        $form->addElement('hidden', 'type', $item_type);
8618
        $form->addElement('hidden', 'post_time', time());
8619
        $form->setDefaults($defaults);
8620
8621
        return $form->returnForm();
8622
    }
8623
8624
    /**
8625
     * @return string
8626
     */
8627
    public function getCurrentBuildingModeURL()
8628
    {
8629
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8630
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8631
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8632
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8633
8634
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8635
8636
        return $currentUrl;
8637
    }
8638
8639
    /**
8640
     * Returns the form to update or create a document.
8641
     *
8642
     * @param string $action (add/edit)
8643
     * @param int    $id ID of the lp_item (if already exists)
8644
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8645
     *
8646
     * @return string HTML form
8647
     * @throws Exception
8648
     * @throws HTML_QuickForm_Error
8649
     * @throws \Doctrine\DBAL\DBALException
8650
     */
8651
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
8652
    {
8653
        $course_id = api_get_course_int_id();
8654
        $_course = api_get_course_info();
8655
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8656
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8657
8658
        $no_display_edit_textarea = false;
8659
        $item_description = '';
8660
        //If action==edit document
8661
        //We don't display the document form if it's not an editable document (html or txt file)
8662
        if ($action == 'edit') {
8663
            if (is_array($extra_info)) {
8664
                $path_parts = pathinfo($extra_info['dir']);
8665
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
8666
                    $no_display_edit_textarea = true;
8667
                }
8668
            }
8669
        }
8670
        $no_display_add = false;
8671
8672
        // If action==add an existing document
8673
        // We don't display the document form if it's not an editable document (html or txt file).
8674
        if ($action == 'add') {
8675
            if (is_numeric($extra_info)) {
8676
                $sql_doc = "SELECT path FROM $tbl_doc 
8677
                            WHERE c_id = $course_id AND iid = ".intval($extra_info);
8678
                $result = Database::query($sql_doc);
8679
                $path_file = Database::result($result, 0, 0);
8680
                $path_parts = pathinfo($path_file);
8681
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8682
                    $no_display_add = true;
8683
                }
8684
            }
8685
        }
8686
        if ($id != 0 && is_array($extra_info)) {
8687
            $item_title = stripslashes($extra_info['title']);
8688
            $item_description = stripslashes($extra_info['description']);
8689
            $item_terms = stripslashes($extra_info['terms']);
8690
            if (empty($item_title)) {
8691
                $path_parts = pathinfo($extra_info['path']);
8692
                $item_title = stripslashes($path_parts['filename']);
8693
            }
8694
        } elseif (is_numeric($extra_info)) {
8695
            $sql = "SELECT path, title FROM $tbl_doc
8696
                    WHERE
8697
                        c_id = ".$course_id." AND
8698
                        iid = ".intval($extra_info);
8699
            $result = Database::query($sql);
8700
            $row = Database::fetch_array($result);
8701
            $item_title = $row['title'];
8702
            $item_title = str_replace('_', ' ', $item_title);
8703
            if (empty($item_title)) {
8704
                $path_parts = pathinfo($row['path']);
8705
                $item_title = stripslashes($path_parts['filename']);
8706
            }
8707
        } else {
8708
            $item_title = '';
8709
            $item_description = '';
8710
        }
8711
        $return = '<legend>';
8712
8713
        if ($id != 0 && is_array($extra_info)) {
8714
            $parent = $extra_info['parent_item_id'];
8715
        } else {
8716
            $parent = 0;
8717
        }
8718
8719
        $sql = "SELECT * FROM $tbl_lp_item
8720
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8721
        $result = Database::query($sql);
8722
        $arrLP = [];
8723
8724
        while ($row = Database::fetch_array($result)) {
8725
            $arrLP[] = [
8726
                'id' => $row['id'],
8727
                'item_type' => $row['item_type'],
8728
                'title' => $row['title'],
8729
                'path' => $row['path'],
8730
                'description' => $row['description'],
8731
                'parent_item_id' => $row['parent_item_id'],
8732
                'previous_item_id' => $row['previous_item_id'],
8733
                'next_item_id' => $row['next_item_id'],
8734
                'display_order' => $row['display_order'],
8735
                'max_score' => $row['max_score'],
8736
                'min_score' => $row['min_score'],
8737
                'mastery_score' => $row['mastery_score'],
8738
                'prerequisite' => $row['prerequisite'],
8739
            ];
8740
        }
8741
8742
        $this->tree_array($arrLP);
8743
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8744
        unset($this->arrMenu);
8745
8746
        if ($action == 'add') {
8747
            $return .= get_lang('CreateTheDocument');
8748
        } elseif ($action == 'move') {
8749
            $return .= get_lang('MoveTheCurrentDocument');
8750
        } else {
8751
            $return .= get_lang('EditTheCurrentDocument');
8752
        }
8753
        $return .= '</legend>';
8754
8755
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
8756
            $return .= Display::return_message(
8757
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8758
                false
8759
            );
8760
        }
8761
        $form = new FormValidator(
8762
            'form',
8763
            'POST',
8764
            $this->getCurrentBuildingModeURL(),
8765
            '',
8766
            ['enctype' => 'multipart/form-data']
8767
        );
8768
        $defaults['title'] = Security::remove_XSS($item_title);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$defaults was never initialized. Although not strictly required by PHP, it is generally a good practice to add $defaults = array(); before regardless.
Loading history...
8769
        if (empty($item_title)) {
8770
            $defaults['title'] = Security::remove_XSS($item_title);
8771
        }
8772
        $defaults['description'] = $item_description;
8773
        $form->addElement('html', $return);
8774
8775
        if ($action != 'move') {
8776
            $data = $this->generate_lp_folder($_course);
8777
            if ($action != 'edit') {
8778
                $folders = DocumentManager::get_all_document_folders(
8779
                    $_course,
8780
                    0,
8781
                    true
8782
                );
8783
                DocumentManager::build_directory_selector(
8784
                    $folders,
8785
                    '',
8786
                    [],
8787
                    true,
8788
                    $form,
8789
                    'directory_parent_id'
8790
                );
8791
            }
8792
8793
            if (isset($data['id'])) {
8794
                $defaults['directory_parent_id'] = $data['id'];
8795
            }
8796
8797
            $form->addElement(
8798
                'text',
8799
                'title',
8800
                get_lang('Title'),
8801
                ['id' => 'idTitle', 'class' => 'col-md-4']
8802
            );
8803
            $form->applyFilter('title', 'html_filter');
8804
        }
8805
8806
        $arrHide[0]['value'] = $this->name;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$arrHide was never initialized. Although not strictly required by PHP, it is generally a good practice to add $arrHide = array(); before regardless.
Loading history...
8807
        $arrHide[0]['padding'] = 20;
8808
8809
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8810
            if ($action != 'add') {
8811
                if ($arrLP[$i]['item_type'] == 'dir' &&
8812
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8813
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8814
                ) {
8815
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8816
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8817
                    if ($parent == $arrLP[$i]['id']) {
8818
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8819
                    }
8820
                }
8821
            } else {
8822
                if ($arrLP[$i]['item_type'] == 'dir') {
8823
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8824
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8825
                    if ($parent == $arrLP[$i]['id']) {
8826
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8827
                    }
8828
                }
8829
            }
8830
        }
8831
8832
        $parent_select = $form->addSelect(
8833
            'parent',
8834
            get_lang('Parent'),
8835
            [],
8836
            [
8837
                'id' => 'idParent',
8838
                'onchange' => 'javascript: load_cbo(this.value);',
8839
            ]
8840
        );
8841
8842
        $my_count = 0;
8843
        foreach ($arrHide as $key => $value) {
8844
            if ($my_count != 0) {
8845
                // The LP name is also the first section and is not in the same charset like the other sections.
8846
                $value['value'] = Security::remove_XSS($value['value']);
8847
                $parent_select->addOption(
8848
                    $value['value'],
8849
                    $key,
8850
                    'style="padding-left:'.$value['padding'].'px;"'
8851
                );
8852
            } else {
8853
                $value['value'] = Security::remove_XSS($value['value']);
8854
                $parent_select->addOption(
8855
                    $value['value'],
8856
                    $key,
8857
                    'style="padding-left:'.$value['padding'].'px;"'
8858
                );
8859
            }
8860
            $my_count++;
8861
        }
8862
8863
        if (!empty($id)) {
8864
            $parent_select->setSelected($parent);
8865
        } else {
8866
            $parent_item_id = Session::read('parent_item_id', 0);
8867
            $parent_select->setSelected($parent_item_id);
8868
        }
8869
8870
        if (is_array($arrLP)) {
8871
            reset($arrLP);
8872
        }
8873
8874
        $arrHide = [];
8875
        $s_selected_position = null;
8876
8877
        // POSITION
8878
        $lastPosition = null;
8879
8880
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8881
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8882
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8883
            ) {
8884
                if ((isset($extra_info['previous_item_id']) &&
8885
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
8886
                ) {
8887
                    $s_selected_position = $arrLP[$i]['id'];
8888
                }
8889
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8890
            }
8891
            $lastPosition = $arrLP[$i]['id'];
8892
        }
8893
8894
        if (empty($s_selected_position)) {
8895
            $s_selected_position = $lastPosition;
8896
        }
8897
8898
        $position = $form->addSelect(
8899
            'previous',
8900
            get_lang('Position'),
8901
            [],
8902
            ['id' => 'previous']
8903
        );
8904
        $position->addOption(get_lang('FirstPosition'), 0);
8905
8906
        foreach ($arrHide as $key => $value) {
8907
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8908
            $position->addOption(
8909
                $value['value'],
8910
                $key,
8911
                'style="padding-left:'.$padding.'px;"'
8912
            );
8913
        }
8914
        $position->setSelected($s_selected_position);
8915
8916
        if (is_array($arrLP)) {
8917
            reset($arrLP);
8918
        }
8919
8920
        if ($action != 'move') {
8921
            $id_prerequisite = 0;
8922
            if (is_array($arrLP)) {
8923
                foreach ($arrLP as $key => $value) {
8924
                    if ($value['id'] == $id) {
8925
                        $id_prerequisite = $value['prerequisite'];
8926
                        break;
8927
                    }
8928
                }
8929
            }
8930
8931
            $arrHide = [];
8932
            for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8933
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8934
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8935
                ) {
8936
                    if (isset($extra_info['previous_item_id']) &&
8937
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8938
                    ) {
8939
                        $s_selected_position = $arrLP[$i]['id'];
8940
                    } elseif ($action == 'add') {
8941
                        $s_selected_position = $arrLP[$i]['id'];
8942
                    }
8943
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8944
                }
8945
            }
8946
8947
            if (!$no_display_add) {
8948
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8949
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8950
                if ($extra_info == 'new' || $item_type == TOOL_DOCUMENT ||
8951
                    $item_type == TOOL_LP_FINAL_ITEM || $edit == 'true'
8952
                ) {
8953
                    if (isset($_POST['content'])) {
8954
                        $content = stripslashes($_POST['content']);
8955
                    } elseif (is_array($extra_info)) {
8956
                        //If it's an html document or a text file
8957
                        if (!$no_display_edit_textarea) {
8958
                            $content = $this->display_document(
8959
                                $extra_info['path'],
8960
                                false,
8961
                                false
8962
                            );
8963
                        }
8964
                    } elseif (is_numeric($extra_info)) {
8965
                        $content = $this->display_document(
8966
                            $extra_info,
8967
                            false,
8968
                            false
8969
                        );
8970
                    } else {
8971
                        $content = '';
8972
                    }
8973
8974
                    if (!$no_display_edit_textarea) {
8975
                        // We need to calculate here some specific settings for the online editor.
8976
                        // The calculated settings work for documents in the Documents tool
8977
                        // (on the root or in subfolders).
8978
                        // For documents in native scorm packages it is unclear whether the
8979
                        // online editor should be activated or not.
8980
8981
                        // A new document, it is in the root of the repository.
8982
                        $relative_path = '';
8983
                        $relative_prefix = '';
8984
                        if (is_array($extra_info) && $extra_info != 'new') {
8985
                            // The document already exists. Whe have to determine its relative path towards the repository root.
8986
                            $relative_path = explode('/', $extra_info['dir']);
8987
                            $cnt = count($relative_path) - 2;
8988
                            if ($cnt < 0) {
8989
                                $cnt = 0;
8990
                            }
8991
                            $relative_prefix = str_repeat('../', $cnt);
8992
                            $relative_path = array_slice($relative_path, 1, $cnt);
8993
                            $relative_path = implode('/', $relative_path);
8994
                            if (strlen($relative_path) > 0) {
8995
                                $relative_path = $relative_path.'/';
8996
                            }
8997
                        } else {
8998
                            $result = $this->generate_lp_folder($_course);
8999
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
9000
                            $relative_prefix = '../../';
9001
                        }
9002
9003
                        $editor_config = [
9004
                            'ToolbarSet' => 'LearningPathDocuments',
9005
                            'Width' => '100%',
9006
                            'Height' => '500',
9007
                            'FullPage' => true,
9008
                            'CreateDocumentDir' => $relative_prefix,
9009
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
9010
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
9011
                        ];
9012
9013
                        if ($_GET['action'] == 'add_item') {
9014
                            $class = 'add';
9015
                            $text = get_lang('LPCreateDocument');
9016
                        } else {
9017
                            if ($_GET['action'] == 'edit_item') {
9018
                                $class = 'save';
9019
                                $text = get_lang('SaveDocument');
9020
                            }
9021
                        }
9022
9023
                        $form->addButtonSave($text, 'submit_button');
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $text does not seem to be defined for all execution paths leading up to this point.
Loading history...
9024
                        $renderer = $form->defaultRenderer();
9025
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
9026
                        $form->addElement('html', '<div class="editor-lp">');
9027
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
9028
                        $form->addElement('html', '</div>');
9029
                        $defaults['content_lp'] = $content;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $content does not seem to be defined for all execution paths leading up to this point.
Loading history...
9030
                    }
9031
                } elseif (is_numeric($extra_info)) {
9032
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9033
9034
                    $return = $this->display_document($extra_info, true, true, true);
9035
                    $form->addElement('html', $return);
9036
                }
9037
            }
9038
        }
9039
        if (isset($extra_info['item_type']) &&
9040
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
9041
        ) {
9042
            $parent_select->freeze();
9043
            $position->freeze();
9044
        }
9045
9046
        if ($action == 'move') {
9047
            $form->addElement('hidden', 'title', $item_title);
9048
            $form->addElement('hidden', 'description', $item_description);
9049
        }
9050
        if (is_numeric($extra_info)) {
9051
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9052
            $form->addElement('hidden', 'path', $extra_info);
9053
        } elseif (is_array($extra_info)) {
9054
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9055
            $form->addElement('hidden', 'path', $extra_info['path']);
9056
        }
9057
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
9058
        $form->addElement('hidden', 'post_time', time());
9059
        $form->setDefaults($defaults);
9060
9061
        return $form->returnForm();
9062
    }
9063
9064
    /**
9065
     * Return HTML form to add/edit a link item.
9066
     *
9067
     * @param string $action (add/edit)
9068
     * @param int    $id Item ID if exists
9069
     * @param mixed  $extra_info
9070
     *
9071
     * @return string HTML form
9072
     * @throws Exception
9073
     * @throws HTML_QuickForm_Error
9074
     * @throws \Doctrine\DBAL\DBALException
9075
     */
9076
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
9077
    {
9078
        $course_id = api_get_course_int_id();
9079
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9080
        $tbl_link = Database::get_course_table(TABLE_LINK);
9081
9082
        if ($id != 0 && is_array($extra_info)) {
9083
            $item_title = stripslashes($extra_info['title']);
9084
            $item_description = stripslashes($extra_info['description']);
9085
            $item_url = stripslashes($extra_info['url']);
9086
        } elseif (is_numeric($extra_info)) {
9087
            $extra_info = intval($extra_info);
9088
            $sql = "SELECT title, description, url FROM ".$tbl_link."
9089
                    WHERE c_id = ".$course_id." AND id = ".$extra_info;
9090
            $result = Database::query($sql);
9091
            $row = Database::fetch_array($result);
9092
            $item_title = $row['title'];
9093
            $item_description = $row['description'];
9094
            $item_url = $row['url'];
9095
        } else {
9096
            $item_title = '';
9097
            $item_description = '';
9098
            $item_url = '';
9099
        }
9100
9101
        $form = new FormValidator(
9102
            'edit_link',
9103
            'POST',
9104
            $this->getCurrentBuildingModeURL()
9105
        );
9106
        $defaults = [];
9107
        if ($id != 0 && is_array($extra_info)) {
9108
            $parent = $extra_info['parent_item_id'];
9109
        } else {
9110
            $parent = 0;
9111
        }
9112
9113
        $sql = "SELECT * FROM $tbl_lp_item
9114
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9115
        $result = Database::query($sql);
9116
        $arrLP = [];
9117
9118
        while ($row = Database::fetch_array($result)) {
9119
            $arrLP[] = [
9120
                'id' => $row['id'],
9121
                'item_type' => $row['item_type'],
9122
                'title' => $row['title'],
9123
                'path' => $row['path'],
9124
                'description' => $row['description'],
9125
                'parent_item_id' => $row['parent_item_id'],
9126
                'previous_item_id' => $row['previous_item_id'],
9127
                'next_item_id' => $row['next_item_id'],
9128
                'display_order' => $row['display_order'],
9129
                'max_score' => $row['max_score'],
9130
                'min_score' => $row['min_score'],
9131
                'mastery_score' => $row['mastery_score'],
9132
                'prerequisite' => $row['prerequisite'],
9133
            ];
9134
        }
9135
9136
        $this->tree_array($arrLP);
9137
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9138
        unset($this->arrMenu);
9139
9140
        if ($action == 'add') {
9141
            $legend = get_lang('CreateTheLink');
9142
        } elseif ($action == 'move') {
9143
            $legend = get_lang('MoveCurrentLink');
9144
        } else {
9145
            $legend = get_lang('EditCurrentLink');
9146
        }
9147
9148
        $form->addHeader($legend);
9149
9150
        if ($action != 'move') {
9151
            $form->addText('title', get_lang('Title'), true, ['class' => 'learnpath_item_form']);
9152
            $defaults['title'] = $item_title;
9153
        }
9154
9155
        $selectParent = $form->addSelect(
9156
            'parent',
9157
            get_lang('Parent'),
9158
            [],
9159
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9160
        );
9161
        $selectParent->addOption($this->name, 0);
9162
        $arrHide = [
9163
            $id,
9164
        ];
9165
9166
        $parent_item_id = Session::read('parent_item_id', 0);
9167
9168
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
9169
            if ($action != 'add') {
9170
                if (
9171
                    ($arrLP[$i]['item_type'] == 'dir') &&
9172
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9173
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9174
                ) {
9175
                    $selectParent->addOption(
9176
                        $arrLP[$i]['title'],
9177
                        $arrLP[$i]['id'],
9178
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9179
                    );
9180
9181
                    if ($parent == $arrLP[$i]['id']) {
9182
                        $selectParent->setSelected($arrLP[$i]['id']);
9183
                    }
9184
                } else {
9185
                    $arrHide[] = $arrLP[$i]['id'];
9186
                }
9187
            } else {
9188
                if ($arrLP[$i]['item_type'] == 'dir') {
9189
                    $selectParent->addOption(
9190
                        $arrLP[$i]['title'],
9191
                        $arrLP[$i]['id'],
9192
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9193
                    );
9194
9195
                    if ($parent_item_id == $arrLP[$i]['id']) {
9196
                        $selectParent->setSelected($arrLP[$i]['id']);
9197
                    }
9198
                }
9199
            }
9200
        }
9201
9202
        if (is_array($arrLP)) {
9203
            reset($arrLP);
9204
        }
9205
9206
        $selectPrevious = $form->addSelect(
9207
            'previous',
9208
            get_lang('Position'),
9209
            [],
9210
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9211
        );
9212
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
9213
9214
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
9215
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9216
                $selectPrevious->addOption(
9217
                    $arrLP[$i]['title'],
9218
                    $arrLP[$i]['id']
9219
                );
9220
9221
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9222
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9223
                } elseif ($action == 'add') {
9224
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9225
                }
9226
            }
9227
        }
9228
9229
        if ($action != 'move') {
9230
            $urlAttributes = ['class' => 'learnpath_item_form'];
9231
9232
            if (is_numeric($extra_info)) {
9233
                $urlAttributes['disabled'] = 'disabled';
9234
            }
9235
9236
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
9237
            $defaults['url'] = $item_url;
9238
9239
            $id_prerequisite = 0;
9240
            if (is_array($arrLP)) {
9241
                foreach ($arrLP as $key => $value) {
9242
                    if ($value['id'] == $id) {
9243
                        $id_prerequisite = $value['prerequisite'];
9244
                        break;
9245
                    }
9246
                }
9247
            }
9248
9249
            $arrHide = [];
9250
            for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
9251
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9252
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9253
                        $s_selected_position = $arrLP[$i]['id'];
9254
                    } elseif ($action == 'add') {
9255
                        $s_selected_position = 0;
9256
                    }
9257
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9258
                }
9259
            }
9260
        }
9261
9262
        if ($action == 'add') {
9263
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
9264
        } else {
9265
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
9266
        }
9267
9268
        if ($action == 'move') {
9269
            $form->addHidden('title', $item_title);
9270
            $form->addHidden('description', $item_description);
9271
        }
9272
9273
        if (is_numeric($extra_info)) {
9274
            $form->addHidden('path', $extra_info);
9275
        } elseif (is_array($extra_info)) {
9276
            $form->addHidden('path', $extra_info['path']);
9277
        }
9278
        $form->addHidden('type', TOOL_LINK);
9279
        $form->addHidden('post_time', time());
9280
        $form->setDefaults($defaults);
9281
9282
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9283
    }
9284
9285
    /**
9286
     * Return HTML form to add/edit a student publication (work).
9287
     *
9288
     * @param string $action
9289
     * @param    int    Item ID if already exists
9290
     * @param string $extra_info
9291
     *
9292
     * @return string HTML form
9293
     * @throws Exception
9294
     * @throws \Doctrine\DBAL\DBALException
9295
     */
9296
    public function display_student_publication_form(
9297
        $action = 'add',
9298
        $id = 0,
9299
        $extra_info = ''
9300
    ) {
9301
        $course_id = api_get_course_int_id();
9302
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9303
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9304
9305
        if ($id != 0 && is_array($extra_info)) {
9306
            $item_title = stripslashes($extra_info['title']);
9307
            $item_description = stripslashes($extra_info['description']);
9308
        } elseif (is_numeric($extra_info)) {
9309
            $extra_info = intval($extra_info);
9310
            $sql = "SELECT title, description
9311
                    FROM $tbl_publication
9312
                    WHERE c_id = $course_id AND id = ".$extra_info;
9313
9314
            $result = Database::query($sql);
9315
            $row = Database::fetch_array($result);
9316
9317
            $item_title = $row['title'];
9318
        } else {
9319
            $item_title = get_lang('Student_publication');
9320
        }
9321
9322
        if ($id != 0 && is_array($extra_info)) {
9323
            $parent = $extra_info['parent_item_id'];
9324
        } else {
9325
            $parent = 0;
9326
        }
9327
9328
        $sql = "SELECT * FROM $tbl_lp_item 
9329
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9330
        $result = Database::query($sql);
9331
        $arrLP = [];
9332
9333
        while ($row = Database::fetch_array($result)) {
9334
            $arrLP[] = [
9335
                'id' => $row['iid'],
9336
                'item_type' => $row['item_type'],
9337
                'title' => $row['title'],
9338
                'path' => $row['path'],
9339
                'description' => $row['description'],
9340
                'parent_item_id' => $row['parent_item_id'],
9341
                'previous_item_id' => $row['previous_item_id'],
9342
                'next_item_id' => $row['next_item_id'],
9343
                'display_order' => $row['display_order'],
9344
                'max_score' => $row['max_score'],
9345
                'min_score' => $row['min_score'],
9346
                'mastery_score' => $row['mastery_score'],
9347
                'prerequisite' => $row['prerequisite'],
9348
            ];
9349
        }
9350
9351
        $this->tree_array($arrLP);
9352
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9353
        unset($this->arrMenu);
9354
9355
        $form = new FormValidator('frm_student_publication', 'post', '#');
9356
9357
        if ($action == 'add') {
9358
            $form->addHeader(get_lang('Student_publication'));
9359
        } elseif ($action == 'move') {
9360
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
9361
        } else {
9362
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
9363
        }
9364
9365
        if ($action != 'move') {
9366
            $form->addText(
9367
                'title',
9368
                get_lang('Title'),
9369
                true,
9370
                ['class' => 'learnpath_item_form', 'id' => 'idTitle']
9371
            );
9372
        }
9373
9374
        $parentSelect = $form->addSelect(
9375
            'parent',
9376
            get_lang('Parent'),
9377
            ['0' => $this->name],
9378
            [
9379
                'onchange' => 'javascript: load_cbo(this.value);',
9380
                'class' => 'learnpath_item_form',
9381
                'id' => 'idParent',
9382
            ]
9383
        );
9384
9385
        $arrHide = [$id];
9386
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
9387
            if ($action != 'add') {
9388
                if (
9389
                    ($arrLP[$i]['item_type'] == 'dir') &&
9390
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9391
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9392
                ) {
9393
                    $parentSelect->addOption(
9394
                        $arrLP[$i]['title'],
9395
                        $arrLP[$i]['id'],
9396
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9397
                    );
9398
9399
                    if ($parent == $arrLP[$i]['id']) {
9400
                        $parentSelect->setSelected($arrLP[$i]['id']);
9401
                    }
9402
                } else {
9403
                    $arrHide[] = $arrLP[$i]['id'];
9404
                }
9405
            } else {
9406
                if ($arrLP[$i]['item_type'] == 'dir') {
9407
                    $parentSelect->addOption(
9408
                        $arrLP[$i]['title'],
9409
                        $arrLP[$i]['id'],
9410
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9411
                    );
9412
9413
                    if ($parent == $arrLP[$i]['id']) {
9414
                        $parentSelect->setSelected($arrLP[$i]['id']);
9415
                    }
9416
                }
9417
            }
9418
        }
9419
9420
        if (is_array($arrLP)) {
9421
            reset($arrLP);
9422
        }
9423
9424
        $previousSelect = $form->addSelect(
9425
            'previous',
9426
            get_lang('Position'),
9427
            ['0' => get_lang('FirstPosition')],
9428
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9429
        );
9430
9431
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
9432
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9433
                $previousSelect->addOption(
9434
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9435
                    $arrLP[$i]['id']
9436
                );
9437
9438
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9439
                    $previousSelect->setSelected($arrLP[$i]['id']);
9440
                } elseif ($action == 'add') {
9441
                    $previousSelect->setSelected($arrLP[$i]['id']);
9442
                }
9443
            }
9444
        }
9445
9446
        if ($action != 'move') {
9447
            $id_prerequisite = 0;
9448
            if (is_array($arrLP)) {
9449
                foreach ($arrLP as $key => $value) {
9450
                    if ($value['id'] == $id) {
9451
                        $id_prerequisite = $value['prerequisite'];
9452
                        break;
9453
                    }
9454
                }
9455
            }
9456
            $arrHide = [];
9457
            for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
9458
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9459
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9460
                        $s_selected_position = $arrLP[$i]['id'];
9461
                    } elseif ($action == 'add') {
9462
                        $s_selected_position = 0;
9463
                    }
9464
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9465
                }
9466
            }
9467
        }
9468
9469
        if ($action == 'add') {
9470
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
9471
        } else {
9472
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
9473
        }
9474
9475
        if ($action == 'move') {
9476
            $form->addHidden('title', $item_title);
9477
            $form->addHidden('description', $item_description);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $item_description does not seem to be defined for all execution paths leading up to this point.
Loading history...
9478
        }
9479
9480
        if (is_numeric($extra_info)) {
9481
            $form->addHidden('path', $extra_info);
9482
        } elseif (is_array($extra_info)) {
9483
            $form->addHidden('path', $extra_info['path']);
9484
        }
9485
9486
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9487
        $form->addHidden('post_time', time());
9488
        $form->setDefaults(['title' => $item_title]);
9489
9490
        $return = '<div class="sectioncomment">';
9491
        $return .= $form->returnForm();
9492
        $return .= '</div>';
9493
9494
        return $return;
9495
    }
9496
9497
    /**
9498
     * Displays the menu for manipulating a step.
9499
     *
9500
     * @param id     $item_id
9501
     * @param string $item_type
9502
     *
9503
     * @return string
9504
     * @throws \Doctrine\DBAL\DBALException
9505
     */
9506
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9507
    {
9508
        $_course = api_get_course_info();
9509
        $course_code = api_get_course_id();
9510
        $return = '<div class="actions">';
9511
        switch ($item_type) {
9512
            case 'dir':
9513
                // Commented the message cause should not show it.
9514
                //$lang = get_lang('TitleManipulateChapter');
9515
                break;
9516
            case TOOL_LP_FINAL_ITEM:
9517
            case TOOL_DOCUMENT:
9518
                // Commented the message cause should not show it.
9519
                //$lang = get_lang('TitleManipulateDocument');
9520
                break;
9521
            case TOOL_LINK:
9522
            case 'link':
9523
                // Commented the message cause should not show it.
9524
                //$lang = get_lang('TitleManipulateLink');
9525
                break;
9526
            case TOOL_QUIZ:
9527
                // Commented the message cause should not show it.
9528
                //$lang = get_lang('TitleManipulateQuiz');
9529
                break;
9530
            case TOOL_STUDENTPUBLICATION:
9531
                // Commented the message cause should not show it.
9532
                //$lang = get_lang('TitleManipulateStudentPublication');
9533
                break;
9534
        }
9535
9536
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9537
        $item_id = intval($item_id);
9538
        $sql = "SELECT * FROM $tbl_lp_item 
9539
                WHERE iid = ".$item_id;
9540
        $result = Database::query($sql);
9541
        $row = Database::fetch_assoc($result);
9542
9543
        $audio_player = null;
9544
        // We display an audio player if needed.
9545
        if (!empty($row['audio'])) {
9546
            $audio_player .= '<div class="lp_mediaplayer" id="container">
9547
                              <a href="http://www.macromedia.com/go/getflashplayer">Get the Flash Player</a> to see this player.
9548
                              </div>';
9549
            $audio_player .= '<script type="text/javascript" src="../inc/lib/mediaplayer/swfobject.js"></script>';
9550
            $audio_player .= '<script>
9551
                var s1 = new SWFObject("../inc/lib/mediaplayer/player.swf","ply","250","20","9","#FFFFFF");
9552
                s1.addParam("allowscriptaccess","always");
9553
                s1.addParam("flashvars","file=../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'].'&autostart=true");
9554
                s1.write("container");
9555
            </script>';
9556
        }
9557
9558
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9559
9560
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9561
            $return .= Display::url(
9562
                Display::return_icon(
9563
                    'edit.png',
9564
                    get_lang('Edit'),
9565
                    [],
9566
                    ICON_SIZE_SMALL
9567
                ),
9568
                $url.'&action=edit_item&path_item='.$row['path']
9569
            );
9570
9571
            $return .= Display::url(
9572
                Display::return_icon(
9573
                    'move.png',
9574
                    get_lang('Move'),
9575
                    [],
9576
                    ICON_SIZE_SMALL
9577
                ),
9578
                $url.'&action=move_item'
9579
            );
9580
        }
9581
9582
        // Commented for now as prerequisites cannot be added to chapters.
9583
        if ($item_type != 'dir') {
9584
            $return .= Display::url(
9585
                Display::return_icon(
9586
                    'accept.png',
9587
                    get_lang('LearnpathPrerequisites'),
9588
                    [],
9589
                    ICON_SIZE_SMALL
9590
                ),
9591
                $url.'&action=edit_item_prereq'
9592
            );
9593
        }
9594
        $return .= Display::url(
9595
            Display::return_icon(
9596
                'delete.png',
9597
                get_lang('Delete'),
9598
                [],
9599
                ICON_SIZE_SMALL
9600
            ),
9601
            $url.'&action=delete_item'
9602
        );
9603
9604
        if ($item_type == TOOL_HOTPOTATOES) {
9605
            $document_data = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9606
            $return .= get_lang('File').': '.$document_data['absolute_path_from_document'];
9607
        }
9608
9609
        if ($item_type == TOOL_DOCUMENT || $item_type == TOOL_LP_FINAL_ITEM) {
9610
            $document_data = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9611
            $return .= get_lang('File').': '.$document_data['absolute_path_from_document'];
9612
        }
9613
9614
        $return .= '</div>';
9615
9616
        if (!empty($audio_player)) {
9617
            $return .= '<br />'.$audio_player;
9618
        }
9619
9620
        return $return;
9621
    }
9622
9623
    /**
9624
     * Creates the javascript needed for filling up the checkboxes without page reload.
9625
     *
9626
     * @return string
9627
     * @throws \Doctrine\DBAL\DBALException
9628
     */
9629
    public function get_js_dropdown_array()
9630
    {
9631
        $course_id = api_get_course_int_id();
9632
        $return = 'var child_name = new Array();'."\n";
9633
        $return .= 'var child_value = new Array();'."\n\n";
9634
        $return .= 'child_name[0] = new Array();'."\n";
9635
        $return .= 'child_value[0] = new Array();'."\n\n";
9636
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9637
        $sql = "SELECT * FROM ".$tbl_lp_item."
9638
                WHERE 
9639
                    c_id = $course_id AND 
9640
                    lp_id = ".$this->lp_id." AND 
9641
                    parent_item_id = 0
9642
                ORDER BY display_order ASC";
9643
        $res_zero = Database::query($sql);
9644
        $i = 0;
9645
9646
        while ($row_zero = Database::fetch_array($res_zero)) {
9647
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
9648
                if ($row_zero['item_type'] == TOOL_QUIZ) {
9649
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
9650
                }
9651
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
9652
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
9653
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
9654
            }
9655
        }
9656
        $return .= "\n";
9657
        $sql = "SELECT * FROM $tbl_lp_item
9658
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9659
        $res = Database::query($sql);
9660
        while ($row = Database::fetch_array($res)) {
9661
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
9662
                           WHERE
9663
                                c_id = ".$course_id." AND
9664
                                parent_item_id = ".$row['iid']."
9665
                           ORDER BY display_order ASC";
9666
            $res_parent = Database::query($sql_parent);
9667
            $i = 0;
9668
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
9669
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
9670
9671
            while ($row_parent = Database::fetch_array($res_parent)) {
9672
                $js_var = json_encode(get_lang('After').' '.$row_parent['title']);
9673
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
9674
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
9675
            }
9676
            $return .= "\n";
9677
        }
9678
9679
        return $return;
9680
    }
9681
9682
    /**
9683
     * Display the form to allow moving an item.
9684
     *
9685
     * @param int $item_id Item ID
9686
     *
9687
     * @return string HTML form
9688
     * @throws Exception
9689
     * @throws HTML_QuickForm_Error
9690
     * @throws \Doctrine\DBAL\DBALException
9691
     */
9692
    public function display_move_item($item_id)
9693
    {
9694
        $return = '';
9695
        if (is_numeric($item_id)) {
9696
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9697
9698
            $sql = "SELECT * FROM $tbl_lp_item
9699
                    WHERE iid = $item_id";
9700
            $res = Database::query($sql);
9701
            $row = Database::fetch_array($res);
9702
9703
            switch ($row['item_type']) {
9704
                case 'dir':
9705
                case 'asset':
9706
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9707
                    $return .= $this->display_item_form(
9708
                        $row['item_type'],
9709
                        get_lang('MoveCurrentChapter'),
9710
                        'move',
9711
                        $item_id,
9712
                        $row
0 ignored issues
show
Bug introduced by
It seems like $row can also be of type array; however, parameter $extra_info of learnpath::display_item_form() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

9712
                        /** @scrutinizer ignore-type */ $row
Loading history...
9713
                    );
9714
                    break;
9715
                case TOOL_DOCUMENT:
9716
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9717
                    $return .= $this->display_document_form('move', $item_id, $row);
9718
                    break;
9719
                case TOOL_LINK:
9720
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9721
                    $return .= $this->display_link_form('move', $item_id, $row);
9722
                    break;
9723
                case TOOL_HOTPOTATOES:
9724
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9725
                    $return .= $this->display_link_form('move', $item_id, $row);
9726
                    break;
9727
                case TOOL_QUIZ:
9728
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9729
                    $return .= $this->display_quiz_form('move', $item_id, $row);
9730
                    break;
9731
                case TOOL_STUDENTPUBLICATION:
9732
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9733
                    $return .= $this->display_student_publication_form('move', $item_id, $row);
0 ignored issues
show
Bug introduced by
It seems like $row can also be of type array; however, parameter $extra_info of learnpath::display_student_publication_form() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

9733
                    $return .= $this->display_student_publication_form('move', $item_id, /** @scrutinizer ignore-type */ $row);
Loading history...
9734
                    break;
9735
                case TOOL_FORUM:
9736
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9737
                    $return .= $this->display_forum_form('move', $item_id, $row);
0 ignored issues
show
Bug introduced by
It seems like $row can also be of type array; however, parameter $extra_info of learnpath::display_forum_form() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

9737
                    $return .= $this->display_forum_form('move', $item_id, /** @scrutinizer ignore-type */ $row);
Loading history...
9738
                    break;
9739
                case TOOL_THREAD:
9740
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9741
                    $return .= $this->display_forum_form('move', $item_id, $row);
9742
                    break;
9743
            }
9744
        }
9745
9746
        return $return;
9747
    }
9748
9749
    /**
9750
     * Displays a basic form on the overview page for changing the item title and the item description.
9751
     *
9752
     * @param string $item_type
9753
     * @param string $title
9754
     * @param array  $data
9755
     *
9756
     * @return string
9757
     */
9758
    public function display_item_small_form($item_type, $title = '', $data = [])
9759
    {
9760
        $url = api_get_self().'?'.api_get_cidreq().'&action=edit_item&lp_id='.$this->lp_id;
9761
        $form = new FormValidator('small_form', 'post', $url);
9762
        $form->addElement('header', $title);
9763
        $form->addElement('text', 'title', get_lang('Title'));
9764
        $form->addButtonSave(get_lang('Save'), 'submit_button');
9765
        $form->addElement('hidden', 'id', $data['id']);
9766
        $form->addElement('hidden', 'parent', $data['parent_item_id']);
9767
        $form->addElement('hidden', 'previous', $data['previous_item_id']);
9768
        $form->setDefaults(['title' => $data['title']]);
9769
9770
        return $form->toHtml();
9771
    }
9772
9773
    /**
9774
     * Return HTML form to allow prerequisites selection.
9775
     *
9776
     * @todo use FormValidator
9777
     *
9778
     * @param    int Item ID
9779
     *
9780
     * @return string HTML form
9781
     * @throws \Doctrine\DBAL\DBALException
9782
     */
9783
    public function display_item_prerequisites_form($item_id = 0)
9784
    {
9785
        $course_id = api_get_course_int_id();
9786
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9787
        $item_id = intval($item_id);
9788
        /* Current prerequisite */
9789
        $sql = "SELECT * FROM $tbl_lp_item
9790
                WHERE iid = $item_id";
9791
        $result = Database::query($sql);
9792
        $row = Database::fetch_array($result);
9793
        $prerequisiteId = $row['prerequisite'];
9794
        $return = '<legend>';
9795
        $return .= get_lang('AddEditPrerequisites');
9796
        $return .= '</legend>';
9797
        $return .= '<form method="POST">';
9798
        $return .= '<div class="table-responsive">';
9799
        $return .= '<table class="table table-hover">';
9800
        $return .= '<thead>';
9801
        $return .= '<tr>';
9802
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
9803
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
9804
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
9805
        $return .= '</tr>';
9806
        $return .= '</thead>';
9807
9808
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
9809
        $return .= '<tbody>';
9810
        $return .= '<tr>';
9811
        $return .= '<td colspan="3">';
9812
        $return .= '<div class="radio learnpath"><label for="idNone">';
9813
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
9814
        $return .= get_lang('None').'</label>';
9815
        $return .= '</div>';
9816
        $return .= '</tr>';
9817
9818
        $sql = "SELECT * FROM $tbl_lp_item
9819
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9820
        $result = Database::query($sql);
9821
        $arrLP = [];
9822
9823
        $selectedMinScore = [];
9824
        $selectedMaxScore = [];
9825
        while ($row = Database::fetch_array($result)) {
9826
            if ($row['id'] == $item_id) {
9827
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
9828
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
9829
            }
9830
            $arrLP[] = [
9831
                'id' => $row['iid'],
9832
                'item_type' => $row['item_type'],
9833
                'title' => $row['title'],
9834
                'ref' => $row['ref'],
9835
                'description' => $row['description'],
9836
                'parent_item_id' => $row['parent_item_id'],
9837
                'previous_item_id' => $row['previous_item_id'],
9838
                'next_item_id' => $row['next_item_id'],
9839
                'max_score' => $row['max_score'],
9840
                'min_score' => $row['min_score'],
9841
                'mastery_score' => $row['mastery_score'],
9842
                'prerequisite' => $row['prerequisite'],
9843
                'next_item_id' => $row['next_item_id'],
9844
                'display_order' => $row['display_order'],
9845
                'prerequisite_min_score' => $row['prerequisite_min_score'],
9846
                'prerequisite_max_score' => $row['prerequisite_max_score'],
9847
            ];
9848
        }
9849
9850
        $this->tree_array($arrLP);
9851
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9852
        unset($this->arrMenu);
9853
9854
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
9855
            $item = $arrLP[$i];
9856
9857
            if ($item['id'] == $item_id) {
9858
                break;
9859
            }
9860
9861
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
9862
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
9863
9864
            $return .= '<tr>';
9865
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
9866
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
9867
            $return .= '<label for="id'.$item['id'].'">';
9868
            $return .= '<input'.(in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '').($item['item_type'] == 'dir' ? ' disabled="disabled" ' : ' ').'id="id'.$item['id'].'" name="prerequisites"  type="radio" value="'.$item['id'].'" />';
9869
9870
            $icon_name = str_replace(' ', '', $item['item_type']);
9871
9872
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
9873
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
9874
            } else {
9875
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
9876
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
9877
                } else {
9878
                    $return .= Display::return_icon('folder_document.png');
9879
                }
9880
            }
9881
9882
            $return .= $item['title'].'</label>';
9883
            $return .= '</div>';
9884
            $return .= '</td>';
9885
9886
            if ($item['item_type'] == TOOL_QUIZ) {
9887
                // lets update max_score Quiz information depending of the Quiz Advanced properties
9888
                $tmp_obj_lp_item = new LpItem($course_id, $item['id']);
9889
                $tmp_obj_exercice = new Exercise($course_id);
9890
                $tmp_obj_exercice->read($tmp_obj_lp_item->path);
9891
                $tmp_obj_lp_item->max_score = $tmp_obj_exercice->get_max_score();
9892
                $tmp_obj_lp_item->update();
9893
                $item['max_score'] = $tmp_obj_lp_item->max_score;
9894
9895
                $return .= '<td>';
9896
                $return .= '<input class="form-control" size="4" maxlength="3" name="min_'.$item['id'].'" type="number" min="0" step="1" max="'.$item['max_score'].'" value="'.$selectedMinScoreValue.'" />';
9897
                $return .= '</td>';
9898
                $return .= '<td>';
9899
                $return .= '<input class="form-control" size="4" maxlength="3" readonly name="max_'.$item['id'].'" type="number" min="0" step="1" max="'.$item['max_score'].'" value="'.$selectedMaxScoreValue.'" />';
9900
                $return .= '</td>';
9901
            }
9902
9903
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
9904
                $return .= '<td>';
9905
                $return .= '<input size="4" maxlength="3" name="min_'.$item['id'].'" type="number" min="0" step="1" max="'.$item['max_score'].'" value="'.$selectedMinScoreValue.'" />';
9906
                $return .= '</td>';
9907
                $return .= '<td>';
9908
                $return .= '<input size="4" maxlength="3" name="max_'.$item['id'].'" readonly type="number" min="0" step="1" max="'.$item['max_score'].'"  value="'.$selectedMaxScoreValue.'" />';
9909
                $return .= '</td>';
9910
            }
9911
            $return .= '</tr>';
9912
        }
9913
        $return .= '<tr>';
9914
        $return .= '</tr>';
9915
        $return .= '</tbody>';
9916
        $return .= '</table>';
9917
        $return .= '</div>';
9918
        $return .= '<div class="form-group">';
9919
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.get_lang('ModifyPrerequisites').'</button>';
9920
        $return .= '</form>';
9921
9922
        return $return;
9923
    }
9924
9925
    /**
9926
     * Return HTML list to allow prerequisites selection for lp.
9927
     *
9928
     * @return string HTML form
9929
     * @throws \Doctrine\DBAL\DBALException
9930
     */
9931
    public function display_lp_prerequisites_list()
9932
    {
9933
        $course_id = api_get_course_int_id();
9934
        $lp_id = $this->lp_id;
9935
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
9936
9937
        // get current prerequisite
9938
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
9939
        $result = Database::query($sql);
9940
        $row = Database::fetch_array($result);
9941
        $prerequisiteId = $row['prerequisite'];
9942
        $session_id = api_get_session_id();
9943
        $session_condition = api_get_session_condition($session_id, true, true);
9944
        $sql = "SELECT * FROM $tbl_lp
9945
                WHERE c_id = $course_id $session_condition
9946
                ORDER BY display_order ";
9947
        $rs = Database::query($sql);
9948
        $return = '';
9949
        $return .= '<select name="prerequisites" class="form-control">';
9950
        $return .= '<option value="0">'.get_lang('None').'</option>';
9951
        if (Database::num_rows($rs) > 0) {
9952
            while ($row = Database::fetch_array($rs)) {
9953
                if ($row['id'] == $lp_id) {
9954
                    continue;
9955
                }
9956
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
9957
            }
9958
        }
9959
        $return .= '</select>';
9960
9961
        return $return;
9962
    }
9963
9964
    /**
9965
     * Creates a list with all the documents in it.
9966
     *
9967
     * @param bool $showInvisibleFiles
9968
     *
9969
     * @return string
9970
     * @throws Exception
9971
     * @throws HTML_QuickForm_Error
9972
     */
9973
    public function get_documents($showInvisibleFiles = false)
9974
    {
9975
        $course_info = api_get_course_info();
9976
        $sessionId = api_get_session_id();
9977
        $documentTree = DocumentManager::get_document_preview(
9978
            $course_info,
9979
            $this->lp_id,
9980
            null,
9981
            $sessionId,
9982
            true,
9983
            null,
9984
            null,
9985
            $showInvisibleFiles,
9986
            true
9987
        );
9988
9989
        $headers = [
9990
            get_lang('Files'),
9991
            get_lang('CreateTheDocument'),
9992
            get_lang('Upload'),
9993
        ];
9994
9995
        $form = new FormValidator(
9996
            'form_upload',
9997
            'POST',
9998
            $this->getCurrentBuildingModeURL(),
9999
            '',
10000
            ['enctype' => 'multipart/form-data']
10001
        );
10002
10003
        $folders = DocumentManager::get_all_document_folders(
10004
            api_get_course_info(),
10005
            0,
10006
            true
10007
        );
10008
10009
        DocumentManager::build_directory_selector(
10010
            $folders,
10011
            '',
10012
            [],
10013
            true,
10014
            $form,
10015
            'directory_parent_id'
10016
        );
10017
10018
        $group = [
10019
            $form->createElement(
10020
                'radio',
10021
                'if_exists',
10022
                get_lang("UplWhatIfFileExists"),
10023
                get_lang('UplDoNothing'),
10024
                'nothing'
10025
            ),
10026
            $form->createElement(
10027
                'radio',
10028
                'if_exists',
10029
                null,
10030
                get_lang('UplOverwriteLong'),
10031
                'overwrite'
10032
            ),
10033
            $form->createElement(
10034
                'radio',
10035
                'if_exists',
10036
                null,
10037
                get_lang('UplRenameLong'),
10038
                'rename'
10039
            ),
10040
        ];
10041
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
10042
10043
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
10044
        $defaultFileExistsOption = 'rename';
10045
        if (!empty($fileExistsOption)) {
10046
            $defaultFileExistsOption = $fileExistsOption;
10047
        }
10048
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
10049
10050
        // Check box options
10051
        $form->addElement(
10052
            'checkbox',
10053
            'unzip',
10054
            get_lang('Options'),
10055
            get_lang('Uncompress')
10056
        );
10057
10058
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
10059
        $form->addMultipleUpload($url);
10060
        $new = $this->display_document_form('add', 0);
10061
        $tabs = Display::tabs(
10062
            $headers,
10063
            [$documentTree, $new, $form->returnForm()],
10064
            'subtab'
10065
        );
10066
10067
        return $tabs;
10068
    }
10069
10070
    /**
10071
     * Creates a list with all the exercises (quiz) in it.
10072
     *
10073
     * @return string
10074
     * @throws \Doctrine\DBAL\DBALException
10075
     */
10076
    public function get_exercises()
10077
    {
10078
        $course_id = api_get_course_int_id();
10079
        $session_id = api_get_session_id();
10080
        $userInfo = api_get_user_info();
10081
10082
        // New for hotpotatoes.
10083
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
10084
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10085
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10086
        $condition_session = api_get_session_condition($session_id, true, true);
10087
        $setting = api_get_configuration_value('show_invisible_exercise_in_lp_list');
10088
10089
        $activeCondition = ' active <> -1 ';
10090
        if ($setting) {
10091
            $activeCondition = ' active = 1 ';
10092
        }
10093
10094
        $sql_quiz = "SELECT * FROM $tbl_quiz
10095
                     WHERE c_id = $course_id AND $activeCondition $condition_session
10096
                     ORDER BY title ASC";
10097
10098
        $sql_hot = "SELECT * FROM $tbl_doc
10099
                     WHERE c_id = $course_id AND path LIKE '".$uploadPath."/%/%htm%'  $condition_session
10100
                     ORDER BY id ASC";
10101
10102
        $res_quiz = Database::query($sql_quiz);
10103
        $res_hot = Database::query($sql_hot);
10104
10105
        $return = '<ul class="lp_resource">';
10106
        $return .= '<li class="lp_resource_element">';
10107
        $return .= Display::return_icon('new_exercice.png');
10108
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10109
            get_lang('NewExercise').'</a>';
10110
        $return .= '</li>';
10111
10112
        $previewIcon = Display::return_icon(
10113
            'preview_view.png',
10114
            get_lang('Preview')
10115
        );
10116
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10117
10118
        // Display hotpotatoes
10119
        while ($row_hot = Database::fetch_array($res_hot)) {
10120
            $link = Display::url(
10121
                $previewIcon,
10122
                $exerciseUrl.'&file='.$row_hot['path'],
10123
                ['target' => '_blank']
10124
            );
10125
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10126
            $return .= '<a class="moved" href="#">';
10127
            $return .= Display::return_icon(
10128
                'move_everywhere.png',
10129
                get_lang('Move'),
10130
                [],
10131
                ICON_SIZE_TINY
10132
            );
10133
            $return .= '</a> ';
10134
            $return .= Display::return_icon('hotpotatoes_s.png');
10135
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10136
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10137
            $return .= '</li>';
10138
        }
10139
10140
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10141
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10142
            $title = strip_tags(
10143
                api_html_entity_decode($row_quiz['title'])
10144
            );
10145
10146
            $link = Display::url(
10147
                $previewIcon,
10148
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10149
                ['target' => '_blank']
10150
            );
10151
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10152
            $return .= '<a class="moved" href="#">';
10153
            $return .= Display::return_icon(
10154
                'move_everywhere.png',
10155
                get_lang('Move'),
10156
                [],
10157
                ICON_SIZE_TINY
10158
            );
10159
            $return .= '</a> ';
10160
            $return .= Display::return_icon(
10161
                'quizz_small.gif',
10162
                '',
10163
                [],
10164
                ICON_SIZE_TINY
10165
            );
10166
            $sessionStar = api_get_session_image(
10167
                $row_quiz['session_id'],
10168
                $userInfo['status']
10169
            );
10170
            $return .= '<a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id.'">'.
10171
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar.
10172
                '</a>';
10173
10174
            $return .= '</li>';
10175
        }
10176
10177
        $return .= '</ul>';
10178
10179
        return $return;
10180
    }
10181
10182
    /**
10183
     * Creates a list with all the links in it.
10184
     *
10185
     * @return string
10186
     * @throws \Doctrine\DBAL\DBALException
10187
     */
10188
    public function get_links()
10189
    {
10190
        $selfUrl = api_get_self();
10191
        $courseIdReq = api_get_cidreq();
10192
        $course = api_get_course_info();
10193
        $userInfo = api_get_user_info();
10194
10195
        $course_id = $course['real_id'];
10196
        $tbl_link = Database::get_course_table(TABLE_LINK);
10197
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10198
        $moveEverywhereIcon = Display::return_icon(
10199
            'move_everywhere.png',
10200
            get_lang('Move'),
10201
            [],
10202
            ICON_SIZE_TINY
10203
        );
10204
10205
        $session_id = api_get_session_id();
10206
        $condition_session = api_get_session_condition(
10207
            $session_id,
10208
            true,
10209
            true,
10210
            "link.session_id"
10211
        );
10212
10213
        $sql = "SELECT 
10214
                    link.id as link_id,
10215
                    link.title as link_title,
10216
                    link.session_id as link_session_id,
10217
                    link.category_id as category_id,
10218
                    link_category.category_title as category_title
10219
                FROM $tbl_link as link
10220
                LEFT JOIN $linkCategoryTable as link_category
10221
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10222
                WHERE link.c_id = ".$course_id." $condition_session
10223
                ORDER BY link_category.category_title ASC, link.title ASC";
10224
        $result = Database::query($sql);
10225
        $categorizedLinks = [];
10226
        $categories = [];
10227
10228
        while ($link = Database::fetch_array($result)) {
10229
            if (!$link['category_id']) {
10230
                $link['category_title'] = get_lang('Uncategorized');
10231
            }
10232
            $categories[$link['category_id']] = $link['category_title'];
10233
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10234
        }
10235
10236
        $linksHtmlCode =
10237
            '<script>
10238
            function toggle_tool(tool, id) {
10239
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10240
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10241
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10242
                } else {
10243
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10244
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10245
                }
10246
            }
10247
        </script>
10248
10249
        <ul class="lp_resource">
10250
            <li class="lp_resource_element">
10251
                '.Display::return_icon('linksnew.gif').'
10252
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
10253
                get_lang('LinkAdd').'
10254
                </a>
10255
            </li>';
10256
10257
        foreach ($categorizedLinks as $categoryId => $links) {
10258
            $linkNodes = null;
10259
            foreach ($links as $key => $linkInfo) {
10260
                $title = $linkInfo['link_title'];
10261
                $linkSessionId = $linkInfo['link_session_id'];
10262
10263
                $link = Display::url(
10264
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10265
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10266
                    ['target' => '_blank']
10267
                );
10268
10269
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10270
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10271
                    $linkNodes .=
10272
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10273
                        <a class="moved" href="#">'.
10274
                            $moveEverywhereIcon.
10275
                        '</a>
10276
                        '.Display::return_icon('lp_link.png').'
10277
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10278
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10279
                        Security::remove_XSS($title).$sessionStar.$link.
10280
                        '</a>
10281
                    </li>';
10282
                }
10283
            }
10284
            $linksHtmlCode .=
10285
                '<li>
10286
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10287
                    <img src="'.Display::returnIconPath('add.gif').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10288
                    align="absbottom" />
10289
                </a>
10290
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10291
            </li>
10292
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10293
        }
10294
        $linksHtmlCode .= '</ul>';
10295
10296
        return $linksHtmlCode;
10297
    }
10298
10299
    /**
10300
     * Creates a list with all the student publications in it.
10301
     *
10302
     * @return string
10303
     */
10304
    public function get_student_publications()
10305
    {
10306
        $sessionId = api_get_session_id();
10307
        $return = '<ul class="lp_resource">';
10308
        $return .= '<li class="lp_resource_element">';
10309
        $return .= Display::return_icon('works_new.gif');
10310
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10311
            get_lang('AddAssignmentPage').'</a>';
10312
        $return .= '</li>';
10313
10314
        if (empty($sessionId)) {
10315
            require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10316
            $works = getWorkListTeacher(0, 100, null, null, null);
10317
            if (!empty($works)) {
10318
                foreach ($works as $work) {
10319
                    $link = Display::url(
10320
                        Display::return_icon('preview_view.png', get_lang('Preview')),
10321
                        api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10322
                        ['target' => '_blank']
10323
                    );
10324
10325
                    $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10326
                    $return .= '<a class="moved" href="#">';
10327
                    $return .= Display::return_icon(
10328
                        'move_everywhere.png',
10329
                        get_lang('Move'),
10330
                        [],
10331
                        ICON_SIZE_TINY
10332
                    );
10333
                    $return .= '</a> ';
10334
10335
                    $return .= Display::return_icon('works.gif');
10336
                    $return .= ' <a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&file='.$work['iid'].'&lp_id='.$this->lp_id.'">'.
10337
                        Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10338
                    </a>';
10339
10340
                    $return .= '</li>';
10341
                }
10342
            }
10343
        }
10344
10345
        $return .= '</ul>';
10346
10347
        return $return;
10348
    }
10349
10350
    /**
10351
     * Creates a list with all the forums in it.
10352
     *
10353
     * @return string
10354
     */
10355
    public function get_forums()
10356
    {
10357
        require_once '../forum/forumfunction.inc.php';
10358
        require_once '../forum/forumconfig.inc.php';
10359
10360
        $forumCategories = get_forum_categories();
10361
        $forumsInNoCategory = get_forums_in_category(0);
10362
        if (!empty($forumsInNoCategory)) {
10363
            $forumCategories = array_merge(
10364
                $forumCategories,
10365
                [
10366
                    [
10367
                        'cat_id' => 0,
10368
                        'session_id' => 0,
10369
                        'visibility' => 1,
10370
                        'cat_comment' => null,
10371
                    ],
10372
                ]
10373
            );
10374
        }
10375
10376
        $forumList = get_forums();
10377
        $a_forums = [];
10378
        foreach ($forumCategories as $forumCategory) {
10379
            // The forums in this category.
10380
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10381
            if (!empty($forumsInCategory)) {
10382
                foreach ($forumList as $forum) {
10383
                    if (isset($forum['forum_category']) &&
10384
                        $forum['forum_category'] == $forumCategory['cat_id']
10385
                    ) {
10386
                        $a_forums[] = $forum;
10387
                    }
10388
                }
10389
            }
10390
        }
10391
10392
        $return = '<ul class="lp_resource">';
10393
10394
        // First add link
10395
        $return .= '<li class="lp_resource_element">';
10396
        $return .= Display::return_icon('new_forum.png');
10397
        $return .= Display::url(
10398
            get_lang('CreateANewForum'),
10399
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10400
                'action' => 'add',
10401
                'content' => 'forum',
10402
                'lp_id' => $this->lp_id,
10403
            ]),
10404
            ['title' => get_lang('CreateANewForum')]
10405
        );
10406
        $return .= '</li>';
10407
10408
        $return .= '<script>
10409
            function toggle_forum(forum_id) {
10410
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10411
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10412
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10413
                } else {
10414
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10415
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10416
                }
10417
            }
10418
        </script>';
10419
10420
        foreach ($a_forums as $forum) {
10421
            if (!empty($forum['forum_id'])) {
10422
                $link = Display::url(
10423
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10424
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10425
                    ['target' => '_blank']
10426
                );
10427
10428
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
10429
                $return .= '<a class="moved" href="#">';
10430
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10431
                $return .= ' </a>';
10432
                $return .= Display::return_icon('lp_forum.png', '', [], ICON_SIZE_TINY);
10433
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
10434
                                <img src="'.Display::returnIconPath('add.gif').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
10435
                            </a>
10436
                            <a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_FORUM.'&forum_id='.$forum['forum_id'].'&lp_id='.$this->lp_id.'" style="vertical-align:middle">'.
10437
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
10438
10439
                $return .= '</li>';
10440
10441
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
10442
                $a_threads = get_threads($forum['forum_id']);
10443
                if (is_array($a_threads)) {
10444
                    foreach ($a_threads as $thread) {
10445
                        $link = Display::url(
10446
                            Display::return_icon('preview_view.png', get_lang('Preview')),
10447
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10448
                            ['target' => '_blank']
10449
                        );
10450
10451
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10452
                        $return .= '&nbsp;<a class="moved" href="#">';
10453
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10454
                        $return .= ' </a>';
10455
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10456
                        $return .= '<a class="moved" href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_THREAD.'&thread_id='.$thread['thread_id'].'&lp_id='.$this->lp_id.'">'.
10457
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10458
                        $return .= '</li>';
10459
                    }
10460
                }
10461
                $return .= '</div>';
10462
            }
10463
        }
10464
        $return .= '</ul>';
10465
10466
        return $return;
10467
    }
10468
10469
    /**
10470
     * // TODO: The output encoding should be equal to the system encoding.
10471
     *
10472
     * Exports the learning path as a SCORM package. This is the main function that
10473
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10474
     * whole thing and returns the zip.
10475
     *
10476
     * This method needs to be called in PHP5, as it will fail with non-adequate
10477
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10478
     * you need to call it on a learnpath object.
10479
     *
10480
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10481
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10482
     * path has been modified, it should use the generic method here below.
10483
     *
10484
     * @return string Returns the zip package string, or null if error
10485
     * @throws \Doctrine\DBAL\DBALException
10486
     */
10487
    public function scorm_export()
10488
    {
10489
        $_course = api_get_course_info();
10490
        $course_id = $_course['real_id'];
10491
10492
        // Remove memory and time limits as much as possible as this might be a long process...
10493
        if (function_exists('ini_set')) {
10494
            api_set_memory_limit('256M');
10495
            ini_set('max_execution_time', 600);
10496
        }
10497
10498
        // Create the zip handler (this will remain available throughout the method).
10499
        $archive_path = api_get_path(SYS_ARCHIVE_PATH);
10500
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10501
        $temp_dir_short = uniqid();
10502
        $temp_zip_dir = $archive_path.'/'.$temp_dir_short;
10503
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10504
        $zip_folder = new PclZip($temp_zip_file);
10505
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10506
        $root_path = $main_path = api_get_path(SYS_PATH);
10507
        $files_cleanup = [];
10508
10509
        // Place to temporarily stash the zip file.
10510
        // create the temp dir if it doesn't exist
10511
        // or do a cleanup before creating the zip file.
10512
        if (!is_dir($temp_zip_dir)) {
10513
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10514
        } else {
10515
            // Cleanup: Check the temp dir for old files and delete them.
10516
            $handle = opendir($temp_zip_dir);
10517
            while (false !== ($file = readdir($handle))) {
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $dir_handle of readdir() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

10517
            while (false !== ($file = readdir(/** @scrutinizer ignore-type */ $handle))) {
Loading history...
10518
                if ($file != '.' && $file != '..') {
10519
                    unlink("$temp_zip_dir/$file");
10520
                }
10521
            }
10522
            closedir($handle);
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $dir_handle of closedir() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

10522
            closedir(/** @scrutinizer ignore-type */ $handle);
Loading history...
10523
        }
10524
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10525
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10526
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10527
        ) {
10528
            // Remove the possible . at the end of the path.
10529
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10530
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10531
            mkdir(
10532
                $dest_path_to_scorm_folder,
10533
                api_get_permissions_for_new_directories(),
10534
                true
10535
            );
10536
            copyr(
10537
                $current_course_path.'/scorm/'.$this->path,
10538
                $dest_path_to_scorm_folder,
10539
                ['imsmanifest'],
10540
                $zip_files
10541
            );
10542
        }
10543
10544
        // Build a dummy imsmanifest structure.
10545
        // Do not add to the zip yet (we still need it).
10546
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
10547
        // Aggregation Model official document, section "2.3 Content Packaging".
10548
        // We are going to build a UTF-8 encoded manifest. Later we will recode it to the desired (and supported) encoding.
10549
        $xmldoc = new DOMDocument('1.0');
10550
        $root = $xmldoc->createElement('manifest');
10551
        $root->setAttribute('identifier', 'SingleCourseManifest');
10552
        $root->setAttribute('version', '1.1');
10553
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
10554
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
10555
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
10556
        $root->setAttribute('xsi:schemaLocation', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd http://www.imsglobal.org/xsd/imsmd_rootv1p2p1 imsmd_rootv1p2p1.xsd http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd');
10557
        // Build mandatory sub-root container elements.
10558
        $metadata = $xmldoc->createElement('metadata');
10559
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
10560
        $metadata->appendChild($md_schema);
10561
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
10562
        $metadata->appendChild($md_schemaversion);
10563
        $root->appendChild($metadata);
10564
10565
        $organizations = $xmldoc->createElement('organizations');
10566
        $resources = $xmldoc->createElement('resources');
10567
10568
        // Build the only organization we will use in building our learnpaths.
10569
        $organizations->setAttribute('default', 'chamilo_scorm_export');
10570
        $organization = $xmldoc->createElement('organization');
10571
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
10572
        // To set the title of the SCORM entity (=organization), we take the name given
10573
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
10574
        // learning path charset) as it is the encoding that defines how it is stored
10575
        // in the database. Then we convert it to HTML entities again as the "&" character
10576
        // alone is not authorized in XML (must be &amp;).
10577
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
10578
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
10579
        $organization->appendChild($org_title);
10580
10581
        $folder_name = 'document';
10582
10583
        // Removes the learning_path/scorm_folder path when exporting see #4841
10584
        $path_to_remove = null;
10585
        $result = $this->generate_lp_folder($_course);
10586
10587
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
10588
            $path_to_remove = 'document'.$result['dir'];
10589
            $path_to_replace = $folder_name.'/';
10590
        }
10591
10592
        // Fixes chamilo scorm exports
10593
        if ($this->ref === 'chamilo_scorm_export') {
10594
            $path_to_remove = 'scorm/'.$this->path.'/document/';
10595
        }
10596
10597
        // For each element, add it to the imsmanifest structure, then add it to the zip.
10598
        // Always call the learnpathItem->scorm_export() method to change it to the SCORM format.
10599
        $link_updates = [];
10600
        $links_to_create = [];
10601
        //foreach ($this->items as $index => $item) {
10602
        foreach ($this->ordered_items as $index => $itemId) {
10603
            $item = $this->items[$itemId];
10604
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
10605
                // Get included documents from this item.
10606
                if ($item->type === 'sco') {
10607
                    $inc_docs = $item->get_resources_from_source(
10608
                        null,
10609
                        api_get_path(SYS_COURSE_PATH).api_get_course_path().'/'.'scorm/'.$this->path.'/'.$item->get_path()
10610
                    );
10611
                } else {
10612
                    $inc_docs = $item->get_resources_from_source();
10613
                }
10614
                // Give a child element <item> to the <organization> element.
10615
                $my_item_id = $item->get_id();
10616
                $my_item = $xmldoc->createElement('item');
10617
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
10618
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
10619
                $my_item->setAttribute('isvisible', 'true');
10620
                // Give a child element <title> to the <item> element.
10621
                $my_title = $xmldoc->createElement(
10622
                    'title',
10623
                    htmlspecialchars(
10624
                        api_utf8_encode($item->get_title()),
10625
                        ENT_QUOTES,
10626
                        'UTF-8'
10627
                    )
10628
                );
10629
                $my_item->appendChild($my_title);
10630
                // Give a child element <adlcp:prerequisites> to the <item> element.
10631
                $my_prereqs = $xmldoc->createElement(
10632
                    'adlcp:prerequisites',
10633
                    $this->get_scorm_prereq_string($my_item_id)
10634
                );
10635
                $my_prereqs->setAttribute('type', 'aicc_script');
10636
                $my_item->appendChild($my_prereqs);
10637
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10638
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
10639
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10640
                //$xmldoc->createElement('adlcp:timelimitaction','');
10641
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10642
                //$xmldoc->createElement('adlcp:datafromlms','');
10643
                // Give a child element <adlcp:masteryscore> to the <item> element.
10644
                $my_masteryscore = $xmldoc->createElement(
10645
                    'adlcp:masteryscore',
10646
                    $item->get_mastery_score()
10647
                );
10648
                $my_item->appendChild($my_masteryscore);
10649
10650
                // Attach this item to the organization element or hits parent if there is one.
10651
                if (!empty($item->parent) && $item->parent != 0) {
10652
                    $children = $organization->childNodes;
10653
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10654
                    if (is_object($possible_parent)) {
10655
                        $possible_parent->appendChild($my_item);
10656
                    } else {
10657
                        if ($this->debug > 0) {
10658
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
10659
                        }
10660
                    }
10661
                } else {
10662
                    if ($this->debug > 0) {
10663
                        error_log('No parent');
10664
                    }
10665
                    $organization->appendChild($my_item);
10666
                }
10667
10668
                // Get the path of the file(s) from the course directory root.
10669
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10670
10671
                if (!empty($path_to_remove)) {
10672
                    //From docs
10673
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $path_to_replace does not seem to be defined for all execution paths leading up to this point.
Loading history...
10674
10675
                    //From quiz
10676
                    if ($this->ref === 'chamilo_scorm_export') {
10677
                        $path_to_remove = 'scorm/'.$this->path.'/';
10678
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
10679
                    }
10680
                } else {
10681
                    $my_xml_file_path = $my_file_path;
10682
                }
10683
10684
                $my_sub_dir = dirname($my_file_path);
10685
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10686
                $my_xml_sub_dir = $my_sub_dir;
10687
                // Give a <resource> child to the <resources> element
10688
                $my_resource = $xmldoc->createElement('resource');
10689
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10690
                $my_resource->setAttribute('type', 'webcontent');
10691
                $my_resource->setAttribute('href', $my_xml_file_path);
10692
                // adlcp:scormtype can be either 'sco' or 'asset'.
10693
                if ($item->type === 'sco') {
10694
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
10695
                } else {
10696
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
10697
                }
10698
                // xml:base is the base directory to find the files declared in this resource.
10699
                $my_resource->setAttribute('xml:base', '');
10700
                // Give a <file> child to the <resource> element.
10701
                $my_file = $xmldoc->createElement('file');
10702
                $my_file->setAttribute('href', $my_xml_file_path);
10703
                $my_resource->appendChild($my_file);
10704
10705
                // Dependency to other files - not yet supported.
10706
                $i = 1;
10707
                if ($inc_docs) {
10708
                    foreach ($inc_docs as $doc_info) {
10709
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
10710
                            continue;
10711
                        }
10712
                        $my_dep = $xmldoc->createElement('resource');
10713
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
10714
                        $my_dep->setAttribute('identifier', $res_id);
10715
                        $my_dep->setAttribute('type', 'webcontent');
10716
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
10717
                        $my_dep_file = $xmldoc->createElement('file');
10718
                        // Check type of URL.
10719
                        if ($doc_info[1] == 'remote') {
10720
                            // Remote file. Save url as is.
10721
                            $my_dep_file->setAttribute('href', $doc_info[0]);
10722
                            $my_dep->setAttribute('xml:base', '');
10723
                        } elseif ($doc_info[1] === 'local') {
10724
                            switch ($doc_info[2]) {
10725
                                case 'url': // Local URL - save path as url for now, don't zip file.
10726
                                    $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10727
                                    $current_dir = dirname($abs_path);
10728
                                    $current_dir = str_replace('\\', '/', $current_dir);
10729
                                    $file_path = realpath($abs_path);
10730
                                    $file_path = str_replace('\\', '/', $file_path);
10731
                                    $my_dep_file->setAttribute('href', $file_path);
10732
                                    $my_dep->setAttribute('xml:base', '');
10733
                                    if (strstr($file_path, $main_path) !== false) {
10734
                                        // The calculated real path is really inside Chamilo's root path.
10735
                                        // Reduce file path to what's under the DocumentRoot.
10736
                                        $file_path = substr($file_path, strlen($root_path) - 1);
10737
                                        //echo $file_path;echo '<br /><br />';
10738
                                        //error_log(__LINE__.'Reduced url path: '.$file_path, 0);
10739
                                        $zip_files_abs[] = $file_path;
10740
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10741
                                        $my_dep_file->setAttribute('href', $file_path);
10742
                                        $my_dep->setAttribute('xml:base', '');
10743
                                    } elseif (empty($file_path)) {
10744
                                        /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH), api_get_path(REL_PATH)));
10745
                                        if (strpos($document_root, -1) == '/') {
10746
                                            $document_root = substr(0, -1, $document_root);
10747
                                        }*/
10748
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
10749
                                        $file_path = str_replace('//', '/', $file_path);
10750
                                        if (file_exists($file_path)) {
10751
                                            $file_path = substr($file_path, strlen($current_dir)); // We get the relative path.
10752
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
10753
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10754
                                            $my_dep_file->setAttribute('href', $file_path);
10755
                                            $my_dep->setAttribute('xml:base', '');
10756
                                        }
10757
                                    }
10758
                                    break;
10759
                                case 'abs': // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
10760
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10761
                                    $my_dep->setAttribute('xml:base', '');
10762
10763
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
10764
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
10765
                                    $abs_img_path_without_subdir = $doc_info[0];
10766
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
10767
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
10768
                                    if ($pos === 0) {
10769
                                        $abs_img_path_without_subdir = '/'.substr($abs_img_path_without_subdir, strlen($relp));
10770
                                    }
10771
10772
                                    //$file_path = realpath(api_get_path(SYS_PATH).$abs_img_path_without_subdir);
10773
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
10774
10775
                                    $file_path = str_replace('\\', '/', $file_path);
10776
                                    $file_path = str_replace('//', '/', $file_path);
10777
10778
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
10779
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
10780
                                    // Check if the current document is in that path.
10781
                                    if (strstr($file_path, $cur_path) !== false) {
10782
                                        // The document is in that path, now get the relative path
10783
                                        // to the containing document.
10784
                                        $orig_file_path = dirname($cur_path.$my_file_path).'/';
10785
                                        $orig_file_path = str_replace('\\', '/', $orig_file_path);
10786
                                        $relative_path = '';
10787
                                        if (strstr($file_path, $cur_path) !== false) {
10788
                                            //$relative_path = substr($file_path, strlen($orig_file_path));
10789
                                            $relative_path = str_replace($cur_path, '', $file_path);
10790
                                            $file_path = substr($file_path, strlen($cur_path));
10791
                                        } else {
10792
                                            // This case is still a problem as it's difficult to calculate a relative path easily
10793
                                            // might still generate wrong links.
10794
                                            //$file_path = substr($file_path,strlen($cur_path));
10795
                                            // Calculate the directory path to the current file (without trailing slash).
10796
                                            $my_relative_path = dirname($file_path);
10797
                                            $my_relative_path = str_replace('\\', '/', $my_relative_path);
10798
                                            $my_relative_file = basename($file_path);
10799
                                            // Calculate the directory path to the containing file (without trailing slash).
10800
                                            $my_orig_file_path = substr($orig_file_path, 0, -1);
10801
                                            $dotdot = '';
10802
                                            $subdir = '';
10803
                                            while (strstr($my_relative_path, $my_orig_file_path) === false && (strlen($my_orig_file_path) > 1) && (strlen($my_relative_path) > 1)) {
10804
                                                $my_relative_path2 = dirname($my_relative_path);
10805
                                                $my_relative_path2 = str_replace('\\', '/', $my_relative_path2);
10806
                                                $my_orig_file_path = dirname($my_orig_file_path);
10807
                                                $my_orig_file_path = str_replace('\\', '/', $my_orig_file_path);
10808
                                                $subdir = substr($my_relative_path, strlen($my_relative_path2) + 1).'/'.$subdir;
10809
                                                $dotdot += '../';
10810
                                                $my_relative_path = $my_relative_path2;
10811
                                            }
10812
                                            $relative_path = $dotdot.$subdir.$my_relative_file;
10813
                                        }
10814
                                        // Put the current document in the zip (this array is the array
10815
                                        // that will manage documents already in the course folder - relative).
10816
                                        $zip_files[] = $file_path;
10817
                                        // Update the links to the current document in the containing document (make them relative).
10818
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $relative_path];
10819
                                        $my_dep_file->setAttribute('href', $file_path);
10820
                                        $my_dep->setAttribute('xml:base', '');
10821
                                    } elseif (strstr($file_path, $main_path) !== false) {
10822
                                        // The calculated real path is really inside Chamilo's root path.
10823
                                        // Reduce file path to what's under the DocumentRoot.
10824
                                        $file_path = substr($file_path, strlen($root_path));
10825
                                        //echo $file_path;echo '<br /><br />';
10826
                                        //error_log('Reduced path: '.$file_path, 0);
10827
                                        $zip_files_abs[] = $file_path;
10828
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10829
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
10830
                                        $my_dep->setAttribute('xml:base', '');
10831
                                    } elseif (empty($file_path)) {
10832
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$doc_info[0];
10833
                                        $file_path = str_replace('//', '/', $file_path);
10834
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10835
                                        $current_dir = dirname($abs_path);
10836
                                        $current_dir = str_replace('\\', '/', $current_dir);
10837
10838
                                        if (file_exists($file_path)) {
10839
                                            // We get the relative path.
10840
                                            $file_path = substr($file_path, strlen($current_dir));
10841
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
10842
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10843
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
10844
                                            $my_dep->setAttribute('xml:base', '');
10845
                                        }
10846
                                    }
10847
                                    break;
10848
                                case 'rel':
10849
                                    // Path relative to the current document.
10850
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
10851
                                    if (substr($doc_info[0], 0, 2) == '..') {
10852
                                        // Relative path going up.
10853
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
10854
                                        $current_dir = str_replace('\\', '/', $current_dir);
10855
                                        $file_path = realpath($current_dir.$doc_info[0]);
10856
                                        $file_path = str_replace('\\', '/', $file_path);
10857
                                        if (strstr($file_path, $main_path) !== false) {
10858
                                            // The calculated real path is really inside Chamilo's root path.
10859
                                            // Reduce file path to what's under the DocumentRoot.
10860
                                            $file_path = substr($file_path, strlen($root_path));
10861
                                            $zip_files_abs[] = $file_path;
10862
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10863
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
10864
                                            $my_dep->setAttribute('xml:base', '');
10865
                                        }
10866
                                    } else {
10867
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
10868
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
10869
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
10870
                                    }
10871
                                    break;
10872
                                default:
10873
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10874
                                    $my_dep->setAttribute('xml:base', '');
10875
                                    break;
10876
                            }
10877
                        }
10878
                        $my_dep->appendChild($my_dep_file);
10879
                        $resources->appendChild($my_dep);
10880
                        $dependency = $xmldoc->createElement('dependency');
10881
                        $dependency->setAttribute('identifierref', $res_id);
10882
                        $my_resource->appendChild($dependency);
10883
                        $i++;
10884
                    }
10885
                }
10886
                $resources->appendChild($my_resource);
10887
                $zip_files[] = $my_file_path;
10888
            } else {
10889
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
10890
                switch ($item->type) {
10891
                    case TOOL_LINK:
10892
                        $my_item = $xmldoc->createElement('item');
10893
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10894
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10895
                        $my_item->setAttribute('isvisible', 'true');
10896
                        // Give a child element <title> to the <item> element.
10897
                        $my_title = $xmldoc->createElement(
10898
                            'title',
10899
                            htmlspecialchars(
10900
                                api_utf8_encode($item->get_title()),
10901
                                ENT_QUOTES,
10902
                                'UTF-8'
10903
                            )
10904
                        );
10905
                        $my_item->appendChild($my_title);
10906
                        // Give a child element <adlcp:prerequisites> to the <item> element.
10907
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
10908
                        $my_prereqs->setAttribute('type', 'aicc_script');
10909
                        $my_item->appendChild($my_prereqs);
10910
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10911
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
10912
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10913
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
10914
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10915
                        //$xmldoc->createElement('adlcp:datafromlms', '');
10916
                        // Give a child element <adlcp:masteryscore> to the <item> element.
10917
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10918
                        $my_item->appendChild($my_masteryscore);
10919
10920
                        // Attach this item to the organization element or its parent if there is one.
10921
                        if (!empty($item->parent) && $item->parent != 0) {
10922
                            $children = $organization->childNodes;
10923
                            for ($i = 0; $i < $children->length; $i++) {
10924
                                $item_temp = $children->item($i);
10925
                                if ($item_temp->nodeName == 'item') {
10926
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
10927
                                        $item_temp->appendChild($my_item);
10928
                                    }
10929
                                }
10930
                            }
10931
                        } else {
10932
                            $organization->appendChild($my_item);
10933
                        }
10934
10935
                        $my_file_path = 'link_'.$item->get_id().'.html';
10936
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
10937
                                WHERE c_id = '.$course_id.' AND id='.$item->path;
10938
                        $rs = Database::query($sql);
10939
                        if ($link = Database::fetch_array($rs)) {
10940
                            $url = $link['url'];
10941
                            $title = stripslashes($link['title']);
10942
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
10943
                            $my_xml_file_path = $my_file_path;
10944
                            $my_sub_dir = dirname($my_file_path);
10945
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10946
                            $my_xml_sub_dir = $my_sub_dir;
10947
                            // Give a <resource> child to the <resources> element.
10948
                            $my_resource = $xmldoc->createElement('resource');
10949
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10950
                            $my_resource->setAttribute('type', 'webcontent');
10951
                            $my_resource->setAttribute('href', $my_xml_file_path);
10952
                            // adlcp:scormtype can be either 'sco' or 'asset'.
10953
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
10954
                            // xml:base is the base directory to find the files declared in this resource.
10955
                            $my_resource->setAttribute('xml:base', '');
10956
                            // give a <file> child to the <resource> element.
10957
                            $my_file = $xmldoc->createElement('file');
10958
                            $my_file->setAttribute('href', $my_xml_file_path);
10959
                            $my_resource->appendChild($my_file);
10960
                            $resources->appendChild($my_resource);
10961
                        }
10962
                        break;
10963
                    case TOOL_QUIZ:
10964
                        $exe_id = $item->path; // Should be using ref when everything will be cleaned up in this regard.
10965
                        $exe = new Exercise();
10966
                        $exe->read($exe_id);
10967
                        $my_item = $xmldoc->createElement('item');
10968
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10969
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10970
                        $my_item->setAttribute('isvisible', 'true');
10971
                        // Give a child element <title> to the <item> element.
10972
                        $my_title = $xmldoc->createElement(
10973
                            'title',
10974
                            htmlspecialchars(
10975
                                api_utf8_encode($item->get_title()),
10976
                                ENT_QUOTES,
10977
                                'UTF-8'
10978
                            )
10979
                        );
10980
                        $my_item->appendChild($my_title);
10981
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
10982
                        //$my_item->appendChild($my_max_score);
10983
                        // Give a child element <adlcp:prerequisites> to the <item> element.
10984
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
10985
                        $my_prereqs->setAttribute('type', 'aicc_script');
10986
                        $my_item->appendChild($my_prereqs);
10987
                        // Give a child element <adlcp:masteryscore> to the <item> element.
10988
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10989
                        $my_item->appendChild($my_masteryscore);
10990
10991
                        // Attach this item to the organization element or hits parent if there is one.
10992
                        if (!empty($item->parent) && $item->parent != 0) {
10993
                            $children = $organization->childNodes;
10994
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10995
                            if ($possible_parent) {
10996
                                if ($possible_parent->getAttribute('identifier') == 'ITEM_'.$item->parent) {
10997
                                    $possible_parent->appendChild($my_item);
10998
                                }
10999
                            }
11000
                        } else {
11001
                            $organization->appendChild($my_item);
11002
                        }
11003
11004
                        // Get the path of the file(s) from the course directory root
11005
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11006
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
11007
                        // Write the contents of the exported exercise into a (big) html file
11008
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
11009
                        $contents = ScormSection::export_exercise_to_scorm(
11010
                            $exe,
11011
                            true
11012
                        );
11013
11014
                        $tmp_file_path = $archive_path.$temp_dir_short.'/'.$my_file_path;
11015
                        $res = file_put_contents($tmp_file_path, $contents);
11016
                        if ($res === false) {
11017
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
11018
                        }
11019
                        $files_cleanup[] = $tmp_file_path;
11020
                        $my_xml_file_path = $my_file_path;
11021
                        $my_sub_dir = dirname($my_file_path);
11022
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11023
                        $my_xml_sub_dir = $my_sub_dir;
11024
                        // Give a <resource> child to the <resources> element.
11025
                        $my_resource = $xmldoc->createElement('resource');
11026
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11027
                        $my_resource->setAttribute('type', 'webcontent');
11028
                        $my_resource->setAttribute('href', $my_xml_file_path);
11029
11030
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11031
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11032
                        // xml:base is the base directory to find the files declared in this resource.
11033
                        $my_resource->setAttribute('xml:base', '');
11034
                        // Give a <file> child to the <resource> element.
11035
                        $my_file = $xmldoc->createElement('file');
11036
                        $my_file->setAttribute('href', $my_xml_file_path);
11037
                        $my_resource->appendChild($my_file);
11038
11039
                        // Get included docs.
11040
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11041
                        // Dependency to other files - not yet supported.
11042
                        $i = 1;
11043
                        foreach ($inc_docs as $doc_info) {
11044
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11045
                                continue;
11046
                            }
11047
                            $my_dep = $xmldoc->createElement('resource');
11048
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11049
                            $my_dep->setAttribute('identifier', $res_id);
11050
                            $my_dep->setAttribute('type', 'webcontent');
11051
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11052
                            $my_dep_file = $xmldoc->createElement('file');
11053
                            // Check type of URL.
11054
                            if ($doc_info[1] == 'remote') {
11055
                                // Remote file. Save url as is.
11056
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11057
                                $my_dep->setAttribute('xml:base', '');
11058
                            } elseif ($doc_info[1] == 'local') {
11059
                                switch ($doc_info[2]) {
11060
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11061
                                        // Save file but as local file (retrieve from URL).
11062
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11063
                                        $current_dir = dirname($abs_path);
11064
                                        $current_dir = str_replace('\\', '/', $current_dir);
11065
                                        $file_path = realpath($abs_path);
11066
                                        $file_path = str_replace('\\', '/', $file_path);
11067
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11068
                                        $my_dep->setAttribute('xml:base', '');
11069
                                        if (strstr($file_path, $main_path) !== false) {
11070
                                            // The calculated real path is really inside the chamilo root path.
11071
                                            // Reduce file path to what's under the DocumentRoot.
11072
                                            $file_path = substr($file_path, strlen($root_path));
11073
                                            $zip_files_abs[] = $file_path;
11074
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => 'document/'.$file_path];
11075
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11076
                                            $my_dep->setAttribute('xml:base', '');
11077
                                        } elseif (empty($file_path)) {
11078
                                            /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH),api_get_path(REL_PATH)));
11079
                                            if (strpos($document_root,-1) == '/') {
11080
                                                $document_root = substr(0, -1, $document_root);
11081
                                            }*/
11082
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11083
                                            $file_path = str_replace('//', '/', $file_path);
11084
                                            if (file_exists($file_path)) {
11085
                                                $file_path = substr($file_path, strlen($current_dir)); // We get the relative path.
11086
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11087
                                                $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => 'document/'.$file_path];
11088
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11089
                                                $my_dep->setAttribute('xml:base', '');
11090
                                            }
11091
                                        }
11092
                                        break;
11093
                                    case 'abs': // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11094
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11095
                                        $current_dir = str_replace('\\', '/', $current_dir);
11096
                                        $file_path = realpath($doc_info[0]);
11097
                                        $file_path = str_replace('\\', '/', $file_path);
11098
                                        $my_dep_file->setAttribute('href', $file_path);
11099
                                        $my_dep->setAttribute('xml:base', '');
11100
11101
                                        if (strstr($file_path, $main_path) !== false) {
11102
                                            // The calculated real path is really inside the chamilo root path.
11103
                                            // Reduce file path to what's under the DocumentRoot.
11104
                                            $file_path = substr($file_path, strlen($root_path));
11105
                                            $zip_files_abs[] = $file_path;
11106
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11107
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11108
                                            $my_dep->setAttribute('xml:base', '');
11109
                                        } elseif (empty($file_path)) {
11110
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$doc_info[0];
11111
                                            $file_path = str_replace('//', '/', $file_path);
11112
                                            if (file_exists($file_path)) {
11113
                                                $file_path = substr($file_path, strlen($current_dir)); // We get the relative path.
11114
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11115
                                                $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11116
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11117
                                                $my_dep->setAttribute('xml:base', '');
11118
                                            }
11119
                                        }
11120
                                        break;
11121
                                    case 'rel':
11122
                                        // Path relative to the current document. Save xml:base as current document's
11123
                                        // directory and save file in zip as subdir.file_path
11124
                                        if (substr($doc_info[0], 0, 2) == '..') {
11125
                                            // Relative path going up.
11126
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11127
                                            $current_dir = str_replace('\\', '/', $current_dir);
11128
                                            $file_path = realpath($current_dir.$doc_info[0]);
11129
                                            $file_path = str_replace('\\', '/', $file_path);
11130
                                            //error_log($file_path.' <-> '.$main_path, 0);
11131
                                            if (strstr($file_path, $main_path) !== false) {
11132
                                                // The calculated real path is really inside Chamilo's root path.
11133
                                                // Reduce file path to what's under the DocumentRoot.
11134
11135
                                                $file_path = substr($file_path, strlen($root_path));
11136
                                                $file_path_dest = $file_path;
11137
11138
                                                // File path is courses/CHAMILO/document/....
11139
                                                $info_file_path = explode('/', $file_path);
11140
                                                if ($info_file_path[0] == 'courses') {
11141
                                                    // Add character "/" in file path.
11142
                                                    $file_path_dest = 'document/'.$file_path;
11143
                                                }
11144
11145
                                                //error_log('Reduced path: '.$file_path, 0);
11146
                                                $zip_files_abs[] = $file_path;
11147
11148
                                                $link_updates[$my_file_path][] = [
11149
                                                    'orig' => $doc_info[0],
11150
                                                    'dest' => $file_path_dest,
11151
                                                ];
11152
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11153
                                                $my_dep->setAttribute('xml:base', '');
11154
                                            }
11155
                                        } else {
11156
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11157
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11158
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11159
                                        }
11160
                                        break;
11161
                                    default:
11162
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11163
                                        $my_dep->setAttribute('xml:base', '');
11164
                                        break;
11165
                                }
11166
                            }
11167
                            $my_dep->appendChild($my_dep_file);
11168
                            $resources->appendChild($my_dep);
11169
                            $dependency = $xmldoc->createElement('dependency');
11170
                            $dependency->setAttribute('identifierref', $res_id);
11171
                            $my_resource->appendChild($dependency);
11172
                            $i++;
11173
                        }
11174
                        $resources->appendChild($my_resource);
11175
                        $zip_files[] = $my_file_path;
11176
                        break;
11177
                    default:
11178
                        // Get the path of the file(s) from the course directory root
11179
                        $my_file_path = 'non_exportable.html';
11180
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11181
                        $my_xml_file_path = $my_file_path;
11182
                        $my_sub_dir = dirname($my_file_path);
11183
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11184
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11185
                        $my_xml_sub_dir = $my_sub_dir;
11186
                        // Give a <resource> child to the <resources> element.
11187
                        $my_resource = $xmldoc->createElement('resource');
11188
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11189
                        $my_resource->setAttribute('type', 'webcontent');
11190
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11191
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11192
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11193
                        // xml:base is the base directory to find the files declared in this resource.
11194
                        $my_resource->setAttribute('xml:base', '');
11195
                        // Give a <file> child to the <resource> element.
11196
                        $my_file = $xmldoc->createElement('file');
11197
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11198
                        $my_resource->appendChild($my_file);
11199
                        $resources->appendChild($my_resource);
11200
11201
                        break;
11202
                }
11203
            }
11204
        }
11205
        $organizations->appendChild($organization);
11206
        $root->appendChild($organizations);
11207
        $root->appendChild($resources);
11208
        $xmldoc->appendChild($root);
11209
11210
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11211
11212
        // TODO: Add a readme file here, with a short description and a link to the Reload player
11213
        // then add the file to the zip, then destroy the file (this is done automatically).
11214
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11215
        foreach ($zip_files as $file_path) {
11216
            if (empty($file_path)) {
11217
                continue;
11218
            }
11219
11220
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11221
            $dest_file = $archive_path.$temp_dir_short.'/'.$file_path;
11222
11223
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11224
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11225
            }
11226
            $this->create_path($dest_file);
11227
            @copy($filePath, $dest_file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for copy(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

11227
            /** @scrutinizer ignore-unhandled */ @copy($filePath, $dest_file);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
11228
11229
            // Check if the file needs a link update.
11230
            if (in_array($file_path, array_keys($link_updates))) {
11231
                $string = file_get_contents($dest_file);
11232
                unlink($dest_file);
11233
                foreach ($link_updates[$file_path] as $old_new) {
11234
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11235
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11236
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11237
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11238
                    if (substr($old_new['dest'], -3) == 'flv' &&
11239
                        substr($old_new['dest'], 0, 5) == 'main/'
11240
                    ) {
11241
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11242
                    } elseif (substr($old_new['dest'], -3) == 'flv' &&
11243
                        substr($old_new['dest'], 0, 6) == 'video/'
11244
                    ) {
11245
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11246
                    }
11247
                    //Fix to avoid problems with default_course_document
11248
                    if (strpos("main/default_course_document", $old_new['dest'] === false)) {
11249
                        //$newDestination = str_replace('document/', $mult.'document/', $old_new['dest']);
11250
                        $newDestination = $old_new['dest'];
11251
                    } else {
11252
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11253
                    }
11254
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11255
11256
                    // Add files inside the HTMLs
11257
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11258
                    $destinationFile = $archive_path.$temp_dir_short.'/'.$old_new['dest'];
11259
                    if (file_exists($sys_course_path.$new_path)) {
11260
                        copy($sys_course_path.$new_path, $destinationFile);
11261
                    }
11262
                }
11263
                file_put_contents($dest_file, $string);
11264
            }
11265
11266
            if (file_exists($filePath) && $copyAll) {
11267
                $extension = $this->get_extension($filePath);
11268
                if (in_array($extension, ['html', 'html'])) {
11269
                    $containerOrigin = dirname($filePath);
11270
                    $containerDestination = dirname($dest_file);
11271
11272
                    $finder = new Finder();
11273
                    $finder->files()->in($containerOrigin)
11274
                        ->notName('*_DELETED_*')
11275
                        ->exclude('share_folder')
11276
                        ->exclude('chat_files')
11277
                        ->exclude('certificates')
11278
                    ;
11279
11280
                    if (is_dir($containerOrigin) &&
11281
                        is_dir($containerDestination)
11282
                    ) {
11283
                        $fs = new Filesystem();
11284
                        $fs->mirror(
11285
                            $containerOrigin,
11286
                            $containerDestination,
11287
                            $finder
11288
                        );
11289
                    }
11290
                }
11291
            }
11292
        }
11293
11294
        foreach ($zip_files_abs as $file_path) {
11295
            if (empty($file_path)) {
11296
                continue;
11297
            }
11298
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11299
                continue;
11300
            }
11301
11302
            $dest_file = $archive_path.$temp_dir_short.'/document/'.$file_path;
11303
            $this->create_path($dest_file);
11304
            copy($main_path.$file_path, $dest_file);
11305
            // Check if the file needs a link update.
11306
            if (in_array($file_path, array_keys($link_updates))) {
11307
                $string = file_get_contents($dest_file);
11308
                unlink($dest_file);
11309
                foreach ($link_updates[$file_path] as $old_new) {
11310
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11311
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11312
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11313
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11314
                    if (substr($old_new['dest'], -3) == 'flv' && substr($old_new['dest'], 0, 5) == 'main/') {
11315
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11316
                    }
11317
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11318
                }
11319
                file_put_contents($dest_file, $string);
11320
            }
11321
        }
11322
11323
        if (is_array($links_to_create)) {
11324
            foreach ($links_to_create as $file => $link) {
11325
                $file_content = '<!DOCTYPE html><head>
11326
                                <meta charset="'.api_get_language_isocode().'" />
11327
                                <title>'.$link['title'].'</title>
11328
                                </head>
11329
                                <body dir="'.api_get_text_direction().'">
11330
                                <div style="text-align:center">
11331
                                <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11332
                                </body>
11333
                                </html>';
11334
                file_put_contents($archive_path.$temp_dir_short.'/'.$file, $file_content);
11335
            }
11336
        }
11337
11338
        // Add non exportable message explanation.
11339
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
11340
        $file_content = '<!DOCTYPE html><head>
11341
                        <meta charset="'.api_get_language_isocode().'" />
11342
                        <title>'.$lang_not_exportable.'</title>
11343
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11344
                        </head>
11345
                        <body dir="'.api_get_text_direction().'">';
11346
        $file_content .=
11347
            <<<EOD
11348
                    <style>
11349
            .error-message {
11350
                font-family: arial, verdana, helvetica, sans-serif;
11351
                border-width: 1px;
11352
                border-style: solid;
11353
                left: 50%;
11354
                margin: 10px auto;
11355
                min-height: 30px;
11356
                padding: 5px;
11357
                right: 50%;
11358
                width: 500px;
11359
                background-color: #FFD1D1;
11360
                border-color: #FF0000;
11361
                color: #000;
11362
            }
11363
        </style>
11364
    <body>
11365
        <div class="error-message">
11366
            $lang_not_exportable
11367
        </div>
11368
    </body>
11369
</html>
11370
EOD;
11371
        if (!is_dir($archive_path.$temp_dir_short.'/document')) {
11372
            @mkdir($archive_path.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for mkdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

11372
            /** @scrutinizer ignore-unhandled */ @mkdir($archive_path.$temp_dir_short.'/document', api_get_permissions_for_new_directories());

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
11373
        }
11374
        file_put_contents($archive_path.$temp_dir_short.'/document/non_exportable.html', $file_content);
11375
11376
        // Add the extra files that go along with a SCORM package.
11377
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11378
11379
        $fs = new Filesystem();
11380
        $fs->mirror($main_code_path, $archive_path.$temp_dir_short);
11381
11382
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11383
        $manifest = @$xmldoc->saveXML();
11384
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11385
        file_put_contents($archive_path.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11386
        $zip_folder->add(
11387
            $archive_path.'/'.$temp_dir_short,
11388
            PCLZIP_OPT_REMOVE_PATH,
11389
            $archive_path.'/'.$temp_dir_short.'/'
11390
        );
11391
11392
        // Clean possible temporary files.
11393
        foreach ($files_cleanup as $file) {
11394
            $res = unlink($file);
11395
            if ($res === false) {
11396
                error_log(
11397
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11398
                    0
11399
                );
11400
            }
11401
        }
11402
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11403
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11404
    }
11405
11406
    /**
11407
     * @param int $lp_id
11408
     *
11409
     * @return bool
11410
     */
11411
    public function scorm_export_to_pdf($lp_id)
11412
    {
11413
        $lp_id = intval($lp_id);
11414
        $files_to_export = [];
11415
        $course_data = api_get_course_info($this->cc);
11416
        if (!empty($course_data)) {
11417
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11418
            $list = self::get_flat_ordered_items_list($lp_id);
11419
            if (!empty($list)) {
11420
                foreach ($list as $item_id) {
11421
                    $item = $this->items[$item_id];
11422
                    switch ($item->type) {
11423
                        case 'document':
11424
                            //Getting documents from a LP with chamilo documents
11425
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11426
                            // Try loading document from the base course.
11427
                            if (empty($file_data) && !empty($sessionId)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sessionId seems to never exist and therefore empty should always be true.
Loading history...
11428
                                $file_data = DocumentManager::get_document_data_by_id(
11429
                                    $item->path,
11430
                                    $this->cc,
11431
                                    false,
11432
                                    0
11433
                                );
11434
                            }
11435
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11436
                            if (file_exists($file_path)) {
11437
                                $files_to_export[] = [
11438
                                    'title' => $item->get_title(),
11439
                                    'path' => $file_path,
11440
                                ];
11441
                            }
11442
                            break;
11443
                        case 'asset': //commes from a scorm package generated by chamilo
11444
                        case 'sco':
11445
                            $file_path = $scorm_path.'/'.$item->path;
11446
                            if (file_exists($file_path)) {
11447
                                $files_to_export[] = [
11448
                                    'title' => $item->get_title(),
11449
                                    'path' => $file_path,
11450
                                ];
11451
                            }
11452
                            break;
11453
                        case 'dir':
11454
                            $files_to_export[] = [
11455
                                'title' => $item->get_title(),
11456
                                'path' => null,
11457
                            ];
11458
                            break;
11459
                    }
11460
                }
11461
            }
11462
            $pdf = new PDF();
11463
            $result = $pdf->html_to_pdf(
11464
                $files_to_export,
11465
                $this->name,
11466
                $this->cc,
11467
                true
11468
            );
11469
11470
            return $result;
11471
        }
11472
11473
        return false;
11474
    }
11475
11476
    /**
11477
     * Temp function to be moved in main_api or the best place around for this.
11478
     * Creates a file path if it doesn't exist.
11479
     *
11480
     * @param string $path
11481
     */
11482
    public function create_path($path)
11483
    {
11484
        $path_bits = explode('/', dirname($path));
11485
11486
        // IS_WINDOWS_OS has been defined in main_api.lib.php
11487
        $path_built = IS_WINDOWS_OS ? '' : '/';
11488
        foreach ($path_bits as $bit) {
11489
            if (!empty($bit)) {
11490
                $new_path = $path_built.$bit;
11491
                if (is_dir($new_path)) {
11492
                    $path_built = $new_path.'/';
11493
                } else {
11494
                    mkdir($new_path, api_get_permissions_for_new_directories());
11495
                    $path_built = $new_path.'/';
11496
                }
11497
            }
11498
        }
11499
    }
11500
11501
    /**
11502
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
11503
     *
11504
     * @return bool The results of the unlink function, or false if there was no image to start with
11505
     * @throws \Doctrine\DBAL\DBALException
11506
     */
11507
    public function delete_lp_image()
11508
    {
11509
        $img = $this->get_preview_image();
11510
        if ($img != '') {
11511
            $del_file = $this->get_preview_image_path(null, 'sys');
11512
            if (isset($del_file) && file_exists($del_file)) {
11513
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
11514
                if (file_exists($del_file_2)) {
0 ignored issues
show
Bug introduced by
It seems like $del_file_2 can also be of type false; however, parameter $filename of file_exists() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

11514
                if (file_exists(/** @scrutinizer ignore-type */ $del_file_2)) {
Loading history...
11515
                    unlink($del_file_2);
0 ignored issues
show
Bug introduced by
It seems like $del_file_2 can also be of type false; however, parameter $filename of unlink() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

11515
                    unlink(/** @scrutinizer ignore-type */ $del_file_2);
Loading history...
11516
                }
11517
                $this->set_preview_image('');
11518
11519
                return @unlink($del_file);
11520
            }
11521
        }
11522
11523
        return false;
11524
    }
11525
11526
    /**
11527
     * Uploads an author image to the upload/learning_path/images directory.
11528
     *
11529
     * @param array    The image array, coming from the $_FILES superglobal
11530
     *
11531
     * @return bool True on success, false on error
11532
     * @throws \Doctrine\DBAL\DBALException
11533
     */
11534
    public function upload_image($image_array)
11535
    {
11536
        if (!empty($image_array['name'])) {
11537
            $upload_ok = process_uploaded_file($image_array);
11538
            $has_attachment = true;
11539
        }
11540
11541
        if ($upload_ok && $has_attachment) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $has_attachment does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $upload_ok does not seem to be defined for all execution paths leading up to this point.
Loading history...
11542
            $courseDir = api_get_course_path().'/upload/learning_path/images';
11543
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
11544
            $updir = $sys_course_path.$courseDir;
11545
            // Try to add an extension to the file if it hasn't one.
11546
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
11547
11548
            if (filter_extension($new_file_name)) {
11549
                $file_extension = explode('.', $image_array['name']);
11550
                $file_extension = strtolower($file_extension[sizeof($file_extension) - 1]);
11551
                $filename = uniqid('');
11552
                $new_file_name = $filename.'.'.$file_extension;
11553
                $new_path = $updir.'/'.$new_file_name;
11554
11555
                // Resize the image.
11556
                $temp = new Image($image_array['tmp_name']);
11557
                $temp->resize(104);
11558
                $result = $temp->send_image($new_path);
11559
11560
                // Storing the image filename.
11561
                if ($result) {
11562
                    $this->set_preview_image($new_file_name);
11563
11564
                    //Resize to 64px to use on course homepage
11565
                    $temp->resize(64);
11566
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
11567
11568
                    return true;
11569
                }
11570
            }
11571
        }
11572
11573
        return false;
11574
    }
11575
11576
    /**
11577
     * @param int    $lp_id
11578
     * @param string $status
11579
     */
11580
    public function set_autolaunch($lp_id, $status)
11581
    {
11582
        $course_id = api_get_course_int_id();
11583
        $lp_id = intval($lp_id);
11584
        $status = intval($status);
11585
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11586
11587
        // Setting everything to autolaunch = 0
11588
        $attributes['autolaunch'] = 0;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$attributes was never initialized. Although not strictly required by PHP, it is generally a good practice to add $attributes = array(); before regardless.
Loading history...
11589
        $where = [
11590
            'session_id = ? AND c_id = ? ' => [
11591
                api_get_session_id(),
11592
                $course_id,
11593
            ],
11594
        ];
11595
        Database::update($lp_table, $attributes, $where);
11596
        if ($status == 1) {
11597
            //Setting my lp_id to autolaunch = 1
11598
            $attributes['autolaunch'] = 1;
11599
            $where = [
11600
                'iid = ? AND session_id = ? AND c_id = ?' => [
11601
                    $lp_id,
11602
                    api_get_session_id(),
11603
                    $course_id,
11604
                ],
11605
            ];
11606
            Database::update($lp_table, $attributes, $where);
11607
        }
11608
    }
11609
11610
    /**
11611
     * Gets previous_item_id for the next element of the lp_item table.
11612
     *
11613
     * @author Isaac flores paz
11614
     *
11615
     * @return int Previous item ID
11616
     * @throws \Doctrine\DBAL\DBALException
11617
     */
11618
    public function select_previous_item_id()
11619
    {
11620
        $course_id = api_get_course_int_id();
11621
        if ($this->debug > 0) {
11622
            error_log('In learnpath::select_previous_item_id()', 0);
11623
        }
11624
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11625
11626
        // Get the max order of the items
11627
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
11628
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11629
        $rs_max_order = Database::query($sql);
11630
        $row_max_order = Database::fetch_object($rs_max_order);
11631
        $max_order = $row_max_order->display_order;
11632
        // Get the previous item ID
11633
        $sql = "SELECT iid as previous FROM $table_lp_item
11634
                WHERE 
11635
                    c_id = $course_id AND 
11636
                    lp_id = ".$this->lp_id." AND 
11637
                    display_order = '$max_order' ";
11638
        $rs_max = Database::query($sql);
11639
        $row_max = Database::fetch_object($rs_max);
11640
11641
        // Return the previous item ID
11642
        return $row_max->previous;
11643
    }
11644
11645
    /**
11646
     * Copies an LP.
11647
     */
11648
    public function copy()
11649
    {
11650
        // Course builder
11651
        $cb = new CourseBuilder();
11652
11653
        //Setting tools that will be copied
11654
        $cb->set_tools_to_build(['learnpaths']);
11655
11656
        //Setting elements that will be copied
11657
        $cb->set_tools_specific_id_list(
11658
            ['learnpaths' => [$this->lp_id]]
11659
        );
11660
11661
        $course = $cb->build();
11662
11663
        //Course restorer
11664
        $course_restorer = new CourseRestorer($course);
11665
        $course_restorer->set_add_text_in_items(true);
11666
        $course_restorer->set_tool_copy_settings(
11667
            ['learnpaths' => ['reset_dates' => true]]
11668
        );
11669
        $course_restorer->restore(
11670
            api_get_course_id(),
11671
            api_get_session_id(),
11672
            false,
11673
            false
11674
        );
11675
    }
11676
11677
    /**
11678
     * Verify document size.
11679
     *
11680
     * @param string $s
11681
     *
11682
     * @return bool
11683
     */
11684
    public static function verify_document_size($s)
11685
    {
11686
        $post_max = ini_get('post_max_size');
11687
        if (substr($post_max, -1, 1) == 'M') {
11688
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
11689
        } elseif (substr($post_max, -1, 1) == 'G') {
11690
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
11691
        }
11692
        $upl_max = ini_get('upload_max_filesize');
11693
        if (substr($upl_max, -1, 1) == 'M') {
11694
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
11695
        } elseif (substr($upl_max, -1, 1) == 'G') {
11696
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
11697
        }
11698
        $documents_total_space = DocumentManager::documents_total_space();
11699
        $course_max_space = DocumentManager::get_course_quota();
11700
        $total_size = filesize($s) + $documents_total_space;
11701
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
11702
            return true;
11703
        } else {
11704
            return false;
11705
        }
11706
    }
11707
11708
    /**
11709
     * Clear LP prerequisites.
11710
     */
11711
    public function clear_prerequisites()
11712
    {
11713
        $course_id = $this->get_course_int_id();
11714
        if ($this->debug > 0) {
11715
            error_log('In learnpath::clear_prerequisites()', 0);
11716
        }
11717
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11718
        $lp_id = $this->get_id();
11719
        //Cleaning prerequisites
11720
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
11721
                WHERE c_id = $course_id AND lp_id = $lp_id";
11722
        Database::query($sql);
11723
11724
        //Cleaning mastery score for exercises
11725
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
11726
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
11727
        Database::query($sql);
11728
    }
11729
11730
    public function set_previous_step_as_prerequisite_for_all_items()
11731
    {
11732
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11733
        $course_id = $this->get_course_int_id();
11734
        $lp_id = $this->get_id();
11735
11736
        if (!empty($this->items)) {
11737
            $previous_item_id = null;
11738
            $previous_item_max = 0;
11739
            $previous_item_type = null;
11740
            $last_item_not_dir = null;
11741
            $last_item_not_dir_type = null;
11742
            $last_item_not_dir_max = null;
11743
11744
            foreach ($this->ordered_items as $itemId) {
11745
                $item = $this->getItem($itemId);
11746
                // if there was a previous item... (otherwise jump to set it)
11747
                if (!empty($previous_item_id)) {
11748
                    $current_item_id = $item->get_id(); //save current id
11749
                    if ($item->get_type() != 'dir') {
11750
                        // Current item is not a folder, so it qualifies to get a prerequisites
11751
                        if ($last_item_not_dir_type == 'quiz') {
11752
                            // if previous is quiz, mark its max score as default score to be achieved
11753
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
11754
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
11755
                            Database::query($sql);
11756
                        }
11757
                        // now simply update the prerequisite to set it to the last non-chapter item
11758
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
11759
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
11760
                        Database::query($sql);
11761
                        // record item as 'non-chapter' reference
11762
                        $last_item_not_dir = $item->get_id();
11763
                        $last_item_not_dir_type = $item->get_type();
11764
                        $last_item_not_dir_max = $item->get_max();
11765
                    }
11766
                } else {
11767
                    if ($item->get_type() != 'dir') {
11768
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
11769
                        $last_item_not_dir = $item->get_id();
11770
                        $last_item_not_dir_type = $item->get_type();
11771
                        $last_item_not_dir_max = $item->get_max();
11772
                    }
11773
                }
11774
                // Saving the item as "previous item" for the next loop
11775
                $previous_item_id = $item->get_id();
11776
                $previous_item_max = $item->get_max();
11777
                $previous_item_type = $item->get_type();
11778
            }
11779
        }
11780
    }
11781
11782
    /**
11783
     * @param array $params
11784
     *
11785
     * @throws \Doctrine\ORM\OptimisticLockException
11786
     */
11787
    public static function createCategory($params)
11788
    {
11789
        $em = Database::getManager();
11790
        $item = new CLpCategory();
11791
        $item->setName($params['name']);
11792
        $item->setCId($params['c_id']);
11793
        $em->persist($item);
11794
        $em->flush();
11795
    }
11796
11797
    /**
11798
     * @param array $params
11799
     *
11800
     * @throws \Doctrine\ORM\ORMException
11801
     * @throws \Doctrine\ORM\OptimisticLockException
11802
     * @throws \Doctrine\ORM\TransactionRequiredException
11803
     */
11804
    public static function updateCategory($params)
11805
    {
11806
        $em = Database::getManager();
11807
        /** @var CLpCategory $item */
11808
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
11809
        if ($item) {
11810
            $item->setName($params['name']);
11811
            $em->merge($item);
11812
            $em->flush();
11813
        }
11814
    }
11815
11816
    /**
11817
     * @param int $id
11818
     */
11819
    public static function moveUpCategory($id)
11820
    {
11821
        $id = (int) $id;
11822
        $em = Database::getManager();
11823
        /** @var CLpCategory $item */
11824
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11825
        if ($item) {
11826
            $position = $item->getPosition() - 1;
11827
            $item->setPosition($position);
11828
            $em->persist($item);
11829
            $em->flush();
11830
        }
11831
    }
11832
11833
    /**
11834
     * @param int $id
11835
     *
11836
     * @throws \Doctrine\ORM\ORMException
11837
     * @throws \Doctrine\ORM\OptimisticLockException
11838
     * @throws \Doctrine\ORM\TransactionRequiredException
11839
     */
11840
    public static function moveDownCategory($id)
11841
    {
11842
        $id = (int) $id;
11843
        $em = Database::getManager();
11844
        /** @var CLpCategory $item */
11845
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11846
        if ($item) {
11847
            $position = $item->getPosition() + 1;
11848
            $item->setPosition($position);
11849
            $em->persist($item);
11850
            $em->flush();
11851
        }
11852
    }
11853
11854
    /**
11855
     * @param int $courseId
11856
     *
11857
     * @return int|mixed
11858
     * @throws \Doctrine\ORM\Query\QueryException
11859
     */
11860
    public static function getCountCategories($courseId)
11861
    {
11862
        if (empty($course_id)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $course_id does not exist. Did you maybe mean $courseId?
Loading history...
11863
            return 0;
11864
        }
11865
        $em = Database::getManager();
11866
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
11867
        $query->setParameter('id', $courseId);
11868
11869
        return $query->getSingleScalarResult();
11870
    }
11871
11872
    /**
11873
     * @param int $courseId
11874
     *
11875
     * @return mixed
11876
     */
11877
    public static function getCategories($courseId)
11878
    {
11879
        $em = Database::getManager();
11880
        //Default behaviour
11881
        /*$items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
11882
            array('cId' => $course_id),
11883
            array('name' => 'ASC')
11884
        );*/
11885
11886
        // Using doctrine extensions
11887
        /** @var SortableRepository $repo */
11888
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
11889
        $items = $repo
11890
            ->getBySortableGroupsQuery(['cId' => $courseId])
11891
            ->getResult();
11892
11893
        return $items;
11894
    }
11895
11896
    /**
11897
     * @param int $id
11898
     *
11899
     * @return CLpCategory
11900
     * @throws \Doctrine\ORM\ORMException
11901
     * @throws \Doctrine\ORM\OptimisticLockException
11902
     * @throws \Doctrine\ORM\TransactionRequiredException
11903
     */
11904
    public static function getCategory($id)
11905
    {
11906
        $id = (int) $id;
11907
        $em = Database::getManager();
11908
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11909
11910
        return $item;
11911
    }
11912
11913
    /**
11914
     * @param int $courseId
11915
     *
11916
     * @return array
11917
     */
11918
    public static function getCategoryByCourse($courseId)
11919
    {
11920
        $em = Database::getManager();
11921
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
11922
            ['cId' => $courseId]
11923
        );
11924
11925
        return $items;
11926
    }
11927
11928
    /**
11929
     * @param int $id
11930
     *
11931
     * @return mixed
11932
     * @throws \Doctrine\DBAL\DBALException
11933
     * @throws \Doctrine\ORM\ORMException
11934
     * @throws \Doctrine\ORM\OptimisticLockException
11935
     * @throws \Doctrine\ORM\TransactionRequiredException
11936
     */
11937
    public static function deleteCategory($id)
11938
    {
11939
        $em = Database::getManager();
11940
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11941
        if ($item) {
11942
            $courseId = $item->getCId();
11943
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
11944
            $query->setParameter('id', $courseId);
11945
            $query->setParameter('catId', $item->getId());
11946
            $lps = $query->getResult();
11947
11948
            // Setting category = 0.
11949
            if ($lps) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lps of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
11950
                foreach ($lps as $lpItem) {
11951
                    $lpItem->setCategoryId(0);
11952
                }
11953
            }
11954
11955
            // Removing category.
11956
            $em->remove($item);
11957
            $em->flush();
11958
11959
            $courseInfo = api_get_course_info_by_id($courseId);
11960
            $sessionId = api_get_session_id();
11961
11962
            // Delete link tool
11963
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
11964
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
11965
            // Delete tools
11966
            $sql = "DELETE FROM $tbl_tool
11967
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
11968
            Database::query($sql);
11969
        }
11970
    }
11971
11972
    /**
11973
     * @param int  $courseId
11974
     * @param bool $addSelectOption
11975
     *
11976
     * @return mixed
11977
     */
11978
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
11979
    {
11980
        $items = self::getCategoryByCourse($courseId);
11981
        $cats = [];
11982
        if ($addSelectOption) {
11983
            $cats = [get_lang('SelectACategory')];
11984
        }
11985
11986
        if (!empty($items)) {
11987
            foreach ($items as $cat) {
11988
                $cats[$cat->getId()] = $cat->getName();
11989
            }
11990
        }
11991
11992
        return $cats;
11993
    }
11994
11995
    /**
11996
     * @param string $courseCode
11997
     * @param int    $lpId
11998
     * @param int    $user_id
11999
     *
12000
     * @return learnpath
12001
     */
12002
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12003
    {
12004
        $debug = 0;
12005
        $learnPath = null;
12006
        $lpObject = Session::read('lpobject');
12007
        if ($lpObject !== null) {
12008
            $learnPath = unserialize($lpObject);
12009
            if ($debug) {
12010
                error_log('getLpFromSession: unserialize');
12011
                error_log('------getLpFromSession------');
12012
                error_log('------unserialize------');
12013
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12014
                error_log("api_get_sessionid: ".api_get_session_id());
12015
            }
12016
        }
12017
12018
        if (!is_object($learnPath)) {
12019
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12020
            if ($debug) {
12021
                error_log('------getLpFromSession------');
12022
                error_log('getLpFromSession: create new learnpath');
12023
                error_log("create new LP with $courseCode - $lpId - $user_id");
12024
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12025
                error_log("api_get_sessionid: ".api_get_session_id());
12026
            }
12027
        }
12028
12029
        return $learnPath;
12030
    }
12031
12032
    /**
12033
     * @param int $itemId
12034
     *
12035
     * @return learnpathItem|false
12036
     */
12037
    public function getItem($itemId)
12038
    {
12039
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12040
            return $this->items[$itemId];
12041
        }
12042
12043
        return false;
12044
    }
12045
12046
    /**
12047
     * @return int
12048
     */
12049
    public function getCategoryId()
12050
    {
12051
        return $this->categoryId;
12052
    }
12053
12054
    /**
12055
     * @param int $categoryId
12056
     *
12057
     * @return bool
12058
     * @throws \Doctrine\DBAL\DBALException
12059
     */
12060
    public function setCategoryId($categoryId)
12061
    {
12062
        $this->categoryId = intval($categoryId);
12063
        $table = Database::get_course_table(TABLE_LP_MAIN);
12064
        $lp_id = $this->get_id();
12065
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12066
                WHERE iid = $lp_id";
12067
        Database::query($sql);
12068
12069
        return true;
12070
    }
12071
12072
    /**
12073
     * Get whether this is a learning path with the possibility to subscribe
12074
     * users or not.
12075
     *
12076
     * @return int
12077
     */
12078
    public function getSubscribeUsers()
12079
    {
12080
        return $this->subscribeUsers;
12081
    }
12082
12083
    /**
12084
     * Set whether this is a learning path with the possibility to subscribe
12085
     * users or not.
12086
     *
12087
     * @param int $value (0 = false, 1 = true)
12088
     *
12089
     * @return bool
12090
     * @throws \Doctrine\DBAL\DBALException
12091
     */
12092
    public function setSubscribeUsers($value)
12093
    {
12094
        if ($this->debug > 0) {
12095
            error_log('In learnpath::set_subscribe_users()', 0);
12096
        }
12097
        $this->subscribeUsers = (int) $value;
12098
        $table = Database::get_course_table(TABLE_LP_MAIN);
12099
        $lp_id = $this->get_id();
12100
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12101
                WHERE iid = $lp_id";
12102
        Database::query($sql);
12103
12104
        return true;
12105
    }
12106
12107
    /**
12108
     * Calculate the count of stars for a user in this LP
12109
     * This calculation is based on the following rules:
12110
     * - the student gets one star when he gets to 50% of the learning path
12111
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12112
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12113
     * - the student gets the final star when the score for the *last* test is >= 80%.
12114
     *
12115
     * @param int $sessionId Optional. The session ID
12116
     *
12117
     * @return int The count of stars
12118
     * @throws \Doctrine\DBAL\DBALException
12119
     */
12120
    public function getCalculateStars($sessionId = 0)
12121
    {
12122
        $stars = 0;
12123
        $progress = self::getProgress(
12124
            $this->lp_id,
12125
            $this->user_id,
12126
            $this->course_int_id,
12127
            $sessionId
12128
        );
12129
12130
        if ($progress >= 50) {
12131
            $stars++;
12132
        }
12133
12134
        // Calculate stars chapters evaluation
12135
        $exercisesItems = $this->getExercisesItems();
12136
12137
        if (!empty($exercisesItems)) {
12138
            $totalResult = 0;
12139
12140
            foreach ($exercisesItems as $exerciseItem) {
12141
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12142
                    $this->user_id,
12143
                    $exerciseItem->path,
12144
                    $this->course_int_id,
12145
                    $sessionId,
12146
                    $this->lp_id,
12147
                    $exerciseItem->db_id
12148
                );
12149
12150
                $exerciseResultInfo = end($exerciseResultInfo);
12151
12152
                if (!$exerciseResultInfo) {
12153
                    continue;
12154
                }
12155
12156
                if (!empty($exerciseResultInfo['exe_weighting'])) {
12157
                    $exerciseResult = $exerciseResultInfo['exe_result'] * 100 / $exerciseResultInfo['exe_weighting'];
12158
                } else {
12159
                    $exerciseResult = 0;
12160
                }
12161
                $totalResult += $exerciseResult;
12162
            }
12163
12164
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12165
12166
            if ($totalExerciseAverage >= 50) {
12167
                $stars++;
12168
            }
12169
12170
            if ($totalExerciseAverage >= 80) {
12171
                $stars++;
12172
            }
12173
        }
12174
12175
        // Calculate star for final evaluation
12176
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12177
12178
        if (!empty($finalEvaluationItem)) {
12179
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12180
                $this->user_id,
12181
                $finalEvaluationItem->path,
12182
                $this->course_int_id,
12183
                $sessionId,
12184
                $this->lp_id,
12185
                $finalEvaluationItem->db_id
12186
            );
12187
12188
            $evaluationResultInfo = end($evaluationResultInfo);
12189
12190
            if ($evaluationResultInfo) {
12191
                $evaluationResult = $evaluationResultInfo['exe_result'] * 100 / $evaluationResultInfo['exe_weighting'];
12192
12193
                if ($evaluationResult >= 80) {
12194
                    $stars++;
12195
                }
12196
            }
12197
        }
12198
12199
        return $stars;
12200
    }
12201
12202
    /**
12203
     * Get the items of exercise type.
12204
     *
12205
     * @return array The items. Otherwise return false
12206
     */
12207
    public function getExercisesItems()
12208
    {
12209
        $exercises = [];
12210
        foreach ($this->items as $item) {
12211
            if ($item->type != 'quiz') {
12212
                continue;
12213
            }
12214
            $exercises[] = $item;
12215
        }
12216
12217
        array_pop($exercises);
12218
12219
        return $exercises;
12220
    }
12221
12222
    /**
12223
     * Get the item of exercise type (evaluation type).
12224
     *
12225
     * @return array The final evaluation. Otherwise return false
12226
     */
12227
    public function getFinalEvaluationItem()
12228
    {
12229
        $exercises = [];
12230
        foreach ($this->items as $item) {
12231
            if ($item->type != 'quiz') {
12232
                continue;
12233
            }
12234
12235
            $exercises[] = $item;
12236
        }
12237
12238
        return array_pop($exercises);
12239
    }
12240
12241
    /**
12242
     * Calculate the total points achieved for the current user in this learning path.
12243
     *
12244
     * @param int $sessionId Optional. The session Id
12245
     *
12246
     * @return int
12247
     */
12248
    public function getCalculateScore($sessionId = 0)
12249
    {
12250
        // Calculate stars chapters evaluation
12251
        $exercisesItems = $this->getExercisesItems();
12252
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12253
        $totalExercisesResult = 0;
12254
        $totalEvaluationResult = 0;
12255
12256
        if ($exercisesItems !== false) {
12257
            foreach ($exercisesItems as $exerciseItem) {
12258
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12259
                    $this->user_id,
12260
                    $exerciseItem->path,
12261
                    $this->course_int_id,
12262
                    $sessionId,
12263
                    $this->lp_id,
12264
                    $exerciseItem->db_id
12265
                );
12266
12267
                $exerciseResultInfo = end($exerciseResultInfo);
12268
12269
                if (!$exerciseResultInfo) {
12270
                    continue;
12271
                }
12272
12273
                $totalExercisesResult += $exerciseResultInfo['exe_result'];
12274
            }
12275
        }
12276
12277
        if (!empty($finalEvaluationItem)) {
12278
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12279
                $this->user_id,
12280
                $finalEvaluationItem->path,
12281
                $this->course_int_id,
12282
                $sessionId,
12283
                $this->lp_id,
12284
                $finalEvaluationItem->db_id
12285
            );
12286
12287
            $evaluationResultInfo = end($evaluationResultInfo);
12288
12289
            if ($evaluationResultInfo) {
12290
                $totalEvaluationResult += $evaluationResultInfo['exe_result'];
12291
            }
12292
        }
12293
12294
        return $totalExercisesResult + $totalEvaluationResult;
12295
    }
12296
12297
    /**
12298
     * Check if URL is not allowed to be show in a iframe.
12299
     *
12300
     * @param string $src
12301
     *
12302
     * @return string
12303
     */
12304
    public function fixBlockedLinks($src)
12305
    {
12306
        $urlInfo = parse_url($src);
12307
        $platformProtocol = 'https';
12308
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12309
            $platformProtocol = 'http';
12310
        }
12311
12312
        $protocolFixApplied = false;
12313
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12314
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12315
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12316
12317
        if ($platformProtocol != $scheme) {
12318
            Session::write('x_frame_source', $src);
12319
            $src = 'blank.php?error=x_frames_options';
12320
            $protocolFixApplied = true;
12321
        }
12322
12323
        if ($protocolFixApplied == 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...
12324
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12325
                // Check X-Frame-Options
12326
                $ch = curl_init();
12327
12328
                $options = [
12329
                    CURLOPT_URL => $src,
12330
                    CURLOPT_RETURNTRANSFER => true,
12331
                    CURLOPT_HEADER => true,
12332
                    CURLOPT_FOLLOWLOCATION => true,
12333
                    CURLOPT_ENCODING => "",
12334
                    CURLOPT_AUTOREFERER => true,
12335
                    CURLOPT_CONNECTTIMEOUT => 120,
12336
                    CURLOPT_TIMEOUT => 120,
12337
                    CURLOPT_MAXREDIRS => 10,
12338
                ];
12339
                curl_setopt_array($ch, $options);
12340
                $response = curl_exec($ch);
12341
                $httpCode = curl_getinfo($ch);
12342
                $headers = substr($response, 0, $httpCode['header_size']);
12343
12344
                $error = false;
12345
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12346
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12347
                ) {
12348
                    $error = true;
12349
                }
12350
12351
                if ($error) {
12352
                    Session::write('x_frame_source', $src);
12353
                    $src = 'blank.php?error=x_frames_options';
12354
                }
12355
            }
12356
        }
12357
12358
        return $src;
12359
    }
12360
12361
    /**
12362
     * Check if this LP has a created forum in the basis course.
12363
     *
12364
     * @return bool
12365
     */
12366
    public function lpHasForum()
12367
    {
12368
        $forumTable = Database::get_course_table(TABLE_FORUM);
12369
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12370
12371
        $fakeFrom = "
12372
            $forumTable f
12373
            INNER JOIN $itemProperty ip
12374
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12375
        ";
12376
12377
        $resultData = Database::select(
12378
            'COUNT(f.iid) AS qty',
12379
            $fakeFrom,
12380
            [
12381
                'where' => [
12382
                    'ip.visibility != ? AND ' => 2,
12383
                    'ip.tool = ? AND ' => TOOL_FORUM,
12384
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12385
                    'f.lp_id = ?' => intval($this->lp_id),
12386
                ],
12387
            ],
12388
            'first'
12389
        );
12390
12391
        return $resultData['qty'] > 0;
12392
    }
12393
12394
    /**
12395
     * Get the forum for this learning path.
12396
     *
12397
     * @param int $sessionId
12398
     *
12399
     * @return bool
12400
     */
12401
    public function getForum($sessionId = 0)
12402
    {
12403
        $forumTable = Database::get_course_table(TABLE_FORUM);
12404
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12405
12406
        $fakeFrom = "$forumTable f
12407
            INNER JOIN $itemProperty ip ";
12408
12409
        if ($this->lp_session_id == 0) {
12410
            $fakeFrom .= "
12411
                ON (
12412
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
12413
                        f.session_id = ip.session_id OR ip.session_id IS NULL
12414
                    )
12415
                )
12416
            ";
12417
        } else {
12418
            $fakeFrom .= "
12419
                ON (
12420
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
12421
                )
12422
            ";
12423
        }
12424
12425
        $resultData = Database::select(
12426
            'f.*',
12427
            $fakeFrom,
12428
            [
12429
                'where' => [
12430
                    'ip.visibility != ? AND ' => 2,
12431
                    'ip.tool = ? AND ' => TOOL_FORUM,
12432
                    'f.session_id = ? AND ' => $sessionId,
12433
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12434
                    'f.lp_id = ?' => intval($this->lp_id),
12435
                ],
12436
            ],
12437
            'first'
12438
        );
12439
12440
        if (empty($resultData)) {
12441
            return false;
12442
        }
12443
12444
        return $resultData;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $resultData returns the type array which is incompatible with the documented return type boolean.
Loading history...
12445
    }
12446
12447
    /**
12448
     * Create a forum for this learning path.
12449
     *
12450
     * @param int $forumCategoryId
12451
     *
12452
     * @return int The forum ID if was created. Otherwise return false
12453
     */
12454
    public function createForum($forumCategoryId)
12455
    {
12456
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
12457
12458
        $forumId = store_forum(
12459
            [
12460
                'lp_id' => $this->lp_id,
12461
                'forum_title' => $this->name,
12462
                'forum_comment' => null,
12463
                'forum_category' => intval($forumCategoryId),
12464
                'students_can_edit_group' => ['students_can_edit' => 0],
12465
                'allow_new_threads_group' => ['allow_new_threads' => 0],
12466
                'default_view_type_group' => ['default_view_type' => 'flat'],
12467
                'group_forum' => 0,
12468
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
12469
            ],
12470
            [],
12471
            true
12472
        );
12473
12474
        return $forumId;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $forumId returns the type string which is incompatible with the documented return type integer.
Loading history...
12475
    }
12476
12477
    /**
12478
     * Get the LP Final Item form.
12479
     *
12480
     * @return string
12481
     * @throws Exception
12482
     * @throws HTML_QuickForm_Error
12483
     */
12484
    public function getFinalItemForm()
12485
    {
12486
        $finalItem = $this->getFinalItem();
12487
        $title = '';
12488
12489
        if ($finalItem) {
12490
            $title = $finalItem->get_title();
12491
            $buttonText = get_lang('Save');
12492
            $content = $this->getSavedFinalItem();
12493
        } else {
12494
            $buttonText = get_lang('LPCreateDocument');
12495
            $content = $this->getFinalItemTemplate();
12496
        }
12497
12498
        $courseInfo = api_get_course_info();
12499
        $result = $this->generate_lp_folder($courseInfo);
12500
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
12501
        $relative_prefix = '../../';
12502
12503
        $editorConfig = [
12504
            'ToolbarSet' => 'LearningPathDocuments',
12505
            'Width' => '100%',
12506
            'Height' => '500',
12507
            'FullPage' => true,
12508
            'CreateDocumentDir' => $relative_prefix,
12509
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
12510
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
12511
        ];
12512
12513
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
12514
            'type' => 'document',
12515
            'lp_id' => $this->lp_id,
12516
        ]);
12517
12518
        $form = new FormValidator('final_item', 'POST', $url);
12519
        $form->addText('title', get_lang('Title'));
12520
        $form->addButtonSave($buttonText);
12521
        $form->addHtml(
12522
            Display::return_message(
12523
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
12524
                'normal',
12525
                false
12526
            )
12527
        );
12528
12529
        $renderer = $form->defaultRenderer();
12530
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
12531
12532
        $form->addHtmlEditor(
12533
            'content_lp_certificate',
12534
            null,
12535
            true,
12536
            false,
12537
            $editorConfig,
12538
            true
12539
        );
12540
        $form->addHidden('action', 'add_final_item');
12541
        $form->addHidden('path', Session::read('pathItem'));
12542
        $form->addHidden('previous', $this->get_last());
12543
        $form->setDefaults(
12544
            ['title' => $title, 'content_lp_certificate' => $content]
12545
        );
12546
12547
        if ($form->validate()) {
12548
            $values = $form->exportValues();
12549
            $lastItemId = $this->get_last();
12550
12551
            if (!$finalItem) {
12552
                $documentId = $this->create_document(
12553
                    $this->course_info,
12554
                    $values['content_lp_certificate'],
12555
                    $values['title']
12556
                );
12557
                $this->add_item(
12558
                    0,
12559
                    $lastItemId,
12560
                    'final_item',
12561
                    $documentId,
12562
                    $values['title'],
12563
                    ''
12564
                );
12565
12566
                Display::addFlash(
12567
                    Display::return_message(get_lang('Added'))
12568
                );
12569
            } else {
12570
                $this->edit_document($this->course_info);
12571
            }
12572
        }
12573
12574
        return $form->returnForm();
12575
    }
12576
12577
    /**
12578
     * Check if the current lp item is first, both, last or none from lp list.
12579
     *
12580
     * @param int $currentItemId
12581
     *
12582
     * @return string
12583
     */
12584
    public function isFirstOrLastItem($currentItemId)
12585
    {
12586
        if ($this->debug > 0) {
12587
            error_log('In learnpath::isFirstOrLastItem('.$currentItemId.')', 0);
12588
        }
12589
12590
        $lpItemId = [];
12591
        $typeListNotToVerify = self::getChapterTypes();
12592
12593
        // Using get_toc() function instead $this->items because returns the correct order of the items
12594
        foreach ($this->get_toc() as $item) {
12595
            if (!in_array($item['type'], $typeListNotToVerify)) {
12596
                $lpItemId[] = $item['id'];
12597
            }
12598
        }
12599
12600
        $lastLpItemIndex = count($lpItemId) - 1;
12601
        $position = array_search($currentItemId, $lpItemId);
12602
12603
        switch ($position) {
12604
            case 0:
12605
                if (!$lastLpItemIndex) {
12606
                    $answer = 'both';
12607
                    break;
12608
                }
12609
12610
                $answer = 'first';
12611
                break;
12612
            case $lastLpItemIndex:
12613
                $answer = 'last';
12614
                break;
12615
            default:
12616
                $answer = 'none';
12617
        }
12618
12619
        return $answer;
12620
    }
12621
12622
    /**
12623
     * Get whether this is a learning path with the accumulated SCORM time or not.
12624
     *
12625
     * @return int
12626
     */
12627
    public function getAccumulateScormTime()
12628
    {
12629
        return $this->accumulateScormTime;
12630
    }
12631
12632
    /**
12633
     * Set whether this is a learning path with the accumulated SCORM time or not.
12634
     *
12635
     * @param int $value (0 = false, 1 = true)
12636
     *
12637
     * @return bool Always returns true
12638
     * @throws \Doctrine\DBAL\DBALException
12639
     */
12640
    public function setAccumulateScormTime($value)
12641
    {
12642
        if ($this->debug > 0) {
12643
            error_log('In learnpath::setAccumulateScormTime()', 0);
12644
        }
12645
        $this->accumulateScormTime = intval($value);
12646
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12647
        $lp_id = $this->get_id();
12648
        $sql = "UPDATE $lp_table 
12649
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
12650
                WHERE iid = $lp_id";
12651
        Database::query($sql);
12652
12653
        return true;
12654
    }
12655
12656
    /**
12657
     * Returns an HTML-formatted link to a resource, to incorporate directly into
12658
     * the new learning path tool.
12659
     *
12660
     * The function is a big switch on tool type.
12661
     * In each case, we query the corresponding table for information and build the link
12662
     * with that information.
12663
     *
12664
     * @author Yannick Warnier <[email protected]> - rebranding based on
12665
     * previous work (display_addedresource_link_in_learnpath())
12666
     *
12667
     * @param int $course_id Course code
12668
     * @param int $learningPathId The learning path ID (in lp table)
12669
     * @param int $id_in_path the unique index in the items table
12670
     * @param int $lpViewId
12671
     *
12672
     * @return string
12673
     * @throws \Doctrine\DBAL\DBALException
12674
     */
12675
    public static function rl_get_resource_link_for_learnpath(
12676
        $course_id,
12677
        $learningPathId,
12678
        $id_in_path,
12679
        $lpViewId
12680
    ) {
12681
        $session_id = api_get_session_id();
12682
        $course_info = api_get_course_info_by_id($course_id);
12683
12684
        $learningPathId = intval($learningPathId);
12685
        $id_in_path = intval($id_in_path);
12686
        $lpViewId = intval($lpViewId);
12687
12688
        $em = Database::getManager();
12689
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
12690
        /** @var CLpItem $rowItem */
12691
        $rowItem = $lpItemRepo->findOneBy([
12692
            'cId' => $course_id,
12693
            'lpId' => $learningPathId,
12694
            'id' => $id_in_path,
12695
        ]);
12696
12697
        if (!$rowItem) {
12698
            // Try one more time with iid
12699
            /** @var CLpItem $rowItem */
12700
            $rowItem = $lpItemRepo->findOneBy([
12701
                'cId' => $course_id,
12702
                'lpId' => $learningPathId,
12703
                'iid' => $id_in_path,
12704
            ]);
12705
12706
            if (!$rowItem) {
12707
                return -1;
12708
            }
12709
        }
12710
12711
        $course_code = $course_info['code'];
12712
        $type = $rowItem->getItemType();
12713
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
12714
        $main_dir_path = api_get_path(WEB_CODE_PATH);
12715
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
12716
        $link = '';
12717
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
12718
12719
        switch ($type) {
12720
            case 'dir':
12721
                return $main_dir_path.'lp/blank.php';
12722
            case TOOL_CALENDAR_EVENT:
12723
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
12724
            case TOOL_ANNOUNCEMENT:
12725
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
12726
            case TOOL_LINK:
12727
                $linkInfo = Link::getLinkInfo($id);
12728
                if (isset($linkInfo['url'])) {
12729
                    return $linkInfo['url'];
12730
                }
12731
12732
                return '';
12733
            case TOOL_QUIZ:
12734
                if (empty($id)) {
12735
                    return '';
12736
                }
12737
12738
                // Get the lp_item_view with the highest view_count.
12739
                $learnpathItemViewResult = $em
12740
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
12741
                    ->findBy(
12742
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
12743
                        ['viewCount' => 'DESC'],
12744
                        1
12745
                    );
12746
                /** @var CLpItemView $learnpathItemViewData */
12747
                $learnpathItemViewData = current($learnpathItemViewResult);
12748
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
12749
12750
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
12751
                    .http_build_query([
12752
                        'lp_init' => 1,
12753
                        'learnpath_item_view_id' => $learnpathItemViewId,
12754
                        'learnpath_id' => $learningPathId,
12755
                        'learnpath_item_id' => $id_in_path,
12756
                        'exerciseId' => $id,
12757
                    ]);
12758
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
12759
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
12760
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
12761
                $myrow = Database::fetch_array($result);
12762
                $path = $myrow['path'];
12763
12764
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
12765
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
12766
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
12767
            case TOOL_FORUM:
12768
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
12769
            case TOOL_THREAD:  //forum post
12770
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
12771
                if (empty($id)) {
12772
                    return '';
12773
                }
12774
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
12775
                $result = Database::query($sql);
12776
                $myrow = Database::fetch_array($result);
12777
12778
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
12779
                    .$extraParams;
12780
            case TOOL_POST:
12781
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12782
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
12783
                $myrow = Database::fetch_array($result);
12784
12785
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
12786
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
12787
            case TOOL_DOCUMENT:
12788
                $document = $em
12789
                    ->getRepository('ChamiloCourseBundle:CDocument')
12790
                    ->findOneBy(['cId' => $course_id, 'iid' => $id]);
12791
12792
                if (!$document) {
12793
                    return '';
12794
                }
12795
12796
                $documentPathInfo = pathinfo($document->getPath());
12797
                $jplayer_supported_files = ['mp4', 'ogv', 'flv', 'm4v'];
12798
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
12799
                $showDirectUrl = !in_array($extension, $jplayer_supported_files);
12800
12801
                $openmethod = 2;
12802
                $officedoc = false;
12803
                Session::write('openmethod', $openmethod);
12804
                Session::write('officedoc', $officedoc);
12805
12806
                if ($showDirectUrl) {
12807
                    return $main_course_path.'document'.$document->getPath().'?'.$extraParams;
12808
                }
12809
12810
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
12811
            case TOOL_LP_FINAL_ITEM:
12812
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
12813
                    .$extraParams;
12814
            case 'assignments':
12815
                return $main_dir_path.'work/work.php?'.$extraParams;
12816
            case TOOL_DROPBOX:
12817
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
12818
            case 'introduction_text': //DEPRECATED
12819
                return '';
12820
            case TOOL_COURSE_DESCRIPTION:
12821
                return $main_dir_path.'course_description?'.$extraParams;
12822
            case TOOL_GROUP:
12823
                return $main_dir_path.'group/group.php?'.$extraParams;
12824
            case TOOL_USER:
12825
                return $main_dir_path.'user/user.php?'.$extraParams;
12826
            case TOOL_STUDENTPUBLICATION:
12827
                if (!empty($rowItem->getPath())) {
12828
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
12829
                }
12830
12831
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
12832
        } //end switch
12833
12834
        return $link;
12835
    }
12836
12837
    /**
12838
     * Gets the name of a resource (generally used in learnpath when no name is provided).
12839
     *
12840
     * @author Yannick Warnier <[email protected]>
12841
     *
12842
     * @param string $course_code Course code
12843
     * @param string $learningPathId The tool type (using constants declared in main_api.lib.php)
12844
     * @param int    $id_in_path The resource ID
12845
     *
12846
     * @return string
12847
     * @throws \Doctrine\DBAL\DBALException
12848
     */
12849
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
12850
    {
12851
        $_course = api_get_course_info($course_code);
12852
        $course_id = $_course['real_id'];
12853
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12854
        $learningPathId = intval($learningPathId);
12855
        $id_in_path = intval($id_in_path);
12856
12857
        $sql_item = "SELECT item_type, title, ref FROM $tbl_lp_item
12858
                     WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
12859
        $res_item = Database::query($sql_item);
12860
12861
        if (Database::num_rows($res_item) < 1) {
12862
            return '';
12863
        }
12864
        $row_item = Database::fetch_array($res_item);
12865
        $type = strtolower($row_item['item_type']);
12866
        $id = $row_item['ref'];
12867
        $output = '';
12868
12869
        switch ($type) {
12870
            case TOOL_CALENDAR_EVENT:
12871
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
12872
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
12873
                $myrow = Database::fetch_array($result);
12874
                $output = $myrow['title'];
12875
                break;
12876
            case TOOL_ANNOUNCEMENT:
12877
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
12878
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
12879
                $myrow = Database::fetch_array($result);
12880
                $output = $myrow['title'];
12881
                break;
12882
            case TOOL_LINK:
12883
                // Doesn't take $target into account.
12884
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
12885
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
12886
                $myrow = Database::fetch_array($result);
12887
                $output = $myrow['title'];
12888
                break;
12889
            case TOOL_QUIZ:
12890
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
12891
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id=$id");
12892
                $myrow = Database::fetch_array($result);
12893
                $output = $myrow['title'];
12894
                break;
12895
            case TOOL_FORUM:
12896
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
12897
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id=$id");
12898
                $myrow = Database::fetch_array($result);
12899
                $output = $myrow['forum_name'];
12900
                break;
12901
            case TOOL_THREAD:  //=topics
12902
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12903
                // Grabbing the title of the post.
12904
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
12905
                $result_title = Database::query($sql_title);
12906
                $myrow_title = Database::fetch_array($result_title);
12907
                $output = $myrow_title['post_title'];
12908
                break;
12909
            case TOOL_POST:
12910
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12911
                //$tbl_post_text = Database::get_course_table(FORUM_POST_TEXT_TABLE);
12912
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
12913
                $result = Database::query($sql);
12914
                $post = Database::fetch_array($result);
12915
                $output = $post['post_title'];
12916
                break;
12917
            case 'dir':
12918
                $title = $row_item['title'];
12919
                if (!empty($title)) {
12920
                    $output = $title;
12921
                } else {
12922
                    $output = '-';
12923
                }
12924
                break;
12925
            case TOOL_DOCUMENT:
12926
                $title = $row_item['title'];
12927
                if (!empty($title)) {
12928
                    $output = $title;
12929
                } else {
12930
                    $output = '-';
12931
                }
12932
                break;
12933
            case 'hotpotatoes':
12934
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
12935
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
12936
                $myrow = Database::fetch_array($result);
12937
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
12938
                $last = count($pathname) - 1; // Making a correct name for the link.
12939
                $filename = $pathname[$last]; // Making a correct name for the link.
12940
                $ext = explode('.', $filename);
12941
                $ext = strtolower($ext[sizeof($ext) - 1]);
12942
                $myrow['path'] = rawurlencode($myrow['path']);
12943
                $output = $filename;
12944
                break;
12945
        }
12946
12947
        return stripslashes($output);
12948
    }
12949
12950
    /**
12951
     * Get the parent names for the current item.
12952
     *
12953
     * @param int $newItemId Optional. The item ID
12954
     *
12955
     * @return array
12956
     */
12957
    public function getCurrentItemParentNames($newItemId = 0)
12958
    {
12959
        $newItemId = $newItemId ?: $this->get_current_item_id();
12960
        $return = [];
12961
        $item = $this->getItem($newItemId);
12962
        $parent = $this->getItem($item->get_parent());
12963
12964
        while ($parent) {
12965
            $return[] = $parent->get_title();
12966
12967
            $parent = $this->getItem($parent->get_parent());
12968
        }
12969
12970
        return array_reverse($return);
12971
    }
12972
12973
    /**
12974
     * Reads and process "lp_subscription_settings" setting.
12975
     *
12976
     * @return array
12977
     */
12978
    public static function getSubscriptionSettings()
12979
    {
12980
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
12981
        if (empty($subscriptionSettings)) {
12982
            // By default allow both settings
12983
            $subscriptionSettings = [
12984
                'allow_add_users_to_lp' => true,
12985
                'allow_add_users_to_lp_category' => true,
12986
            ];
12987
        } else {
12988
            $subscriptionSettings = $subscriptionSettings['options'];
12989
        }
12990
12991
        return $subscriptionSettings;
12992
    }
12993
12994
    /**
12995
     * Get the depth level of LP item.
12996
     *
12997
     * @param array $items
12998
     * @param int   $currentItemId
12999
     *
13000
     * @return int
13001
     */
13002
    private static function get_level_for_item($items, $currentItemId)
13003
    {
13004
        $parentItemId = $items[$currentItemId]->parent;
13005
        if ($parentItemId == 0) {
13006
            return 0;
13007
        } else {
13008
            return self::get_level_for_item($items, $parentItemId) + 1;
13009
        }
13010
    }
13011
13012
    /**
13013
     * Generate the link for a learnpath category as course tool.
13014
     *
13015
     * @param int $categoryId
13016
     *
13017
     * @return string
13018
     */
13019
    private static function getCategoryLinkForTool($categoryId)
13020
    {
13021
        $categoryId = (int) $categoryId;
13022
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
13023
            .http_build_query(
13024
                [
13025
                    'action' => 'view_category',
13026
                    'id' => $categoryId,
13027
                ]
13028
            );
13029
13030
        return $link;
13031
    }
13032
13033
    /**
13034
     * Return the scorm item type object with spaces replaced with _
13035
     * The return result is use to build a css classname like scorm_type_$return.
13036
     *
13037
     * @param $in_type
13038
     *
13039
     * @return mixed
13040
     */
13041
    private static function format_scorm_type_item($in_type)
13042
    {
13043
        return str_replace(' ', '_', $in_type);
13044
    }
13045
13046
    /**
13047
     * Check and obtain the lp final item if exist.
13048
     *
13049
     * @return learnpathItem
13050
     */
13051
    private function getFinalItem()
13052
    {
13053
        if (empty($this->items)) {
13054
            return null;
13055
        }
13056
13057
        foreach ($this->items as $item) {
13058
            if ($item->type !== 'final_item') {
13059
                continue;
13060
            }
13061
13062
            return $item;
13063
        }
13064
    }
13065
13066
    /**
13067
     * Get the LP Final Item Template.
13068
     *
13069
     * @return string
13070
     */
13071
    private function getFinalItemTemplate()
13072
    {
13073
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
13074
    }
13075
13076
    /**
13077
     * Get the LP Final Item Url.
13078
     *
13079
     * @return string
13080
     */
13081
    private function getSavedFinalItem()
13082
    {
13083
        $finalItem = $this->getFinalItem();
13084
        $doc = DocumentManager::get_document_data_by_id(
13085
            $finalItem->path,
13086
            $this->cc
13087
        );
13088
        if ($doc && file_exists($doc['absolute_path'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $doc of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
13089
            return file_get_contents($doc['absolute_path']);
13090
        }
13091
13092
        return '';
13093
    }
13094
}
13095